initial commit

Change-Id: I5063800f2ddaf90a350325a9186479c25f90f8e1
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..3c96d86
--- /dev/null
+++ b/README.md
@@ -0,0 +1,85 @@
+# OpenCORD Bare Metal Provisioning
+
+OpenCORD leverages Canonical's Metal as a Service (MAAS) solution. The MAAS solution provides a PXE boot environment. The basic
+bare metal provisioning flow is:
+   1. Install and provisioning MAAS and other utilities on one compute node that will have the **head node** role
+   1. Boot the other components in a CORD POD (switches and other compute nodes)
+   1. Once other components are operational perform some additional provisioning to prepare them to be part of a CORD POD and
+      to be compliant with best practices of a CORD POD
+
+After the base bare metal provisioning is complete further provisioning, such as XOS or leaf-spine fabric can be deployed.
+
+# Ansible Roles Provided
+
+## docker
+
+Ensures Docker tools are available on the target system. Specifically `docker-engine` and `docker-compose`.
+
+## java8-oracle
+
+Ensures that the Oracle version of Java8 is available on the target system.
+
+## fabric-switch
+
+Ensures the OpenFlow agent (ofdpa) is available on the target switch as well as utility scripts that have been
+helpful in the lab.
+
+### Configuration
+
+One of the scripts made available via this role is called `connect`. This script initiates the connection from
+the switch to an OpenFlow controller. To connect to the controller the **DPID** of the switch is required as is
+the **IP Address** of the SDN controller.
+
+The **IP Address** of the controller is likely universal for all switches and can be either set via the command
+line, using the `--extra-vars` command line option or via a global `vars` file.
+
+The **DPID** is a per switch setting and represents the OpenFlow ID for the switch and will be in the for or
+`0x0000000000000012` and will be unique for each switch. This value can be set either in a host specific variable
+file or if the playbook is being run against a single switch using the `--extra-vars` command line option.
+
+## compute-node
+
+Ensures the 40G network interface card (NIC) drivers are installed and that the interfaces on the compute node are
+named according to best practices. Specificall the 2 40G ports are `eth0` and `eth1`; the 2 10G ports are `eth2` and
+`eth3`. Additionally, this roles sets a default password for the ubuntu user so that console logins are possible
+for debug purposes. This last change, default password, should be eliminated for proxuction use.
+
+### Configuration
+
+Each compute node is statically assigned an IP address for the leaf-spine fabric. This can be configured
+in a host specific variable file found in the `host_vars` directory or could be specified on the command line
+using the `--extra-vars` option if the play book is be run against a single target system.
+
+### Dependencies
+
+This role depends on the `docker` role.
+
+## onos-fabric
+
+Ensures
+
+## mmas
+
+Ensures that Canonical's Metal as a Service (MAAS) is available on the target system and configured according to
+best practices for a CORD POD. This role is meant to be applied to a head node in the CORD POD.
+
+### Assumptions
+
+   - A 2 port _Intel_ 40G card is installed on the head node
+   - A 2 port 10G card is installed on the head node
+   - Head Node has _Internet_ connectivity via the 10G interface named `eth3`
+
+   _Additionally configuration variables, including network IP addressing information can be found in 
+   the file `vars/main.yml`._
+
+### Comments
+
+   - `iptables` rules will be established to `NAT` traffic out interface `eth3`
+   - This role installs and starts two docker images to help manage the MAAs install including:
+      - `cord/maas-dhcp-harvester:0.1-prerelease` - adds DHCP addresses to the DNS server that MAAS misses
+      - `cord/maas-automation:0.1-prerelease` - automates compute nodes through the states of MAAS to the
+         deployed state so that they can use used as part of the CORD POD.
+
+### Dependencies
+
+This role depends on the `docker` role.
diff --git a/fabric.yml b/fabric.yml
new file mode 100644
index 0000000..11f14f7
--- /dev/null
+++ b/fabric.yml
@@ -0,0 +1,3 @@
+- hosts: switches
+  roles:
+    - fabric-switch
diff --git a/host_vars/cord-r6-s1 b/host_vars/cord-r6-s1
new file mode 100644
index 0000000..c3a1a5f
--- /dev/null
+++ b/host_vars/cord-r6-s1
@@ -0,0 +1 @@
+fabric_ip: 10.6.1.1/24
diff --git a/host_vars/cord-r6-s2 b/host_vars/cord-r6-s2
new file mode 100644
index 0000000..1591f03
--- /dev/null
+++ b/host_vars/cord-r6-s2
@@ -0,0 +1 @@
+fabric_ip: 10.6.1.2/24
diff --git a/host_vars/cord-r6-s3 b/host_vars/cord-r6-s3
new file mode 100644
index 0000000..f767de6
--- /dev/null
+++ b/host_vars/cord-r6-s3
@@ -0,0 +1 @@
+fabric_ip: 10.6.2.3/24
diff --git a/host_vars/cord-r6-s4 b/host_vars/cord-r6-s4
new file mode 100644
index 0000000..57aae55
--- /dev/null
+++ b/host_vars/cord-r6-s4
@@ -0,0 +1 @@
+fabric_ip: 10.6.2.4/24
diff --git a/hosts b/hosts
new file mode 100644
index 0000000..4f6fe62
--- /dev/null
+++ b/hosts
@@ -0,0 +1,8 @@
+[switches]
+spine-1
+spine-2
+leaf-1
+leaf-2
+
+[switches:vars]
+ansible_ssh_user=root
diff --git a/roles/compute-node/files/i40e-1.4.25.tar.gz b/roles/compute-node/files/i40e-1.4.25.tar.gz
new file mode 100644
index 0000000..6a30a04
--- /dev/null
+++ b/roles/compute-node/files/i40e-1.4.25.tar.gz
Binary files differ
diff --git a/roles/compute-node/files/rename_ifaces.sh b/roles/compute-node/files/rename_ifaces.sh
new file mode 100755
index 0000000..be8cb72
--- /dev/null
+++ b/roles/compute-node/files/rename_ifaces.sh
@@ -0,0 +1,180 @@
+#!/bin/bash
+
+function ip2int {
+    local a b c d
+    { IFS=. read a b c d; } <<< $1
+    echo $(((((((a << 8) | b) << 8) | c) << 8) | d))
+}
+
+function int2ip {
+    local ui32=$1; shift
+    local ip n
+    for n in 1 2 3 4; do
+        ip=$((ui32 & 0xff))${ip:+.}$ip
+        ui32=$((ui32 >> 8))
+    done
+    echo $ip
+}
+
+function netmask {
+    local mask=$((0xffffffff << (32 - $1))); shift
+    int2ip $mask
+}
+
+function broadcast {
+    local addr=$(ip2int $1); shift
+    local mask=$((0xffffffff << (32 -$1))); shift
+    int2ip $((addr | ~mask))
+}
+
+function network {
+    local addr=$(ip2int $1); shift
+    local mask=$((0xffffffff << (32 -$1))); shift
+    int2ip $((addr & mask))
+}
+
+function first {
+    local addr=$(ip2int $1)
+    addr=`expr $addr + 1`
+    int2ip $addr
+}
+
+function guess_type {
+    local CNT=$(echo "$1" | sed -e 's/[:.]/ /g' | wc -w)
+    if [ $CNT -ne 1 ]; then
+        # drop all sub and vlan interfaces
+        echo "DNC"
+        return
+    fi
+    local DRIVER=$(ethtool -i $1 2>/dev/null | grep driver | awk '{print $2}')
+    local RESULT="DNC"
+    case $DRIVER in
+        i40e)
+            RESULT="I40G"
+            ;;
+        igb)
+            RESULT="ETH"
+            ;;
+        *) ;;
+    esac
+    echo $RESULT
+}
+
+function get_mac {
+  echo $(ifconfig $1 | grep HWaddr | awk '{print $5}')
+}
+
+function generate_persistent_names {
+    local OUT=$NAMES_FILE
+#"70-persistent-net.rules"
+    rm -rf $OUT
+
+    IDX=0
+    for i in $(cat $1 | sort); do
+        echo "SUBSYSTEM==\"net\", ACTION==\"add\", DRIVERS==\"?*\", ATTR{address}==\"$i\", ATTR{dev_id}==\"0x0\", ATTR{type}==\"1\", KERNEL==\"eth*\", NAME=\"eth$IDX\"" >> $OUT
+        IDX=$(expr $IDX + 1)
+    done
+
+    for i in $(cat $2 | sort); do
+        echo "SUBSYSTEM==\"net\", ACTION==\"add\", DRIVERS==\"?*\", ATTR{address}==\"$i\", NAME=\"eth$IDX\"" >> $OUT
+        IDX=$(expr $IDX + 1)
+    done
+}
+
+function generate_interfaces {
+    OUT=$IFACES_FILE
+    rm -rf $OUT
+    echo "# This file describes the network interfaces available on your system" >> $OUT
+    echo "# and how to activate them. For more information, see interfaces(5)." >> $OUT
+    echo "" >> $OUT
+    echo "# The loopback network interface" >> $OUT
+    echo "auto lo" >> $OUT
+    echo "iface lo inet loopback" >> $OUT
+    echo "" >> $OUT
+
+    IDX=0
+    FIRST=1
+    for i in $(cat $1); do
+        if [ $FIRST -eq 1 ]; then
+            echo "auto eth$IDX" >> $OUT
+            echo "iface eth$IDX inet static" >> $OUT
+            echo "    address $IP" >> $OUT
+            echo "    network $NETWORK" >> $OUT
+            echo "    netmask $NETMASK" >> $OUT
+            FIRST=0
+        else
+            echo "iface eth$IDX inet manual" >> $OUT
+        fi
+        echo "" >> $OUT
+        IDX=$(expr $IDX + 1)
+    done
+
+    FIRST=1
+    for i in $(cat $2); do
+        if [ $FIRST -eq 1 ]; then
+            echo "auto eth$IDX" >> $OUT
+            echo "iface eth$IDX inet dhcp" >> $OUT
+            FIRST=0
+        else
+            echo "iface eth$IDX inet manual" >> $OUT
+        fi
+        echo "" >> $OUT
+        IDX=$(expr $IDX + 1)
+    done
+}
+
+ADDR=$1
+IP=$(echo $ADDR | cut -d/ -f1)
+MASKBITS=$(echo $ADDR | cut -d/ -f2)
+NETWORK=$(network $IP $MASKBITS)
+NETMASK=$(netmask $MASKBITS)
+
+LIST_ETH=$(mktemp -u)
+LIST_40G=$(mktemp -u)
+IFACES_FILE=$(mktemp -u)
+NAMES_FILE=$(mktemp -u)
+
+IFACES=$(ifconfig -a | grep "^[a-z]" | awk '{print $1}')
+
+for i in $IFACES; do
+    TYPE=$(guess_type $i)
+    case $TYPE in
+        ETH)
+            echo "$(get_mac $i)" >> $LIST_ETH
+            ;;
+        I40G)
+            echo "$(get_mac $i)" >> $LIST_40G
+            ;;
+        *) ;;
+    esac
+done
+
+RESULT="false"
+
+generate_interfaces $LIST_40G $LIST_ETH
+diff /etc/network/interfaces $IFACES_FILE 2>&1 > /dev/null
+if [ $? -ne 0 ]; then
+  RESULT="true"
+  cp /etc/network/interfaces /etc/network/interfaces.1
+  cp $IFACES_FILE /etc/network/interfaces
+fi
+
+generate_persistent_names $LIST_40G $LIST_ETH
+if [ -r /etc/udev/rules.d/70-persistent-net.rules ]; then
+  diff /etc/udev/rules.d/70-persistent-net.rules $NAMES_FILE 2>&1 > /dev/null
+  if [ $? -ne 0 ]; then
+    RESULT="true"
+    cp /etc/udev/rules.d/70-persistent-net.rules /etc/udev/rules.d/70-persistent-net.rules.1
+    cp $NAMES_FILE /etc/udev/rules.d/70-persistent-net.rules
+  fi
+else
+  RESULT="true"
+  cp $NAMES_FILE /etc/udev/rules.d/70-persistent-net.rules
+fi
+
+rm -rf $IFACES_FILE
+rm -rf $NAMES_FILE
+rm -rf $LIST_ETH
+rm -rf $LIST_40G
+
+echo -n $RESULT
diff --git a/roles/compute-node/files/rename_ifaces.sh.back b/roles/compute-node/files/rename_ifaces.sh.back
new file mode 100755
index 0000000..76056ff
--- /dev/null
+++ b/roles/compute-node/files/rename_ifaces.sh.back
@@ -0,0 +1,110 @@
+#!/bin/bash
+
+BASE="10.4"
+LEAF="1"
+SERVER="1"
+
+function guess_type {
+    local CNT=$(echo "$1" | sed -e 's/[:.]/ /g' | wc -w)
+    if [ $CNT -ne 1 ]; then
+        # drop all sub and vlan interfaces
+        echo "DNC"
+        return
+    fi
+    local DRIVER=$(ethtool -i $1 2>/dev/null | grep driver | awk '{print $2}')
+    local RESULT="DNC"
+    case $DRIVER in
+        i40e)
+            RESULT="I40G"
+            ;;
+        igb)
+            RESULT="ETH"
+            ;;
+        *) ;;
+    esac
+    echo $RESULT
+}
+
+function get_mac {
+  echo $(ifconfig $1 | grep HWaddr | awk '{print $5}')
+}
+
+function generate_persistent_names {
+    local OUT="70-persistent-net.rules"
+    rm -rf $OUT
+
+    IDX=0
+    for i in $(cat $1 | sort); do
+        echo "SUBSYSTEM==\"net\", ACTION==\"add\", DRIVERS==\"?*\", ATTR{address}==\"$i\", ATTR{dev_id}==\"0x0\", ATTR{type}==\"1\", KERNEL==\"eth*\", NAME=\"eth$IDX\"" >> $OUT
+        IDX=$(expr $IDX + 1)
+    done
+
+    for i in $(cat $2 | sort); do
+        echo "SUBSYSTEM==\"net\", ACTION==\"add\", DRIVERS==\"?*\", ATTR{address}==\"$i\", NAME=\"eth$IDX\"" >> $OUT
+        IDX=$(expr $IDX + 1)
+    done
+}
+
+function generate_interfaces {
+    OUT="interfaces"
+    rm -rf $OUT
+    echo "# This file describes the network interfaces available on your system" >> $OUT
+    echo "# and how to activate them. For more information, see interfaces(5)." >> $OUT
+    echo "" >> $OUT
+    echo "# The loopback network interface" >> $OUT
+    echo "auto lo" >> $OUT
+    echo "iface lo inet loopback" >> $OUT
+    echo "" >> $OUT
+
+    IDX=0
+    FIRST=1
+    for i in $(cat $1); do
+        if [ $FIRST -eq 1 ]; then
+            echo "auto eth$IDX" >> $OUT
+            echo "iface eth$IDX inet static" >> $OUT
+            echo "    address $BASE.$LEAF.$SERVER" >> $OUT
+            echo "    network $BASE.$LEAF.0" >> $OUT
+            echo "    netmask 255.255.255.0" >> $OUT
+            FIRST=0
+        else
+            echo "iface eth$IDX inet manual" >> $OUT
+        fi
+        echo "" >> $OUT
+        IDX=$(expr $IDX + 1)
+    done
+
+    FIRST=1
+    for i in $(cat $2); do
+        if [ $FIRST -eq 1 ]; then
+            echo "auto eth$IDX" >> $OUT
+            echo "iface eth$IDX inet dhcp" >> $OUT
+            FIRST=0
+        else
+            echo "iface eth$IDX inet manual" >> $OUT
+        fi
+        echo "" >> $OUT
+        IDX=$(expr $IDX + 1)
+    done
+}
+
+LIST_ETH=$(mktemp -u)
+LIST_40G=$(mktemp -u)
+IFACES=$(ifconfig -a | grep "^[a-z]" | awk '{print $1}')
+
+for i in $IFACES; do
+    TYPE=$(guess_type $i)
+    case $TYPE in
+        ETH)
+            echo "$(get_mac $i)" >> $LIST_ETH
+            ;;
+        I40G)
+            echo "$(get_mac $i)" >> $LIST_40G
+            ;;
+        *) ;;
+    esac
+done
+
+generate_persistent_names $LIST_40G $LIST_ETH
+generate_interfaces $LIST_40G $LIST_ETH
+rm -rf $LIST_ETH
+rm -rf $LIST_40G
diff --git a/roles/compute-node/meta/main.yml b/roles/compute-node/meta/main.yml
new file mode 100644
index 0000000..45b0e96
--- /dev/null
+++ b/roles/compute-node/meta/main.yml
@@ -0,0 +1,15 @@
+---
+galaxy_info:
+  author: Ciena Blueplanet
+  description: CORD POD Compute Node Base
+  company: Ciena Blueplanet
+  license: Apache 2.0
+  min_ansible_version: 2.0
+  platforms:
+    - name: Ubuntu
+      versions:
+        - trusty
+  galaxy_tags:
+    - cord
+dependencies:
+  - { role : docker }
diff --git a/roles/compute-node/tasks/i40e_driver.yml b/roles/compute-node/tasks/i40e_driver.yml
new file mode 100644
index 0000000..5f6b199
--- /dev/null
+++ b/roles/compute-node/tasks/i40e_driver.yml
@@ -0,0 +1,40 @@
+---
+- name: Copy i40e Interface Driver
+  unarchive:
+    src=files/i40e-1.4.25.tar.gz
+    dest={{ ansible_env.HOME }}
+    owner=ubuntu
+    group=ubuntu
+
+- name: Build i40e Driver
+  command: make
+  args:
+    chdir: i40e-1.4.25/src
+    creates: "{{ ansible_env.HOME }}/i40e-1.4.25/src/i40e/i40e.ko"
+
+- name: Unload i40e Driver
+  become: yes
+  modprobe: name=i40e state=absent
+
+- name: Install i40e Driver
+  become: yes
+  command: make install
+  args:
+    chdir: i40e-1.4.25/src
+
+- name: Load i40e Driver
+  become: yes
+  modprobe: name=i40e state=present
+
+- name: Persist i40e Driver Loadi
+  become: yes
+  lineinfile:
+    dest=/etc/modules
+    line="i40e"
+    state=present
+    insertafter=EOF
+
+- name: Remove Build Files
+  file:
+    path={{ ansible_env.HOME }}/i40e-1.4.25
+    state=absent
diff --git a/roles/compute-node/tasks/main.yml b/roles/compute-node/tasks/main.yml
new file mode 100644
index 0000000..d64adfe
--- /dev/null
+++ b/roles/compute-node/tasks/main.yml
@@ -0,0 +1,32 @@
+---
+- name: Applications
+  become: yes
+  apt: name={{ item }} state=present
+  with_items:
+    - build-essential 
+
+- name: Set Default Password
+  become: yes
+  user:
+    name=ubuntu
+    password="$6$TjhJuOgh8xp.v$z/4GwFbn5koVmkD6Ex9wY7bgP7L3uP2ujZkZSs1HNdzQdz9YclbnZH9GvqMC/M1iwC0MceL05.13HoFz/bai0/"
+
+- name: Verify i40e Driver
+  command: modinfo --field=version i40e
+  register: i40e_version
+  changed_when: False
+
+- name: Update i40e Driver
+  include: tasks/i40e_driver.yml
+  when: i40e_version.stdout != '1.4.25'
+
+- name: Consistent Interface Naming
+  become: yes
+  script: files/rename_ifaces.sh {{ fabric_ip }}
+  register: ifaces_changed
+  changed_when: ifaces_changed.stdout != "false"
+
+- name: Reboot Required
+  become: yes
+  command: /sbin/reboot
+  when: ifaces_changed.stdout != "false"
diff --git a/roles/docker/meta/main.yml b/roles/docker/meta/main.yml
new file mode 100644
index 0000000..bf39d8c
--- /dev/null
+++ b/roles/docker/meta/main.yml
@@ -0,0 +1,15 @@
+---
+galaxy_info:
+  author: Ciena Blueplanet
+  description: Docker Engine and Docker Compose
+  company: Ciena Blueplanet
+  license: Apache 2.0
+  min_ansible_version: 2.0
+  platforms:
+    - name: Ubuntu
+      versions:
+        - trusty
+  galaxy_tags:
+    - development
+    - system
+dependencies: []
diff --git a/roles/docker/tasks/main.yml b/roles/docker/tasks/main.yml
new file mode 100644
index 0000000..6e7c5e6
--- /dev/null
+++ b/roles/docker/tasks/main.yml
@@ -0,0 +1,38 @@
+- name: Apt Information
+  become: yes
+  apt: name={{ item }} state=latest force=yes
+  with_items:
+    - apt-transport-https
+    - ca-certificates
+
+- name: Docker Apt Key
+  become: yes
+  apt_key:
+    keyserver: hkp://p80.pool.sks-keyservers.net:80
+    id: 58118E89F3A912897C070ADBF76221572C52609D
+
+- name: Docker repository
+  become: yes
+  apt_repository:
+    repo: deb https://apt.dockerproject.org/repo ubuntu-trusty main
+    update_cache: yes
+    state: present
+
+- name: Docker Engine
+  become: yes
+  apt:
+    name: docker-engine
+    state: latest
+    force: yes
+
+- name: Docker Compose
+  become: yes
+  get_url:
+    url: https://github.com/docker/compose/releases/download/1.4.1/docker-compose-Linux-x86_64
+    dest: /usr/local/bin/docker-compose
+
+- name: Docker Compose Permissions
+  become: yes
+  file:
+    path: /usr/local/bin/docker-compose
+    mode: 0755
diff --git a/roles/fabric-switch/files/connect b/roles/fabric-switch/files/connect
new file mode 100755
index 0000000..a114178
--- /dev/null
+++ b/roles/fabric-switch/files/connect
@@ -0,0 +1,17 @@
+#!/bin/bash
+
+BG=0
+
+while [ $# -gt 0 ]; do
+  case $1 in
+    -bg|-background)
+      BG=1
+  esac
+  shift
+done
+
+if [ $BG -eq 1 ]; then
+  nohup brcm-indigo-ofdpa-ofagent --dpid={{ switch_id }} --controller={{ controller_ip }} 2>&1 > connect.log &
+else
+  brcm-indigo-ofdpa-ofagent --dpid={{ switch_id }} --controller={{ controller_ip }}
+fi
diff --git a/roles/fabric-switch/files/killit b/roles/fabric-switch/files/killit
new file mode 100755
index 0000000..2ed34a1
--- /dev/null
+++ b/roles/fabric-switch/files/killit
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+killall -9 brcm-indigo-ofdpa-ofagent
diff --git a/roles/fabric-switch/files/purge b/roles/fabric-switch/files/purge
new file mode 100755
index 0000000..296a8cf
--- /dev/null
+++ b/roles/fabric-switch/files/purge
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+/usr/bin/ofdpa-i.12.1.1/examples/client_cfg_purge
diff --git a/roles/fabric-switch/files/reset b/roles/fabric-switch/files/reset
new file mode 100755
index 0000000..4f58bd5
--- /dev/null
+++ b/roles/fabric-switch/files/reset
@@ -0,0 +1,6 @@
+#!/bin/bash
+./killit
+./purge
+service ofdpa restart
+./purge
+
diff --git a/roles/fabric-switch/meta/main.yml b/roles/fabric-switch/meta/main.yml
new file mode 100644
index 0000000..0bcac2c
--- /dev/null
+++ b/roles/fabric-switch/meta/main.yml
@@ -0,0 +1,14 @@
+---
+galaxy_info:
+  author: Ciena Blueplanet
+  description: Openflow Agent and Basic Utils for Fabric Switch
+  company: Ciena Blueplanet
+  license: Apache 2.0
+  min_ansible_version: 2.0
+  platforms:
+    - name: Ubuntu
+      versions:
+        - trusty
+  galaxy_tags:
+    - openflow
+dependencies: []
diff --git a/roles/fabric-switch/tasks/main.yml b/roles/fabric-switch/tasks/main.yml
new file mode 100644
index 0000000..326219a
--- /dev/null
+++ b/roles/fabric-switch/tasks/main.yml
@@ -0,0 +1,45 @@
+---
+- name: Verify controller_ip Set
+  fail: msg="Please set variable 'controller_ip'. This can be set via a variable file or via the command line using the '--extra-vars' option."
+  when: controller_ip is not defined
+
+- name: Verify switch_id Set
+  fail: msg="Please set variable 'switch_id'. This can be set via a host specific variable file or via the command line using the '--extra-vars' option."
+  when: switch_id is not defined
+
+- name: Openflow Agent Version
+  shell: ofdpa --version
+  register: ofdpa_version
+  changed_when: false
+
+- name: Version I.12.1.1+1.1 Openflow Agent
+  include: ofdpa.yml
+  when: ofdpa_version.stdout.find('version I.12.1.1+1.1') == -1
+
+- name: Utilities Scripts
+  template:
+    src: files/{{ item }}
+    dest: /root
+    owner: root
+    group: root
+    mode: 0755
+  with_items:
+    - purge
+    - killit
+    - connect
+    - reset
+  register: utils
+
+- name: Mark Persistent
+  command: persist {{ item }}
+  with_items:
+    - purge
+    - killit
+    - connect
+    - reset
+  when: utils.changed
+
+- name: Persist
+  command: savepersist
+  when: utils.changed
+  failed_when: false
diff --git a/roles/fabric-switch/tasks/ofdpa.yml b/roles/fabric-switch/tasks/ofdpa.yml
new file mode 100644
index 0000000..2c643e8
--- /dev/null
+++ b/roles/fabric-switch/tasks/ofdpa.yml
@@ -0,0 +1,22 @@
+---
+- name: Openflow Agent Debian Archive
+  get_url:
+    url: http://github.com/ciena/ZeroTouchProvisioning/raw/master/ofdpa-i.12.1.1_12.1.1%2Baccton1.7-1_amd64.deb
+    validate_certs: false
+    dest: /mnt/flash2/ofdpa-i.12.1.1_12.1.1%2Baccton1.7-1_amd64.deb
+
+- name: OpenFlow Agent Stopped
+  service: name=ofdpa state=stopped
+
+- name: Openflow Agent
+  apt: deb="/mnt/flash2/ofdpa-i.12.1.1_12.1.1%2Baccton1.7-1_amd64.deb" force=true
+
+- name: OpenFlow Agent Started
+  service: name=ofdpa state=started
+
+- name: Mark Persist Openflow Agent
+  command: persist /etc/accton/ofdpa.conf
+
+- name: Persist Openflow Agent
+  command: savepersist
+  failed_when: false
diff --git a/roles/java8-oracle/meta/main.yml b/roles/java8-oracle/meta/main.yml
new file mode 100644
index 0000000..a51875b
--- /dev/null
+++ b/roles/java8-oracle/meta/main.yml
@@ -0,0 +1,15 @@
+---
+galaxy_info:
+  author: Ciena Blueplanet
+  description: Java 8 from Oracle
+  company: Ciena Blueplanet
+  license: Apache 2.0
+  min_ansible_version: 2.0
+  platforms:
+    - name: Ubuntu
+      versions:
+        - trusty
+  galaxy_tags:
+    - development
+    - system
+dependencies: []
diff --git a/roles/java8-oracle/tasks/main.yml b/roles/java8-oracle/tasks/main.yml
new file mode 100644
index 0000000..9397fff
--- /dev/null
+++ b/roles/java8-oracle/tasks/main.yml
@@ -0,0 +1,20 @@
+---
+- name: Install add-apt-repostory
+  become: yes
+  apt: name=software-properties-common state=latest
+
+- name: Add Oracle Java Repository
+  become: yes
+  apt_repository: repo='ppa:webupd8team/java'
+
+- name: Accept Java 8 License
+  become: yes
+  debconf: name='oracle-java8-installer' question='shared/accepted-oracle-license-v1-1' value='true' vtype='select'
+
+- name: Install Oracle Java 8
+  become: yes
+  apt: name={{item}} state=latest
+  with_items:
+    - oracle-java8-installer
+    - ca-certificates
+    - oracle-java8-set-default
diff --git a/roles/maas/files/cord_id_rsa b/roles/maas/files/cord_id_rsa
new file mode 100644
index 0000000..e4a3947
--- /dev/null
+++ b/roles/maas/files/cord_id_rsa
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEA4ixakIQFlpSXd3NJ98btbO7Dt0o9ioDl0ZLZa2v1g0dWifn0
+W5gkqo9VSkY2fPwUtgxnyoyVFiEc1MyLNxAkd6UHl+YzFw4dDCrEpu5XDCPCkvky
+dd2P3OiuxmheDqjpklPMWddsWSIA061qSdBQ+pYm9Qq5tfekvOVcAPPkoB22ftPr
+gnOSovvTwHfnZH/gfiI+IxUcWwLkneiUenw5oO+3Jie1Gbn3yFqf3pk+VG6M+8YH
+gZKY0/TBaFBHB+acKN3/yIhnnQzfwRyLX5txnYyaDI0HjVLJQiFK0XKgTGhm4U7C
+mpWkEH6WLF/cOs9+chQDDSwOxRD/VIw6W4sXIwIDAQABAoIBABhQFkg0uPkP7hxc
+G1Z0Xu931zgr1eO+qXXW6GJgz5qWH5pjcT4rY72l/NAoLhFPc9aCDOI8LIaddqD1
+f/2iUZk+90r/5vwSe1LkghFDy721VmRAP4lmEOH5bVhMvderlrgxI+WAf9gxDI+0
+s5lNuHbHj1aGGaKTBXV83mAH18rSUxxPZ+M5xT9RE5uKJwZcRLrqnOI3dleGy7xc
+Pxrtm0v/507DVjRdQzpjcYkndGQXfOqjNLEtwMADwmYCOEF3sqaSSDeqYtg/IOIR
+hiM65ekl7R+bqMaowv82V5NCfdSXKyRLvk4Nr9E9Jji9gPA2bYkx6ASkkWduGzA3
+tYqs5kkCgYEA9y396CV/NQfxGGwRD/lLtpNG/YgzihO+IojLd5RsdTCUxvxB5BCx
+i/KGWisg5PLORBh1UGpyrSIKaoMJXbrSSpQwZri+Xnpjt/qHIT8KWUN4tuiHKluV
+DkWQFkD1aOIZujWEX9J25C/SGICtyCIWp/ylNPX6Kwf/cm8W7A7GPU0CgYEA6j53
+dHW2ia4k0ze0HI0PWaiZoi2jFhzI33le1MMBjRMIS3TW2LajWB3TjFs+AdLb831P
+gQrA76oMQ5KDZ3o2b4NrixwfRAQaBBW2cCsRLkxZEIsoVa3QthT5UihqR/t6G6FB
+u9pl+fK8IsHoc5pKFtOkCD9c/Axyu0m30BWqLi8CgYEAjbEPm8Pi58NlsVpBbaa6
+gC5sw2kQIlau550DBclPYt42atqv6sym+lJMMeQHNzb4hpB+r1pV4mlhDy2OcOxn
+H9lS5Y+BkScXgp9aVvSMOh8zU6Z31RAqocO+lQMnqrfxh4ymFUfQX34KMYGSHOdt
+lV5+VZ2rin9LL43+1dKiUQECgYEAlOCQ4ZbzJjxlMU1lDwRkbjKnOplQ3vv6e3ZT
+XFx4fuZKzlJ7Po+N77I9Qya2mUgf/Xh2cGiaSXjFhKj5FWpqcKORVX/RK1SECHaY
+VmA48jkaHlajkxj+3ssjzyDas9dUO31ZHwDm8V5iTqD5kYfNcQagaZGEEroCraBj
+0EAEwocCgYEAmQ2RumGHjYdSgkxw1MWCAXg1RPBaifZifErhe1MiVkZuHdCxYn3p
+Yv7KPaOQtYfbZEN1Ww3ScWqIRZzOmRdEakFGFh+d1V+qK1r/Bj6SBSOND1UZFm+j
++DeGwmHuPzdNbtoW4dqyFM5OFib3N9P6r87Kfl1X3q31R8gVhK4wtOo=
+-----END RSA PRIVATE KEY-----
diff --git a/roles/maas/files/cord_id_rsa.pub b/roles/maas/files/cord_id_rsa.pub
new file mode 100644
index 0000000..36daa90
--- /dev/null
+++ b/roles/maas/files/cord_id_rsa.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDiLFqQhAWWlJd3c0n3xu1s7sO3Sj2KgOXRktlra/WDR1aJ+fRbmCSqj1VKRjZ8/BS2DGfKjJUWIRzUzIs3ECR3pQeX5jMXDh0MKsSm7lcMI8KS+TJ13Y/c6K7GaF4OqOmSU8xZ12xZIgDTrWpJ0FD6lib1Crm196S85VwA8+SgHbZ+0+uCc5Ki+9PAd+dkf+B+Ij4jFRxbAuSd6JR6fDmg77cmJ7UZuffIWp/emT5Uboz7xgeBkpjT9MFoUEcH5pwo3f/IiGedDN/BHItfm3GdjJoMjQeNUslCIUrRcqBMaGbhTsKalaQQfpYsX9w6z35yFAMNLA7FEP9UjDpbixcj cord@cord.lab
diff --git a/roles/maas/files/dhcp_harvest.inc b/roles/maas/files/dhcp_harvest.inc
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/roles/maas/files/dhcp_harvest.inc
diff --git a/roles/maas/files/dhcpd.blacklist b/roles/maas/files/dhcpd.blacklist
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/roles/maas/files/dhcpd.blacklist
diff --git a/roles/maas/files/dhcpd.conf.template b/roles/maas/files/dhcpd.conf.template
new file mode 100644
index 0000000..ca19b1b
--- /dev/null
+++ b/roles/maas/files/dhcpd.conf.template
@@ -0,0 +1,48 @@
+# WARNING: Do not edit /var/lib/maas/dhcpd.conf yourself.  MAAS will overwrite any
+# changes made there.
+#
+# Instead, edit /etc/maas/templates/dhcp/dhcpd.conf.template and your changes
+# will be present whenever MAAS rewrites the DHCP configuration.  Update and save
+# the cluster's configuration in MAAS to trigger an update to this file.
+
+include "/etc/dhcp/dhcpd.blacklist";
+
+option arch code 93 = unsigned integer 16; # RFC4578
+option path-prefix code 210 = text; #RFC5071
+{{for dhcp_subnet in dhcp_subnets}}
+subnet {{dhcp_subnet['subnet']}} netmask {{dhcp_subnet['subnet_mask']}} {
+       {{bootloader}}
+       interface "{{dhcp_subnet['interface']}}";
+       ignore-client-uids true;
+       option subnet-mask {{dhcp_subnet['subnet_mask']}};
+       option broadcast-address {{dhcp_subnet['broadcast_ip']}};
+       #{{if dhcp_subnet.get('dns_servers')}}
+       #option domain-name-servers {{dhcp_subnet['dns_servers']}};
+       #{{endif}}
+       option domain-name "{{dhcp_subnet['domain_name']}}";
+       {{if dhcp_subnet['router_ip'] }}
+       option routers {{dhcp_subnet['router_ip']}};
+       option domain-name-servers {{dhcp_subnet['router_ip']}};
+       next-server {{dhcp_subnet['router_ip']}};
+       option dhcp-server-identifier {{dhcp_subnet['router_ip']}};
+       {{endif}}
+       {{if dhcp_subnet.get('ntp_server')}}
+       option ntp-servers {{dhcp_subnet['ntp_server']}};
+       {{endif}}
+       range dynamic-bootp {{dhcp_subnet['ip_range_low']}} {{dhcp_subnet['ip_range_high']}};
+       class "PXE" {
+          match if substring (option vendor-class-identifier, 0, 3) = "PXE";
+          default-lease-time 30;
+          max-lease-time 30;
+       }
+}
+{{endfor}}
+
+include "/etc/dhcp/dhcpd.reservations";
+
+omapi-port 7911;
+key omapi_key {
+    algorithm HMAC-MD5;
+    secret "{{omapi_key}}";
+};
+omapi-key omapi_key;
diff --git a/roles/maas/files/dhcpd.reservations b/roles/maas/files/dhcpd.reservations
new file mode 100644
index 0000000..a7860c3
--- /dev/null
+++ b/roles/maas/files/dhcpd.reservations
@@ -0,0 +1,79 @@
+############################################################################
+## RESERVATIONS
+############################################################################
+
+# RACK1 - Rack with two fabric switches
+host fabric01 {
+    hardware ethernet cc:37:ab:17:7b:c0;
+    fixed-address  10.0.128.100;
+}
+host fabric02 {
+    hardware ethernet 70:72:cf:f5:60:9e;
+    fixed-address 10.0.128.101;
+}
+host cord-r1-s2 {
+    hardware ethernet 2c:60:0c:e3:c4:2d;
+    fixed-address 10.0.128.113;
+}
+host cord-r1-s2-ipmi {
+    hardware ethernet 2c:60:0c:e3:c4:2f;
+    fixed-address 10.0.128.107;
+}
+host cord-r1-s3 {
+    hardware ethernet 2c:60:0c:cb:00:ef;
+    fixed-address 10.0.128.115;
+}
+host cord-r1-s3-ipmi {
+    hardware ethernet 2c:60:0c:cb:00:f1;
+    fixed-address 10.0.128.108;
+}
+host cord-r1-s4 {
+    hardware ethernet 2c:60:0c:cb:00:3b;
+    fixed-address 10.0.128.116;
+}
+host cord-r1-s4-ipmi {
+    hardware ethernet 2c:60:0c:cb:00:3d;
+    fixed-address 10.0.128.110;
+}
+
+# RACK 2 - Rack with full fabric (2 leaf / 2 spine)
+host spine01 {
+    hardware ethernet cc:37:ab:6e:e3:40;
+    fixed-address 192.168.42.238;
+}
+host spine02 {
+    hardware ethernet cc:37:ab:6b:0d:a6;
+    fixed-address 192.168.42.223;
+}
+host leaf01 {
+    hardware ethernet cc:37:ab:6e:e3:c2;
+    fixed-address 192.168.42.221;
+}
+host leaf02 {
+    hardware ethernet cc:37:ab:6e:e4:c6;
+    fixed-address 192.168.42.222;
+}
+host cord-r2-s2 {
+    hardware ethernet 00:25:90:fa:5f:78;
+    fixed-address 10.0.128.113;
+}
+host cord-r2-s2-ipmi {
+    hardware ethernet 00:25:90:ff:a7:97;
+    fixed-address 10.0.128.118;
+}
+host cord-r2-s3 {
+    hardware ethernet 00:25:90:fa:5f:52;
+    fixed-address 10.0.128.115;
+}
+host cord-r2-s3-ipmi {
+    hardware ethernet 00:25:90:ff:a7:3e;
+    fixed-address 10.0.128.121;
+}
+host cord-r2-s4 {
+    hardware ethernet 00:25:90:fa:5f:4e;
+    fixed-address 10.0.128.124;
+}
+host cord-r2-s4-ipmi {
+    hardware ethernet 00:25:90:ff:a7:3c;
+    fixed-address 10.0.128.116;
+}
diff --git a/roles/maas/files/generate_network_config.sh b/roles/maas/files/generate_network_config.sh
new file mode 100755
index 0000000..eb8f7a2
--- /dev/null
+++ b/roles/maas/files/generate_network_config.sh
@@ -0,0 +1,94 @@
+#!/bin/bash
+
+IFACE_MGMT=$1
+NET_MGMT=$2
+NET_BRIDGE=$3
+MGMTBR=$4
+
+ip2int()
+{
+    local a b c d
+    { IFS=. read a b c d; } <<< $1
+    echo $(((((((a << 8) | b) << 8) | c) << 8) | d))
+}
+
+int2ip()
+{
+    local ui32=$1; shift
+    local ip n
+    for n in 1 2 3 4; do
+        ip=$((ui32 & 0xff))${ip:+.}$ip
+        ui32=$((ui32 >> 8))
+    done
+    echo $ip
+}
+
+netmask()
+{
+    local mask=$((0xffffffff << (32 - $1))); shift
+    int2ip $mask
+}
+
+
+broadcast()
+{
+    local addr=$(ip2int $1); shift
+    local mask=$((0xffffffff << (32 -$1))); shift
+    int2ip $((addr | ~mask))
+}
+
+network()
+{
+    local addr=$(ip2int $1); shift
+    local mask=$((0xffffffff << (32 -$1))); shift
+    int2ip $((addr & mask))
+}
+
+first()
+{
+    local addr=$(ip2int $1)
+    addr=`expr $addr + 1`
+    int2ip $addr
+}
+
+MBITS=`echo "$NET_MGMT" | cut -d/ -f2`
+MNETW=`echo "$NET_MGMT" | cut -d/ -f1`
+MMASK=`netmask $MBITS`
+MHOST=`first $MNETW`
+
+BBITS=`echo "$NET_BRIDGE" | cut -d/ -f2`
+BNETW=`echo "$NET_BRIDGE" | cut -d/ -f1`
+BMASK=`netmask $BBITS`
+BHOST=`first $BNETW`
+
+OUT=$(mktemp -u)
+cat /etc/network/interfaces | awk '/## CORD - DO NOT EDIT BELOW THIS LINE/{exit};1' | awk "/^auto / { if (\$2 == \"${IFACE_MGMT}\") { IN=1 } else {IN=0} } /^iface / { if (\$2 == \"${IFACE_MGMT}\") { IN=1 } else {IN=0}}  /^#/ || /^\s*\$/ { IN=0 } IN==0 {print} IN==1 { print \"#\" \$0 }" > $OUT
+
+cat <<EOT >> $OUT
+## CORD - DO NOT EDIT BELOW THIS LINE
+
+auto ${IFACE_MGMT}
+iface ${IFACE_MGMT} inet static
+    address ${MHOST}
+    network ${MNETW}
+    netmask ${MMASK}
+    gateway ${MHOST}
+
+auto ${MGMTBR}
+iface ${MGMTBR} inet static
+    address ${BHOST}
+    network ${BNETW}
+    netmask ${BMASK}
+    gateway ${BHOST}
+EOT
+
+diff /etc/network/interfaces $OUT 2>&1 > /dev/null
+if [ $? -ne 0 ]; then
+    cp /etc/network/interfaces /etc/network/interfaces.last
+    cp $OUT /etc/network/interfaces
+    echo -n "true"
+else
+    echo -n "false"
+fi
+
+rm $OUT
diff --git a/roles/maas/files/mappings.json b/roles/maas/files/mappings.json
new file mode 100644
index 0000000..9dc033d
--- /dev/null
+++ b/roles/maas/files/mappings.json
@@ -0,0 +1,20 @@
+{
+   "2c:60:0c:e3:c4:2d":{
+      "hostname":"cord-r1-s2"
+   },
+   "2c:60:0c:cb:00:ef":{
+      "hosname":"cord-r1-s3"
+   },
+   "2c:60:0c:cb:00:3b":{
+      "hostname":"cord-r1-s4"
+   },
+   "00:25:90:fa:5f:78":{
+      "hostname":"cord-r2-s2"
+   },
+   "00:25:90:fa:5f:52":{
+      "hostname":"cord-r2-s3"
+   },
+   "00:25:90:fa:5f:4e":{
+      "hostname":"cord-r2-s4"
+   }
+}
diff --git a/roles/maas/files/named.conf.options.inside.maas b/roles/maas/files/named.conf.options.inside.maas
new file mode 100644
index 0000000..ad87061
--- /dev/null
+++ b/roles/maas/files/named.conf.options.inside.maas
@@ -0,0 +1,9 @@
+forwarders {
+    8.8.8.8;
+};
+
+dnssec-validation auto;
+
+allow-query { any; };
+allow-recursion { trusted; };
+allow-query-cache { trusted; };
diff --git a/roles/maas/files/update_dns_template.sh b/roles/maas/files/update_dns_template.sh
new file mode 100755
index 0000000..448d3fc
--- /dev/null
+++ b/roles/maas/files/update_dns_template.sh
@@ -0,0 +1,72 @@
+#!/bin/bash
+
+LSUB=$1
+DOMAIN=$2
+
+ip2int() {
+    local a b c d
+    { IFS=. read a b c d; } <<< $1
+    echo $(((((((a << 8) | b) << 8) | c) << 8) | d))
+}
+
+int2ip() {
+    local ui32=$1; shift
+    local ip n
+    for n in 1 2 3 4; do
+        ip=$((ui32 & 0xff))${ip:+.}$ip
+        ui32=$((ui32 >> 8))
+    done
+    echo $ip
+}
+
+netmask() {
+    local mask=$((0xffffffff << (32 - $1))); shift
+    int2ip $mask
+}
+
+
+broadcast() {
+    local addr=$(ip2int $1); shift
+    local mask=$((0xffffffff << (32 -$1))); shift
+    int2ip $((addr | ~mask))
+}
+
+network() {
+    local addr=$(ip2int $1); shift
+    local mask=$((0xffffffff << (32 -$1))); shift
+    int2ip $((addr & mask))
+}
+
+first() {
+    local addr=$(ip2int $1)
+    addr=`expr $addr + 1`
+    int2ip $addr
+}
+
+LBITS=`echo "$LSUB" | cut -d/ -f2`
+LNETW=` echo "$LSUB" | cut -d/ -f1`
+LMASK=`netmask $LBITS`
+LHOST=`first $LNETW`
+
+DEST=/etc/maas/templates/dns/zone.template
+OUT=$(mktemp -u)
+cat /tmp/zone.template | awk '/; CORD - DO NOT EDIT BELOW THIS LINE/{exit};1' | awk "/^auto / { if (\$2 == \"${IFACE_MGMT}\") { IN=1 } else {IN=0} } /^iface / { if (\$2 == \"${IFACE_MGMT}\") { IN=1 } else {IN=0}}  /^#/ || /^\s*\$/ { IN=0 } IN==0 {print} IN==1 { print \"#\" \$0 }" > $OUT
+
+cat <<EOT >> $OUT
+; CORD - DO NOT EDIT BELOW THIS LINE
+{{if domain == '$DOMAIN'}}
+\$INCLUDE "/etc/bind/maas/dhcp_harvest.inc"
+$HOSTNAME IN A $LHOST
+{{endif}}
+EOT
+
+diff $DEST $OUT 2>&1 > /dev/null
+if [ $? -ne 0 ]; then
+    cp $DEST $DEST.last
+    cp $OUT $DEST
+    echo -n "true"
+else
+    echo -n "false"
+fi
+
+rm $OUT
diff --git a/roles/maas/files/zone.template b/roles/maas/files/zone.template
new file mode 100644
index 0000000..f3a22fc
--- /dev/null
+++ b/roles/maas/files/zone.template
@@ -0,0 +1,25 @@
+; Zone file modified: {{modified}}.
+; Note that the modification time of this file doesn't reflect
+; the actual modification time.  MAAS controls the modification time
+; of this file to be able to force the zone to be reloaded by BIND.
+$TTL    300
+@   IN    SOA {{domain}}. nobody.example.com. (
+              {{serial}} ; serial
+              600 ; Refresh
+              1800 ; Retry
+              604800 ; Expire
+              300 ; TTL
+              )
+
+    IN  NS  {{domain}}.
+{{for type, directive in generate_directives.items()}}
+{{for iterator_values, rdns, hostname in directive}}
+$GENERATE {{iterator_values}} {{rdns}} IN {{type}} {{hostname}}
+{{endfor}}
+{{endfor}}
+
+{{for type, mapping in mappings.items()}}
+{{for item_from, item_to in mapping}}
+{{item_from}} IN {{type}} {{item_to}}
+{{endfor}}
+{{endfor}}
diff --git a/roles/maas/meta/main.yml b/roles/maas/meta/main.yml
new file mode 100644
index 0000000..bc5468b
--- /dev/null
+++ b/roles/maas/meta/main.yml
@@ -0,0 +1,14 @@
+---
+galaxy_info:
+  author: David Bainbridge
+  description: Ubuntu MAAS from Canonical
+  min_ansible_version: 2
+  platforms:
+    - name: Ubuntu
+      versions:
+        - trusty
+  categories:
+    - development
+    - system
+
+dependencies: [docker]
diff --git a/roles/maas/tasks/main.yml b/roles/maas/tasks/main.yml
new file mode 100644
index 0000000..601154b
--- /dev/null
+++ b/roles/maas/tasks/main.yml
@@ -0,0 +1,193 @@
+---
+- name: Install Prerequisites
+  become: yes
+  apt: name={{ item }} state=latest
+  with_items:
+    - git
+    - bridge-utils
+    - curl
+    - python-pycurl
+    - python-pip
+    - ethtool
+
+- name: Install Python Prerequisites
+  become: yes
+  pip: name={{ item }} state=latest
+  with_items:
+    - docker-py
+
+- name: Stop MAAS Automation Container
+  become: yes
+  docker:
+    name: automation
+    image: cord/maas-automation:0.1-prerelease
+    state: absent
+
+- name: Stop DHCP Harvester Container
+  become: yes
+  docker:
+    name: harvester
+    image: cord/maas-dhcp-harvester:0.1-prerelease
+    state: absent
+
+- name: MAAS Repository
+  become: yes
+  apt_repository:
+    repo: ppa:maas/stable
+    update_cache: yes
+    state: present
+
+- name: MAAS
+  become: yes
+  apt:
+    name: maas
+    state: latest
+
+- name: MAAS Configuration Directory
+  become: yes
+  file:
+    path: /etc/maas
+    owner: maas
+    group: maas
+    mode: 0755
+    state: directory
+
+- name: Host Name Mapping File
+  become: yes
+  copy:
+    src: files/mappings.json
+    dest: /etc/maas/mappings.json
+    owner: maas
+    group: maas
+    mode: 0644
+
+- name: Verify MAAS admin User
+  become: yes
+  shell: maas-region-admin apikey --username=admin 2>/dev/null | wc -l
+  register: maas_admin_user_exists
+  changed_when: false
+
+- name: MAAS admin User
+  become: yes
+  command: maas-region-admin createadmin --username=admin --password=admin --email={{ maas.admin_email }}
+  when: maas_admin_user_exists.stdout == '0'
+
+- name: Verify MAAS User
+  become: yes
+  shell: maas-region-admin apikey --username={{ maas.user }} 2>/dev/null | wc -l
+  register: maas_user_exists
+  changed_when: false
+
+- name: MAAS User
+  become: yes
+  command: maas-region-admin createadmin --username={{ maas.user }} --password={{ maas.user_password }} --email={{ maas.user_email }}
+  when: maas_user_exists.stdout == '0'
+
+- name: MAAS User API Key
+  become: yes
+  command: maas-region-admin apikey --username={{ maas.user }}
+  register: apikey
+  changed_when: false
+
+- name: Verify Default Virsh Network
+  shell: virsh net-list | grep default | wc -l
+  register: virsh_default_network_exists
+  changed_when: false
+
+- name: Default Virsh Network Absent
+  become: yes
+  command: virsh net-destroy default
+  when: virsh_default_network_exists.stdout != '0'
+
+- name: Network Configuration
+  become: yes
+  script: files/generate_network_config.sh {{ interfaces.management }} {{ networks.management }} {{ networks.bridge }} {{ networks.bridge_name }}
+  register: network_config_changed
+  changed_when: network_config_changed.stdout == 'true'
+
+- name: Network Masquerading (NAT)
+  become: yes
+  template:
+    src: templates/nat.j2
+    dest: /etc/network/if-pre-up.d/nat
+    owner: root
+    group: root
+    mode: 0755
+
+- name: Activate Masquerading (NAT)
+  become: yes
+  command: /etc/network/if-pre-up.d/nat report-changed
+  register: masq_changed
+  changed_when: masq_changed.stdout == 'true'
+
+- name: VM Bridge
+  become: yes
+  template:
+    src: templates/create_bridge.j2
+    dest: /etc/network/if-pre-up.d/create_bridge_{{ networks.bridge_name }}
+    owner: root
+    group: root
+    mode: 0755
+
+- name: Activate VM Bridge
+  become: yes
+  command: /etc/network/if-pre-up.d/create_bridge_{{ networks.bridge_name }} report-changed
+  register: bridge_changed
+  changed_when: bridge_changed.stdout == 'true'
+
+- name: Management Interface
+  become: yes
+  shell: ifdown {{ interfaces.management }} && ifup {{ interfaces.management }}
+  when: network_config_changed.stdout == 'true'
+
+- name: Management Interface IP Address
+  shell: ifconfig {{ interfaces.management }} 2>&1 | grep "inet addr:" | sed -e 's/.*:\([.0-9]*\)[ ]*Bcast.*/\1/g'
+  register: mgmt_ip_address
+  changed_when: false
+
+- name: Switch Boot Resources
+  copy:
+    src=files/{{ item }}
+    dest=/var/www/html/{{ item }}
+    owner=root
+    group=root
+    mode=0644
+  with_items:
+    - onie-installer-x86_64-accton_as5712_54x-r0
+    - onie-installer-x86_64-accton_as6712_32x-r0
+
+- name: Wait for MAAS to Intialize (start)
+  pause:
+    seconds=30
+  changed_when: false
+
+- name: Configure MAAS
+  become: yes
+  command: docker run -ti ciena/cord-maas-bootstrap:0.1-prerelease --apikey='{{apikey.stdout}}' --sshkey='{{maas.user_sshkey}}' --url='http://{{mgmt_ip_address.stdout}}/MAAS/api/1.0' --network='{{networks.management}}' --interface='{{interfaces.management}}' --zone='administrative' --cluster='Cluster master' --domain='{{maas.domain}}' --bridge='{{networks.bridge_name}}' --bridge-subnet='{{networks.bridge}}'
+  register: maas_config_result
+  changed_when: maas_config_result.stdout.find("CHANGED") != -1
+  failed_when: "'ERROR' in maas_config_result.stdout"
+
+- name: Custom MAAS Configuration Template
+  become: yes
+  copy:
+    src: files/{{ item.src }}
+    dest: "{{ item.dest }}"
+    owner: maas
+    group: maas
+    mode: 0644
+  with_items:
+    - { src: 'dhcpd.blacklist', dest: '/etc/dhcp' }
+    - { src: 'dhcpd.reservations', dest: '/etc/dhcp' }
+    - { src: 'dhcp_harvest.inc', dest: '/etc/bind/maas' }
+    - { src: 'named.conf.options.inside.maas', dest: '/etc/bind/maas' }
+    - { src: 'dhcpd.conf.template', dest: '/etc/maas/templates/dhcp' }
+    - { src: 'dhcp_harvest.inc', dest: '/etc/maas/templates/dns' }
+    - { src: 'zone.template', dest: '/tmp' }
+
+- name: Custom DNS Zone Template
+  become: yes
+  script: files/update_dns_template.sh {{ networks.management }} {{ maas.domain }}
+  register: dns_template_changed
+  changed_when: dns_template_changed.stdout == 'true'
+
diff --git a/roles/maas/templates/automation-compose.yml.j2 b/roles/maas/templates/automation-compose.yml.j2
new file mode 100644
index 0000000..1d8d751
--- /dev/null
+++ b/roles/maas/templates/automation-compose.yml.j2
@@ -0,0 +1,13 @@
+automation:
+  image: ciena/cord-maas-automation:0.1-prerelease
+  container_name: automation
+  labels:
+    - "lab.solution=CORD"
+    - "lab.component=automation"
+  restart: always
+  environment:
+    # need to explicitly set the resolver, else go will skip the /etc/hosts file
+    - "GODEBUG=netdns=go"
+  volumes:
+    - ".:/mappings"
+  command: [ "-apiVersion", "1.0", "-apikey", "{{ apikey.stdout }}", "-maas", "http://{{ ip_address.stdout }}/MAAS", "-period", "30s", "-mappings", "@/mappings/mappings.json", "-always-rename" ]
diff --git a/roles/maas/templates/create_bridge.j2 b/roles/maas/templates/create_bridge.j2
new file mode 100755
index 0000000..5f12261
--- /dev/null
+++ b/roles/maas/templates/create_bridge.j2
@@ -0,0 +1,17 @@
+#!/bin/bash
+
+REPORT_CHANGED=0
+if [ $# -gt 0 ]; then
+    REPORT_CHANGED=1
+fi
+CHANGED='false'
+
+FOUND=$(brctl show | grep "^{{ networks.bridge_name }}" | wc -l)
+if [ $FOUND -eq 0 ]; then
+    CHANGED='true'
+    brctl addbr {{ networks.bridge_name }}
+fi
+
+if [ $REPORT_CHANGED -ne 0 ]; then
+    echo -n $CHANGED
+fi
diff --git a/roles/maas/templates/harvest-compose.yml.j2 b/roles/maas/templates/harvest-compose.yml.j2
new file mode 100644
index 0000000..b19b0b1
--- /dev/null
+++ b/roles/maas/templates/harvest-compose.yml.j2
@@ -0,0 +1,15 @@
+harvester:
+    image: ciena/cord-maas-dhcp-harvester:0.1-prerelease
+    container_name: harvester
+    restart: always
+    labels:
+        - "lab.solution=cord"
+        - "lab.component=harvester"
+    volumes:
+        - "/var/lib/maas/dhcp:/dhcp"
+        - "/etc/bind/maas:/bind"
+        - "/etc/bind/maas:/key"
+        - "/etc/dhcp:/etc/dhcp"
+    ports:
+        - "8954:8954"
+    command: [ "--server", "{{ ip_address.stdout }}", "--port", "954", "--key", "/key/rndc.conf.maas", "--zone", "cord.lab", "--update", "--verify", "--timeout", "1s", "--repeat", "5m", "--quiet", "2s", "--workers", "10", "--filter", "^(?!cord)" ]
diff --git a/roles/maas/templates/nat.j2 b/roles/maas/templates/nat.j2
new file mode 100755
index 0000000..2a540be
--- /dev/null
+++ b/roles/maas/templates/nat.j2
@@ -0,0 +1,25 @@
+#!/bin/bash
+
+# Add rules to configure NAT
+
+REPORT_CHANGED=0
+if [ $# -gt 0 ]; then
+    REPORT_CHANGED=1
+fi
+CHANGED='false'
+
+iptables --table nat --check POSTROUTING --out-interface {{ interfaces.external }} -j MASQUERADE &>> /dev/null
+if [ $? -ne 0 ]; then
+    iptables --table nat --append POSTROUTING --out-interface {{ interfaces.external }} -j MASQUERADE
+    CHANGED='true'
+fi
+
+iptables --check FORWARD --in-interface {{ interfaces.management }} -j ACCEPT &>> /dev/null
+if [ $? -ne 0 ]; then
+    iptables --append FORWARD --in-interface {{ interfaces.management }} -j ACCEPT
+    CHANGED='true'
+fi
+
+if [ $REPORT_CHANGED -ne 0 ]; then
+    echo -n $CHANGED
+fi
diff --git a/roles/maas/vars/main.yml b/roles/maas/vars/main.yml
new file mode 100644
index 0000000..f392709
--- /dev/null
+++ b/roles/maas/vars/main.yml
@@ -0,0 +1,38 @@
+maas:
+    admin_email: admin@cord.lab
+    user: cord
+    user_password: cord
+    user_email: cord@cord.lab
+    user_sshkey: "{{ lookup('file', 'files/cord_id_rsa.pub') }}"
+
+    # CHANGE:
+    #   'domain' specifies the domain name configured in to MAAS
+    domain: cord.lab
+
+interfaces:
+    # CHANGE:
+    #   'external'   specifies the interface on which the head node is
+    #                connected to the internet
+    #   'management' specifies the interface on which the head node will
+    #                service DHCP and PXE boot requests
+    external: eth3
+    management: eth2
+
+networks:
+    # CHANGE:
+    #   'management' specifies the network that MAAS will allocate
+    #                via DHCP over the 'management' interface specified
+    #                above
+    #   'bridge'     specifies the network that MAAS will allocate
+    #                via DHCP to the VM created in support of XOS
+    #                on the head node.
+    #   'fabric'     specifies the network that will be assigned to
+    #                the leaf - spine fabric
+    management: 10.6.0.0/24
+    bridge: 172.18.0.0/24
+    fabric: 10.6.1.0/24
+
+    # CHANGE:
+    #   'bridge' name of the bride to create that is used when connecting
+    #            the VMs created in support of XOS
+    bridge_name: mgmtbr
diff --git a/roles/onos-fabric/files/bin/minify b/roles/onos-fabric/files/bin/minify
new file mode 100755
index 0000000..b91023c
--- /dev/null
+++ b/roles/onos-fabric/files/bin/minify
@@ -0,0 +1,29 @@
+#!/bin/bash
+
+PROG=$(basename $0)
+
+usage() {
+    echo "$PROG: [options]"
+    echo ""
+    echo "    -h | --help       display this message"
+}
+
+FILE=
+while [ $# -gt 0 ]; do
+    case $1 in
+        -h|--help)
+            usage
+            exit
+            ;;
+        *)
+            FILE=$1
+            ;;
+    esac
+    shift
+done
+
+if [ "$FILE x" == " x" ]; then
+    sed -e 's|//.*$||g' -e '/^\s*$/d' # <&0
+else
+    cat $FILE | sed -e 's|//.*$||g' -e '/^\s*$/d'
+fi
diff --git a/roles/onos-fabric/files/bin/onos-cfg-delete b/roles/onos-fabric/files/bin/onos-cfg-delete
new file mode 100755
index 0000000..4404b5c
--- /dev/null
+++ b/roles/onos-fabric/files/bin/onos-cfg-delete
@@ -0,0 +1,4 @@
+#!/bin/bash
+
+curl -slL -X DELETE --header "Accept: application/json" "http://karaf:karaf@localhost:8181/onos/v1/network/configuration" $*
+
diff --git a/roles/onos-fabric/files/bin/onos-cfg-get b/roles/onos-fabric/files/bin/onos-cfg-get
new file mode 100755
index 0000000..8d0cabf
--- /dev/null
+++ b/roles/onos-fabric/files/bin/onos-cfg-get
@@ -0,0 +1,4 @@
+#!/bin/bash
+
+curl -sLl -X GET --header "Accept: application/json" "http://karaf:karaf@localhost:8181/onos/v1/network/configuration" | python -m json.tool
+
diff --git a/roles/onos-fabric/files/bin/onos-cfg-post b/roles/onos-fabric/files/bin/onos-cfg-post
new file mode 100755
index 0000000..328b8b0
--- /dev/null
+++ b/roles/onos-fabric/files/bin/onos-cfg-post
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+cat $1 | sed -e 's|//.*$||g' -e '/^\s*$/d' | curl -slL -X POST --header "Content-Type: application/json" --header "Accept: application/json" -d "@-" "http://karaf:karaf@localhost:8181/onos/v1/network/configuration"
diff --git a/roles/onos-fabric/files/bin/ping-test.sh b/roles/onos-fabric/files/bin/ping-test.sh
new file mode 100755
index 0000000..d7b894e
--- /dev/null
+++ b/roles/onos-fabric/files/bin/ping-test.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+HOSTS="10.3.1.1 10.3.1.2 10.3.2.1 10.3.2.2 10.3.1.254 10.3.2.254 192.168.10.1 8.8.8.8"
+
+ME=$(ifconfig | grep "10\.3\.[0-9]\.[0-9]" | sed -e 's/.*addr:\(10\.3\.[0-9]\.[0-9]\).*/\1/g' 2> /dev/null) 
+echo "FROM: $ME"
+for TO in $HOSTS; do
+    T=$(ping -q -c 1 -W 1 -I eth0 $TO | grep rtt | awk '{print $4}' | sed -e 's|/| |g') #sed -e 's|r| |')
+    echo  "$TO: $T" | awk '{printf("    %-15s %-7s\n", $1, $2, $3, $4)}'
+done
diff --git a/roles/onos-fabric/files/bin/restart-vms.sh b/roles/onos-fabric/files/bin/restart-vms.sh
new file mode 100755
index 0000000..ef14e5f
--- /dev/null
+++ b/roles/onos-fabric/files/bin/restart-vms.sh
@@ -0,0 +1,39 @@
+#!/bin/bash
+
+function verify {
+    local L=$1
+    for i in $L; do
+        grep $i /etc/bind/maas/dhcp_harvest.inc > /dev/null 2>&1
+        if [ $? -ne 0 ]; then
+            echo "0"
+            return
+        fi
+    done
+    echo "1"
+}
+
+for i in $(uvt-kvm list); do
+    virsh start $i
+done
+
+LIST=$(uvt-kvm list)
+CNT=$(uvt-kvm list | wc -l)
+# plus 4 for the switches
+
+RETRY=5
+VERIFIED=0
+while [ $VERIFIED -ne 1 -a $RETRY -gt 0 ]; do
+    echo "INFO: Waiting for VMs to start"
+    sleep 5
+    curl -slL -XPOST http://127.0.0.1:8954/harvest >> /dev/null
+    VERIFIED=$(verify $LIST)
+    RETRY=$(expr $RETRY - 1)
+    echo "INFO: Verifing all VMs started"
+done
+
+if [ $VERIFIED -ne 1 ]; then
+    echo "ERROR: Likely VMs did not all boot correctly"
+    exit 1
+else
+    echo "INFO: Looks like all VM started correctly"
+fi
diff --git a/roles/onos-fabric/meta/main.yml b/roles/onos-fabric/meta/main.yml
new file mode 100644
index 0000000..bf39d8c
--- /dev/null
+++ b/roles/onos-fabric/meta/main.yml
@@ -0,0 +1,15 @@
+---
+galaxy_info:
+  author: Ciena Blueplanet
+  description: Docker Engine and Docker Compose
+  company: Ciena Blueplanet
+  license: Apache 2.0
+  min_ansible_version: 2.0
+  platforms:
+    - name: Ubuntu
+      versions:
+        - trusty
+  galaxy_tags:
+    - development
+    - system
+dependencies: []
diff --git a/roles/onos-fabric/tasks/main.yml b/roles/onos-fabric/tasks/main.yml
new file mode 100644
index 0000000..b2bccc5
--- /dev/null
+++ b/roles/onos-fabric/tasks/main.yml
@@ -0,0 +1,45 @@
+---
+- name: User Local bin directory
+  file:
+    path={{ ansible_env.HOME }}/bin
+    state=directory
+    owner=ubuntu
+    group=ubuntu
+    mode=0755
+
+- name: Copy Utility Commands
+  copy:
+    src=files/bin/{{ item }}
+    dest={{ ansible_env.HOME }}/bin
+    owner=ubuntu
+    group=ubuntu
+    mode=0755
+  with_items:
+    - minify
+    - onos-cfg-get
+    - onos-cfg-post
+    - onos-cfg-delete
+    - ping-test.sh
+
+- name: Include Utility Commands in User Path
+  lineinfile:
+    dest={{ ansible_env.HOME }}/.bashrc
+    line="PATH=$HOME/bin:$PATH"
+    state=present
+    insertafter=EOF
+
+- name: Custom ONOS
+  unarchive:
+    src=files/onos-1.6.0.ubuntu.tar.gz
+    dest={{ ansible_env.HOME }}
+    owner=ubuntu
+    group=ubuntu
+
+- name: ONOS Fabric Configuration
+  template:
+    src=templates/fabric-network-config.json.j2
+    dest={{ ansible_env.HOME }}/fabric-network.config.json
+    owner=ubuntu
+    group=ubuntu
+    mode=0644
+    
diff --git a/roles/onos-fabric/templates/fabric-network-config.json.j2 b/roles/onos-fabric/templates/fabric-network-config.json.j2
new file mode 100644
index 0000000..f39cf60
--- /dev/null
+++ b/roles/onos-fabric/templates/fabric-network-config.json.j2
@@ -0,0 +1,218 @@
+// This is a commented JSON data file. The comments must stripped from the
+// file before passing it to a JSON parser. This can be done various way
+// including the JavaScript JSON.minify() function or a simple sed script
+// such as "catting" this file through sed -e '|s//.*$||g'.
+
+
+// This file represents the network configuration for the cord demo pod number
+// two (2).
+{
+    "ports" : {
+
+        // Leaf-1/port-1 connected to cord-r2-s1/eth0
+        "of:0000000000000021/1" : {
+            "interfaces" : [
+                {
+                    "ips" : [ "10.3.1.254/24" ] // Represents a fake gateway
+                                                // for this subnet. ONOS will
+                                                // ARP this and respond.
+                }
+            ]
+        },
+
+        // Leaf-1/port-2 connected to cord-r2-s2/eth0
+        "of:0000000000000021/2" : {
+            "interfaces" : [
+                {
+                    "ips" : [ "10.3.1.254/24" ] // Represents a fake gateway
+                                                // for this subnet. ONOS will
+                                                // ARP this and respond.
+                },
+                {
+                    "vlan" : "1000" // cross-connect s-tag 1000 to Tibit OLT
+                },
+                // Need to specify the public IP for the vSG
+                {
+                    "ips" : [ "10.3.1.130/32" ] // vSG public IP address /32
+                }
+            ]
+        },
+
+	// Leaf-1/port-129 connected to Tibit OLT
+        // The physical port is port 25, but we are using a break out cable and thus the switch
+        // create virtual ports
+        "of:0000000000000021/129" : {
+            "interfaces" : [
+                {
+                    "name" : "tibit-olt", // unused
+                    "vlan" : "1000" // cross-connect s-tag 42 to vSG
+                }
+            ]
+        },
+
+        // Leaf-2/port-3 connected to cord-r2-s3/eth0
+        "of:0000000000000022/3" : {
+            "interfaces" : [
+                {
+                    "ips" : [ "10.3.2.254/24" ] // Represents a fake gateway
+                                                // for this subnet. ONOS will
+                                                // ARP this and respond.
+                }
+            ]
+        },
+
+        // Leaf-2/port-4 connected to cord-r2-s4/eth0
+        "of:0000000000000022/4" : {
+            "interfaces" : [
+                {
+                    "ips" : [ "10.3.2.254/24" ] // Represents a fake gateway
+                                                // for this subnet. ONOS will
+                                                // ARP this and respond.
+                }
+            ]
+        }
+    },
+
+    "devices" : {
+        "of:0000000000000021" : {
+            "segmentrouting" : {
+                "name" : "leaf-1",
+                "nodeSid" : 101,
+                "routerIp" : "10.3.1.254",
+                "routerMac" : "cc:37:ab:7c:b9:d6",
+                "isEdgeRouter" : true,
+                "adjacencySids" : []
+            }
+        },
+        "of:0000000000000022" : {
+            "segmentrouting" : {
+                "name" : "leaf-2",
+                "nodeSid" : 102,
+                "routerIp" : "10.3.2.254",
+                "routerMac" : "cc:37:ab:7c:ba:da",
+                "isEdgeRouter" : true,
+                "adjacencySids" : []
+            }
+        },
+        "of:0000000000000011" : {
+            "segmentrouting" : {
+                "name" : "spine-1",
+                "nodeSid" : 103,
+                "routerIp" : "10.2.30.1",
+                "routerMac" : "cc:37:ab:7c:be:68",
+                "isEdgeRouter" : false,
+                "adjacencySids" : []
+            }
+        },
+        "of:0000000000000012" : {
+            "segmentrouting" : {
+                "name" : "spine-2",
+                "nodeSid" : 104,
+                "routerIp" : "10.2.30.2",
+                "routerMac" : "cc:37:ab:7c:bf:ee",
+                "isEdgeRouter" : false,
+                "adjacencySids" : []
+            }
+        }
+    },
+    "links": {
+        // spine 1/1 connected to leaf 1/31
+        "of:0000000000000011/1-of:0000000000000021/31": {
+            "basic": {}
+        },
+
+        // spine 1/2 connected to leaf 2/31
+        "of:0000000000000011/2-of:0000000000000022/31": {
+            "basic": {}
+        },
+
+        // spine 2/1 connected to leaf 1/32
+        "of:0000000000000012/1-of:0000000000000021/32": {
+            "basic": {}
+        },
+
+        // spine 2/2 connected to leaf 2/32
+        "of:0000000000000012/2-of:0000000000000022/32": {
+            "basic": {}
+        },
+
+        // leaf 1/31 connected to spine 1/1
+        "of:0000000000000021/31-of:0000000000000011/1": {
+            "basic": {}
+        },
+
+        // leaf 1/32 connected to spine 2/1
+        "of:0000000000000021/32-of:0000000000000012/1": {
+            "basic": {}
+        },
+
+        // leaf 2/31 connected to spine 1/2
+        "of:0000000000000022/31-of:0000000000000011/2": {
+            "basic": {}
+        },
+
+        // leaf 2/23 connected to spine 2/2
+        "of:0000000000000022/32-of:0000000000000012/2": {
+            "basic": {}
+        }
+    },
+    "hosts" : {
+        // cord-r2-s1 iface eth0
+        "3c:fd:fe:9e:93:10/-1" : {
+            "basic": {
+                "ips": ["10.3.1.1"],                  // host IP on fabric
+                "location": "of:0000000000000021/1"   // link back to fabric leaf-1/port-1
+            }
+        },
+
+        // cord-r2-s2 iface eth0
+        "3c:fd:fe:9e:8a:88/-1" : {
+            "basic": {
+                "ips": ["10.3.1.2"],                 // host IP on fabric
+                "location": "of:0000000000000021/2"  // link back to fabric leaf-1/port-2
+            }
+        },
+
+       // fa:16:3e:94:7e:c5
+       // fa:16:3e:94:7e:c5
+       // OLD: 02:42:0a:03:01:82
+
+        "fa:16:3e:94:7e:c5/-1" : { // vSG1
+            "basic": {
+                "ips": ["10.3.1.130"], // vSG1 public IP address
+                "location": "of:0000000000000001/5"
+            }
+        },
+
+	// OLD: 02:42:0a:03:01:83
+        "fa:16:3e:91:82:6a/-1" : { // vSG1 VM
+            "basic" : {
+                "ips": ["10.3.1.131"],
+                "location": "of:0000000000000001/5"
+             }
+         },
+
+        // cord-r2-s3 iface eth0
+        "3c:fd:fe:9e:94:98/-1" : {
+            "basic": {
+                "ips": ["10.3.2.1"],                 // host IP on fabric
+                "location": "of:0000000000000022/3"  // link back to fabric leaf-2/port-3
+            }
+        },
+
+        // cord-r2-s4 iface eth0
+        "3c:fd:fe:9e:97:98/-1" : {
+            "basic": {
+                "ips": ["10.3.2.2"],                 // host IP on fabric
+                "location": "of:0000000000000022/4"  // link back to fabric leaf-2/port-4
+            }
+        }
+    },
+    "apps" : {
+        "org.onosproject.core" : {
+            "core" : {
+                "linkDiscoveryMode" : "STRICT" // enable strict link validation
+            }    
+        }
+    }
+}
diff --git a/roles/onos-fabric/vars/main.yml b/roles/onos-fabric/vars/main.yml
new file mode 100644
index 0000000..457d453
--- /dev/null
+++ b/roles/onos-fabric/vars/main.yml
@@ -0,0 +1,20 @@
+---
+fabric:
+  network: 10.6.1
+  spine1:
+    of_id: of:0000000000000011
+  spine2:
+    of_id: of:0000000000000012
+  leaf1:
+    of_id: of:0000000000000021
+  leaf2:
+    of_id: of:0000000000000022
+  hosts:
+    cord-r6-s1:
+      mac: 3c:fd:fe:9e:94:30
+      ip: 10.6.1.1
+      location:
+        leaf: 1
+        port: 1
+  
+