Adds OpenDaylight HA (OVSDB Clustering) 43/10943/9
authorTim Rozet <trozet@redhat.com>
Wed, 2 Mar 2016 18:05:17 +0000 (13:05 -0500)
committerTim Rozet <trozet@redhat.com>
Sun, 6 Mar 2016 13:43:34 +0000 (08:43 -0500)
OVSDB Clustering changes include:
 - Modifications to create an OpenDaylight
   VIP for communication between Neutron SB <-> ODL NB.
 - OpenDaylight configured in HA mode in THT via puppet-opendaylight
 - OVS instances configured with managers pointing to all 3 ODL
   instances for HA mode
 - Build now points to latest Beryllium release
 - Modified puppet-neutron in build to pull latest stable/liberty

JIRA: APEX-107

Change-Id: Iab510db922dfcd2fbd4962b9751cd2f7e5724f44
Signed-off-by: Tim Rozet <trozet@redhat.com>
build/instack.sh
build/network-environment.yaml
build/opendaylight-puppet-neutron.patch [deleted file]
build/opendaylight.yaml [deleted file]
build/opnfv-puppet-tripleo.patch [new file with mode: 0644]
build/opnfv-tripleo-heat-templates.patch

index b03348d..2ba18b5 100755 (executable)
@@ -12,14 +12,16 @@ declare -i CNT
 
 #rdo_images_uri=https://repos.fedorapeople.org/repos/openstack-m/rdo-images-centos-liberty-opnfv
 rdo_images_uri=file:///stable-images
+rdo_images_cache=/stable-images
 onos_artifacts_uri=file:///stable-images/onos
+odl_artifacts_cache=/stable-images/odl
 
 vm_index=4
 RDO_RELEASE=liberty
 SSH_OPTIONS=(-o StrictHostKeyChecking=no -o GlobalKnownHostsFile=/dev/null -o UserKnownHostsFile=/dev/null)
 OPNFV_NETWORK_TYPES="admin_network private_network public_network storage_network"
 
-# check for dependancy packages
+# check for dependency packages
 for i in rpm-build createrepo libguestfs-tools python-docutils bsdtar; do
     if ! rpm -q $i > /dev/null; then
         sudo yum install -y $i
@@ -276,14 +278,14 @@ LIBGUESTFS_BACKEND=direct virt-customize \
 cat > /tmp/opendaylight.repo << EOF
 [opendaylight]
 name=OpenDaylight \$releasever - \$basearch
-baseurl=http://cbs.centos.org/repos/nfv7-opendaylight-4-testing/\$basearch/os/
+baseurl=http://cbs.centos.org/repos/nfv7-opendaylight-40-release/\$basearch/os/
 enabled=1
 gpgcheck=0
 EOF
 
-odlrpm=opendaylight-4.0.0-1.rc3.1.el7.noarch.rpm
-if [ -f ${rdo_images_uri}/$odlrpm ]; then
-    LIBGUESTFS_BACKEND=direct virt-customize --upload ${rdo_images_uri}/$odlrpm:/tmp/
+odlrpm=opendaylight-4.0.0-1.el7.noarch.rpm
+if [ -f ${rdo_images_cache}/$odlrpm ]; then
+    LIBGUESTFS_BACKEND=direct virt-customize --upload ${rdo_images_cache}/$odlrpm:/tmp/
     opendaylight=/tmp/$odlrpm
 else
     opendaylight=opendaylight
@@ -295,23 +297,35 @@ LIBGUESTFS_BACKEND=direct virt-customize \
     --install ${opendaylight},python-networking-odl \
     -a overcloud-full-opendaylight.qcow2
 
+# install Jolokia for ODL HA
+LIBGUESTFS_BACKEND=direct virt-customize \
+    --upload ${odl_artifacts_cache}/jolokia.tar.gz:/tmp/ \
+    --run-command "tar -xvf /tmp/jolokia.tar.gz -C /opt/opendaylight/system/org" \
+    -a overcloud-full-opendaylight.qcow2
+
 ## WORK AROUND
 ## when OpenDaylight lands in upstream RDO manager this can be removed
 
 # upload the opendaylight puppet module
 rm -rf puppet-opendaylight
-if [ -f ${rdo_images_uri}/puppet-opendaylight-3.2.2.tar.gz ]; then
-    cp ${rdo_images_uri}/puppet-opendaylight-3.2.2.tar.gz puppet-opendaylight.tar.gz
-else
-    git clone -b opnfv_integration https://github.com/dfarrell07/puppet-opendaylight
-    pushd puppet-opendaylight
-    git archive --format=tar.gz --prefix=opendaylight/ HEAD > ../puppet-opendaylight.tar.gz
-    popd
-fi
+# TMP FIX to see if this works
+git clone -b odl_ha_proxy_fix https://github.com/trozet/puppet-opendaylight
+pushd puppet-opendaylight
+git archive --format=tar.gz --prefix=opendaylight/ HEAD > ../puppet-opendaylight.tar.gz
+popd
+
+# grab latest puppet-neutron module
+rm -rf puppet-neutron
+git clone -b stable/liberty https://github.com/openstack/puppet-neutron.git
+pushd puppet-neutron
+git archive --format=tar.gz --prefix=neutron/ HEAD > ../puppet-neutron.tar.gz
+popd
+
 LIBGUESTFS_BACKEND=direct virt-customize --upload puppet-opendaylight.tar.gz:/etc/puppet/modules/ \
                                          --run-command "cd /etc/puppet/modules/ && tar xzf puppet-opendaylight.tar.gz" \
-                                         --upload ../opendaylight-puppet-neutron.patch:/tmp \
-                                         --run-command "cd /etc/puppet/modules/neutron && patch -Np1 < /tmp/opendaylight-puppet-neutron.patch" \
+                                         --run-command "rm -rf /etc/puppet/modules/neutron" \
+                                         --upload puppet-neutron.tar.gz:/etc/puppet/modules/ \
+                                         --run-command "cd /etc/puppet/modules/ && tar xzf puppet-neutron.tar.gz" \
                                          -a overcloud-full-opendaylight.qcow2
 
 # Patch in OpenDaylight installation and configuration
@@ -319,11 +333,12 @@ LIBGUESTFS_BACKEND=direct virt-customize --upload ../opnfv-tripleo-heat-template
                                          --run-command "cd /usr/share/openstack-tripleo-heat-templates/ && patch -Np1 < /tmp/opnfv-tripleo-heat-templates.patch" \
                                          -a instack.qcow2
 
-# REMOVE ME AFTER Brahmaputra
-LIBGUESTFS_BACKEND=direct virt-customize --upload ../puppet-neutron-force-metadata.patch:/tmp \
-                                         --run-command "cd /etc/puppet/modules/neutron && patch -Np1 < /tmp/puppet-neutron-force-metadata.patch" \
+# Patch in OPNFV custom puppet-tripleO
+LIBGUESTFS_BACKEND=direct virt-customize --upload ../opnfv-puppet-tripleo.patch:/tmp \
+                                         --run-command "cd /etc/puppet/modules/tripleo && patch -Np1 < /tmp/opnfv-puppet-tripleo.patch" \
                                          -a overcloud-full-opendaylight.qcow2
 
+# REMOVE ME AFTER Brahmaputra
 LIBGUESTFS_BACKEND=direct virt-customize --upload ../puppet-cinder-quota-fix.patch:/tmp \
                                          --run-command "cd /etc/puppet/modules/cinder && patch -Np1 < /tmp/puppet-cinder-quota-fix.patch" \
                                          -a overcloud-full-opendaylight.qcow2
index f9aa41c..a958d58 100644 (file)
@@ -48,6 +48,7 @@ parameters:
     NeutronTenantNetwork: tenant
     CeilometerApiNetwork: internal_api
     AodhApiNetwork: internal_api
+    OpenDaylightApiNetwork: internal_api
     MongoDbNetwork: internal_api
     CinderApiNetwork: internal_api
     CinderIscsiNetwork: storage
diff --git a/build/opendaylight-puppet-neutron.patch b/build/opendaylight-puppet-neutron.patch
deleted file mode 100644 (file)
index 9e0d713..0000000
+++ /dev/null
@@ -1,290 +0,0 @@
-From 8f1ca7078619b8ab67de2580522f7174bed40774 Mon Sep 17 00:00:00 2001
-From: Tim Rozet <trozet@redhat.com>
-Date: Tue, 24 Nov 2015 14:39:12 -0500
-Subject: [PATCH] Adds configuration support for OpenDaylight SDN Controller
-
-In order to use OpenDaylight with Neutron, ML2 must be configured to
-point to the OpenDaylight controller instance.  It also requires the
-networking-odl python library to drive communication with ODL.
-Additionally each Open vSwitch instance must be configured to set the ODL
-Controller as it's manager.
-
-Change-Id: If067e1057bec2d48f700838d86077a550bd27bd2
-Signed-off-by: Tim Rozet <trozet@redhat.com>
----
- manifests/plugins/ml2/opendaylight.pp              | 51 +++++++++++++++++
- manifests/plugins/ovs/opendaylight.pp              | 63 +++++++++++++++++++++
- .../neutron_plugins_ml2_opendaylight_spec.rb       | 65 ++++++++++++++++++++++
- .../neutron_plugins_ovs_opendaylight_spec.rb       | 60 ++++++++++++++++++++
- 4 files changed, 239 insertions(+)
- create mode 100644 manifests/plugins/ml2/opendaylight.pp
- create mode 100644 manifests/plugins/ovs/opendaylight.pp
- create mode 100644 spec/classes/neutron_plugins_ml2_opendaylight_spec.rb
- create mode 100644 spec/classes/neutron_plugins_ovs_opendaylight_spec.rb
-
-diff --git a/manifests/plugins/ml2/opendaylight.pp b/manifests/plugins/ml2/opendaylight.pp
-new file mode 100644
-index 0000000..7dc7937
---- /dev/null
-+++ b/manifests/plugins/ml2/opendaylight.pp
-@@ -0,0 +1,51 @@
-+#
-+# Install the OpenDaylight and generate config file
-+# from parameters in the other classes.
-+#
-+# === Parameters
-+#
-+# [*odl_controller_ip*]
-+# (required) The OpenDaylight controller IP
-+#
-+# [*package_ensure*]
-+# (optional) The intended state of the python-networking-odl
-+# package, i.e. any of the possible values of the 'ensure'
-+# property for a package resource type.
-+# Defaults to 'present'
-+#
-+# [*odl_username*]
-+# (optional) The opendaylight controller username
-+# Defaults to 'admin'
-+#
-+# [*odl_password*]
-+# (optional) The opendaylight controller password
-+# Defaults to 'admin'
-+#
-+# [*odl_port*]
-+# (optional) The opendaylight controller port
-+# Defaults to '8080'
-+#
-+class neutron::plugins::ml2::opendaylight (
-+  $odl_controller_ip,
-+  $package_ensure    = 'present',
-+  $odl_username      = 'admin',
-+  $odl_password      = 'admin',
-+  $odl_port          = '8080',
-+) {
-+  include ::neutron::params
-+  require ::neutron::plugins::ml2
-+
-+  ensure_resource('package', 'python-networking-odl',
-+    {
-+      ensure => $package_ensure,
-+      tag    => 'openstack',
-+    }
-+  )
-+
-+  neutron_plugin_ml2 {
-+    'ml2_odl/username': value => $odl_username;
-+    'ml2_odl/password': value => $odl_password;
-+    'ml2_odl/url':      value => "http://${odl_controller_ip}:${odl_port}/controller/nb/v2/neutron";
-+  }
-+
-+}
-diff --git a/manifests/plugins/ovs/opendaylight.pp b/manifests/plugins/ovs/opendaylight.pp
-new file mode 100644
-index 0000000..3ebdb0e
---- /dev/null
-+++ b/manifests/plugins/ovs/opendaylight.pp
-@@ -0,0 +1,63 @@
-+#
-+# Configure OVS to use OpenDaylight
-+#
-+# === Parameters
-+#
-+# [*odl_controller_ip*]
-+# (required) The OpenDaylight controller IP
-+#
-+# [*tunnel_ip*]
-+# (required) The IP of the host to use for tunneling
-+# tenant VXLAN/GRE over
-+#
-+# [*odl_port*]
-+# (optional) The opendaylight controller port
-+# Defaults to '8080'
-+#
-+# [*provider_mappings*]
-+# (optional) bridge mappings required if using VLAN
-+# tenant type.  Example: provider_mappings=br-ex:eth0
-+# Defaults to false
-+#
-+# [*odl_username*]
-+# (optional) The opendaylight controller username
-+# Defaults to 'admin'
-+#
-+# [*odl_password*]
-+# (optional) The opendaylight controller password
-+# Defaults to 'admin'
-+#
-+class neutron::plugins::ovs::opendaylight (
-+  $odl_controller_ip,
-+  $tunnel_ip,
-+  $odl_port          = '8080',
-+  $provider_mappings = false,
-+  $odl_username      = 'admin',
-+  $odl_password      = 'admin',
-+) {
-+
-+  exec { 'Wait for NetVirt OVSDB to come up':
-+    command   => "/bin/curl -o /dev/null --fail --silent --head -u ${odl_username}:${odl_password} \
-+                    http://${odl_controller_ip}:${odl_port}/restconf/operational/network-topology:network-topology/topology/netvirt:1",
-+    tries     => 20,
-+    try_sleep => 60,
-+  } ->
-+  # OVS manager
-+  exec { 'Set OVS Manager to OpenDaylight':
-+    command => "/usr/bin/ovs-vsctl set-manager tcp:${odl_controller_ip}:6640",
-+    unless  => "/usr/bin/ovs-vsctl show | /usr/bin/grep 'Manager \"tcp:${odl_controller_ip}:6640\"'",
-+  } ->
-+  # local ip
-+  exec { 'Set local_ip Other Option':
-+    command => "/usr/bin/ovs-vsctl set Open_vSwitch $(ovs-vsctl get Open_vSwitch . _uuid) other_config:local_ip=${tunnel_ip}",
-+    unless  => "/usr/bin/ovs-vsctl list Open_vSwitch | /usr/bin/grep 'local_ip=\"${tunnel_ip}\"'",
-+  }
-+
-+  # set mappings for VLAN
-+  if $provider_mappings {
-+    exec { 'Set provider_mappings Other Option':
-+      command => "/usr/bin/ovs-vsctl set Open_vSwitch $(ovs-vsctl get Open_vSwitch . _uuid) other_config:provider_mappings=${provider_mappings}",
-+      unless  => "/usr/bin/ovs-vsctl list Open_vSwitch | /usr/bin/grep 'provider_mappings' | /usr/bin/grep ${provider_mappings}",
-+    }
-+  }
-+}
-diff --git a/spec/classes/neutron_plugins_ml2_opendaylight_spec.rb b/spec/classes/neutron_plugins_ml2_opendaylight_spec.rb
-new file mode 100644
-index 0000000..5772b3b
---- /dev/null
-+++ b/spec/classes/neutron_plugins_ml2_opendaylight_spec.rb
-@@ -0,0 +1,65 @@
-+require 'spec_helper'
-+
-+describe 'neutron::plugins::ml2::opendaylight' do
-+
-+  let :pre_condition do
-+    "class { 'neutron::server': auth_password => 'password'}
-+     class { 'neutron':
-+      rabbit_password => 'passw0rd',
-+      core_plugin     => 'neutron.plugins.ml2.plugin.Ml2Plugin' }"
-+  end
-+
-+  let :default_params do
-+    {
-+      :package_ensure => 'present',
-+      :odl_username   => 'admin',
-+      :odl_password   => 'admin',
-+      :odl_port       => '8080',
-+    }
-+  end
-+
-+  let :params do
-+    {
-+      :odl_controller_ip => '127.0.0.1',
-+    }
-+  end
-+
-+  let :test_facts do
-+    {
-+      :operatingsystem        => 'default',
-+      :operatingsystemrelease => 'default',
-+    }
-+  end
-+
-+
-+  shared_examples_for 'neutron plugin opendaylight ml2' do
-+    before do
-+      params.merge!(default_params)
-+    end
-+
-+    it { is_expected.to contain_class('neutron::params') }
-+
-+    it 'should have' do
-+      is_expected.to contain_package('python-networking-odl').with(
-+        :ensure => params[:package_ensure],
-+        :tag    => 'openstack'
-+        )
-+    end
-+  end
-+
-+  context 'on RedHat platforms' do
-+    let :facts do
-+      test_facts.merge({:osfamily => 'RedHat'})
-+    end
-+
-+    it_configures 'neutron plugin opendaylight ml2'
-+  end
-+
-+  context 'on Debian platforms' do
-+    let :facts do
-+      test_facts.merge({:osfamily => 'Debian'})
-+    end
-+
-+    it_configures 'neutron plugin opendaylight ml2'
-+  end
-+end
-diff --git a/spec/classes/neutron_plugins_ovs_opendaylight_spec.rb b/spec/classes/neutron_plugins_ovs_opendaylight_spec.rb
-new file mode 100644
-index 0000000..d6b93df
---- /dev/null
-+++ b/spec/classes/neutron_plugins_ovs_opendaylight_spec.rb
-@@ -0,0 +1,60 @@
-+require 'spec_helper'
-+
-+describe 'neutron::plugins::ovs::opendaylight' do
-+
-+  let :pre_condition do
-+    "class { 'neutron::server': auth_password => 'password'}
-+     class { 'neutron':
-+      rabbit_password => 'passw0rd',
-+      core_plugin     => 'neutron.plugins.ml2.plugin.Ml2Plugin' }"
-+  end
-+
-+  let :default_params do
-+    {
-+      :provider_mappings => false,
-+      :odl_username      => 'admin',
-+      :odl_password      => 'admin',
-+      :odl_port          => '8080',
-+    }
-+  end
-+
-+  let :params do
-+    {
-+      :odl_controller_ip => '127.0.0.1',
-+      :tunnel_ip         => '127.0.0.1',
-+    }
-+  end
-+
-+  let :test_facts do
-+    {
-+      :operatingsystem        => 'default',
-+      :operatingsystemrelease => 'default',
-+    }
-+  end
-+
-+
-+  shared_examples_for 'neutron plugin opendaylight ovs' do
-+    before do
-+      params.merge!(default_params)
-+    end
-+
-+    it { is_expected.to contain_class('neutron::params') }
-+
-+  end
-+
-+  context 'on RedHat platforms' do
-+    let :facts do
-+      test_facts.merge({:osfamily => 'RedHat'})
-+    end
-+
-+    it_configures 'neutron plugin opendaylight ovs'
-+  end
-+
-+  context 'on Debian platforms' do
-+    let :facts do
-+      test_facts.merge({:osfamily => 'Debian'})
-+    end
-+
-+    it_configures 'neutron plugin opendaylight ovs'
-+  end
-+end
--- 
-2.5.0
-
diff --git a/build/opendaylight.yaml b/build/opendaylight.yaml
deleted file mode 100644 (file)
index 39e4aa3..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-parameters:
-    ExtraConfig:
-      neutron_mechanism_drivers: ['opendaylight']
-      neutron_tenant_network_type: vxlan
diff --git a/build/opnfv-puppet-tripleo.patch b/build/opnfv-puppet-tripleo.patch
new file mode 100644 (file)
index 0000000..99d3c74
--- /dev/null
@@ -0,0 +1,62 @@
+From 9f012bc3e4f23fa756f5435ee69e5d51dd6fc874 Mon Sep 17 00:00:00 2001
+From: Tim Rozet <tdrozet@gmail.com>
+Date: Thu, 3 Mar 2016 14:36:11 -0500
+Subject: [PATCH] Adds ODL to load balancer
+
+---
+ manifests/loadbalancer.pp | 27 +++++++++++++++++++++++++++
+ 1 file changed, 27 insertions(+)
+
+diff --git a/manifests/loadbalancer.pp b/manifests/loadbalancer.pp
+index 2fcfac6..6e13566 100644
+--- a/manifests/loadbalancer.pp
++++ b/manifests/loadbalancer.pp
+@@ -247,6 +247,10 @@
+ #  (optional) Enable or not Redis binding
+ #  Defaults to false
+ #
++# [*opendaylight*]
++#  (optional) Enable or not OpenDaylight binding
++#  Defaults to false
++#
+ class tripleo::loadbalancer (
+   $controller_virtual_ip,
+   $control_virtual_interface,
+@@ -299,6 +303,7 @@ class tripleo::loadbalancer (
+   $mysql_clustercheck        = false,
+   $rabbitmq                  = false,
+   $redis                     = false,
++  $opendaylight              = false,
+ ) {
+   if !$controller_host and !$controller_hosts {
+@@ -1044,4 +1049,26 @@ class tripleo::loadbalancer (
+     }
+   }
++  $opendaylight_api_vip = hiera('opendaylight_api_vip', $controller_virtual_ip)
++  $opendaylight_bind_opts = {
++    "${opendaylight_api_vip}:8081" => [],
++    "${public_virtual_ip}:8081" => [],
++  }
++
++  if $opendaylight {
++    haproxy::listen { 'opendaylight':
++      bind             => $opendaylight_bind_opts,
++      options          => {
++        'balance'   => 'source',
++      },
++      collect_exported => false,
++    }
++    haproxy::balancermember { 'opendaylight':
++      listening_service => 'opendaylight',
++      ports             => '8081',
++      ipaddresses       => hiera('opendaylight_api_node_ips', $controller_hosts_real),
++      server_names      => $controller_hosts_names_real,
++      options           => ['check', 'inter 2000', 'rise 2', 'fall 5'],
++    }
++  }
+ }
+-- 
+2.5.0
+
index f75c430..019424b 100644 (file)
@@ -1,4 +1,4 @@
-From d86b6e5cc493645e01484a1ea6cfff29cce2fa3d Mon Sep 17 00:00:00 2001
+From 516a741efddd1276d2b02010a7095737b6ce49cf Mon Sep 17 00:00:00 2001
 From: Tim Rozet <tdrozet@gmail.com>
 Date: Tue, 12 Jan 2016 16:49:57 -0500
 Subject: [PATCH] Adds current opnfv patch with ODL and ONOS support
@@ -6,24 +6,25 @@ Subject: [PATCH] Adds current opnfv patch with ODL and ONOS support
 ---
  environments/onos.yaml                             |   8 +
  environments/opendaylight-external.yaml            |  25 ++
- environments/opendaylight.yaml                     |  25 ++
+ environments/opendaylight.yaml                     |  26 ++
  environments/opendaylight_l3.yaml                  |   9 +
  environments/opendaylight_sdnvpn.yaml              |  29 ++
  environments/opendaylight_sfc.yaml                 |  28 ++
  network/endpoints/endpoint_map.yaml                |  31 ++
  overcloud-resource-registry-puppet.yaml            |   3 +
- overcloud-without-mergepy.yaml                     |  93 +++++
- puppet/all-nodes-config.yaml                       |  17 +
- puppet/compute.yaml                                |  35 ++
- puppet/controller.yaml                             |  93 ++++-
+ overcloud-without-mergepy.yaml                     | 103 +++++
+ puppet/all-nodes-config.yaml                       |  27 ++
+ puppet/compute.yaml                                |  41 ++
+ puppet/controller.yaml                             | 104 ++++-
  puppet/hieradata/common.yaml                       |   1 +
  puppet/hieradata/controller.yaml                   |   5 +-
- puppet/manifests/overcloud_compute.pp              |  31 +-
- puppet/manifests/overcloud_controller.pp           | 128 +++++-
- puppet/manifests/overcloud_controller_pacemaker.pp | 456 ++++++++++++++-------
+ puppet/manifests/overcloud_compute.pp              |  49 ++-
+ puppet/manifests/overcloud_controller.pp           | 130 +++++-
+ puppet/manifests/overcloud_controller_pacemaker.pp | 482 ++++++++++++++-------
  puppet/manifests/overcloud_opendaylight.pp         |  27 ++
  puppet/opendaylight-puppet.yaml                    | 223 ++++++++++
- 19 files changed, 1105 insertions(+), 162 deletions(-)
+ puppet/vip-config.yaml                             |   1 +
+ 20 files changed, 1190 insertions(+), 162 deletions(-)
  create mode 100644 environments/onos.yaml
  create mode 100644 environments/opendaylight-external.yaml
  create mode 100644 environments/opendaylight.yaml
@@ -80,10 +81,10 @@ index 0000000..411df21
 +      #opendaylight_install: true
 diff --git a/environments/opendaylight.yaml b/environments/opendaylight.yaml
 new file mode 100644
-index 0000000..c8abf75
+index 0000000..cfa4ad3
 --- /dev/null
 +++ b/environments/opendaylight.yaml
-@@ -0,0 +1,25 @@
+@@ -0,0 +1,26 @@
 +# Environment file used to enable OpenDaylight
 +# Currently uses overcloud image that is assumed
 +# to be virt-customized with ODL RPM already on it
@@ -102,6 +103,7 @@ index 0000000..c8abf75
 +    # increase this if you need more ODL nodes
 +    # OpenDaylightCount: 1
 +    NeutronL3HA: false
++    OpenDaylightEnableHA: true
 +    ExtraConfig:
 +      neutron_mechanism_drivers: ['opendaylight']
 +      neutron_tenant_network_type: vxlan
@@ -271,7 +273,7 @@ index 4cfed6b..adecc79 100644
    # NodeUserData == Cloud-init additional user-data, e.g cloud-config
    # ControllerExtraConfigPre == Controller configuration pre service deployment
 diff --git a/overcloud-without-mergepy.yaml b/overcloud-without-mergepy.yaml
-index a532c2f..965ca4c 100644
+index a532c2f..9c6e3cd 100644
 --- a/overcloud-without-mergepy.yaml
 +++ b/overcloud-without-mergepy.yaml
 @@ -15,6 +15,11 @@ parameters:
@@ -297,7 +299,7 @@ index a532c2f..965ca4c 100644
    NeutronEnableTunnelling:
      type: string
      default: "True"
-@@ -227,6 +236,31 @@ parameters:
+@@ -227,6 +236,35 @@ parameters:
      default: false
      description: Should MongoDb journaling be disabled
      type: boolean
@@ -309,6 +311,10 @@ index a532c2f..965ca4c 100644
 +    description: Knob to enable/disable ODL L3
 +    type: string
 +    default: 'no'
++  OpenDaylightEnableHA:
++    description: Knob to enable/disable ODL HA
++    type: boolean
++    default: false
 +  OpenDaylightFeatures:
 +    description: List of features to install with ODL
 +    type: comma_delimited_list
@@ -329,15 +335,16 @@ index a532c2f..965ca4c 100644
    PublicVirtualFixedIPs:
      default: []
      description: >
-@@ -575,6 +609,7 @@ parameters:
+@@ -575,6 +613,8 @@ parameters:
      default:
        NeutronTenantNetwork: tenant
        CeilometerApiNetwork: internal_api
 +      AodhApiNetwork: internal_api
++      OpenDaylightNetwork: internal_api
        MongoDbNetwork: internal_api
        CinderApiNetwork: internal_api
        CinderIscsiNetwork: storage
-@@ -664,6 +699,18 @@ parameters:
+@@ -664,6 +704,18 @@ parameters:
        structure as ExtraConfig.
      type: json
  
@@ -356,7 +363,7 @@ index a532c2f..965ca4c 100644
    # Hostname format for each role
    # Note %index% is translated into the index of the node, e.g 0/1/2 etc
    # and %stackname% is replaced with OS::stack_name in the template below.
-@@ -688,6 +735,10 @@ parameters:
+@@ -688,6 +740,10 @@ parameters:
      type: string
      description: Format for CephStorage node hostnames
      default: '%stackname%-cephstorage-%index%'
@@ -367,7 +374,7 @@ index a532c2f..965ca4c 100644
  
    # Identifiers to trigger tasks on nodes
    UpdateIdentifier:
-@@ -758,6 +809,7 @@ resources:
+@@ -758,6 +814,7 @@ resources:
      properties:
        CloudName: {get_param: CloudName}
        CeilometerApiVirtualIP: {get_attr: [VipMap, net_ip_map, {get_param: [ServiceNetMap, CeilometerApiNetwork]}]}
@@ -375,7 +382,7 @@ index a532c2f..965ca4c 100644
        CinderApiVirtualIP: {get_attr: [VipMap, net_ip_map, {get_param: [ServiceNetMap, CinderApiNetwork]}]}
        GlanceApiVirtualIP: {get_attr: [VipMap, net_ip_map, {get_param: [ServiceNetMap, GlanceApiNetwork]}]}
        GlanceRegistryVirtualIP: {get_attr: [VipMap, net_ip_map, {get_param: [ServiceNetMap, GlanceRegistryNetwork]}]}
-@@ -770,6 +822,29 @@ resources:
+@@ -770,6 +827,29 @@ resources:
        SwiftProxyVirtualIP: {get_attr: [VipMap, net_ip_map, {get_param: [ServiceNetMap, SwiftProxyNetwork]}]}
        PublicVirtualIP: {get_attr: [VipMap, net_ip_map, external]}
  
@@ -405,7 +412,7 @@ index a532c2f..965ca4c 100644
    Controller:
      type: OS::Heat::ResourceGroup
      depends_on: Networks
-@@ -781,6 +856,7 @@ resources:
+@@ -781,6 +861,7 @@ resources:
          properties:
            AdminPassword: {get_param: AdminPassword}
            AdminToken: {get_param: AdminToken}
@@ -413,7 +420,7 @@ index a532c2f..965ca4c 100644
            CeilometerBackend: {get_param: CeilometerBackend}
            CeilometerMeteringSecret: {get_param: CeilometerMeteringSecret}
            CeilometerPassword: {get_param: CeilometerPassword}
-@@ -832,6 +908,7 @@ resources:
+@@ -832,6 +913,7 @@ resources:
            NeutronBridgeMappings: {get_param: NeutronBridgeMappings}
            NeutronExternalNetworkBridge: {get_param: NeutronExternalNetworkBridge}
            NeutronEnableTunnelling: {get_param: NeutronEnableTunnelling}
@@ -421,7 +428,7 @@ index a532c2f..965ca4c 100644
            NeutronNetworkVLANRanges: {get_param: NeutronNetworkVLANRanges}
            NeutronPublicInterface: {get_param: NeutronPublicInterface}
            NeutronPublicInterfaceDefaultRoute: {get_param: NeutronPublicInterfaceDefaultRoute}
-@@ -853,6 +930,12 @@ resources:
+@@ -853,6 +935,13 @@ resources:
            NovaPassword: {get_param: NovaPassword}
            NtpServer: {get_param: NtpServer}
            MongoDbNoJournal: {get_param: MongoDbNoJournal}
@@ -431,28 +438,31 @@ index a532c2f..965ca4c 100644
 +          OpenDaylightFeatures: {get_param: OpenDaylightFeatures}
 +          OpenDaylightPassword: {get_param: OpenDaylightPassword}
 +          OpenDaylightEnableL3: {get_param: OpenDaylightEnableL3}
++          OpenDaylightEnableHA: {get_param: OpenDaylightEnableHA}
            PcsdPassword: {get_resource: PcsdPassword}
            PublicVirtualInterface: {get_param: PublicVirtualInterface}
            RabbitPassword: {get_param: RabbitPassword}
-@@ -878,6 +961,7 @@ resources:
+@@ -878,6 +967,8 @@ resources:
            ServiceNetMap: {get_param: ServiceNetMap}
            EndpointMap: {get_attr: [EndpointMap, endpoint_map]}
            CeilometerApiVirtualIP: {get_attr: [VipMap, net_ip_map, {get_param: [ServiceNetMap, CeilometerApiNetwork]}]}
 +          AodhApiVirtualIP: {get_attr: [VipMap, net_ip_map, {get_param: [ServiceNetMap, AodhApiNetwork]}]}
++          OpenDaylightApiVirtualIP: {get_attr: [VipMap, net_ip_map, {get_param: [ServiceNetMap, OpenDaylightApiNetwork]}]}
            CinderApiVirtualIP: {get_attr: [VipMap, net_ip_map, {get_param: [ServiceNetMap, CinderApiNetwork]}]}
            HeatApiVirtualIP: {get_attr: [VipMap, net_ip_map, {get_param: [ServiceNetMap, HeatApiNetwork]}]}
            GlanceApiVirtualIP: {get_attr: [VipMap, net_ip_map, {get_param: [ServiceNetMap, GlanceApiNetwork]}]}
-@@ -948,6 +1032,9 @@ resources:
+@@ -948,6 +1039,10 @@ resources:
            NovaPublicIP: {get_attr: [PublicVirtualIP, ip_address]}
            NovaPassword: {get_param: NovaPassword}
            NtpServer: {get_param: NtpServer}
 +          OpenDaylightPort: {get_param: OpenDaylightPort}
 +          OpenDaylightUsername: {get_param: OpenDaylightUsername}
 +          OpenDaylightPassword: {get_param: OpenDaylightPassword}
++          OpenDaylightEnableHA: {get_param: OpenDaylightEnableHA}
            RabbitHost: {get_attr: [VipMap, net_ip_map, {get_param: [ServiceNetMap, RabbitMqNetwork]}]}
            RabbitPassword: {get_param: RabbitPassword}
            RabbitUserName: {get_param: RabbitUserName}
-@@ -1068,6 +1155,7 @@ resources:
+@@ -1068,6 +1163,7 @@ resources:
        compute_hosts: {get_attr: [Compute, hosts_entry]}
        controller_hosts: {get_attr: [Controller, hosts_entry]}
        controller_ips: {get_attr: [Controller, ip_address]}
@@ -460,23 +470,25 @@ index a532c2f..965ca4c 100644
        block_storage_hosts: {get_attr: [BlockStorage, hosts_entry]}
        object_storage_hosts: {get_attr: [ObjectStorage, hosts_entry]}
        ceph_storage_hosts: {get_attr: [CephStorage, hosts_entry]}
-@@ -1081,6 +1169,7 @@ resources:
+@@ -1081,6 +1177,8 @@ resources:
        heat_api_node_ips: {get_attr: [ControllerIpListMap, net_ip_map, {get_param: [ServiceNetMap, HeatApiNetwork]}]}
        swift_proxy_node_ips: {get_attr: [ControllerIpListMap, net_ip_map, {get_param: [ServiceNetMap, SwiftProxyNetwork]}]}
        ceilometer_api_node_ips: {get_attr: [ControllerIpListMap, net_ip_map, {get_param: [ServiceNetMap, CeilometerApiNetwork]}]}
 +      aodh_api_node_ips: {get_attr: [ControllerIpListMap, net_ip_map, {get_param: [ServiceNetMap, AodhApiNetwork]}]}
++      opendaylight_api_node_ips: {get_attr: [ControllerIpListMap, net_ip_map, {get_param: [ServiceNetMap, OpenDaylightApiNetwork]}]}
        nova_api_node_ips: {get_attr: [ControllerIpListMap, net_ip_map, {get_param: [ServiceNetMap, NovaApiNetwork]}]}
        nova_metadata_node_ips: {get_attr: [ControllerIpListMap, net_ip_map, {get_param: [ServiceNetMap, NovaMetadataNetwork]}]}
        glance_api_node_ips: {get_attr: [ControllerIpListMap, net_ip_map, {get_param: [ServiceNetMap, GlanceApiNetwork]}]}
-@@ -1189,6 +1278,7 @@ resources:
+@@ -1189,6 +1287,8 @@ resources:
          nova_api_vip: {get_attr: [VipMap, net_ip_map, {get_param: [ServiceNetMap, NovaApiNetwork]}]}
          nova_metadata_vip: {get_attr: [VipMap, net_ip_map, {get_param: [ServiceNetMap, NovaMetadataNetwork]}]}
          ceilometer_api_vip: {get_attr: [VipMap, net_ip_map, {get_param: [ServiceNetMap, CeilometerApiNetwork]}]}
 +        aodh_api_vip: {get_attr: [VipMap, net_ip_map, {get_param: [ServiceNetMap, AodhApiNetwork]}]}
++        opendaylight_api_vip: {get_attr: [VipMap, net_ip_map, {get_param: [ServiceNetMap, OpenDaylightApiNetwork]}]}
          heat_api_vip: {get_attr: [VipMap, net_ip_map, {get_param: [ServiceNetMap, HeatApiNetwork]}]}
          horizon_vip: {get_attr: [VipMap, net_ip_map, {get_param: [ServiceNetMap, HorizonNetwork]}]}
          redis_vip: {get_attr: [RedisVirtualIP, ip_address]}
-@@ -1434,6 +1524,9 @@ outputs:
+@@ -1434,6 +1534,9 @@ outputs:
    PublicVip:
      description: Controller VIP for public API endpoints
      value: {get_attr: [PublicVirtualIP, ip_address]}
@@ -487,7 +499,7 @@ index a532c2f..965ca4c 100644
      description: VIP for Ceilometer API internal endpoint
      value: {get_attr: [VipMap, net_ip_map, {get_param: [ServiceNetMap, CeilometerApiNetwork]}]}
 diff --git a/puppet/all-nodes-config.yaml b/puppet/all-nodes-config.yaml
-index 2bc519b..d649ba0 100644
+index 2bc519b..1ebaff5 100644
 --- a/puppet/all-nodes-config.yaml
 +++ b/puppet/all-nodes-config.yaml
 @@ -8,6 +8,8 @@ parameters:
@@ -499,16 +511,18 @@ index 2bc519b..d649ba0 100644
    block_storage_hosts:
      type: comma_delimited_list
    object_storage_hosts:
-@@ -34,6 +36,8 @@ parameters:
+@@ -34,6 +36,10 @@ parameters:
      type: comma_delimited_list
    ceilometer_api_node_ips:
      type: comma_delimited_list
 +  aodh_api_node_ips:
++    type: comma_delimited_list
++  opendaylight_api_node_ips:
 +    type: comma_delimited_list
    nova_api_node_ips:
      type: comma_delimited_list
    nova_metadata_node_ips:
-@@ -82,6 +86,10 @@ resources:
+@@ -82,6 +88,10 @@ resources:
                raw_data: {get_file: hieradata/RedHat.yaml}
              all_nodes:
                mapped_data:
@@ -519,7 +533,7 @@ index 2bc519b..d649ba0 100644
                  controller_node_ips:
                    list_join:
                    - ','
-@@ -166,6 +174,14 @@ resources:
+@@ -166,6 +176,22 @@ resources:
                          list_join:
                          - "','"
                          - {get_param: ceilometer_api_node_ips}
@@ -531,10 +545,18 @@ index 2bc519b..d649ba0 100644
 +                        list_join:
 +                        - "','"
 +                        - {get_param: aodh_api_node_ips}
++                opendaylight_api_node_ips:
++                  str_replace:
++                    template: "['SERVERS_LIST']"
++                    params:
++                      SERVERS_LIST:
++                        list_join:
++                        - "','"
++                        - {get_param: opendaylight_api_node_ips}
                  nova_api_node_ips:
                    str_replace:
                      template: "['SERVERS_LIST']"
-@@ -239,6 +255,7 @@ resources:
+@@ -239,6 +265,7 @@ resources:
                  neutron::rabbit_hosts: *rabbit_nodes_array
                  nova::rabbit_hosts: *rabbit_nodes_array
                  keystone::rabbit_hosts: *rabbit_nodes_array
@@ -543,10 +565,10 @@ index 2bc519b..d649ba0 100644
  outputs:
    config_id:
 diff --git a/puppet/compute.yaml b/puppet/compute.yaml
-index 70c7403..13fd4f6 100644
+index 70c7403..ba7cbfd 100644
 --- a/puppet/compute.yaml
 +++ b/puppet/compute.yaml
-@@ -213,6 +213,23 @@ parameters:
+@@ -213,6 +213,27 @@ parameters:
    NtpServer:
      type: string
      default: ''
@@ -563,6 +585,10 @@ index 70c7403..13fd4f6 100644
 +    type: string
 +    description: The password for the opendaylight server.
 +    hidden: true
++  OpenDaylightEnableHA:
++    description: Knob to enable/disable ODL HA
++    type: boolean
++    default: false
 +  ONOSPort:
 +    default: 8181
 +    description: Set onos service port
@@ -570,7 +596,7 @@ index 70c7403..13fd4f6 100644
    RabbitHost:
      type: string
      default: ''  # Has to be here because of the ignored empty value bug
-@@ -320,6 +337,11 @@ resources:
+@@ -320,6 +341,11 @@ resources:
      properties:
        ControlPlaneIP: {get_attr: [NovaCompute, networks, ctlplane, 0]}
  
@@ -582,7 +608,7 @@ index 70c7403..13fd4f6 100644
    NetIpMap:
      type: OS::TripleO::Network::Ports::NetIpMap
      properties:
-@@ -327,6 +349,7 @@ resources:
+@@ -327,6 +353,7 @@ resources:
        InternalApiIp: {get_attr: [InternalApiPort, ip_address]}
        StorageIp: {get_attr: [StoragePort, ip_address]}
        TenantIp: {get_attr: [TenantPort, ip_address]}
@@ -590,7 +616,7 @@ index 70c7403..13fd4f6 100644
  
    NetworkConfig:
      type: OS::TripleO::Compute::Net::SoftwareConfig
-@@ -335,6 +358,7 @@ resources:
+@@ -335,6 +362,7 @@ resources:
        InternalApiIpSubnet: {get_attr: [InternalApiPort, ip_subnet]}
        StorageIpSubnet: {get_attr: [StoragePort, ip_subnet]}
        TenantIpSubnet: {get_attr: [TenantPort, ip_subnet]}
@@ -598,29 +624,31 @@ index 70c7403..13fd4f6 100644
  
    NetworkDeployment:
      type: OS::TripleO::SoftwareDeployment
-@@ -406,6 +430,10 @@ resources:
+@@ -406,6 +434,11 @@ resources:
                  neutron::rabbit_user: {get_input: rabbit_user}
                  neutron::rabbit_use_ssl: {get_input: rabbit_client_use_ssl}
                  neutron::rabbit_port: {get_input: rabbit_client_port}
 +                opendaylight_port: {get_input: opendaylight_port}
 +                opendaylight_username: {get_input: opendaylight_username}
 +                opendaylight_password: {get_input: opendaylight_password}
++                opendaylight_enable_ha: {get_input: opendaylight_enable_ha}
 +                onos_port: {get_input: onos_port}
                  neutron_flat_networks: {get_input: neutron_flat_networks}
                  neutron_host: {get_input: neutron_host}
                  neutron::agents::ml2::ovs::local_ip: {get_input: neutron_local_ip}
-@@ -459,6 +487,10 @@ resources:
+@@ -459,6 +492,11 @@ resources:
          snmpd_readonly_user_name: {get_param: SnmpdReadonlyUserName}
          snmpd_readonly_user_password: {get_param: SnmpdReadonlyUserPassword}
          glance_api_servers: {get_param: [EndpointMap, GlanceInternal, uri]}
 +        opendaylight_port: {get_param: OpenDaylightPort}
 +        opendaylight_username: {get_param: OpenDaylightUsername}
 +        opendaylight_password: {get_param: OpenDaylightPassword}
++        opendaylight_enable_ha: {get_param: OpenDaylightEnableHA}
 +        onos_port: {get_param: ONOSPort}
          neutron_flat_networks: {get_param: NeutronFlatNetworks}
          neutron_host: {get_param: NeutronHost}
          neutron_local_ip: {get_attr: [NetIpMap, net_ip_map, {get_param: [ServiceNetMap, NeutronTenantNetwork]}]}
-@@ -570,6 +602,9 @@ outputs:
+@@ -570,6 +608,9 @@ outputs:
    tenant_ip_address:
      description: IP address of the server in the tenant network
      value: {get_attr: [TenantPort, ip_address]}
@@ -631,7 +659,7 @@ index 70c7403..13fd4f6 100644
      description: Hostname of the server
      value: {get_attr: [NovaCompute, name]}
 diff --git a/puppet/controller.yaml b/puppet/controller.yaml
-index ea0b3af..bd82e93 100644
+index ea0b3af..0a3668e 100644
 --- a/puppet/controller.yaml
 +++ b/puppet/controller.yaml
 @@ -14,6 +14,14 @@ parameters:
@@ -660,7 +688,7 @@ index ea0b3af..bd82e93 100644
    NeutronEnableTunnelling:
      type: string
      default: "True"
-@@ -443,6 +455,35 @@ parameters:
+@@ -443,6 +455,42 @@ parameters:
    NtpServer:
      type: string
      default: ''
@@ -685,10 +713,17 @@ index ea0b3af..bd82e93 100644
 +    description: Knob to enable/disable ODL L3
 +    type: string
 +    default: 'no'
++  OpenDaylightEnableHA:
++    description: Knob to enable/disable ODL HA
++    type: boolean
++    default: false
 +  OpenDaylightFeatures:
 +    description: List of features to install with ODL
 +    type: comma_delimited_list
 +    default: "odl-ovsdb-openstack"
++  OpenDaylightApiVirtualIP:
++    type: string
++    default: ''
 +  ONOSPort:
 +    default: 8181
 +    description: Set onos service port
@@ -696,7 +731,7 @@ index ea0b3af..bd82e93 100644
    PcsdPassword:
      type: string
      description: The password for the 'pcsd' user.
-@@ -696,6 +737,7 @@ resources:
+@@ -696,6 +744,7 @@ resources:
        input_values:
          bootstack_nodeid: {get_attr: [Controller, name]}
          neutron_enable_tunneling: {get_param: NeutronEnableTunnelling}
@@ -704,7 +739,7 @@ index ea0b3af..bd82e93 100644
          haproxy_log_address: {get_param: HAProxySyslogAddress}
          heat.watch_server_url:
            list_join:
-@@ -774,6 +816,7 @@ resources:
+@@ -774,6 +823,7 @@ resources:
                - {get_param: MysqlVirtualIP}
                - '/heat'
          keystone_ca_certificate: {get_param: KeystoneCACertificate}
@@ -712,7 +747,7 @@ index ea0b3af..bd82e93 100644
          keystone_signing_key: {get_param: KeystoneSigningKey}
          keystone_signing_certificate: {get_param: KeystoneSigningCertificate}
          keystone_ssl_certificate: {get_param: KeystoneSSLCertificate}
-@@ -805,6 +848,13 @@ resources:
+@@ -805,6 +855,14 @@ resources:
              template: tripleo-CLUSTER
              params:
                CLUSTER: {get_param: MysqlClusterUniquePart}
@@ -721,12 +756,13 @@ index ea0b3af..bd82e93 100644
 +        opendaylight_username: {get_param: OpenDaylightUsername}
 +        opendaylight_password: {get_param: OpenDaylightPassword}
 +        opendaylight_enable_l3: {get_param: OpenDaylightEnableL3}
++        opendaylight_enable_ha: {get_param: OpenDaylightEnableHA}
 +        opendaylight_features: {get_param: OpenDaylightFeatures}
 +        onos_port: {get_param: ONOSPort}
          neutron_flat_networks: {get_param: NeutronFlatNetworks}
          neutron_metadata_proxy_shared_secret: {get_param: NeutronMetadataProxySharedSecret}
          neutron_agent_mode: {get_param: NeutronAgentMode}
-@@ -879,6 +929,7 @@ resources:
+@@ -879,6 +937,7 @@ resources:
          ceilometer_backend: {get_param: CeilometerBackend}
          ceilometer_metering_secret: {get_param: CeilometerMeteringSecret}
          ceilometer_password: {get_param: CeilometerPassword}
@@ -734,7 +770,7 @@ index ea0b3af..bd82e93 100644
          ceilometer_coordination_url:
            list_join:
              - ''
-@@ -891,6 +942,12 @@ resources:
+@@ -891,6 +950,12 @@ resources:
              - - 'mysql://ceilometer:unset@'
                - {get_param: MysqlVirtualIP}
                - '/ceilometer'
@@ -747,15 +783,16 @@ index ea0b3af..bd82e93 100644
          snmpd_readonly_user_name: {get_param: SnmpdReadonlyUserName}
          snmpd_readonly_user_password: {get_param: SnmpdReadonlyUserPassword}
          nova_password: {get_param: NovaPassword}
-@@ -948,6 +1005,7 @@ resources:
+@@ -948,6 +1013,8 @@ resources:
          neutron_api_network: {get_attr: [NetIpMap, net_ip_map, {get_param: [ServiceNetMap, NeutronApiNetwork]}]}
          neutron_local_ip: {get_attr: [NetIpMap, net_ip_map, {get_param: [ServiceNetMap, NeutronTenantNetwork]}]}
          ceilometer_api_network: {get_attr: [NetIpMap, net_ip_map, {get_param: [ServiceNetMap, CeilometerApiNetwork]}]}
 +        aodh_api_network: {get_attr: [NetIpMap, net_ip_map, {get_param: [ServiceNetMap, AodhApiNetwork]}]}
++        opendaylight_api_network: {get_attr: [NetIpMap, net_ip_map, {get_param: [ServiceNetMap, OpenDaylightApiNetwork]}]}
          nova_api_network: {get_attr: [NetIpMap, net_ip_map, {get_param: [ServiceNetMap, NovaApiNetwork]}]}
          nova_metadata_network: {get_attr: [NetIpMap, net_ip_map, {get_param: [ServiceNetMap, NovaMetadataNetwork]}]}
          horizon_network: {get_attr: [NetIpMap, net_ip_map, {get_param: [ServiceNetMap, HorizonNetwork]}]}
-@@ -1041,7 +1099,7 @@ resources:
+@@ -1041,7 +1108,7 @@ resources:
                  cinder_iscsi_ip_address: {get_input: cinder_iscsi_network}
                  cinder::database_connection: {get_input: cinder_dsn}
                  cinder::api::keystone_password: {get_input: cinder_password}
@@ -764,7 +801,7 @@ index ea0b3af..bd82e93 100644
                  cinder::api::identity_uri: {get_input: keystone_identity_uri}
                  cinder::api::bind_host: {get_input: cinder_api_network}
                  cinder::rabbit_userid: {get_input: rabbit_username}
-@@ -1136,6 +1194,17 @@ resources:
+@@ -1136,6 +1203,18 @@ resources:
                  mysql_bind_host: {get_input: mysql_network}
                  mysql_virtual_ip: {get_input: mysql_virtual_ip}
  
@@ -774,6 +811,7 @@ index ea0b3af..bd82e93 100644
 +                opendaylight_username: {get_input: opendaylight_username}
 +                opendaylight_password: {get_input: opendaylight_password}
 +                opendaylight_enable_l3: {get_input: opendaylight_enable_l3}
++                opendaylight_enable_ha: {get_input: opendaylight_enable_ha}
 +                opendaylight_features: {get_input: opendaylight_features}
 +
 +                # ONOS
@@ -782,7 +820,7 @@ index ea0b3af..bd82e93 100644
                  # Neutron
                  neutron::bind_host: {get_input: neutron_api_network}
                  neutron::rabbit_password: {get_input: rabbit_password}
-@@ -1152,6 +1221,7 @@ resources:
+@@ -1152,6 +1231,7 @@ resources:
                  neutron_flat_networks: {get_input: neutron_flat_networks}
                  neutron::agents::metadata::shared_secret: {get_input: neutron_metadata_proxy_shared_secret}
                  neutron::agents::metadata::metadata_ip: {get_input: neutron_api_network}
@@ -790,7 +828,7 @@ index ea0b3af..bd82e93 100644
                  neutron_agent_mode: {get_input: neutron_agent_mode}
                  neutron_router_distributed: {get_input: neutron_router_distributed}
                  neutron::core_plugin: {get_input: neutron_core_plugin}
-@@ -1198,6 +1268,27 @@ resources:
+@@ -1198,6 +1278,27 @@ resources:
                  snmpd_readonly_user_name: {get_input: snmpd_readonly_user_name}
                  snmpd_readonly_user_password: {get_input: snmpd_readonly_user_password}
  
@@ -818,6 +856,14 @@ index ea0b3af..bd82e93 100644
                  # Nova
                  nova::rabbit_userid: {get_input: rabbit_username}
                  nova::rabbit_password: {get_input: rabbit_password}
+@@ -1244,6 +1345,7 @@ resources:
+                 tripleo::loadbalancer::haproxy_log_address: {get_input: haproxy_log_address}
+                 tripleo::packages::enable_install: {get_input: enable_package_install}
+                 tripleo::packages::enable_upgrade: {get_input: enable_package_upgrade}
++                tripleo::loadbalancer::opendaylight: {get_input: opendaylight_enable_ha}
+   # Hook for site-specific additional pre-deployment config, e.g extra hieradata
+   ControllerExtraConfigPre:
 diff --git a/puppet/hieradata/common.yaml b/puppet/hieradata/common.yaml
 index 030f661..5840016 100644
 --- a/puppet/hieradata/common.yaml
@@ -868,10 +914,10 @@ index 4b7fd81..7dbc2e9 100644
  tripleo::loadbalancer::heat_cloudwatch: true
  tripleo::loadbalancer::heat_cfn: true
 diff --git a/puppet/manifests/overcloud_compute.pp b/puppet/manifests/overcloud_compute.pp
-index cd41cc7..110ca1d 100644
+index cd41cc7..474d782 100644
 --- a/puppet/manifests/overcloud_compute.pp
 +++ b/puppet/manifests/overcloud_compute.pp
-@@ -75,9 +75,34 @@ class { '::neutron::plugins::ml2':
+@@ -75,9 +75,52 @@ class { '::neutron::plugins::ml2':
    tenant_network_types => [hiera('neutron_tenant_network_type')],
  }
  
@@ -882,17 +928,35 @@ index cd41cc7..110ca1d 100644
 +
 +  if str2bool(hiera('opendaylight_install', 'false')) {
 +    $controller_ips = split(hiera('controller_node_ips'), ',')
-+    $opendaylight_controller_ip = $controller_ips[0]
++    if hiera('opendaylight_enable_ha', false) {
++      $odl_ovsdb_iface = "tcp:${controller_ips[0]}:6640 tcp:${controller_ips[1]}:6640 tcp:${controller_ips[2]}:6640"
++      # Workaround to work with current puppet-neutron
++      # This isn't the best solution, since the odl check URL ends up being only the first node in HA case
++      $opendaylight_controller_ip = $controller_ips[0]
++      # Bug where netvirt:1 doesn't come up right with HA
++      # Check ovsdb:1 instead
++      $net_virt_url = 'restconf/operational/network-topology:network-topology/topology/ovsdb:1'
++    } else {
++      $opendaylight_controller_ip = $controller_ips[0]
++      $odl_ovsdb_iface = "tcp:${opendaylight_controller_ip}:6640"
++      $net_virt_url = 'restconf/operational/network-topology:network-topology/topology/netvirt:1'
++    }
 +  } else {
 +    $opendaylight_controller_ip = hiera('opendaylight_controller_ip')
++    $odl_ovsdb_iface = "tcp:${opendaylight_controller_ip}:6640"
++    $net_virt_url = 'restconf/operational/network-topology:network-topology/topology/netvirt:1'
 +  }
 +
-+  class { 'neutron::plugins::ovs::opendaylight':
-+      odl_controller_ip => $opendaylight_controller_ip,
-+      tunnel_ip         => hiera('neutron::agents::ml2::ovs::local_ip'),
-+      odl_port          => hiera('opendaylight_port'),
-+      odl_username      => hiera('opendaylight_username'),
-+      odl_password      => hiera('opendaylight_password'),
++  $opendaylight_port = hiera('opendaylight_port')
++  $private_ip = hiera('neutron::agents::ml2::ovs::local_ip')
++  $opendaylight_url = "http://${opendaylight_controller_ip}:${opendaylight_port}/${net_virt_url}"
++
++  class { '::neutron::plugins::ovs::opendaylight':
++    tunnel_ip             => $private_ip,
++    odl_username          => hiera('opendaylight_username'),
++    odl_password          => hiera('opendaylight_password'),
++    odl_check_url         => $opendaylight_url,
++    odl_ovsdb_iface       => $odl_ovsdb_iface,
 +  }
 +
 +} elsif 'onos_ml2' in hiera('neutron_mechanism_drivers') {
@@ -910,7 +974,7 @@ index cd41cc7..110ca1d 100644
  
  if 'cisco_n1kv' in hiera('neutron_mechanism_drivers') {
 diff --git a/puppet/manifests/overcloud_controller.pp b/puppet/manifests/overcloud_controller.pp
-index 1f6c2be..1095758 100644
+index 1f6c2be..5114f80 100644
 --- a/puppet/manifests/overcloud_controller.pp
 +++ b/puppet/manifests/overcloud_controller.pp
 @@ -30,6 +30,21 @@ if hiera('step') >= 1 {
@@ -955,7 +1019,7 @@ index 1f6c2be..1095758 100644
    include ::neutron::agents::dhcp
    include ::neutron::agents::metadata
  
-@@ -237,15 +253,101 @@ if hiera('step') >= 3 {
+@@ -237,15 +253,103 @@ if hiera('step') >= 3 {
      require => Package['neutron'],
    }
  
@@ -1018,21 +1082,23 @@ index 1f6c2be..1095758 100644
 +      }
 +    }
 +
-+    class { 'neutron::plugins::ml2::opendaylight':
-+      odl_controller_ip => $opendaylight_controller_ip,
-+      odl_username      => hiera('opendaylight_username'),
-+      odl_password      => hiera('opendaylight_password'),
-+      odl_port          => hiera('opendaylight_port'),
++    $private_ip = hiera('neutron::agents::ml2::ovs::local_ip')
++    $net_virt_url = 'restconf/operational/network-topology:network-topology/topology/netvirt:1'
++    $opendaylight_url = "http://${opendaylight_controller_ip}:${opendaylight_port}/${net_virt_url}"
++    $odl_ovsdb_iface = "tcp:${opendaylight_controller_ip}:6640"
++
++    class { '::neutron::plugins::ml2::opendaylight':
++      odl_username  => hiera('opendaylight_username'),
++      odl_password  => hiera('opendaylight_password'),
++      odl_url => "http://${opendaylight_controller_ip}:${opendaylight_port}/controller/nb/v2/neutron";
 +    }
 +
-+    if str2bool(hiera('opendaylight_install', 'false')) {
-+      class { 'neutron::plugins::ovs::opendaylight':
-+        odl_controller_ip => $opendaylight_controller_ip,
-+        tunnel_ip         => hiera('neutron::agents::ml2::ovs::local_ip'),
-+        odl_port          => hiera('opendaylight_port'),
-+        odl_username      => hiera('opendaylight_username'),
-+        odl_password      => hiera('opendaylight_password'),
-+      }
++    class { '::neutron::plugins::ovs::opendaylight':
++      tunnel_ip             => $private_ip,
++      odl_username          => hiera('opendaylight_username'),
++      odl_password          => hiera('opendaylight_password'),
++      odl_check_url         => $opendaylight_url,
++      odl_ovsdb_iface       => $odl_ovsdb_iface,
 +    }
 +
 +  } elsif 'onos_ml2' in hiera('neutron_mechanism_drivers') {
@@ -1060,7 +1126,7 @@ index 1f6c2be..1095758 100644
    if 'cisco_n1kv' in hiera('neutron_mechanism_drivers') {
      include ::neutron::plugins::ml2::cisco::nexus1000v
  
-@@ -280,8 +382,6 @@ if hiera('step') >= 3 {
+@@ -280,8 +384,6 @@ if hiera('step') >= 3 {
    }
  
    Service['neutron-server'] -> Service['neutron-dhcp-service']
@@ -1069,7 +1135,7 @@ index 1f6c2be..1095758 100644
    Service['neutron-server'] -> Service['neutron-metadata']
  
    include ::cinder
-@@ -447,6 +547,20 @@ if hiera('step') >= 3 {
+@@ -447,6 +549,20 @@ if hiera('step') >= 3 {
  
    Cron <| title == 'ceilometer-expirer' |> { command => "sleep $((\$(od -A n -t d -N 3 /dev/urandom) % 86400)) && ${::ceilometer::params::expirer_command}" }
  
@@ -1091,18 +1157,26 @@ index 1f6c2be..1095758 100644
    include ::heat
    include ::heat::api
 diff --git a/puppet/manifests/overcloud_controller_pacemaker.pp b/puppet/manifests/overcloud_controller_pacemaker.pp
-index 3fb92f3..cb00e9a 100644
+index 3fb92f3..29f9c7f 100644
 --- a/puppet/manifests/overcloud_controller_pacemaker.pp
 +++ b/puppet/manifests/overcloud_controller_pacemaker.pp
-@@ -380,6 +380,21 @@ if hiera('step') >= 2 {
+@@ -380,6 +380,29 @@ if hiera('step') >= 2 {
  
    }
  
 +  if str2bool(hiera('opendaylight_install', 'false')) {
++    $node_string = split(hiera('bootstack_nodeid'), '-')
++    $controller_index = $node_string[-1]
++    $ha_node_index = $controller_index + 1
++
 +    class {"opendaylight":
 +      extra_features => any2array(hiera('opendaylight_features', 'odl-ovsdb-openstack')),
 +      odl_rest_port  => hiera('opendaylight_port'),
++      odl_bind_ip    => $controller_node_ips[$controller_index],
 +      enable_l3      => hiera('opendaylight_enable_l3', 'no'),
++      enable_ha      => hiera('opendaylight_enable_ha', false),
++      ha_node_ips    => split(hiera('controller_node_ips'), ','),
++      ha_node_index  => $ha_node_index,
 +    }
 +  }
 +
@@ -1116,7 +1190,7 @@ index 3fb92f3..cb00e9a 100644
    exec { 'galera-ready' :
      command     => '/usr/bin/clustercheck >/dev/null',
      timeout     => 30,
-@@ -584,7 +599,14 @@ if hiera('step') >= 3 {
+@@ -584,7 +607,14 @@ if hiera('step') >= 3 {
    include ::nova::network::neutron
  
    # Neutron class definitions
@@ -1132,7 +1206,7 @@ index 3fb92f3..cb00e9a 100644
    class { '::neutron::server' :
      sync_db        => $sync_db,
      manage_service => false,
-@@ -594,10 +616,6 @@ if hiera('step') >= 3 {
+@@ -594,10 +624,6 @@ if hiera('step') >= 3 {
      manage_service => false,
      enabled        => false,
    }
@@ -1143,7 +1217,7 @@ index 3fb92f3..cb00e9a 100644
    class { '::neutron::agents::metadata':
      manage_service => false,
      enabled        => false,
-@@ -609,18 +627,80 @@ if hiera('step') >= 3 {
+@@ -609,18 +635,98 @@ if hiera('step') >= 3 {
      notify  => Service['neutron-dhcp-service'],
      require => Package['neutron'],
    }
@@ -1174,30 +1248,48 @@ index 3fb92f3..cb00e9a 100644
 +  if 'opendaylight' in hiera('neutron_mechanism_drivers') {
 +    if str2bool(hiera('opendaylight_install', 'false')) {
 +      $controller_ips = split(hiera('controller_node_ips'), ',')
-+      $opendaylight_controller_ip = $controller_ips[0]
++      if hiera('opendaylight_enable_ha', false) {
++        $odl_ovsdb_iface = "tcp:${controller_ips[0]}:6640 tcp:${controller_ips[1]}:6640 tcp:${controller_ips[2]}:6640"
++        # Workaround to work with current puppet-neutron
++        # This isn't the best solution, since the odl check URL ends up being only the first node in HA case
++        $opendaylight_controller_ip = $controller_ips[0]
++        # Bug where netvirt:1 doesn't come up right with HA
++        # Check ovsdb:1 instead
++        $net_virt_url = 'restconf/operational/network-topology:network-topology/topology/ovsdb:1'
++      } else {
++        $opendaylight_controller_ip = $controller_ips[0]
++        $odl_ovsdb_iface = "tcp:${opendaylight_controller_ip}:6640"
++        $net_virt_url = 'restconf/operational/network-topology:network-topology/topology/netvirt:1'
++      }
 +    } else {
 +      $opendaylight_controller_ip = hiera('opendaylight_controller_ip')
++      $odl_ovsdb_iface = "tcp:${opendaylight_controller_ip}:6640"
++      $net_virt_url = 'restconf/operational/network-topology:network-topology/topology/netvirt:1'
 +    }
 +
 +    $opendaylight_port = hiera('opendaylight_port')
 +    $private_ip = hiera('neutron::agents::ml2::ovs::local_ip')
++    $opendaylight_url = "http://${opendaylight_controller_ip}:${opendaylight_port}/${net_virt_url}"
++    $odl_vip = hiera('opendaylight_api_vip')
 +
-+    class { 'neutron::plugins::ml2::opendaylight':
-+      odl_controller_ip => $opendaylight_controller_ip,
-+      odl_username      => hiera('opendaylight_username'),
-+      odl_password      => hiera('opendaylight_password'),
-+      odl_port          => hiera('opendaylight_port'),
++    if ! $odl_vip {
++      fail('ODL VIP not set in hiera or empty')
 +    }
 +
-+    if str2bool(hiera('opendaylight_install', 'false')) {
-+      class { 'neutron::plugins::ovs::opendaylight':
-+        odl_controller_ip => $opendaylight_controller_ip,
-+        tunnel_ip         => hiera('neutron::agents::ml2::ovs::local_ip'),
-+        odl_port          => hiera('opendaylight_port'),
-+        odl_username      => hiera('opendaylight_username'),
-+        odl_password      => hiera('opendaylight_password'),
-+      }
++    class { '::neutron::plugins::ml2::opendaylight':
++      odl_username  => hiera('opendaylight_username'),
++      odl_password  => hiera('opendaylight_password'),
++      odl_url => "http://${odl_vip}:${opendaylight_port}/controller/nb/v2/neutron";
++    }
++
++    class { '::neutron::plugins::ovs::opendaylight':
++      tunnel_ip             => $private_ip,
++      odl_username          => hiera('opendaylight_username'),
++      odl_password          => hiera('opendaylight_password'),
++      odl_check_url         => $opendaylight_url,
++      odl_ovsdb_iface       => $odl_ovsdb_iface,
 +    }
 +    if ! str2bool(hiera('opendaylight_enable_l3', 'no')) {
 +      class { '::neutron::agents::l3' :
 +        manage_service => false,
@@ -1208,7 +1300,7 @@ index 3fb92f3..cb00e9a 100644
 +    #config ml2_conf.ini with onos url address
 +    $onos_port = hiera('onos_port')
 +    $private_ip = hiera('neutron::agents::ml2::ovs::local_ip')
++
 +    neutron_plugin_ml2 {
 +      'onos/username':         value => 'admin';
 +      'onos/password':         value => 'admin';
@@ -1230,7 +1322,7 @@ index 3fb92f3..cb00e9a 100644
    if 'cisco_ucsm' in hiera('neutron_mechanism_drivers') {
      include ::neutron::plugins::ml2::cisco::ucsm
    }
-@@ -645,8 +725,10 @@ if hiera('step') >= 3 {
+@@ -645,8 +751,10 @@ if hiera('step') >= 3 {
    if hiera('neutron_enable_bigswitch_ml2', false) {
      include ::neutron::plugins::ml2::bigswitch::restproxy
    }
@@ -1243,7 +1335,7 @@ index 3fb92f3..cb00e9a 100644
    }
    neutron_dhcp_agent_config {
      'DEFAULT/ovs_use_veth': value => hiera('neutron_ovs_use_veth', false);
-@@ -813,13 +895,13 @@ if hiera('step') >= 3 {
+@@ -813,13 +921,13 @@ if hiera('step') >= 3 {
      swift::storage::filter::healthcheck { $swift_components : }
    }
  
@@ -1258,7 +1350,7 @@ index 3fb92f3..cb00e9a 100644
        $ceilometer_database_connection = "mongodb://${mongo_node_string}/ceilometer?replicaSet=${mongodb_replset}"
      }
    }
-@@ -879,6 +961,62 @@ if hiera('step') >= 3 {
+@@ -879,6 +987,62 @@ if hiera('step') >= 3 {
      enabled        => false,
    }
  
@@ -1321,7 +1413,7 @@ index 3fb92f3..cb00e9a 100644
    # httpd/apache and horizon
    # NOTE(gfidente): server-status can be consumed by the pacemaker resource agent
    class { '::apache' :
-@@ -1055,62 +1193,21 @@ if hiera('step') >= 4 {
+@@ -1055,62 +1219,21 @@ if hiera('step') >= 4 {
        clone_params => 'interleave=true',
        require      => Pacemaker::Resource::Service[$::keystone::params::service_name],
      }
@@ -1388,7 +1480,7 @@ index 3fb92f3..cb00e9a 100644
      pacemaker::constraint::base { 'keystone-to-neutron-server-constraint':
        constraint_type => 'order',
        first_resource  => "${::keystone::params::service_name}-clone",
-@@ -1120,65 +1217,110 @@ if hiera('step') >= 4 {
+@@ -1120,65 +1243,110 @@ if hiera('step') >= 4 {
        require         => [Pacemaker::Resource::Service[$::keystone::params::service_name],
                            Pacemaker::Resource::Service[$::neutron::params::server_service]],
      }
@@ -1555,7 +1647,7 @@ index 3fb92f3..cb00e9a 100644
      # Nova
      pacemaker::resource::service { $::nova::params::api_service_name :
        clone_params => 'interleave=true',
-@@ -1276,7 +1418,7 @@ if hiera('step') >= 4 {
+@@ -1276,7 +1444,7 @@ if hiera('step') >= 4 {
                    Pacemaker::Resource::Service[$::nova::params::conductor_service_name]],
      }
  
@@ -1564,7 +1656,7 @@ index 3fb92f3..cb00e9a 100644
      case downcase(hiera('ceilometer_backend')) {
        /mysql/: {
          pacemaker::resource::service { $::ceilometer::params::agent_central_service_name :
-@@ -1298,10 +1440,19 @@ if hiera('step') >= 4 {
+@@ -1298,10 +1466,19 @@ if hiera('step') >= 4 {
      pacemaker::resource::service { $::ceilometer::params::api_service_name :
        clone_params => 'interleave=true',
      }
@@ -1586,7 +1678,7 @@ index 3fb92f3..cb00e9a 100644
        clone_params => 'interleave=true',
      }
      pacemaker::resource::service { $::ceilometer::params::agent_notification_service_name :
-@@ -1315,8 +1466,19 @@ if hiera('step') >= 4 {
+@@ -1315,8 +1492,19 @@ if hiera('step') >= 4 {
      # Fedora doesn't know `require-all` parameter for constraints yet
      if $::operatingsystem == 'Fedora' {
        $redis_ceilometer_constraint_params = undef
@@ -1606,7 +1698,7 @@ index 3fb92f3..cb00e9a 100644
      }
      pacemaker::constraint::base { 'redis-then-ceilometer-central-constraint':
        constraint_type   => 'order',
-@@ -1328,6 +1490,16 @@ if hiera('step') >= 4 {
+@@ -1328,6 +1516,16 @@ if hiera('step') >= 4 {
        require           => [Pacemaker::Resource::Ocf['redis'],
                              Pacemaker::Resource::Service[$::ceilometer::params::agent_central_service_name]],
      }
@@ -1623,7 +1715,7 @@ index 3fb92f3..cb00e9a 100644
      pacemaker::constraint::base { 'keystone-then-ceilometer-central-constraint':
        constraint_type => 'order',
        first_resource  => "${::keystone::params::service_name}-clone",
-@@ -1378,53 +1550,37 @@ if hiera('step') >= 4 {
+@@ -1378,53 +1576,37 @@ if hiera('step') >= 4 {
        require => [Pacemaker::Resource::Service[$::ceilometer::params::api_service_name],
                    Pacemaker::Resource::Ocf['delay']],
      }
@@ -1955,6 +2047,18 @@ index 0000000..6488e0e
 +      - ','
 +      - - {get_attr: [OpenDaylightDeployment, deploy_stdout]}
 +        - {get_param: UpdateIdentifier}
+diff --git a/puppet/vip-config.yaml b/puppet/vip-config.yaml
+index 1dec489..727bb79 100644
+--- a/puppet/vip-config.yaml
++++ b/puppet/vip-config.yaml
+@@ -27,6 +27,7 @@ resources:
+                 horizon_vip: {get_input: horizon_vip}
+                 redis_vip: {get_input: redis_vip}
+                 mysql_vip: {get_input: mysql_vip}
++                opendaylight_api_vip: {get_input: opendaylight_api_vip}
+                 tripleo::loadbalancer::public_virtual_ip: {get_input: public_virtual_ip}
+                 tripleo::loadbalancer::controller_virtual_ip: {get_input: control_virtual_ip}
+                 tripleo::loadbalancer::internal_api_virtual_ip: {get_input: internal_api_virtual_ip}
 -- 
-1.8.3.1
+2.5.0