Merge remote-tracking branch 'origin/master' into roles
diff --git a/aztest-playbook.yml b/aztest-playbook.yml
new file mode 100644
index 0000000..837c14a
--- /dev/null
+++ b/aztest-playbook.yml
@@ -0,0 +1,23 @@
+---
+# aztest playbook, for installing OpenCloud
+
+# Prepare the head node and install juju
+- hosts: head
+  roles:
+    - { role: common-prep, become: yes }
+    - { role: head-prep, become: yes }
+    - juju-user-prep
+    - juju-setup
+
+# prepare the compute nodes
+- hosts: compute
+  become: yes
+  roles:
+    - { role: common-prep, become: yes }
+    - { role: compute-prep, become: yes }
+
+# Finish the openstack config on head (needs compute nodes up to finish)
+- hosts: head
+  roles:
+    - juju-openstack-config
+
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/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/aztest.yml b/group_vars/aztest.yml
new file mode 100644
index 0000000..ef3310d
--- /dev/null
+++ b/group_vars/aztest.yml
@@ -0,0 +1,6 @@
+---
+# file: group_vars/aztest.yml
+
+mgmt_net_prefix: 192.168.250
+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..d7e8cd4
--- /dev/null
+++ b/inventory/aztest
@@ -0,0 +1,8 @@
+[all:vars]
+ansible_ssh_user=ubuntu
+
+[head]
+node11.opencloud.cs.arizona.edu
+
+[compute]
+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-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..70850e0
--- /dev/null
+++ b/library/juju_facts.py
@@ -0,0 +1,27 @@
+#!/usr/bin/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
+
+print json.dumps({
+    "changed": True,
+    "ansible_facts" : {
+        "juju_enviromnment": juju_status['environment'],
+        "juju_machines": juju_machines,
+        "juju_services": juju_status['services'],
+    },
+})
+
diff --git a/roles/cloudlab-prep/tasks/main.yml b/roles/cloudlab-prep/tasks/main.yml
new file mode 100644
index 0000000..e23168b
--- /dev/null
+++ b/roles/cloudlab-prep/tasks/main.yml
@@ -0,0 +1,7 @@
+---
+# roles/cloudlab-prep/tasks/main.yml
+
+- name:  Set up extra disk space
+  command: /usr/testbed/bin/mkextrafs -f /var/lib/uvtool/libvirt/images
+    creates=/var/lib/uvtool/libvirt/images/lost+found
+
diff --git a/roles/common-prep/handlers/main.yml b/roles/common-prep/handlers/main.yml
new file mode 100644
index 0000000..79f43d9
--- /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 machine
+  shell: sleep 2 && shutdown -r now "Ansible updates triggered"
+  async: 1
+  poll: 0
+  ignore_errors: true
+
+# wait 1m, then try to contact machine for 5m
+- name: wait for machine
+  become: false
+  local_action:
+     wait_for host={{ inventory_hostname }}
+     port=22
+     delay=60 timeout=300
+     state=started
+
diff --git a/roles/common-prep/tasks/main.yml b/roles/common-prep/tasks/main.yml
new file mode 100644
index 0000000..a7ffc3b
--- /dev/null
+++ b/roles/common-prep/tasks/main.yml
@@ -0,0 +1,20 @@
+---
+# file: roles/common-prep/tasks/main.yml
+
+- name: Upgrade system to current using apt
+  apt: update_cache=yes upgrade=dist
+  notify:
+    - restart machine
+    - wait for machine
+
+- 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/tasks/main.yml b/roles/compute-prep/tasks/main.yml
new file mode 100644
index 0000000..aedd62a
--- /dev/null
+++ b/roles/compute-prep/tasks/main.yml
@@ -0,0 +1,8 @@
+---
+# file: roles/compute-prep/tasks/main.yml
+
+- name:  Install packages
+  apt: name={{ item }} state=latest update_cache=yes
+  with_items:
+    - python-yaml
+
diff --git a/roles/head-prep/tasks/main.yml b/roles/head-prep/tasks/main.yml
new file mode 100644
index 0000000..7755eba
--- /dev/null
+++ b/roles/head-prep/tasks/main.yml
@@ -0,0 +1,76 @@
+---
+# 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
+
+- name: Obtain the juju-ansible tool from github
+  git:
+    repo=https://github.com/cmars/juju-ansible.git
+    dest=/usr/local/src/juju-ansible
+    version="HEAD"
+
+- name: Create symlinks to the juju-ansible tool
+  file:
+    src=/usr/local/src/juju-ansible/juju-ansible
+    dest={{ item }}
+    state=link
+  with_items:
+    - "/usr/local/bin/juju-ansible"
+    - "/usr/local/bin/juju-ansible-playbook"
+
+- name: Prepare user account and generate SSH key
+  user:
+    name={{ ansible_user_id }}
+    generate_ssh_key=yes
+    groups="libvirtd" append=yes
+
+- name: Get public key
+  shell: cat {{ ansible_user_dir }}/.ssh/id_rsa.pub
+  register: sshkey
+
+- name: Add key to this user account
+  authorized_key:
+    user={{ ansible_user_id }}
+    key="{{ sshkey.stdout }}"
+
+- name: Copy keypair to /tmp
+  shell: cp -f {{ ansible_user_dir }}/.ssh/{{ item }} /tmp; chmod +r /tmp/{{ item }}
+  with_items:
+    - id_rsa
+    - id_rsa.pub
+
+- name: Get ubuntu image for uvtool
+  shell: uvt-simplestreams-libvirt sync --source http://cloud-images.ubuntu.com/daily release={{ ansible_distribution_release }} arch=amd64
+
+
+
+
+
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..47d4cc7
--- /dev/null
+++ b/roles/juju-openstack-config/tasks/main.yml
@@ -0,0 +1,36 @@
+---
+# roles/juju-openstack-config/main/tasks.yml
+
+- 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: Obtain keystone IP address
+  command: uvt-kvm ip keystone
+  register: keystone_ip
+
+- name: Create admin-openrc.sh credentials file
+  template:
+   src=admin-openrc.sh.j2
+   dest={{ ansible_user_dir }}/admin-openrc.sh
+
+- 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 -m script -u ubuntu -a "/usr/local/src/network-setup.sh"
+
+- name: Copy nova-cloud-controller CA certificate to local
+  become: yes
+  command: juju scp {{ juju_services['nova-cloud-controller']['units'].keys()[0] }}:/usr/local/share/ca-certificates/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-openstack-config/templates/admin-openrc.sh.j2 b/roles/juju-openstack-config/templates/admin-openrc.sh.j2
new file mode 100644
index 0000000..4ef1a08
--- /dev/null
+++ b/roles/juju-openstack-config/templates/admin-openrc.sh.j2
@@ -0,0 +1,5 @@
+export OS_USERNAME=admin
+export OS_PASSWORD={{ keystone_password.stdout }}
+export OS_TENANT_NAME=admin
+export OS_AUTH_URL=http://keystone:5000/v2.0
+export OS_REGION_NAME=RegionOne
diff --git a/roles/juju-setup/defaults/main.yml b/roles/juju-setup/defaults/main.yml
new file mode 100644
index 0000000..f98ca2a
--- /dev/null
+++ b/roles/juju-setup/defaults/main.yml
@@ -0,0 +1,124 @@
+---
+# roles/juju-setup/defaults/main.yml
+
+openstack_version: kilo
+
+openstack_cfg_path: /usr/local/src/openstack.cfg
+
+head_vm_list:
+  - name: "juju"
+    cpu: "1"
+    memMB: "2048"
+    diskGB: "20"
+
+  - name: "ceilometer"
+    cpu: "1"
+    memMB: "2048"
+    diskGB: "20"
+
+  - name: "glance"
+    cpu: "2"
+    memMB: "4096"
+    diskGB: "160"
+
+  - name: "keystone"
+    cpu: "2"
+    memMB: "4096"
+    diskGB: "40"
+
+  - name: "mysql"
+    cpu: "2"
+    memMB: "4096"
+    diskGB: "40"
+
+  - name: "nagios"
+    cpu: "1"
+    memMB: "2048"
+    diskGB: "20"
+
+  - name: "neutron-api"
+    cpu: "2"
+    memMB: "4096"
+    diskGB: "40"
+
+  - name: "neutron-gateway"
+    cpu: "2"
+    memMB: "4096"
+    diskGB: "40"
+
+  - name: "nova-cloud-controller"
+    cpu: "2"
+    memMB: "4096"
+    diskGB: "40"
+
+  - name: "openstack-dashboard"
+    cpu: "1"
+    memMB: "2048"
+    diskGB: "20"
+
+  - name: "rabbitmq-server"
+    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", ]
+
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/files/qemu b/roles/juju-setup/files/qemu
new file mode 100644
index 0000000..1c947f9
--- /dev/null
+++ b/roles/juju-setup/files/qemu
@@ -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/juju-setup/handlers/main.yml b/roles/juju-setup/handlers/main.yml
new file mode 100644
index 0000000..d54f5dc
--- /dev/null
+++ b/roles/juju-setup/handlers/main.yml
@@ -0,0 +1,12 @@
+---
+# roles/juju-setup/handlers/tasks.yml
+
+- name: reload libvirt-bin
+  service:
+    name=libvirt-bin
+    state=reloaded
+
+- name: run qemu hook
+  command: /etc/libvirt/hooks/qemu start start
+
+
diff --git a/roles/juju-setup/tasks/main.yml b/roles/juju-setup/tasks/main.yml
new file mode 100644
index 0000000..6b7c25e
--- /dev/null
+++ b/roles/juju-setup/tasks/main.yml
@@ -0,0 +1,103 @@
+---
+# 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: Discover VM IP addresses
+  shell: "uvt-kvm ip {{ item.name }}"
+  with_items: "{{ head_vm_list }}"
+  register: vm_ip
+
+- name: Create /etc/hosts with VM IP addresses
+  become: yes
+  template:
+    src=hosts.j2
+    dest=/etc/hosts
+
+- 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: 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
+  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='name') | list | difference( juju_machines.keys() ) }}"
+
+- 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 }}'"
+  ignore_errors: True
+  with_subelements:
+    - "{{ service_relations }}"
+    - relations
+
+# Need to wait for services to come up here
+# Possibly do so by using wait_for and wating on forwarded ports after next step?
+
+- name: Have libvirt enable port forwarding to VM's
+  become: yes
+  copy:
+    src={{ item }}
+    dest=/etc/libvirt/hooks/{{ item }}
+    mode=0755 owner=root
+  with_items:
+    - daemon
+    - qemu
+  notify:
+    - reload libvirt-bin
+    - run qemu hook
+
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/hosts.j2 b/roles/juju-setup/templates/hosts.j2
new file mode 100644
index 0000000..ebc3706
--- /dev/null
+++ b/roles/juju-setup/templates/hosts.j2
@@ -0,0 +1,10 @@
+127.0.0.1	localhost
+127.0.1.1	ubuntu
+{% for host in vm_ip.results -%} 
+{{ host.stdout }} {{ host.item.name }}
+{% endfor -%}
+
+# The following lines are desirable for IPv6 capable hosts
+::1     localhost ip6-localhost ip6-loopback
+ff02::1 ip6-allnodes
+ff02::2 ip6-allrouters
diff --git a/roles/juju-setup/templates/openstack.cfg.j2 b/roles/juju-setup/templates/openstack.cfg.j2
new file mode 100644
index 0000000..063d239
--- /dev/null
+++ b/roles/juju-setup/templates/openstack.cfg.j2
@@ -0,0 +1,39 @@
+ceilometer:
+ceilometer-agent:
+glance:
+   openstack-origin: "cloud:trusty-kilo"
+keystone:
+   admin-password: ""
+   https-service-endpoints: "True"
+   openstack-origin: "cloud:trusty-kilo"
+   use-https: "yes"
+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"
+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..6f675fd
--- /dev/null
+++ b/roles/juju-user-prep/tasks/main.yml
@@ -0,0 +1,15 @@
+---
+# 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
+
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 }}