Merge remote-tracking branch 'origin/master' into roles
diff --git a/aztest-playbook.yml b/aztest-playbook.yml
new file mode 100644
index 0000000..e3d9428
--- /dev/null
+++ b/aztest-playbook.yml
@@ -0,0 +1,49 @@
+---
+# aztest playbook, for installing an OpenCloud site
+
+- name: Include Configuration
+  hosts: all
+  tasks:
+  - include_vars: vars/opencloud_defaults.yml
+  - include_vars: vars/aztest.yml
+  - include_vars: vars/aztest_keystone.yml
+
+- name: Prep systems, and enable virtualization
+  hosts: all
+  become: yes
+  roles:
+    - common-prep
+    - dell-virt
+
+- name: DNS Server Setup
+  hosts: head
+  become: yes
+  roles:
+    - dns-nsd
+    - dns-unbound
+
+- name: Configure all hosts to use DNS server
+  hosts: all
+  become: yes
+  roles:
+    - dns-configure
+
+- name: Configure head node, create VM's, and start Juju setup
+  hosts: head
+  roles:
+    - { role: head-prep, become: yes }
+    - { role: config-virt, become: yes }
+    - juju-user-prep
+    - juju-setup
+
+- name: Configure compute nodes
+  hosts: compute
+  become: yes
+  roles:
+    - compute-prep
+
+- name: Configure Openstack using Juju
+  hosts: head
+  roles:
+    - juju-openstack-config
+
diff --git a/cord-playbook.yml b/cord-playbook.yml
new file mode 100644
index 0000000..9c72ab2
--- /dev/null
+++ b/cord-playbook.yml
@@ -0,0 +1,38 @@
+---
+# CORD playbook, for installing a single-node CORD test-environment on cloudlab
+
+- name: Include Configuration
+  hosts: all
+  tasks:
+  - include_vars: vars/cord_defaults.yml
+  - include_vars: vars/cord.yml
+  - include_vars: vars/cord_keystone.yml
+
+- name: Prep systems
+  hosts: all
+  become: yes
+  roles:
+    - common-prep
+    - cloudlab-prep
+
+- name: DNS Server Setup
+  hosts: head
+  become: yes
+  roles:
+    - dns-nsd
+    - dns-unbound
+
+- name: Configure all hosts to use DNS server
+  hosts: all
+  become: yes
+  roles:
+    - dns-configure
+
+- name: Configure head node, create VM's, and start Juju setup
+  hosts: head
+  roles:
+    - { role: head-prep, become: yes }
+    - { role: config-virt, become: yes }
+    - juju-user-prep
+    - juju-setup
+
diff --git a/docs/install_opencloud_site.md b/docs/install_opencloud_site.md
index 26c50f1..daaa23b 100644
--- a/docs/install_opencloud_site.md
+++ b/docs/install_opencloud_site.md
@@ -1,16 +1,16 @@
-**Introduction**
+## Introduction
 
-The following steps areis required in order to bring up a new opencloud sites.
+The following steps are required in order to bring up a new OpenCloud sites.
 
 1. Allocate servers
 
-2. (Re)IInstall Uubuntu
+2. Install Uubuntu
 
-3. Install openstack controller & compute nodes
+3. Install OpenStack controller & compute nodes
 
-4. Add site’s openstack controller to xos
+4. Add site’s OpenStack controller to xos
 
-**Allocate Servers**
+## Allocate Servers
 
 **It may happen that for different reasons that few servers are offline. **Allocating servers involves finding those nodes that are offline and bringing them back online. In most cases just rebooting the nodes will bring them back online. Sometimes they may be offline for hardware malfunctions or maintenance. In that case someone would need to provide help, locally from the facility.
 
@@ -18,7 +18,7 @@
 
 Note: For example, for the Stanford cluster, the script should be located I’ve installed the ipmi-cmd.sh on node4.stanford.vicci.org. You should be able to reboot nodes from there.
 
-**Install Ubuntu**
+## Install Ubuntu
 
 Opencloud nodes are expected to be Ubuntu 14.x.
 
@@ -42,6 +42,14 @@
 
 After reboot, the machine should go through the Ubuntu installation automatically. At the end of the process, the ones registered as administrators should be notified of the successfully installation. If you’re not an official opencloud.us administrator, just try to log into the machines again after 20-30 mins form the reboot.
 
+3. Update Ubuntu
+
+```
+sudo apt-get update
+sudo apt-get dist-upgrade
+```
+
+
 **Install Openstack**
 
 Ansible is a software that enables easy centralized configuration and management of a set of machines.
diff --git a/enable-virt-dell.yml b/enable-virt-dell.yml
deleted file mode 100644
index 2e84c05..0000000
--- a/enable-virt-dell.yml
+++ /dev/null
@@ -1,21 +0,0 @@
-# Enable virtualization on Dell R410s
----
-- hosts: onlab-compute
-  sudo: yes
-  tasks:
-  - name: Update files
-    copy: src=files/etc/apt/sources.list.d/linux.dell.com.sources.list
-      dest=/etc/apt/sources.list.d/linux.dell.com.sources.list
-
-  - shell: gpg --keyserver pool.sks-keyservers.net --recv-key 1285491434D8786F
-
-  - shell: gpg -a --export 1285491434D8786F | sudo apt-key add -
-
-  - name: Update apt cache
-    apt: upgrade=yes update_cache=yes
-
-  - name: Install packages
-    apt: name=dtk-scripts state=present
-
-  - name: Enable virtualization in BIOS
-    shell: /opt/dell/toolkit/bin/syscfg --virtualization=enable
diff --git a/group_vars/arizona.yml b/group_vars/arizona.yml
new file mode 100644
index 0000000..d945d36
--- /dev/null
+++ b/group_vars/arizona.yml
@@ -0,0 +1,5 @@
+---
+# file: group_vars/arizona.yml
+
+mgmt_net_prefix: 192.168.102
+cloudlab: false
diff --git a/group_vars/cloudlab.yml b/group_vars/cloudlab.yml
new file mode 100644
index 0000000..340ac15
--- /dev/null
+++ b/group_vars/cloudlab.yml
@@ -0,0 +1,6 @@
+---
+# file: group_vars/cloudlab.yml
+
+mgmt_net_prefix: 192.168.100
+cloudlab: true
+
diff --git a/group_vars/cord-test.yml b/group_vars/cord-test.yml
new file mode 100644
index 0000000..9836d41
--- /dev/null
+++ b/group_vars/cord-test.yml
@@ -0,0 +1,4 @@
+---
+# file: group_vars/cord-test.yml
+
+
diff --git a/group_vars/cord.yml b/group_vars/cord.yml
new file mode 100644
index 0000000..3b1cf22
--- /dev/null
+++ b/group_vars/cord.yml
@@ -0,0 +1,3 @@
+---
+# file: group_vars/cord.yml
+
diff --git a/group_vars/princeton.yml b/group_vars/princeton.yml
new file mode 100644
index 0000000..7c1e6ba
--- /dev/null
+++ b/group_vars/princeton.yml
@@ -0,0 +1,6 @@
+---
+# file: group_vars/princeton.yml
+
+mgmt_net_prefix: 192.168.100
+cloudlab: false
+
diff --git a/group_vars/singapore.yml b/group_vars/singapore.yml
new file mode 100644
index 0000000..85ba602
--- /dev/null
+++ b/group_vars/singapore.yml
@@ -0,0 +1,5 @@
+---
+# file: group_vars/singapore.yml
+
+mgmt_net_prefix: 192.168.103
+cloudlab: false
diff --git a/group_vars/stanford.yml b/group_vars/stanford.yml
new file mode 100644
index 0000000..fee7f1c
--- /dev/null
+++ b/group_vars/stanford.yml
@@ -0,0 +1,6 @@
+---
+# file: group_vars/stanford.yml
+
+mgmt_net_prefix: 192.168.101
+cloudlab: false
+
diff --git a/inventory/arizona b/inventory/arizona
new file mode 100644
index 0000000..161f143
--- /dev/null
+++ b/inventory/arizona
@@ -0,0 +1,12 @@
+[all:vars]
+ansible_ssh_user=ubuntu
+
+[head]
+node1.cs.arizona.edu
+
+[compute]
+node2.cs.arizona.edu
+node3.cs.arizona.edu
+node4.cs.arizona.edu
+node5.cs.arizona.edu
+node6.cs.arizona.edu
diff --git a/inventory/aztest b/inventory/aztest
new file mode 100644
index 0000000..e8ff1fa
--- /dev/null
+++ b/inventory/aztest
@@ -0,0 +1,10 @@
+[all:vars]
+ansible_ssh_user=ubuntu
+
+[head]
+node09.opencloud.cs.arizona.edu
+
+[compute]
+node10.opencloud.cs.arizona.edu
+node11.opencloud.cs.arizona.edu
+node12.opencloud.cs.arizona.edu
diff --git a/inventory/cloudlab b/inventory/cloudlab
new file mode 100644
index 0000000..4dc377f
--- /dev/null
+++ b/inventory/cloudlab
@@ -0,0 +1,8 @@
+[all:vars]
+ansible_ssh_user=ubuntu
+
+[head]
+ctl.install.xos-pg0.clemson.cloudlab.us
+
+[compute]
+cp-1.install.xos-pg0.clemson.cloudlab.us
diff --git a/inventory/cord b/inventory/cord
new file mode 100644
index 0000000..1d94507
--- /dev/null
+++ b/inventory/cord
@@ -0,0 +1,25 @@
+[all:vars]
+ansible_ssh_user=cord
+
+[head]
+localhost connection=local
+
+[compute]
+node1
+node2
+node3
+
+[openstack]
+mysql
+rabbitmq-server
+keystone
+glance
+nova-cloud-controller
+openstack-dashboard
+ceilometer
+nagios
+neutron-api
+
+[openstack:vars]
+ansible_ssh_user=ubuntu
+
diff --git a/inventory/cord-cloudlab b/inventory/cord-cloudlab
new file mode 100644
index 0000000..cb5cc3b
--- /dev/null
+++ b/inventory/cord-cloudlab
@@ -0,0 +1,6 @@
+[all:vars]
+ansible_ssh_user=zdw
+
+[head]
+c220g2-011128.wisc.cloudlab.us
+
diff --git a/inventory/cord-test b/inventory/cord-test
new file mode 100644
index 0000000..29c1dc3
--- /dev/null
+++ b/inventory/cord-test
@@ -0,0 +1,24 @@
+[all:vars]
+ansible_ssh_user=ubuntu
+test_setup=true
+
+[head]
+localhost connection=local
+
+[compute]
+nova-compute
+
+[openstack]
+mysql
+rabbitmq-server
+keystone
+glance
+nova-cloud-controller
+openstack-dashboard
+ceilometer
+nagios
+neutron-api
+
+[openstack:vars]
+ansible_ssh_user=ubuntu
+
diff --git a/inventory/princeton b/inventory/princeton
new file mode 100644
index 0000000..821d605
--- /dev/null
+++ b/inventory/princeton
@@ -0,0 +1,24 @@
+[all:vars]
+ansible_ssh_user=ubuntu
+
+[head]
+node70.princeton.vicci.org
+
+[compute]
+node37.princeton.vicci.org
+node39.princeton.vicci.org
+node41.princeton.vicci.org
+node43.princeton.vicci.org
+node45.princeton.vicci.org
+node49.princeton.vicci.org
+node51.princeton.vicci.org
+node52.princeton.vicci.org
+node54.princeton.vicci.org
+node55.princeton.vicci.org
+node57.princeton.vicci.org
+node59.princeton.vicci.org
+node65.princeton.vicci.org
+node66.princeton.vicci.org
+node67.princeton.vicci.org
+node68.princeton.vicci.org
+node69.princeton.vicci.org
diff --git a/inventory/singapore b/inventory/singapore
new file mode 100644
index 0000000..eb2164b
--- /dev/null
+++ b/inventory/singapore
@@ -0,0 +1,11 @@
+[all:vars]
+ansible_ssh_user=ubuntu
+
+[head]
+opencloud0.sing.internet2.edu
+
+[compute]
+opencloud1.sing.internet2.edu
+opencloud2.sing.internet2.edu
+opencloud3.sing.internet2.edu
+
diff --git a/inventory/stanford b/inventory/stanford
new file mode 100644
index 0000000..5b3068f
--- /dev/null
+++ b/inventory/stanford
@@ -0,0 +1,70 @@
+[all:vars]
+ansible_ssh_user=ubuntu
+
+[head]
+node1.stanford.vicci.org
+
+[compute]
+node2.stanford.vicci.org
+node3.stanford.vicci.org
+node5.stanford.vicci.org
+node6.stanford.vicci.org
+node7.stanford.vicci.org
+node8.stanford.vicci.org
+node9.stanford.vicci.org
+node10.stanford.vicci.org
+node11.stanford.vicci.org
+node12.stanford.vicci.org
+node13.stanford.vicci.org
+node14.stanford.vicci.org
+node15.stanford.vicci.org
+node16.stanford.vicci.org
+node17.stanford.vicci.org
+node18.stanford.vicci.org
+node19.stanford.vicci.org
+node20.stanford.vicci.org
+node21.stanford.vicci.org
+node22.stanford.vicci.org
+node23.stanford.vicci.org
+node24.stanford.vicci.org
+node25.stanford.vicci.org
+node26.stanford.vicci.org
+node27.stanford.vicci.org
+node28.stanford.vicci.org
+node29.stanford.vicci.org
+node30.stanford.vicci.org
+node31.stanford.vicci.org
+node32.stanford.vicci.org
+node33.stanford.vicci.org
+node34.stanford.vicci.org
+node35.stanford.vicci.org
+node37.stanford.vicci.org
+node38.stanford.vicci.org
+node39.stanford.vicci.org
+node40.stanford.vicci.org
+node41.stanford.vicci.org
+node42.stanford.vicci.org
+node43.stanford.vicci.org
+node44.stanford.vicci.org
+node45.stanford.vicci.org
+node46.stanford.vicci.org
+node47.stanford.vicci.org
+node48.stanford.vicci.org
+node49.stanford.vicci.org
+node50.stanford.vicci.org
+node52.stanford.vicci.org
+node54.stanford.vicci.org
+node55.stanford.vicci.org
+node57.stanford.vicci.org
+node58.stanford.vicci.org
+node59.stanford.vicci.org
+node60.stanford.vicci.org
+node61.stanford.vicci.org
+node62.stanford.vicci.org
+node63.stanford.vicci.org
+node64.stanford.vicci.org
+node67.stanford.vicci.org
+node68.stanford.vicci.org
+node69.stanford.vicci.org
+node70.stanford.vicci.org
+
diff --git a/arizona-hosts b/legacy/arizona-hosts
similarity index 100%
rename from arizona-hosts
rename to legacy/arizona-hosts
diff --git a/cloudlab-hosts b/legacy/cloudlab-hosts
similarity index 100%
rename from cloudlab-hosts
rename to legacy/cloudlab-hosts
diff --git a/cord-hosts b/legacy/cord-hosts
similarity index 100%
rename from cord-hosts
rename to legacy/cord-hosts
diff --git a/cord-test-hosts b/legacy/cord-test-hosts
similarity index 100%
rename from cord-test-hosts
rename to legacy/cord-test-hosts
diff --git a/princeton-hosts b/legacy/princeton-hosts
similarity index 100%
rename from princeton-hosts
rename to legacy/princeton-hosts
diff --git a/singapore-hosts b/legacy/singapore-hosts
similarity index 100%
rename from singapore-hosts
rename to legacy/singapore-hosts
diff --git a/stanford-hosts b/legacy/stanford-hosts
similarity index 100%
rename from stanford-hosts
rename to legacy/stanford-hosts
diff --git a/tasks/vm-ips.yml b/legacy/tasks/vm-ips.yml
similarity index 100%
rename from tasks/vm-ips.yml
rename to legacy/tasks/vm-ips.yml
diff --git a/library/juju_facts.py b/library/juju_facts.py
new file mode 100644
index 0000000..3ba02c8
--- /dev/null
+++ b/library/juju_facts.py
@@ -0,0 +1,33 @@
+#!/usr/bin/env python
+
+import json
+import subprocess
+
+def dict_keys_dash_to_underscore(dashed):
+    underscored = dict((k.replace('-','_'),v) for k,v in dashed.items())
+    return underscored
+
+juju_status_json = subprocess.check_output("juju status --format=json", shell=True)
+juju_status = json.loads(juju_status_json)
+
+juju_machines = {}
+for index, data in juju_status['machines'].iteritems():
+    data_underscore = dict_keys_dash_to_underscore(data)
+    juju_machines[data_underscore["dns_name"]] = data_underscore
+    juju_machines[data_underscore["dns_name"]]["machine_id"] = index
+
+juju_compute_nodes = {}
+if 'nova-compute' in juju_status['services']:
+    for name, data in juju_status['services']['nova-compute']['units'].iteritems():
+        juju_compute_nodes[data['public-address']] = data
+
+print json.dumps({
+    "changed": True,
+    "ansible_facts" : {
+        "juju_environment": juju_status['environment'],
+        "juju_machines": juju_machines,
+        "juju_services": juju_status['services'],
+        "juju_compute_nodes": juju_compute_nodes,
+    },
+})
+
diff --git a/roles/cloudlab-prep/defaults/main.yml b/roles/cloudlab-prep/defaults/main.yml
new file mode 100644
index 0000000..00a4575
--- /dev/null
+++ b/roles/cloudlab-prep/defaults/main.yml
@@ -0,0 +1,6 @@
+---
+# roles/cloudlab-prep/defaults/main.yml
+
+cloudlab_extrafs:
+  - /var/lib/nova
+  #  - /var/lib/uvtool/libvirt/images
diff --git a/roles/cloudlab-prep/tasks/main.yml b/roles/cloudlab-prep/tasks/main.yml
new file mode 100644
index 0000000..3dc0985
--- /dev/null
+++ b/roles/cloudlab-prep/tasks/main.yml
@@ -0,0 +1,15 @@
+---
+# roles/cloudlab-prep/tasks/main.yml
+
+- name: create directories to mount extra filesystems
+  file: 
+    dest={{ item }}
+    state=directory
+  with_items: "{{ cloudlab_extrafs }}"
+
+
+- name: Set up extra disk space
+  command: /usr/testbed/bin/mkextrafs -f {{ item }}
+    creates={{ item }}/lost+found
+  with_items: "{{ cloudlab_extrafs }}"
+
diff --git a/roles/common-prep/handlers/main.yml b/roles/common-prep/handlers/main.yml
new file mode 100644
index 0000000..537ccb3
--- /dev/null
+++ b/roles/common-prep/handlers/main.yml
@@ -0,0 +1,19 @@
+---
+# file: roles/common-prep/handlers/main.yml
+
+# from https://support.ansible.com/hc/en-us/articles/201958037-Reboot-a-server-and-wait-for-it-to-come-back
+- name: restart host
+  shell: sleep 2 && shutdown -r now "Ansible updates triggered"
+  async: 1
+  poll: 0
+  ignore_errors: true
+
+# wait 1m, then try to contact machine for 10m
+- name: wait for host
+  become: false
+  local_action:
+     wait_for host={{ inventory_hostname }}
+     port=22
+     delay=60 timeout=600
+     state=started
+
diff --git a/roles/common-prep/tasks/main.yml b/roles/common-prep/tasks/main.yml
new file mode 100644
index 0000000..1ea1ad9
--- /dev/null
+++ b/roles/common-prep/tasks/main.yml
@@ -0,0 +1,31 @@
+---
+# file: roles/common-prep/tasks/main.yml
+
+- name: Upgrade system to current using apt
+  apt:
+    upgrade=dist
+    update_cache=yes
+    cache_valid_time=3600
+
+- stat:
+    path=/var/run/reboot-required
+  register: reboot-required
+
+- name: reboot if required
+  when: reboot-required.exists is defined
+  debug: msg="System will reboot"
+  notify:
+    - restart host
+    - wait for host
+
+- name: Install standard packages
+  apt:  pkg={{ item }} state=present
+  with_items:
+   - tmux
+   - vim
+
+- name: Enable vim syntax highlighting
+  lineinfile: dest=/etc/vim/vimrc
+    regexp="^\s*syntax on"
+    line="syntax on"
+
diff --git a/roles/compute-prep/handlers/main.yml b/roles/compute-prep/handlers/main.yml
new file mode 100644
index 0000000..eee1556
--- /dev/null
+++ b/roles/compute-prep/handlers/main.yml
@@ -0,0 +1,5 @@
+---
+# file: roles/compute-prep/handlers/main.yml
+
+- name: run rc.local
+  command: /etc/rc.local
diff --git a/roles/compute-prep/tasks/main.yml b/roles/compute-prep/tasks/main.yml
new file mode 100644
index 0000000..ac65bc8
--- /dev/null
+++ b/roles/compute-prep/tasks/main.yml
@@ -0,0 +1,28 @@
+---
+# file: roles/compute-prep/tasks/main.yml
+
+- name: Install packages
+  apt:
+    name={{ item }}
+    state=latest
+  with_items:
+    - python-yaml
+
+- name: Add head node ubuntu user key
+  authorized_key:
+    user=ubuntu
+    key="{{ hostvars[groups['head'][0]]['head_ssh_pubkey']['stdout'] }}"
+
+- name: Add route via /etc/rc.local
+  template:
+    src=rc.local.j2
+    dest=/etc/rc.local
+    mode=0755
+  notify:
+    - run rc.local
+
+- name: Create /var/lib/nova dir
+  file:
+    path=/var/lib/nova
+    state=directory
+
diff --git a/roles/compute-prep/templates/rc.local.j2 b/roles/compute-prep/templates/rc.local.j2
new file mode 100644
index 0000000..73b12fb
--- /dev/null
+++ b/roles/compute-prep/templates/rc.local.j2
@@ -0,0 +1,19 @@
+#!/bin/sh -e
+#
+# rc.local
+#
+# This script is executed at the end of each multiuser runlevel.
+# Make sure that the script will "exit 0" on success or any other
+# value on error.
+#
+# In order to enable or disable this script just change the execution
+# bits.
+
+{% set head_host = groups['head'][0] -%}
+{% set head_ip = hostvars[head_host]['ansible_default_ipv4']['address'] -%}
+{% set virt_network = hostvars[head_host]['ansible_virbr0']['ipv4']['network'] -%}
+{% set virt_netmask = hostvars[head_host]['ansible_virbr0']['ipv4']['netmask'] -%}
+
+route add -net {{ virt_network }} netmask {{ virt_netmask }} gw {{ head_ip }} || true
+
+exit 0
diff --git a/roles/config-virt/defaults/main.yml b/roles/config-virt/defaults/main.yml
new file mode 100644
index 0000000..8a1faf1
--- /dev/null
+++ b/roles/config-virt/defaults/main.yml
@@ -0,0 +1,4 @@
+---
+# roles/config-virt/defaults/main.yml
+
+mgmtbr_name: virbr0
diff --git a/roles/config-virt/handlers/main.yml b/roles/config-virt/handlers/main.yml
new file mode 100644
index 0000000..d3708e6
--- /dev/null
+++ b/roles/config-virt/handlers/main.yml
@@ -0,0 +1,15 @@
+---
+# roles/juju-setup/handlers/tasks.yml
+
+- name: recreate default network
+  command: virsh net-destroy default ; virsh net-start default
+
+- name: reload libvirt-bin
+  service:
+    name=libvirt-bin
+    state=restarted
+
+- name: run qemu hook
+  command: /etc/libvirt/hooks/qemu start start
+
+
diff --git a/roles/config-virt/tasks/main.yml b/roles/config-virt/tasks/main.yml
new file mode 100644
index 0000000..d74d4f3
--- /dev/null
+++ b/roles/config-virt/tasks/main.yml
@@ -0,0 +1,28 @@
+---
+# roles/config-virt/tasks/main.yml
+
+- name: Get ubuntu image for uvtool
+  command: uvt-simplestreams-libvirt sync --source http://cloud-images.ubuntu.com/daily \
+    release={{ ansible_distribution_release }} arch=amd64
+
+- name: Have libvirt enable port forwarding to VM's
+  become: yes
+  template:
+    src={{ item }}.j2
+    dest=/etc/libvirt/hooks/{{ item }}
+    mode=0755 owner=root
+  with_items:
+    - daemon
+    - qemu
+  notify:
+    - reload libvirt-bin
+    - run qemu hook
+
+- name: configure libvirt mgmtbr network DHCP range and IP assignments
+  virt_net:
+    command=define
+    name=default
+    xml='{{ lookup("template", "default.xml.j2") }}'
+    autostart=yes
+    state=active
+
diff --git a/roles/config-virt/templates/daemon.j2 b/roles/config-virt/templates/daemon.j2
new file mode 100644
index 0000000..c79bf4a
--- /dev/null
+++ b/roles/config-virt/templates/daemon.j2
@@ -0,0 +1,39 @@
+#!/bin/sh
+
+SHELL="/bin/bash"
+
+NIC=$( route|grep default|awk '{print $NF}' )
+
+NAME="${1}"
+OP="${2}"
+SUBOP="${3}"
+ARGS="${4}"
+
+add_port_fwd_rule() {
+    DPORT=$1
+    VMIP=$2
+    TOPORT=$3
+
+    iptables -t nat -C PREROUTING -p tcp -i $NIC --dport $DPORT -j DNAT --to-destination $VMIP:$TOPORT
+    if [ "$?" -ne 0 ]
+    then
+        iptables -t nat -A PREROUTING -p tcp -i $NIC --dport $DPORT -j DNAT --to-destination $VMIP:$TOPORT
+    fi
+}
+
+if [ "$OP" = "start" ] || [ "$OP" = "reload" ]
+then
+    iptables -t nat -F
+
+{% for vm in head_vm_list -%}
+{% if vm.forwarded_ports is defined -%}
+{% for port in vm.forwarded_ports -%}
+    add_port_fwd_rule {{ port.ext }} "{{ mgmtbr_prefix }}.{{ vm.ipv4_last_octet }}" {{ port.int }}
+{% endfor -%}
+{% endif -%}
+{% endfor -%}
+
+    # Also flush the filter table before rules re-added
+    iptables -F
+fi
+
diff --git a/roles/config-virt/templates/default.xml.j2 b/roles/config-virt/templates/default.xml.j2
new file mode 100644
index 0000000..7914a2e
--- /dev/null
+++ b/roles/config-virt/templates/default.xml.j2
@@ -0,0 +1,19 @@
+<network>
+  <name>default</name>
+  <bridge name="{{ mgmtbr_name }}"/>
+  <forward/>
+  <domain name="{{ site_suffix }}" localonly="no"/>
+  <dns>
+{% for ns in dns_servers %}
+  <forwarder addr="{{ ns }}"/>
+{% endfor %}
+  </dns>
+  <ip address="{{ mgmtbr_prefix }}.1" netmask="255.255.255.0">
+    <dhcp>
+      <range start="{{ mgmtbr_prefix }}.2" end="{{ mgmtbr_prefix }}.254"/>
+{% for vm in head_vm_list %}
+      <host name='{{ vm.name }}' ip='{{ mgmtbr_prefix }}.{{ vm.ipv4_last_octet }}'/>
+{% endfor %}
+    </dhcp>
+  </ip>
+</network>
diff --git a/roles/config-virt/templates/qemu.j2 b/roles/config-virt/templates/qemu.j2
new file mode 100644
index 0000000..1c947f9
--- /dev/null
+++ b/roles/config-virt/templates/qemu.j2
@@ -0,0 +1,44 @@
+#!/bin/sh
+
+SHELL="/bin/bash"
+
+NIC=$( route|grep default|awk '{print $NF}' )
+PORTAL=$( dig +short portal.opencloud.us | tail -1 )
+
+NAME="${1}"
+OP="${2}"
+SUBOP="${3}"
+ARGS="${4}"
+
+add_rule() {
+    CHAIN=$1
+    ARGS=$2
+    iptables -C $CHAIN $ARGS
+    if [ "$?" -ne 0 ]
+    then
+        iptables -I $CHAIN 1 $ARGS
+    fi
+}
+
+add_local_access_rules() {
+    SUBNET=$( ip addr show $NIC|grep "inet "|awk '{print $2}' )
+    PRIVATENET=$( ip addr show virbr0|grep "inet "|awk '{print $2}' )
+    add_rule "FORWARD" "-s $SUBNET -j ACCEPT"
+    # Don't NAT traffic from service VMs destined to the local subnet
+    add_rule "POSTROUTING" "-t nat -s $PRIVATENET -d $SUBNET -j RETURN"
+}
+
+add_portal_access_rules() {
+    add_rule "FORWARD" "-s $PORTAL -j ACCEPT"
+}
+
+add_web_access_rules() {
+    add_rule "FORWARD" "-p tcp --dport 80 -j ACCEPT"
+}
+
+if [ "$OP" = "start" ]
+then
+	add_local_access_rules
+	add_portal_access_rules
+	add_web_access_rules
+fi
diff --git a/roles/dell-virt/tasks/main.yml b/roles/dell-virt/tasks/main.yml
new file mode 100644
index 0000000..cfc60a6
--- /dev/null
+++ b/roles/dell-virt/tasks/main.yml
@@ -0,0 +1,28 @@
+---
+# file: roles/dell-virt/prep/tasks/main.yml
+
+- name: Trust the Dell apt repository
+  apt_key:
+    keyserver=pool.sks-keyservers.net
+    id=1285491434D8786F
+
+- name: Add Dell apt repo
+  apt_repository:
+    repo="deb http://linux.dell.com/repo/community/{{ ansible_distribution | lower }} {{ ansible_distribution_release }} openmanage"
+
+- name: Install BIOS configuration scripts
+  apt:
+    update_cache=yes
+    cache_valid_time=3600
+    name=dtk-scripts
+
+# KVM virtualization is either "kvm_intel" or "kvm_amd" on amd64
+- name: Check to see if processor KVM virtualization module loaded in kernel
+  shell: lsmod | grep kvm_
+  ignore_errors: true
+  register: virtualization_enabled
+
+- name: Enable virtualization in BIOS
+  command: /opt/dell/toolkit/bin/syscfg --virtualization=enable
+  when: virtualization_enabled|failed
+
diff --git a/roles/dns-configure/defaults/main.yml b/roles/dns-configure/defaults/main.yml
new file mode 100644
index 0000000..f17d1fa
--- /dev/null
+++ b/roles/dns-configure/defaults/main.yml
@@ -0,0 +1,9 @@
+---
+# roles/dns-configure/defaults
+
+dns_servers:
+  - 8.8.8.8
+  - 8.8.4.4
+
+dns_search: {}
+
diff --git a/roles/dns-configure/tasks/main.yml b/roles/dns-configure/tasks/main.yml
new file mode 100644
index 0000000..7aff9f4
--- /dev/null
+++ b/roles/dns-configure/tasks/main.yml
@@ -0,0 +1,9 @@
+---
+# roles/dns-configure/tasks.yml
+
+- name: Configure resolv.conf to use nameservers
+  template:
+    src="resolv.conf.j2"
+    dest="/etc/resolv.conf"
+    mode=0644 owner=root group=root
+
diff --git a/roles/dns-configure/templates/resolv.conf.j2 b/roles/dns-configure/templates/resolv.conf.j2
new file mode 100644
index 0000000..a6bd8ea
--- /dev/null
+++ b/roles/dns-configure/templates/resolv.conf.j2
@@ -0,0 +1,7 @@
+# resolv.conf (ansible managed)
+{% for ns in dns_servers %}
+nameserver {{ ns }}
+{% endfor %}
+{% if dns_search is defined %}
+search{% for searchdom in dns_search %} {{ searchdom }}{% endfor %}
+{% endif %}
diff --git a/roles/dns-nsd/defaults/main.yml b/roles/dns-nsd/defaults/main.yml
new file mode 100644
index 0000000..5dcbddf
--- /dev/null
+++ b/roles/dns-nsd/defaults/main.yml
@@ -0,0 +1,10 @@
+---
+
+nsd_ip: 127.0.0.1
+nsd_conf: "/var/nsd/etc/nsd.conf"
+nsd_zonesdir: "/var/nsd/zones"
+nsd_group: "nsd"
+
+# default DNS TTL
+dns_ttl: 3600
+
diff --git a/roles/dns-nsd/handlers/main.yml b/roles/dns-nsd/handlers/main.yml
new file mode 100644
index 0000000..559cc55
--- /dev/null
+++ b/roles/dns-nsd/handlers/main.yml
@@ -0,0 +1,8 @@
+---
+#file: roles/dns-nsd/handlers/main.yml
+
+- name: reload-nsd
+  service: name=nsd state=reloaded
+
+- name: restart-nsd
+  service: name=nsd state=restarted
diff --git a/roles/dns-nsd/tasks/main.yml b/roles/dns-nsd/tasks/main.yml
new file mode 100644
index 0000000..0df2533
--- /dev/null
+++ b/roles/dns-nsd/tasks/main.yml
@@ -0,0 +1,42 @@
+---
+#file: roles/dns-nsd/tasks/main.yml
+
+# OS specific vars
+- include_vars: "{{ ansible_os_family }}.yml"
+
+# Debian specific installation
+- include: nsd-Debian.yml
+  when: ansible_os_family == 'Debian'
+
+- name: Ensure that zones directory exists
+  file:
+    name={{ nsd_zonesdir }}
+    state=directory
+    mode=0755 owner=root group={{ nsd_group }}
+
+- name: Create nsd.conf from template
+  template:
+    src=nsd.conf.j2
+    dest={{ nsd_conf }}
+    mode=0644 owner=root group={{ nsd_group }}
+  notify:
+    - restart-nsd
+
+- name: create forward zonefiles from template
+  template:
+    src=zone.forward.j2
+    dest={{ nsd_zonesdir }}/{{ item.name }}.forward
+    mode=0644 owner=root group={{ nsd_group }}
+  with_items: '{{ nsd_zones }}'
+  notify:
+    - reload-nsd
+
+- name: create reverse zonefiles from template
+  template:
+    src=zone.reverse.j2
+    dest={{ nsd_zonesdir }}/{{ item.name }}.reverse
+    mode=0644 owner=root group={{ nsd_group }}
+  with_items: '{{ nsd_zones }}'
+  notify:
+    - reload-nsd
+
diff --git a/roles/dns-nsd/tasks/nsd-Debian.yml b/roles/dns-nsd/tasks/nsd-Debian.yml
new file mode 100644
index 0000000..776cbdc
--- /dev/null
+++ b/roles/dns-nsd/tasks/nsd-Debian.yml
@@ -0,0 +1,9 @@
+---
+
+- name: Install nsd
+  apt:
+    name={{ item }}
+    state=present
+  with_items:
+    - nsd
+
diff --git a/roles/dns-nsd/templates/nsd.conf.j2 b/roles/dns-nsd/templates/nsd.conf.j2
new file mode 100644
index 0000000..29ba41a
--- /dev/null
+++ b/roles/dns-nsd/templates/nsd.conf.j2
@@ -0,0 +1,28 @@
+# nsd.conf
+# configured by Ansible!
+
+server:
+  hide-version: yes
+## bind to a specific address/port
+  ip-address: {{ nsd_ip }}
+## port number
+  port: {{ nsd_port|default(53) }} 
+  server-count: 1
+  ip4-only: yes
+  zonesdir: {{ nsd_zonesdir }}
+
+remote-control:
+  control-enable: yes
+
+# zones to load
+{% for zone in nsd_zones %}
+zone:
+  name: {{ zone.name }}
+  zonefile: {{ zone.name }}.forward
+
+zone:
+  name: {{ (zone.ipv4_first_octets ~ ".0") | ipaddr('revdns') | regex_replace('^0\.','') }} 
+  zonefile: {{ zone.name }}.reverse
+
+{% endfor %}
+
diff --git a/roles/dns-nsd/templates/zone.forward.j2 b/roles/dns-nsd/templates/zone.forward.j2
new file mode 100644
index 0000000..f5f8d4e
--- /dev/null
+++ b/roles/dns-nsd/templates/zone.forward.j2
@@ -0,0 +1,32 @@
+;## NSD authoritative only DNS
+;## FORWARD Zone
+
+$ORIGIN {{ item.name }}. ; default zone domain
+$TTL {{ item.ttl | default(dns_ttl) }} ; default time to live
+
+@ IN SOA {{ item.soa }}.{{ item.name }}. admin.{{ item.name }}. (
+         {{ item.serial | default(ansible_date_time.epoch) }}   ; Serial, must be incremented every time you change this file
+         3600        ; Refresh [1hr]
+         600         ; Retry [10m]
+         3600        ; Expire [1hr]
+         60          ; Min TTL [1m]
+         )
+         
+; Name Servers
+{% for ns in item.ns %}
+        IN      NS      {{ ns.name ~ '.' ~ item.name }}.
+{% endfor %}
+
+
+{% set nodes = vars[item.nodelist] %}
+
+;A and CNAME records
+{% for node in nodes %}
+{{ node.name }}    IN    A    {{ item.ipv4_first_octets ~ "." ~ node.ipv4_last_octet }}
+{% if node.aliases is defined %}
+{% for alias in node.aliases %}
+{{ alias }}    IN    CNAME    {{ node.name }}
+{% endfor %}
+{% endif %}
+{% endfor %}
+
diff --git a/roles/dns-nsd/templates/zone.reverse.j2 b/roles/dns-nsd/templates/zone.reverse.j2
new file mode 100644
index 0000000..f327d4b
--- /dev/null
+++ b/roles/dns-nsd/templates/zone.reverse.j2
@@ -0,0 +1,21 @@
+;## NSD authoritative only DNS
+;## REVERSE Zone for {{ item.name }}
+
+$ORIGIN {{ item.name }}. ; default zone domain
+$TTL {{ item.ttl | default(dns_ttl) }} ; default time to live
+
+
+{{ (item.ipv4_first_octets ~ ".0") | ipaddr('revdns') | regex_replace('^0\.','') }} IN SOA {{ item.soa }}.{{ item.name }}. admin.{{ item.name }}. (
+         {{ item.serial | default(ansible_date_time.epoch) }}   ; Serial, must be incremented every time you change this file
+         3600        ; Refresh [1hr]
+         600         ; Retry [10m]
+         3600        ; Expire [1hr]
+         60          ; Min TTL [1m]
+         )
+
+{% set nodes = vars[item.nodelist] %}
+
+;PTR records
+{% for node in nodes %}
+{{ (item.ipv4_first_octets ~ "." ~ node.ipv4_last_octet) | ipaddr('revdns') }} IN PTR {{ node.name }}
+{% endfor %}
diff --git a/roles/dns-nsd/vars/Debian.yml b/roles/dns-nsd/vars/Debian.yml
new file mode 100644
index 0000000..eef2bd6
--- /dev/null
+++ b/roles/dns-nsd/vars/Debian.yml
@@ -0,0 +1,6 @@
+---
+
+nsd_conf: "/etc/nsd/nsd.conf"
+nsd_zonesdir: "/var/lib/nsd/zones"
+nsd_group: "nsd"
+
diff --git a/roles/dns-unbound/defaults/main.yml b/roles/dns-unbound/defaults/main.yml
new file mode 100644
index 0000000..d0553b1
--- /dev/null
+++ b/roles/dns-unbound/defaults/main.yml
@@ -0,0 +1,5 @@
+---
+
+unbound_conf: "/var/unbound/etc/unbound.conf"
+unbound_group: "wheel"
+
diff --git a/roles/dns-unbound/handlers/main.yml b/roles/dns-unbound/handlers/main.yml
new file mode 100644
index 0000000..6ad5de0
--- /dev/null
+++ b/roles/dns-unbound/handlers/main.yml
@@ -0,0 +1,5 @@
+---
+#file: roles/dns-unbound/handlers/main.yml
+
+- name: restart-unbound
+  service: name=unbound state=restarted
diff --git a/roles/dns-unbound/tasks/main.yml b/roles/dns-unbound/tasks/main.yml
new file mode 100644
index 0000000..2666538
--- /dev/null
+++ b/roles/dns-unbound/tasks/main.yml
@@ -0,0 +1,19 @@
+---
+#file: roles/dns-unbound/tasks/main.yml
+
+# OS specific vars
+- include_vars: "{{ ansible_os_family }}.yml"
+
+# Debian specific installation
+- include: unbound-Debian.yml
+  when: ansible_os_family == 'Debian'
+
+- name: create unbound.conf from template
+  template:
+    src=unbound.conf.j2
+    dest={{ unbound_conf }}
+    mode=0644 owner=root group={{ unbound_group }}
+    # validate='unbound-checkconf %s' - can't use, checks path, not just config.
+  notify:
+   - restart-unbound
+
diff --git a/roles/dns-unbound/tasks/unbound-Debian.yml b/roles/dns-unbound/tasks/unbound-Debian.yml
new file mode 100644
index 0000000..da0254f
--- /dev/null
+++ b/roles/dns-unbound/tasks/unbound-Debian.yml
@@ -0,0 +1,9 @@
+---
+
+- name: Install unbound
+  apt:
+    name={{ item }}
+    state=present
+  with_items:
+    - unbound
+
diff --git a/roles/dns-unbound/templates/unbound.conf.j2 b/roles/dns-unbound/templates/unbound.conf.j2
new file mode 100644
index 0000000..2e1c89f
--- /dev/null
+++ b/roles/dns-unbound/templates/unbound.conf.j2
@@ -0,0 +1,42 @@
+# unbound.conf (configured by Ansible)
+
+server:
+  {% for cidr_ipv4 in unbound_interfaces %}
+  interface: {{ cidr_ipv4 | ipaddr('address') }}
+  {% endfor %}
+  verbosity: 1
+  port: 53
+  do-ip4: yes
+  do-udp: yes
+  do-tcp: yes
+
+  # allow from localhost
+  access-control: 127.0.0.0/24 allow
+
+  # allow from local networks
+  {% for cidr_ipv4 in unbound_interfaces %}
+  access-control: {{ cidr_ipv4 }} allow
+  {% endfor %}
+
+{% if nsd_zones is defined %}
+# allow unbound to query localhost, where nsd is listening
+do-not-query-localhost: no
+
+# allow reverse queries for RFC1918 addresses
+{% for zone in nsd_zones %}
+local-zone: "{{ zone.name_reverse_unbound }}." nodefault
+{% endfor %}
+
+# stub-zones zones that nsd is serving
+{% for zone in nsd_zones %}
+stub-zone:
+  name: "{{ zone.name }}"
+  stub-addr: {{ nsd_ip | default("127.0.0.1") }}
+
+stub-zone:
+  name: "{{ zone.name_reverse_unbound }}."
+  stub-addr: {{ nsd_ip | default("127.0.0.1") }}
+
+{% endfor %}
+{% endif %}
+
diff --git a/roles/dns-unbound/vars/Debian.yml b/roles/dns-unbound/vars/Debian.yml
new file mode 100644
index 0000000..1edb86c
--- /dev/null
+++ b/roles/dns-unbound/vars/Debian.yml
@@ -0,0 +1,5 @@
+---
+
+unbound_conf: "/etc/unbound/unbound.conf"
+unbound_group: "unbound"
+
diff --git a/roles/head-prep/tasks/main.yml b/roles/head-prep/tasks/main.yml
new file mode 100644
index 0000000..a930f9a
--- /dev/null
+++ b/roles/head-prep/tasks/main.yml
@@ -0,0 +1,49 @@
+---
+# file: roles/head-prep/tasks/main.yml
+
+- name:  Install prerequisites for using PPA repos
+  apt:
+    name={{ item }}
+  with_items:
+    - python-pycurl
+    - software-properties-common
+
+- name: Add Ansible/Juju repositories
+  apt_repository:
+    repo={{ item }}
+  with_items:
+     - "ppa:juju/stable"
+     - "ppa:ansible/ansible"
+
+- name: Install packages
+  apt:
+    name={{ item }}
+    state=latest
+    update_cache=yes
+  with_items:
+    - ansible
+    - uvtool
+    - git
+    - bzr
+    - juju-core
+    - python-novaclient
+    - python-neutronclient
+    - python-keystoneclient
+    - python-glanceclient
+    - virt-top
+
+- name: Prep user account by adding to libvirtd group and generating SSH key
+  user:
+    name={{ ansible_user_id }}
+    generate_ssh_key=yes
+    groups="libvirtd" append=yes
+
+- name: Register public key in variable
+  shell: cat {{ ansible_user_dir }}/.ssh/id_rsa.pub
+  register: sshkey
+
+- name: Add public key to this user account
+  authorized_key:
+    user={{ ansible_user_id }}
+    key="{{ sshkey.stdout }}"
+
diff --git a/roles/juju-openstack-config/defaults/main.yml b/roles/juju-openstack-config/defaults/main.yml
new file mode 100644
index 0000000..4a0158f
--- /dev/null
+++ b/roles/juju-openstack-config/defaults/main.yml
@@ -0,0 +1,20 @@
+---
+# roles/juju-setup/defaults/main.yml
+
+openstack_version: kilo
+
+openstack_cfg_path: /usr/local/src/openstack.cfg
+
+compute_relations:
+  - name: nova-compute
+    relations: [ "glance", "nova-cloud-controller", "neutron-openvswitch", "nagios", "nrpe", ]
+
+  - name: "nova-compute:shared-db"
+    relations: [ "mysql:shared-db", ]
+
+  - name: "nova-compute:amqp"
+    relations: [ "rabbitmq-server:amqp", ]
+
+  - name: ntp
+    relations: [ "nova-compute", ]
+
diff --git a/roles/juju-openstack-config/files/network-setup.sh b/roles/juju-openstack-config/files/network-setup.sh
new file mode 100755
index 0000000..05e4c12
--- /dev/null
+++ b/roles/juju-openstack-config/files/network-setup.sh
@@ -0,0 +1,40 @@
+#!/bin/bash
+
+source ~/admin-openrc.sh
+
+function create-flat-net {
+    NAME=$1
+    neutron net-show $NAME-net 2>&1 > /dev/null
+    if [ "$?" -ne 0 ]
+    then
+	neutron net-create --provider:physical_network=$NAME --provider:network_type=flat --shared $NAME-net
+    fi
+}
+
+function create-subnet {
+    NAME=$1
+    CIDR=$2
+    GW=$3
+
+    neutron subnet-show $NAME-net 2>&1 > /dev/null
+    if [ "$?" -ne 0 ]
+    then
+	neutron subnet-create $NAME-net --name $NAME-net $CIDR --gateway=$GW --disable-dhcp
+    fi
+}
+
+function create-subnet-no-gateway {
+    NAME=$1
+    CIDR=$2
+
+    neutron subnet-show $NAME-net 2>&1 > /dev/null
+    if [ "$?" -ne 0 ]
+    then
+	neutron subnet-create $NAME-net --name $NAME-net $CIDR --no-gateway --disable-dhcp
+    fi
+}
+
+create-flat-net nat
+create-subnet nat 172.16.0.0/16 172.16.0.1
+
+create-flat-net ext
diff --git a/roles/juju-openstack-config/handlers/main.yml b/roles/juju-openstack-config/handlers/main.yml
new file mode 100644
index 0000000..c90c918
--- /dev/null
+++ b/roles/juju-openstack-config/handlers/main.yml
@@ -0,0 +1,7 @@
+---
+# roles/juju-openstack-config/handlers/tasks.yml
+
+- name: update-ca-certificates
+  become: yes
+  command: update-ca-certificates
+
diff --git a/roles/juju-openstack-config/tasks/main.yml b/roles/juju-openstack-config/tasks/main.yml
new file mode 100644
index 0000000..7d6d1b0
--- /dev/null
+++ b/roles/juju-openstack-config/tasks/main.yml
@@ -0,0 +1,56 @@
+---
+# roles/juju-openstack-config/main/tasks.yml
+
+- name: add compute nodes
+  command: juju add-machine ssh:ubuntu@{{ item }}
+  with_items: "{{ groups['compute'] | difference( juju_machines.keys() ) }}"
+  register: added-compute-nodes
+
+# run this again, so add-machine items will be in the juju_compute_nodes list
+- name: Obtain Juju Facts after adding compute nodes
+  when: added-compute-nodes
+  juju_facts:
+
+# the crazy [ ] in the with-items is so that jinja compares arrays of strings,
+# rather than strings of characters
+- name: add-unit nova-compute to first compute node
+  command: "juju deploy nova-compute --to {{ juju_machines[item]['machine_id'] }} --config={{ openstack_cfg_path }}"
+  with_items: "{{ [ groups['compute'][0] ] | difference( juju_compute_nodes.keys() ) }}"
+  register: added-first-nova-compute
+
+# run this again, so first nova compute will be in the juju_compute_nodes list
+- name: Obtain Juju Facts nova-compute deploy
+  juju_facts:
+  when: added-first-nova-compute
+
+- name: add-unit nova-compute to other compute nodes
+  command: "juju add-unit nova-compute --to {{ juju_machines[item]['machine_id'] }}"
+  with_items: "{{ groups['compute'] | difference( juju_compute_nodes.keys() )  }}"
+
+# added this to openstack.cfg
+# - name: Have nova-compute use KVM as its virt-type
+#   command: juju set nova-compute virt-type=kvm
+
+- name: Create relations to compute
+  command: "juju add-relation '{{ item.0.name }}' '{{ item.1 }}'"
+  register: compute_relation
+  failed_when: "compute_relation|failed and 'relation already exists' not in compute_relation.stderr"
+  with_subelements:
+    - "{{ compute_relations }}"
+    - relations
+
+# need to ansible-ify these
+- name: Copy credentials file to nova-cloud-controller
+  command: "scp {{ ansible_user_dir }}/admin-openrc.sh ubuntu@nova-cloud-controller:"
+
+- name: Copy network setup script
+  become: yes
+  copy:
+    src=network-setup.sh
+    dest=/usr/local/src/network-setup.sh
+    mode=0644 owner=root
+
+- name: Run network setup script
+  command: ansible nova-cloud-controller-1 -m script -u ubuntu -a "/usr/local/src/network-setup.sh"
+
+
diff --git a/roles/juju-setup/defaults/main.yml b/roles/juju-setup/defaults/main.yml
new file mode 100644
index 0000000..cd21505
--- /dev/null
+++ b/roles/juju-setup/defaults/main.yml
@@ -0,0 +1,2 @@
+---
+
diff --git a/roles/juju-setup/files/daemon b/roles/juju-setup/files/daemon
new file mode 100644
index 0000000..8d9102b
--- /dev/null
+++ b/roles/juju-setup/files/daemon
@@ -0,0 +1,40 @@
+#!/bin/sh
+
+SHELL="/bin/bash"
+
+NIC=$( route|grep default|awk '{print $NF}' )
+
+NAME="${1}"
+OP="${2}"
+SUBOP="${3}"
+ARGS="${4}"
+
+add_port_fwd_rule() {
+    DPORT=$1
+    VM=$2
+    TOPORT=$3
+
+    VMIP=$( getent ahosts $VM|head -1|awk '{print $1}' )
+    iptables -t nat -C PREROUTING -p tcp -i $NIC --dport $DPORT -j DNAT --to-destination $VMIP:$TOPORT
+    if [ "$?" -ne 0 ]
+    then
+        iptables -t nat -A PREROUTING -p tcp -i $NIC --dport $DPORT -j DNAT --to-destination $VMIP:$TOPORT
+    fi
+}
+
+if [ "$OP" = "start" ] || [ "$OP" = "reload" ]
+then
+    iptables -t nat -F
+    add_port_fwd_rule 35357 keystone 35357
+    add_port_fwd_rule 4990 keystone 4990
+    add_port_fwd_rule 5000 keystone 5000
+    add_port_fwd_rule 8774 nova-cloud-controller 8774
+    add_port_fwd_rule 9696 neutron-api 9696
+    add_port_fwd_rule 9292 glance 9292
+    add_port_fwd_rule 8080 openstack-dashboard 80
+    add_port_fwd_rule 3128 nagios 80
+    add_port_fwd_rule 8777 ceilometer 8777
+
+    # Also flush the filter table before rules re-added
+    iptables -F
+fi
diff --git a/roles/juju-setup/tasks/main.yml b/roles/juju-setup/tasks/main.yml
new file mode 100644
index 0000000..672c9be
--- /dev/null
+++ b/roles/juju-setup/tasks/main.yml
@@ -0,0 +1,124 @@
+---
+# roles/juju-setup/main/tasks.yml
+
+- name: create Virtual Machines with uvt-kvm
+  shell: uvt-kvm create {{ item.name }} --cpu={{ item.cpu }} --memory={{ item.memMB }} --disk={{ item.diskGB }} ; \
+    uvt-kvm wait --insecure {{ item.name }}
+    creates=/var/lib/uvtool/libvirt/images/{{ item.name }}.qcow
+  with_items: "{{ head_vm_list }}"
+
+- name: Have VMs autostart on reboot
+  command: virsh autostart {{ item.name }}
+  with_items: "{{ head_vm_list }}"
+
+- name: Create /etc/ansible/hosts file
+  become: yes
+  template:
+    src=ansible_hosts.j2
+    dest=/etc/ansible/hosts
+
+- name: Verify that we can log into every VM
+  command: ansible services -m ping -u ubuntu
+
+- name: Update software in all the VMs
+  command: ansible services -m apt -b -u ubuntu -a "upgrade=dist update_cache=yes cache_valid_time=3600"
+
+- name: Create VM's eth0 interface config file for DNS config via resolvconf program
+  template:
+    src=eth0.cfg.j2
+    dest={{ ansible_user_dir }}/eth0.cfg
+
+- name: Copy eth0 interface config file to all VMs
+  command: ansible services -b -u ubuntu -m copy -a "src={{ ansible_user_dir }}/eth0.cfg dest=/etc/network/interfaces.d/eth0.cfg owner=root group=root mode=0644"
+
+- name: Restart eth0 interface on all VMs
+  command: ansible services -b -u ubuntu -m shell -a "ifdown eth0 ; ifup eth0"
+
+- name: Initialize Juju
+  command: juju generate-config
+    creates={{ ansible_user_dir }}/.juju/environments.yaml
+
+- name: Create Juju config file from template
+  template:
+    src=environments.yaml.j2
+    dest={{ ansible_user_dir }}/.juju/environments.yaml
+
+- name: Bootstrap Juju
+  command: juju bootstrap
+    creates={{ ansible_user_dir }}/.juju/environments/manual.jenv
+
+- name: Create openstack.cfg for Juju from template
+  become: yes
+  template:
+    src=openstack.cfg.j2
+    dest={{ openstack_cfg_path }}
+
+# Code for this is in library/juju_facts.py
+- name: Obtain Juju Facts for creating machines
+  juju_facts:
+
+# For setwise operations on desired vs Juju state:
+# list of VM names in head_vm_list dict: head_vm_list | map(attribute='name') | list
+# list of active juju_machines names: juju_machines.keys()
+# list of active juju_services names: juju_services.keys()
+
+- name: Add machines to Juju
+  command: "juju add-machine ssh:{{ item }}"
+  with_items: "{{ head_vm_list | map(attribute='service') | list | difference( juju_machines.keys() ) }}"
+
+# run this again, so machines will be in the juju_machines list
+- name: Obtain Juju Facts after machine creation
+  juju_facts:
+
+- name: Deploy services that are hosted in their own VM
+  command: "juju deploy {{ item }} --to {{ juju_machines[item]['machine_id'] }} --config={{ openstack_cfg_path }}"
+  with_items: "{{ vm_service_list | difference( juju_services.keys() ) }}"
+
+- name: Deploy mongodb to ceilometer VM
+  command: "juju deploy mongodb --to {{ juju_machines['ceilometer']['machine_id'] }} --config={{ openstack_cfg_path }}"
+  when: juju_services['mongodb'] is undefined
+
+- name: Deploy services that don't have their own VM
+  command: "juju deploy {{ item }} --config={{ openstack_cfg_path }}"
+  with_items: "{{ standalone_service_list | difference( juju_services.keys() ) }}"
+
+# FIXME: ignoring errors when creating relationships.
+# Previous method wasn't idempotent either
+- name: Create relations between services
+  command: "juju add-relation '{{ item.0.name }}' '{{ item.1 }}'"
+  register: juju_relation
+  failed_when: "juju_relation|failed and 'relation already exists' not in juju_relation.stderr"
+  with_subelements:
+    - "{{ service_relations }}"
+    - relations
+
+# run another time, so services will be in juju_services list
+- name: Obtain Juju Facts after service creation
+  juju_facts:
+
+# This should be able to test for the VM's coming up, but not working right now
+#- name: Wait for juju services on VM's to come up
+#  wait_for:
+#    port={{ item.ext }}
+#    timeout=10
+#  with_items: "{{ head_vm_list | map(attribute='forwarded_ports') | reject('undefined') | list }}"
+
+# - name: Obtain keystone admin password
+#   command: "juju run --unit={{ juju_services['keystone']['units'].keys()[0] }} 'sudo cat /var/lib/keystone/keystone.passwd'"
+#   register: keystone_password
+
+- name: Create admin-openrc.sh credentials file
+  template:
+   src=admin-openrc.sh.j2
+   dest={{ ansible_user_dir }}/admin-openrc.sh
+
+- name: Copy nova-cloud-controller CA certificate to head
+  command: juju scp {{ juju_services['nova-cloud-controller']['units'].keys()[0] }}:/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt {{ ansible_user_dir }}
+    creates={{ ansible_user_dir }}/keystone_juju_ca_cert.crt
+
+- name: Move cert to system location
+  become: yes
+  command: mv {{ ansible_user_dir }}/keystone_juju_ca_cert.crt /usr/local/share/ca-certificates
+    creates=/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt
+  notify: update-ca-certificates
+
diff --git a/roles/juju-setup/templates/admin-openrc.sh.j2 b/roles/juju-setup/templates/admin-openrc.sh.j2
new file mode 100644
index 0000000..bd195a4
--- /dev/null
+++ b/roles/juju-setup/templates/admin-openrc.sh.j2
@@ -0,0 +1,5 @@
+export OS_USERNAME=admin
+export OS_PASSWORD={{ keystone_admin_password }}
+export OS_TENANT_NAME=admin
+export OS_AUTH_URL=https://keystone.{{ site_suffix }}:5000/v2.0
+export OS_REGION_NAME=RegionOne
diff --git a/roles/juju-setup/templates/ansible_hosts.j2 b/roles/juju-setup/templates/ansible_hosts.j2
new file mode 100644
index 0000000..9b6096e
--- /dev/null
+++ b/roles/juju-setup/templates/ansible_hosts.j2
@@ -0,0 +1,8 @@
+[localhost]
+127.0.0.1 hostname={{ ansible_fqdn }}
+
+[services]
+{% for vm in head_vm_list -%}
+{{ vm.name }}
+{% endfor -%}
+
diff --git a/roles/juju-setup/templates/environments.yaml.j2 b/roles/juju-setup/templates/environments.yaml.j2
new file mode 100644
index 0000000..e575b23
--- /dev/null
+++ b/roles/juju-setup/templates/environments.yaml.j2
@@ -0,0 +1,7 @@
+default: manual
+environments:
+    manual:
+        type: manual
+        bootstrap-host: juju
+        bootstrap-user: ubuntu
+        default-series: {{ ansible_distribution_release }}
diff --git a/roles/juju-setup/templates/eth0.cfg.j2 b/roles/juju-setup/templates/eth0.cfg.j2
new file mode 100644
index 0000000..0baa7a8
--- /dev/null
+++ b/roles/juju-setup/templates/eth0.cfg.j2
@@ -0,0 +1,7 @@
+# The primary network interface
+auto eth0
+iface eth0 inet dhcp
+    dns-nameservers{% for ns in dns_servers %} {{ ns }}{% endfor %} 
+{% if dns_search is defined %}
+    dns-search{% for searchdom in dns_search %} {{ searchdom }}{% endfor %}
+{% endif %}
diff --git a/roles/juju-setup/templates/openstack.cfg.j2 b/roles/juju-setup/templates/openstack.cfg.j2
new file mode 100644
index 0000000..5a1f224
--- /dev/null
+++ b/roles/juju-setup/templates/openstack.cfg.j2
@@ -0,0 +1,44 @@
+ceilometer:
+ceilometer-agent:
+glance:
+   openstack-origin: "cloud:trusty-kilo"
+keystone:
+   use-https: "yes"
+   https-service-endpoints: "True"
+   openstack-origin: "cloud:trusty-kilo"
+mysql:
+mongodb:
+nagios:
+neutron-api:
+   flat-network-providers: "*"
+   openstack-origin: "cloud:trusty-kilo"
+   vlan-ranges: "physnet1:1000:2000 nat"
+neutron-gateway:
+   bridge-mappings: "physnet1:br-data nat:br-nat"
+   flat-network-providers: "*"
+   instance-mtu: "1400"
+   openstack-origin: "cloud:trusty-kilo"
+   vlan-ranges: "physnet1:1000:2000 nat"
+neutron-openvswitch:
+   bridge-mappings: "physnet1:br-data nat:br-nat"
+   disable-security-groups: "True"
+   flat-network-providers: "*"
+   vlan-ranges: "physnet1:1000:2000 nat"
+nova-cloud-controller:
+   console-access-protocol: "novnc"
+   network-manager: "Neutron"
+   openstack-origin: "cloud:trusty-kilo"
+nova-compute:
+   virt-type:kvm
+   config-flags: "firewall_driver=nova.virt.firewall.NoopFirewallDriver"
+#   config-flags: "firewall_driver=nova.virt.firewall.NoopFirewallDriver,xos_api_url=http://portal.opencloud.us"
+   disable-neutron-security-groups: "True"
+   openstack-origin: "cloud:trusty-kilo"
+ntp:
+   source: "0.ubuntu.pool.ntp.org 1.ubuntu.pool.ntp.org 2.ubuntu.pool.ntp.org 3.ubuntu.pool.ntp.org"
+nrpe:
+openstack-dashboard:
+   openstack-origin: "cloud:trusty-kilo"
+rabbitmq-server:
+  ssl: "on"
+
diff --git a/roles/juju-user-prep/files/ansible.cfg b/roles/juju-user-prep/files/ansible.cfg
new file mode 100644
index 0000000..dd43d2b
--- /dev/null
+++ b/roles/juju-user-prep/files/ansible.cfg
@@ -0,0 +1,2 @@
+[defaults]
+host_key_checking = false
diff --git a/roles/juju-user-prep/tasks/main.yml b/roles/juju-user-prep/tasks/main.yml
new file mode 100644
index 0000000..6e61ad3
--- /dev/null
+++ b/roles/juju-user-prep/tasks/main.yml
@@ -0,0 +1,21 @@
+---
+# roles/juju-user-prep/main/tasks.yml
+
+- name: Disable host key checking in ~/.ssh/config
+  lineinfile:
+    dest={{ ansible_user_dir }}/.ssh/config
+    line="StrictHostKeyChecking no"
+    create=yes
+    mode=0600
+
+- name: Disable host key checking in ~/.ansible.cfg
+  copy:
+    src=ansible.cfg
+    dest={{ ansible_user_dir }}/.ansible.cfg
+
+
+- name: Get public ssh key from head node ubuntu user
+  command: cat {{ ansible_user_dir }}/.ssh/id_rsa.pub
+  register: head_ssh_pubkey
+
+
diff --git a/roles/test-prep/tasks/main.yml b/roles/test-prep/tasks/main.yml
new file mode 100644
index 0000000..1ebf604
--- /dev/null
+++ b/roles/test-prep/tasks/main.yml
@@ -0,0 +1,9 @@
+---
+# roles/test-prep/tasks/main.yml
+
+- name: Add local resolver to /etc/resolv.conf
+  lineinfile:
+    dest=/etc/resolv.conf
+    insertafter=".*DO NOT EDIT THIS FILE.*" 
+    line="nameserver 192.168.122.1"
+
diff --git a/roles/xos-install/defaults/main.yml b/roles/xos-install/defaults/main.yml
new file mode 100644
index 0000000..ba26bf6
--- /dev/null
+++ b/roles/xos-install/defaults/main.yml
@@ -0,0 +1,6 @@
+---
+# default variables for xos-install role
+
+xos_repo_url: "https://github.com/open-cloud/xos.git"
+xos_repo_dest: "~/xos"
+xos_repo_branch: "HEAD"
diff --git a/roles/xos-install/tasks/main.yml b/roles/xos-install/tasks/main.yml
new file mode 100644
index 0000000..d982c26
--- /dev/null
+++ b/roles/xos-install/tasks/main.yml
@@ -0,0 +1,7 @@
+---
+# tasks for xos-install role
+
+ - name: checkout XOS repo
+   git: repo={{ xos_repo_url }}
+        dest={{ xos_repo_dest }}
+        version={{ xos_repo_branch }}
diff --git a/vars/aztest.yml b/vars/aztest.yml
new file mode 100644
index 0000000..fd11cd8
--- /dev/null
+++ b/vars/aztest.yml
@@ -0,0 +1,33 @@
+---
+# file: group_vars/aztest.yml
+
+# IP prefix for VMs
+mgmtbr_prefix: 192.168.250
+
+# site domain suffix
+site_suffix: aztest.infra.opencloud.us
+
+# NSD/Unbound settings
+
+nsd_zones:
+  - name: aztest.infra.opencloud.us
+    ipv4_first_octets: 192.168.250
+    name_reverse_unbound: "168.192.in-addr.arpa"
+    soa: ns1
+    ns:
+      - { name: ns1 }
+    nodelist: head_vm_list
+
+unbound_interfaces:
+  #  - 192.168.250.1/24
+  - 206.207.253.10/28
+
+# resolv.conf settings
+dns_servers:
+  - 206.207.253.10
+
+dns_search:
+  - aztest.infra.opencloud.us
+  - opencloud.cs.arizona.edu
+
+
diff --git a/vars/cord.yml b/vars/cord.yml
new file mode 100644
index 0000000..0103ec8
--- /dev/null
+++ b/vars/cord.yml
@@ -0,0 +1,31 @@
+---
+# file: group_vars/aztest.yml
+
+# IP prefix for VMs
+mgmtbr_prefix: 192.168.122
+
+# site domain suffix
+site_suffix: cordtest.opencloud.us
+
+# NSD/Unbound settings
+
+nsd_zones:
+  - name: cordtest.opencloud.us
+    ipv4_first_octets: 192.168.122
+    name_reverse_unbound: "168.192.in-addr.arpa"
+    soa: ns1
+    ns:
+      - { name: ns1 }
+    nodelist: head_vm_list
+
+unbound_interfaces:
+  - 128.104.222.180/23
+
+# resolv.conf settings
+dns_servers:
+  - 128.104.222.180
+
+dns_search:
+  - cordtest.opencloud.us
+
+
diff --git a/vars/cord_defaults.yml b/vars/cord_defaults.yml
new file mode 100644
index 0000000..5fc9d7d
--- /dev/null
+++ b/vars/cord_defaults.yml
@@ -0,0 +1,215 @@
+---
+# vars/opencloud_defaults.yml
+
+openstack_version: kilo
+openstack_cfg_path: /usr/local/src/openstack.cfg
+
+mgmtbr_name: mgmtbr
+
+head_vm_list:
+  - name: "juju-1"
+    service: "juju"
+    aliases:
+       - "juju"
+    ipv4_last_octet: 10
+    cpu: 1
+    memMB: 2048
+    diskGB: 20
+
+  - name: "ceilometer-1"
+    service: "ceilometer"
+    aliases:
+      - "ceilometer"
+    ipv4_last_octet: 20
+    cpu: 1
+    memMB: 2048
+    diskGB: 20
+    forwarded_ports:
+      - { ext: 8777, int: 8777 }
+
+  - name: "glance-1"
+    service: "glance"
+    aliases:
+      - "glance"
+    ipv4_last_octet: 30
+    cpu: 2
+    memMB: 4096
+    diskGB: 160
+    forwarded_ports:
+      - { ext: 9292, int: 9292 }
+
+  - name: "keystone-1"
+    service: "keystone"
+    aliases:
+      - "keystone"
+    ipv4_last_octet: 40
+    cpu: 2
+    memMB: 4096
+    diskGB: 40
+    forwarded_ports:
+      - { ext: 35357, int: 35357 }
+      - { ext: 4990, int: 4990 }
+      - { ext: 5000, int: 5000 }
+
+  - name: "mysql-1"
+    service: "mysql"
+    aliases:
+      - "mysql"
+    ipv4_last_octet: 50
+    cpu: 2
+    memMB: 4096
+    diskGB: 40
+
+  - name: "nagios-1"
+    service: "nagios"
+    aliases:
+      - "nagios"
+    ipv4_last_octet: 60
+    cpu: 1
+    memMB: 2048
+    diskGB: 20
+    forwarded_ports:
+      - { ext: 3128, int: 80 }
+
+  - name: "neutron-api-1"
+    service: "neutron-api"
+    aliases:
+      - "neutron-api"
+    ipv4_last_octet: 70
+    cpu: 2
+    memMB: 4096
+    diskGB: 40
+    forwarded_ports:
+      - { ext: 9696, int: 9696 }
+
+  - name: "nova-cloud-controller-1"
+    service: "nova-cloud-controller"
+    aliases:
+      - "nova-cloud-controller"
+    ipv4_last_octet: 80
+    cpu: 2
+    memMB: 4096
+    diskGB: 40
+    forwarded_ports:
+      - { ext: 8774, int: 8774 }
+
+  - name: "openstack-dashboard-1"
+    service: "openstack-dashboard"
+    aliases:
+      - "openstack-dashboard"
+    ipv4_last_octet: 90
+    cpu: 1
+    memMB: 2048
+    diskGB: 20
+    forwarded_ports:
+      - { ext: 8080, int: 80 }
+
+  - name: "rabbitmq-server-1"
+    service: "rabbitmq-server"
+    aliases:
+      - "rabbitmq-server"
+    ipv4_last_octet: 100
+    cpu: 2
+    memMB: 4096
+    diskGB: 40
+
+  - name: "onos-cord-1"
+    aliases:
+      - "onos-cord"
+    ipv4_last_octet: 110
+    cpu: 2
+    memMB: 4096
+    diskGB: 40
+
+  - name: "onos-fabric-1"
+    aliases:
+      - "onos-fabric"
+    ipv4_last_octet: 120
+    cpu: 2
+    memMB: 4096
+    diskGB: 40
+
+  - name: "xos-1"
+    aliases:
+      - "xos"
+    ipv4_last_octet: 130
+    cpu: 2
+    memMB: 4096
+    diskGB: 40
+
+  - name: "nova-compute-1"
+    service: "nova-compute"
+    aliases:
+      - "nova-compute"
+    ipv4_last_octet: 140
+    cpu: 6
+    memMB: 16384
+    diskGB: 240
+
+
+vm_service_list:
+  - ceilometer
+  - glance
+  - keystone
+  - mysql
+  - nagios
+  - neutron-api
+  - nova-cloud-controller
+  - openstack-dashboard
+  - rabbitmq-server
+  - nova-compute
+
+standalone_service_list:
+  - ntp
+  - nrpe
+  - ceilometer-agent
+
+service_relations:
+  - name: keystone
+    relations: [ "mysql", "nrpe", ]
+
+  - name: nova-cloud-controller
+    relations: [ "mysql", "rabbitmq-server", "glance", "keystone", "nrpe", ]
+
+  - name: glance
+    relations: [ "mysql", "keystone", "nrpe", ]
+
+  - name: neutron-api
+    relations: [ "keystone", "neutron-openvswitch", "mysql", "rabbitmq-server", "nova-cloud-controller", "nrpe", ]
+
+  - name: neutron-openvswitch
+    relations: [ "rabbitmq-server", ]
+
+  - name: openstack-dashboard
+    relations: [ "keystone", "nrpe", ]
+
+  - name: nagios
+    relations: [ "nrpe", ]
+
+  - name: "mysql:juju-info"
+    relations: [ "nrpe:general-info", ]
+
+  - name: rabbitmq-server
+    relations: [ "nrpe", ]
+
+  - name: ceilometer
+    relations: [ "mongodb", "rabbitmq-server", "nagios", "nrpe", ]
+
+  - name: "ceilometer:identity-service"
+    relations: [ "keystone:identity-service", ]
+
+  - name: "ceilometer:ceilometer-service"
+    relations: [ "ceilometer-agent:ceilometer-service", ]
+
+  - name: nova-compute
+    relations: [ "glance", "nova-cloud-controller", "nagios", "nrpe", ]
+
+  - name: "nova-compute:shared-db"
+    relations: [ "mysql:shared-db", ]
+
+  - name: "nova-compute:amqp"
+    relations: [ "rabbitmq-server:amqp", ]
+
+  - name: ntp
+    relations: [ "nova-compute", ]
+
diff --git a/vars/example_keystone.yml b/vars/example_keystone.yml
new file mode 100644
index 0000000..14df06f
--- /dev/null
+++ b/vars/example_keystone.yml
@@ -0,0 +1,4 @@
+---
+
+keystone_admin_password: "VeryLongKeystoneAdminPassword"
+
diff --git a/vars/opencloud_defaults.yml b/vars/opencloud_defaults.yml
new file mode 100644
index 0000000..41628a8
--- /dev/null
+++ b/vars/opencloud_defaults.yml
@@ -0,0 +1,184 @@
+---
+# vars/opencloud_defaults.yml
+
+openstack_version: kilo
+openstack_cfg_path: /usr/local/src/openstack.cfg
+
+head_vm_list:
+  - name: "juju-1"
+    service: "juju"
+    aliases:
+       - "juju"
+    ipv4_last_octet: 10
+    cpu: 1
+    memMB: 2048
+    diskGB: 20
+
+  - name: "ceilometer-1"
+    service: "ceilometer"
+    aliases:
+      - "ceilometer"
+    ipv4_last_octet: 20
+    cpu: 1
+    memMB: 2048
+    diskGB: 20
+    forwarded_ports:
+      - { ext: 8777, int: 8777 }
+
+  - name: "glance-1"
+    service: "glance"
+    aliases:
+      - "glance"
+    ipv4_last_octet: 30
+    cpu: 2
+    memMB: 4096
+    diskGB: 160
+    forwarded_ports:
+      - { ext: 9292, int: 9292 }
+
+  - name: "keystone-1"
+    service: "keystone"
+    aliases:
+      - "keystone"
+    ipv4_last_octet: 40
+    cpu: 2
+    memMB: 4096
+    diskGB: 40
+    forwarded_ports:
+      - { ext: 35357, int: 35357 }
+      - { ext: 4990, int: 4990 }
+      - { ext: 5000, int: 5000 }
+
+  - name: "mysql-1"
+    service: "mysql"
+    aliases:
+      - "mysql"
+    ipv4_last_octet: 50
+    cpu: 2
+    memMB: 4096
+    diskGB: 40
+
+  - name: "nagios-1"
+    service: "nagios"
+    aliases:
+      - "nagios"
+    ipv4_last_octet: 60
+    cpu: 1
+    memMB: 2048
+    diskGB: 20
+    forwarded_ports:
+      - { ext: 3128, int: 80 }
+
+  - name: "neutron-api-1"
+    service: "neutron-api"
+    aliases:
+      - "neutron-api"
+    ipv4_last_octet: 70
+    cpu: 2
+    memMB: 4096
+    diskGB: 40
+    forwarded_ports:
+      - { ext: 9696, int: 9696 }
+
+  - name: "neutron-gateway-1"
+    service: "neutron-gateway"
+    aliases:
+      - "neutron-gateway"
+    ipv4_last_octet: 80
+    cpu: 2
+    memMB: 4096
+    diskGB: 40
+
+  - name: "nova-cloud-controller-1"
+    service: "nova-cloud-controller"
+    aliases:
+      - "nova-cloud-controller"
+    ipv4_last_octet: 90
+    cpu: 2
+    memMB: 4096
+    diskGB: 40
+    forwarded_ports:
+      - { ext: 8774, int: 8774 }
+
+  - name: "openstack-dashboard-1"
+    service: "openstack-dashboard"
+    aliases:
+      - "openstack-dashboard"
+    ipv4_last_octet: 100
+    cpu: 1
+    memMB: 2048
+    diskGB: 20
+    forwarded_ports:
+      - { ext: 8080, int: 80 }
+
+  - name: "rabbitmq-server-1"
+    service: "rabbitmq-server"
+    aliases:
+      - "rabbitmq-server"
+    ipv4_last_octet: 110
+    cpu: 2
+    memMB: 4096
+    diskGB: 40
+
+vm_service_list:
+  - ceilometer
+  - glance
+  - keystone
+  - mysql
+  - nagios
+  - neutron-api
+  - neutron-gateway
+  - nova-cloud-controller
+  - openstack-dashboard
+  - rabbitmq-server
+
+standalone_service_list:
+  - ntp
+  - nrpe
+  - ceilometer-agent
+  - neutron-openvswitch
+
+service_relations:
+  - name: keystone
+    relations: [ "mysql", "nrpe", ]
+
+  - name: nova-cloud-controller
+    relations: [ "mysql", "rabbitmq-server", "glance", "keystone", "nrpe", ]
+
+  - name: glance
+    relations: [ "mysql", "keystone", "nrpe", ]
+
+  - name: neutron-gateway
+    relations: [ "neutron-api", "nova-cloud-controller", "mysql", "nrpe", ]
+
+  - name: "neutron-gateway:amqp"
+    relations: [ "rabbitmq-server:amqp", ]
+
+  - name: neutron-api
+    relations: [ "keystone", "neutron-openvswitch", "mysql", "rabbitmq-server", "nova-cloud-controller", "nrpe", ]
+
+  - name: neutron-openvswitch
+    relations: [ "rabbitmq-server", ]
+
+  - name: openstack-dashboard
+    relations: [ "keystone", "nrpe", ]
+
+  - name: nagios
+    relations: [ "nrpe", ]
+
+  - name: "mysql:juju-info"
+    relations: [ "nrpe:general-info", ]
+
+  - name: rabbitmq-server
+    relations: [ "nrpe", ]
+
+  - name: ceilometer
+    relations: [ "mongodb", "rabbitmq-server", "nagios", "nrpe", ]
+
+  - name: "ceilometer:identity-service"
+    relations: [ "keystone:identity-service", ]
+
+  - name: "ceilometer:ceilometer-service"
+    relations: [ "ceilometer-agent:ceilometer-service", ]
+
+