From 57496c4147e648eb7432a8b06f47666b4e3c86ff Mon Sep 17 00:00:00 2001
From: Marcus G K Williams <marcus.williams@intel.com>
Date: Wed, 4 Jan 2017 16:58:48 -0800
Subject: [PATCH] Add OpenDaylight role

Adds role for OpenDaylight deploy.

Change-Id: I1e697ea4d3f33aab4b0f55863a377b39eda8f609
Co-Authored-By: Mauricio Lima <mauriciolimab@gmail.com>
Co-Authored-By: Jiri Prokes <jirix.x.prokes@intel.com>
Co-Authored-By: Eduardo Gonzalez <dabarren@gmail.com>
Partially-Implements: blueprint opendaylight-support
---
 ansible/group_vars/all.yml                    |  32 +++-
 ansible/inventory/all-in-one                  |   3 +
 ansible/inventory/multinode                   |   3 +
 .../roles/haproxy/templates/haproxy.cfg.j2    |  27 +++
 ansible/roles/neutron/defaults/main.yml       |  22 ++-
 .../roles/neutron/templates/dhcp_agent.ini.j2 |   4 +
 .../roles/neutron/templates/ml2_conf.ini.j2   |  14 +-
 .../neutron/templates/neutron-server.json.j2  |   2 +-
 .../roles/neutron/templates/neutron.conf.j2   |   7 +-
 ansible/roles/opendaylight/defaults/main.yml  |  24 +++
 ansible/roles/opendaylight/handlers/main.yml  |  31 +++
 ansible/roles/opendaylight/meta/main.yml      |   3 +
 ansible/roles/opendaylight/tasks/check.yml    |   1 +
 ansible/roles/opendaylight/tasks/config.yml   | 178 ++++++++++++++++++
 ansible/roles/opendaylight/tasks/deploy.yml   |   5 +
 ansible/roles/opendaylight/tasks/main.yml     |   2 +
 ansible/roles/opendaylight/tasks/precheck.yml | 111 +++++++++++
 ansible/roles/opendaylight/tasks/pull.yml     |  10 +
 .../roles/opendaylight/tasks/reconfigure.yml  |   2 +
 ansible/roles/opendaylight/tasks/upgrade.yml  |   5 +
 .../roles/opendaylight/templates/akka.conf.j2 |  33 ++++
 .../templates/custom.properties.j2            |  47 +++++
 .../roles/opendaylight/templates/jetty.xml.j2 |  90 +++++++++
 .../templates/module-shards.conf.j2           |  59 ++++++
 .../opendaylight/templates/modules.conf.j2    |  20 ++
 .../netvirt-aclservice-config.xml.j2          |   4 +
 ...irt-impl-config_netvirt-impl-config.xml.j2 |   7 +
 .../templates/opendaylight.json.j2            |  90 +++++++++
 .../org.apache.karaf.features.cfg.j2          |  24 +++
 .../org.opendaylight.ovsdb.library.cfg.j2     |   7 +
 .../templates/org.ops4j.pax.logging.cfg.j2    |  52 +++++
 .../templates/org.ops4j.pax.url.mvn.cfg.j2    |  34 ++++
 .../roles/opendaylight/templates/setenv.j2    |  24 +++
 .../roles/opendaylight/templates/start-odl.j2 |   3 +
 .../templates/tomcat-server.xml.j2            |  46 +++++
 ansible/roles/openvswitch/handlers/main.yml   |   3 +
 ansible/roles/openvswitch/tasks/config.yml    |  26 +++
 .../templates/openvswitch-db-server.json.j2   |   9 +-
 .../templates/openvswitch-vswitchd.json.j2    |  11 +-
 .../roles/openvswitch/templates/start-ovs.j2  |  10 +
 .../templates/start-ovsdb-server.j2           |  33 ++++
 ansible/site.yml                              |   8 +
 etc/kolla/globals.yml                         |   9 +-
 etc/kolla/passwords.yml                       |   5 +
 .../opendaylight-role-b1787bc458da5bc4.yaml   |   3 +
 45 files changed, 1128 insertions(+), 15 deletions(-)
 create mode 100644 ansible/roles/opendaylight/defaults/main.yml
 create mode 100644 ansible/roles/opendaylight/handlers/main.yml
 create mode 100644 ansible/roles/opendaylight/meta/main.yml
 create mode 100644 ansible/roles/opendaylight/tasks/check.yml
 create mode 100644 ansible/roles/opendaylight/tasks/config.yml
 create mode 100644 ansible/roles/opendaylight/tasks/deploy.yml
 create mode 100644 ansible/roles/opendaylight/tasks/main.yml
 create mode 100644 ansible/roles/opendaylight/tasks/precheck.yml
 create mode 100644 ansible/roles/opendaylight/tasks/pull.yml
 create mode 100644 ansible/roles/opendaylight/tasks/reconfigure.yml
 create mode 100644 ansible/roles/opendaylight/tasks/upgrade.yml
 create mode 100644 ansible/roles/opendaylight/templates/akka.conf.j2
 create mode 100644 ansible/roles/opendaylight/templates/custom.properties.j2
 create mode 100644 ansible/roles/opendaylight/templates/jetty.xml.j2
 create mode 100644 ansible/roles/opendaylight/templates/module-shards.conf.j2
 create mode 100644 ansible/roles/opendaylight/templates/modules.conf.j2
 create mode 100644 ansible/roles/opendaylight/templates/netvirt-aclservice-config.xml.j2
 create mode 100644 ansible/roles/opendaylight/templates/netvirt-impl-config_netvirt-impl-config.xml.j2
 create mode 100644 ansible/roles/opendaylight/templates/opendaylight.json.j2
 create mode 100644 ansible/roles/opendaylight/templates/org.apache.karaf.features.cfg.j2
 create mode 100644 ansible/roles/opendaylight/templates/org.opendaylight.ovsdb.library.cfg.j2
 create mode 100644 ansible/roles/opendaylight/templates/org.ops4j.pax.logging.cfg.j2
 create mode 100644 ansible/roles/opendaylight/templates/org.ops4j.pax.url.mvn.cfg.j2
 create mode 100644 ansible/roles/opendaylight/templates/setenv.j2
 create mode 100644 ansible/roles/opendaylight/templates/start-odl.j2
 create mode 100644 ansible/roles/opendaylight/templates/tomcat-server.xml.j2
 create mode 100644 ansible/roles/openvswitch/templates/start-ovs.j2
 create mode 100644 ansible/roles/openvswitch/templates/start-ovsdb-server.j2
 create mode 100644 releasenotes/notes/opendaylight-role-b1787bc458da5bc4.yaml

diff --git a/ansible/group_vars/all.yml b/ansible/group_vars/all.yml
index ef6ccc7d6b..992eb4b2d9 100644
--- a/ansible/group_vars/all.yml
+++ b/ansible/group_vars/all.yml
@@ -122,7 +122,7 @@ bifrost_network_interface: "{{ network_interface }}"
 dns_interface: "{{ network_interface }}"
 tunnel_interface_address: "{{ hostvars[inventory_hostname]['ansible_' + tunnel_interface]['ipv4']['address'] }}"
 
-# Valid options are [ openvswitch, linuxbridge, vmware_nsxv, vmware_dvs ]
+# Valid options are [ openvswitch, linuxbridge, vmware_nsxv, vmware_dvs, opendaylight ]
 neutron_plugin_agent: "openvswitch"
 
 # The default ports used by each service.
@@ -260,6 +260,19 @@ watcher_api_port: "9322"
 
 zun_api_port: "9517"
 
+opendaylight_clustering_port: "2550"
+opendaylight_restconf_port: "8087"
+opendaylight_restconf_port_backup: "8182"
+opendaylight_haproxy_restconf_port: "8088"
+opendaylight_haproxy_restconf_port_backup: "8183"
+opendaylight_jetty_conf_port: "8543"
+opendaylight_jetty_conf2_port: "8443"
+opendaylight_tomcat_port: "8282"
+opendaylight_tomcat_redirect_port: "8663"
+opendaylight_karaf_ssh_port: "8101"
+opendaylight_openflow_port: "6653"
+opendaylight_ovsdb_port: "6641"
+opendaylight_haproxy_ovsdb_port: "6642"
 
 public_protocol: "{{ 'https' if kolla_enable_tls_external | bool else 'http' }}"
 internal_protocol: "http"
@@ -375,6 +388,7 @@ enable_neutron_provider_networks: "no"
 enable_neutron_sfc: "no"
 enable_nova_serialconsole_proxy: "no"
 enable_octavia: "no"
+enable_opendaylight: "no"
 enable_openvswitch: "{{ neutron_plugin_agent != 'linuxbridge' | bool }}"
 enable_osprofiler: "no"
 enable_panko: "no"
@@ -530,7 +544,7 @@ neutron_type_drivers: "flat,vlan,vxlan"
 # NOTE: for ironic this list should also contain 'flat'
 neutron_tenant_network_types: "vxlan"
 
-computes_need_external_bridge: "{{ enable_neutron_dvr | bool or enable_neutron_provider_networks | bool and neutron_plugin_agent != 'vmware_dvs' }}"
+computes_need_external_bridge: "{{ enable_neutron_dvr | bool or enable_neutron_provider_networks | bool or enable_opendaylight | bool and neutron_plugin_agent != 'vmware_dvs' }}"
 
 #######################
 # Nova options
@@ -612,3 +626,17 @@ vmware_vcenter_host_ip:
 vmware_vcenter_host_username:
 vmware_vcenter_host_password:
 vmware_vcenter_cluster_name:
+
+######################
+# OpenDaylight
+######################
+opendaylight_release: "0.6.1-Carbon"
+opendaylight_mechanism_driver: "opendaylight_v2"
+opendaylight_l3_service_plugin: "odl-router_v2"
+opendaylight_acl_impl: "learn"
+enable_opendaylight_qos: "no"
+enable_opendaylight_l3: "{{ enable_opendaylight }}"
+enable_opendaylight_legacy_netvirt_conntrack: "no"
+opendaylight_port_binding_type: "pseudo-agentdb-binding"
+opendaylight_features: "odl-mdsal-apidocs,odl-netvirt-openstack"
+opendaylight_allowed_network_types: '"flat", "vlan", "vxlan"'
diff --git a/ansible/inventory/all-in-one b/ansible/inventory/all-in-one
index 154abf5e6f..f9c6de918b 100644
--- a/ansible/inventory/all-in-one
+++ b/ansible/inventory/all-in-one
@@ -103,6 +103,9 @@ network
 compute
 manila-share
 
+[opendaylight:children]
+network
+
 [cinder:children]
 control
 
diff --git a/ansible/inventory/multinode b/ansible/inventory/multinode
index 8b57b3d4d1..c319219ce3 100644
--- a/ansible/inventory/multinode
+++ b/ansible/inventory/multinode
@@ -124,6 +124,9 @@ network
 compute
 manila-share
 
+[opendaylight:children]
+network
+
 [cinder:children]
 control
 
diff --git a/ansible/roles/haproxy/templates/haproxy.cfg.j2 b/ansible/roles/haproxy/templates/haproxy.cfg.j2
index 49c7e029d4..bdde3f92da 100644
--- a/ansible/roles/haproxy/templates/haproxy.cfg.j2
+++ b/ansible/roles/haproxy/templates/haproxy.cfg.j2
@@ -873,3 +873,30 @@ listen mariadb
 {% endfor %}
 {% endif %}
 
+{% if enable_opendaylight | bool %}
+listen opendaylight_api
+  bind {{ kolla_internal_vip_address }}:{{ opendaylight_haproxy_restconf_port }}
+  balance source
+{% for host in groups['opendaylight'] %}
+  server {{ hostvars[host]['ansible_hostname'] }} {{ hostvars[host]['ansible_' + hostvars[host]['api_interface']]['ipv4']['address'] }}:{{ opendaylight_restconf_port }} check fall 5 inter 2000 rise 2
+{% endfor %}
+
+listen opendaylight_api_backup
+  bind {{ kolla_internal_vip_address }}:{{ opendaylight_haproxy_restconf_port_backup }}
+  balance source
+{% for host in groups['opendaylight'] %}
+  server {{ hostvars[host]['ansible_hostname'] }} {{ hostvars[host]['ansible_' + hostvars[host]['api_interface']]['ipv4']['address'] }}:{{ opendaylight_restconf_port_backup }} check fall 5 inter 2000 rise 2
+{% endfor %}
+
+listen opendaylight_ovsdb
+  mode tcp
+  timeout client 3600s
+  timeout server 3600s
+  option tcplog
+  option tcpka
+  bind {{ kolla_internal_vip_address }}:{{ opendaylight_haproxy_ovsdb_port }}
+{% for host in groups['opendaylight'] %}
+  server {{ hostvars[host]['ansible_hostname'] }} {{ hostvars[host]['ansible_' + hostvars[host]['api_interface']]['ipv4']['address'] }}:{{ opendaylight_ovsdb_port }} check inter 2000 rise 2 fall 5 {% if not loop.first %}backup{% endif %}
+
+{% endfor %}
+{% endif %}
diff --git a/ansible/roles/neutron/defaults/main.yml b/ansible/roles/neutron/defaults/main.yml
index f24ab2b4aa..e52d60a692 100644
--- a/ansible/roles/neutron/defaults/main.yml
+++ b/ansible/roles/neutron/defaults/main.yml
@@ -81,7 +81,7 @@ neutron_services:
     container_name: "neutron_l3_agent"
     image: "{{ neutron_l3_agent_image_full }}"
     privileged: True
-    enabled: "{{ not enable_neutron_vpnaas | bool and neutron_plugin_agent not in ['vmware_nsxv', 'vmware_dvs'] }}"
+    enabled: "{{ not enable_neutron_vpnaas | bool and neutron_plugin_agent not in ['vmware_nsxv', 'vmware_dvs'] and not enable_opendaylight_l3 | bool }}"
     host_in_groups: >-
       {{
       inventory_hostname in groups['neutron-l3-agent']
@@ -90,6 +90,7 @@ neutron_services:
     volumes:
       - "{{ node_config_directory }}/neutron-l3-agent/:{{ container_config_directory }}/:ro"
       - "/etc/localtime:/etc/localtime:ro"
+      - "/lib/modules:/lib/modules:ro"
       - "/run:/run:shared"
       - "neutron_metadata_socket:/var/lib/neutron/kolla/"
       - "kolla_logs:/var/log/kolla/"
@@ -186,7 +187,7 @@ neutron_openvswitch_agent_image: "{{ docker_registry ~ '/' if docker_registry el
 neutron_openvswitch_agent_tag: "{{ neutron_tag }}"
 neutron_openvswitch_agent_image_full: "{{ neutron_openvswitch_agent_image }}:{{ neutron_openvswitch_agent_tag }}"
 
-neutron_server_image: "{{ docker_registry ~ '/' if docker_registry else '' }}{{ docker_namespace }}/{{ kolla_base_distro }}-{{ neutron_install_type }}-neutron-server"
+neutron_server_image: "{{ docker_registry ~ '/' if docker_registry else '' }}{{ docker_namespace }}/{{ kolla_base_distro }}-{{ neutron_install_type }}-neutron-server{{ '-opendaylight' if enable_opendaylight | bool else '' }}"
 neutron_server_tag: "{{ neutron_tag }}"
 neutron_server_image_full: "{{ neutron_server_image }}:{{ neutron_server_tag }}"
 
@@ -218,7 +219,7 @@ openstack_neutron_auth: "{{ openstack_auth }}"
 ####################
 extension_drivers:
   - name: "qos"
-    enabled: "{{ enable_neutron_qos | bool }}"
+    enabled: "{{ enable_neutron_qos | bool or enable_opendaylight_qos | bool }}"
   - name: "port_security"
     enabled: true
   - name: "dns"
@@ -241,13 +242,15 @@ service_plugins:
   - name: "vpnaas"
     enabled: "{{ enable_neutron_vpnaas | bool }}"
   - name: "qos"
-    enabled: "{{ enable_neutron_qos | bool }}"
+    enabled: "{{ enable_neutron_qos | bool or enable_opendaylight_qos | bool}}"
   - name: "router"
-    enabled: true
+    enabled: "{{ not enable_opendaylight_l3 | bool }}"
   - name: "sfc"
     enabled: "{{ enable_neutron_sfc | bool }}"
   - name: "neutron_dynamic_routing.services.bgp.bgp_plugin.BgpPlugin"
     enabled: "{{ enable_neutron_bgp_dragent | bool }}"
+  - name: "{{ opendaylight_l3_service_plugin }}"
+    enabled: "{{ enable_opendaylight_l3 | bool and enable_opendaylight | bool }}"
 
 neutron_service_plugins: "{{ service_plugins|selectattr('enabled', 'equalto', true)|list }}"
 
@@ -302,3 +305,12 @@ vmware_dvs_host_password: "password"
 vmware_dvs_insecure: "True"
 vmware_dvs_dvs_name: "VDS-1"
 vmware_dvs_dhcp_override_mac: ""
+
+######################
+# Notification Drivers
+######################
+notification_drivers:
+  - name: "odl-qos-v2"
+    enabled: "{{ enable_opendaylight_qos | bool }}"
+
+neutron_notification_drivers: "{{ notification_drivers|selectattr('enabled', 'equalto', true)|list }}"
diff --git a/ansible/roles/neutron/templates/dhcp_agent.ini.j2 b/ansible/roles/neutron/templates/dhcp_agent.ini.j2
index 71c40967cc..240482db44 100644
--- a/ansible/roles/neutron/templates/dhcp_agent.ini.j2
+++ b/ansible/roles/neutron/templates/dhcp_agent.ini.j2
@@ -16,6 +16,10 @@ dhcp_override_mac = {{ vmware_dvs_dhcp_override_mac }}
 {% endif %}
 {% endif %}
 
+{% if enable_opendaylight | bool %}
+interface_driver = openvswitch
+{% endif %}
+
 [ovs]
 ovsdb_interface = native
 ovsdb_connection = tcp:{{ api_interface_address }}:6640
diff --git a/ansible/roles/neutron/templates/ml2_conf.ini.j2 b/ansible/roles/neutron/templates/ml2_conf.ini.j2
index 054b8faa43..279423ba3a 100644
--- a/ansible/roles/neutron/templates/ml2_conf.ini.j2
+++ b/ansible/roles/neutron/templates/ml2_conf.ini.j2
@@ -12,6 +12,8 @@ mechanism_drivers = openvswitch,l2population
 {% endif %}
 {% elif neutron_plugin_agent == "linuxbridge" %}
 mechanism_drivers = linuxbridge,l2population
+{% elif neutron_plugin_agent == "opendaylight" %}
+mechanism_drivers = {{ opendaylight_mechanism_driver }}
 {% endif %}
 
 {% if neutron_extension_drivers %}
@@ -20,6 +22,14 @@ extension_drivers = {{ neutron_extension_drivers|map(attribute='name')|join(',')
 extension_drivers = port_security
 {% endif %}
 
+{% if enable_opendaylight | bool %}
+[ml2_odl]
+url = {{ internal_protocol }}://{{ kolla_internal_vip_address }}:{{ opendaylight_haproxy_restconf_port }}/controller/nb/v2/neutron
+username = admin
+password = {{ opendaylight_password }}
+port_binding_controller = {{ opendaylight_port_binding_type }}
+{% endif %}
+
 [ml2_type_vlan]
 {% if enable_ironic | bool %}
 network_vlan_ranges = physnet1
@@ -41,13 +51,13 @@ vxlan_group = 239.1.1.1
 {% endif %}
 
 [securitygroup]
-{% if neutron_plugin_agent == "openvswitch" %}
+{% if neutron_plugin_agent == "openvswitch" or neutron_plugin_agent == "opendaylight" %}
 firewall_driver = neutron.agent.linux.iptables_firewall.OVSHybridIptablesFirewallDriver
 {% elif neutron_plugin_agent == "linuxbridge" %}
 firewall_driver = neutron.agent.linux.iptables_firewall.IptablesFirewallDriver
 {% endif %}
 
-{% if neutron_plugin_agent == "openvswitch" %}
+{% if neutron_plugin_agent == "openvswitch" or neutron_plugin_agent == "opendaylight" %}
 {% if not enable_hyperv | bool %}
 [agent]
 tunnel_types = vxlan
diff --git a/ansible/roles/neutron/templates/neutron-server.json.j2 b/ansible/roles/neutron/templates/neutron-server.json.j2
index 076e9a5eae..9f83c71f9d 100644
--- a/ansible/roles/neutron/templates/neutron-server.json.j2
+++ b/ansible/roles/neutron/templates/neutron-server.json.j2
@@ -1,5 +1,5 @@
 {
-    "command": "neutron-server --config-file /etc/neutron/neutron.conf {% if neutron_plugin_agent in ['openvswitch', 'linuxbridge'] %} --config-file /etc/neutron/plugins/ml2/ml2_conf.ini --config-file /etc/neutron/neutron_lbaas.conf --config-file /etc/neutron/neutron_vpnaas.conf {% elif neutron_plugin_agent in ['vmware_nsx', 'vmware_dvs'] %} --config-file /etc/neutron/plugins/vmware/nsx.ini {% endif %} --config-file /etc/neutron/fwaas_driver.ini",
+    "command": "neutron-server --config-file /etc/neutron/neutron.conf {% if neutron_plugin_agent in ['openvswitch', 'linuxbridge', 'opendaylight'] %} --config-file /etc/neutron/plugins/ml2/ml2_conf.ini --config-file /etc/neutron/neutron_lbaas.conf --config-file /etc/neutron/neutron_vpnaas.conf {% elif neutron_plugin_agent in ['vmware_nsx', 'vmware_dvs'] %} --config-file /etc/neutron/plugins/vmware/nsx.ini {% endif %} --config-file /etc/neutron/fwaas_driver.ini",
     "config_files": [
         {
             "source": "{{ container_config_directory }}/neutron.conf",
diff --git a/ansible/roles/neutron/templates/neutron.conf.j2 b/ansible/roles/neutron/templates/neutron.conf.j2
index 919c7422c6..d1aa1d8681 100644
--- a/ansible/roles/neutron/templates/neutron.conf.j2
+++ b/ansible/roles/neutron/templates/neutron.conf.j2
@@ -24,7 +24,7 @@ rpc_state_report_workers = {{ openstack_service_rpc_workers }}
 # in it is because we are sharing this socket in a volume which is it's own dir
 metadata_proxy_socket = /var/lib/neutron/kolla/metadata_proxy
 
-{% if neutron_plugin_agent == "openvswitch" %}
+{% if neutron_plugin_agent == "openvswitch" or neutron_plugin_agent == "opendaylight" %}
 interface_driver = openvswitch
 {% elif neutron_plugin_agent == "linuxbridge" %}
 interface_driver = linuxbridge
@@ -145,3 +145,8 @@ hmac_keys = {{ osprofiler_secret }}
 connection_string = elasticsearch://{{ elasticsearch_address }}:{{ elasticsearch_port }}
 {% endif %}
 {% endif %}
+
+{% if enable_opendaylight_qos | bool %}
+[qos]
+notification_drivers = {{ neutron_notification_drivers|map(attribute='name')|join(',') }}
+{% endif %}
diff --git a/ansible/roles/opendaylight/defaults/main.yml b/ansible/roles/opendaylight/defaults/main.yml
new file mode 100644
index 0000000000..f2d35bda47
--- /dev/null
+++ b/ansible/roles/opendaylight/defaults/main.yml
@@ -0,0 +1,24 @@
+---
+project_name: "opendaylight"
+
+opendaylight_services:
+  opendaylight:
+    container_name: "opendaylight"
+    image: "{{ opendaylight_image_full }}"
+    enabled: True
+    privileged: True
+    group: "opendaylight"
+    host_in_groups: "{{ inventory_hostname in groups['opendaylight'] }}"
+    volumes:
+      - "{{ node_config_directory }}/opendaylight/:{{ container_config_directory }}/:ro"
+      - "/etc/localtime:/etc/localtime:ro"
+      - "kolla_logs:/var/log/kolla/"
+
+####################
+# Docker
+####################
+
+opendaylight_install_type: "{{ kolla_install_type }}"
+opendaylight_image: "{{ docker_registry ~ '/' if docker_registry else '' }}{{ docker_namespace }}/{{ kolla_base_distro }}-{{ opendaylight_install_type }}-opendaylight"
+opendaylight_tag: "{{ openstack_release }}"
+opendaylight_image_full: "{{ opendaylight_image }}:{{ opendaylight_tag }}"
diff --git a/ansible/roles/opendaylight/handlers/main.yml b/ansible/roles/opendaylight/handlers/main.yml
new file mode 100644
index 0000000000..0a5f9eec70
--- /dev/null
+++ b/ansible/roles/opendaylight/handlers/main.yml
@@ -0,0 +1,31 @@
+---
+- name: Restart opendaylight container
+  vars:
+    service_name: "opendaylight"
+    service: "{{ opendaylight_services[service_name] }}"
+  kolla_docker:
+    action: "recreate_or_restart_container"
+    common_options: "{{ docker_common_options }}"
+    name: "{{ service.container_name }}"
+    image: "{{ service.image }}"
+    volumes: "{{ service.volumes }}"
+    privileged: "{{ service.privileged | default(False) }}"
+  when:
+    - action != "config"
+    - service.enabled | bool
+    - service.host_in_groups | bool
+    - opendaylight_config_json | changed
+      or opendaylight_config_start_odl | changed
+      or opendaylight_config_custom_props | changed
+      or opendaylight_config_jetty | changed
+      or opendaylight_config_features | changed
+      or opendaylight_config_ovsdb | changed
+      or opendaylight_config_tomcat | changed
+      or opendaylight_config_logging | changed
+      or opendaylight_config_netvirt | changed
+      or opendaylight_config_netvirt_acl | changed
+      or opendaylight_config_env | changed
+      or opendaylight_config_akka | changed
+      or opendaylight_config_modules | changed
+      or opendaylight_config_module_shards | changed
+      or check_opendaylight_containers | changed
diff --git a/ansible/roles/opendaylight/meta/main.yml b/ansible/roles/opendaylight/meta/main.yml
new file mode 100644
index 0000000000..6b4fff8fef
--- /dev/null
+++ b/ansible/roles/opendaylight/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+  - { role: common }
diff --git a/ansible/roles/opendaylight/tasks/check.yml b/ansible/roles/opendaylight/tasks/check.yml
new file mode 100644
index 0000000000..ed97d539c0
--- /dev/null
+++ b/ansible/roles/opendaylight/tasks/check.yml
@@ -0,0 +1 @@
+---
diff --git a/ansible/roles/opendaylight/tasks/config.yml b/ansible/roles/opendaylight/tasks/config.yml
new file mode 100644
index 0000000000..06e4957337
--- /dev/null
+++ b/ansible/roles/opendaylight/tasks/config.yml
@@ -0,0 +1,178 @@
+---
+- name: Setting sysctl values
+  sysctl: name={{ item.name }} value={{ item.value }} sysctl_set=yes
+  with_items:
+    - { name: "net.bridge.bridge-nf-call-iptables", value: 1}
+    - { name: "net.bridge.bridge-nf-call-ip6tables", value: 1}
+    - { name: "net.ipv4.conf.all.rp_filter", value: 0}
+    - { name: "net.ipv4.conf.default.rp_filter", value: 0}
+  when:
+    - set_sysctl | bool
+    - inventory_hostname in groups['opendaylight']
+
+- name: Ensuring config directories exist
+  file:
+    path: "{{ node_config_directory }}/{{ item }}"
+    state: "directory"
+    recurse: yes
+  with_items:
+    - "opendaylight"
+
+- name: Copying over config.json files for services
+  register: opendaylight_config_json
+  template:
+    src: "{{ item }}.json.j2"
+    dest: "{{ node_config_directory }}/{{ item }}/config.json"
+  with_items:
+    - "opendaylight"
+  notify:
+    - Restart opendaylight container
+
+- name: Copying over custom.properties
+  register: opendaylight_config_custom_props
+  template:
+    src: "{{ role_path }}/templates/custom.properties.j2"
+    dest: "{{ node_config_directory }}/opendaylight/custom.properties"
+  notify:
+    - Restart opendaylight container
+
+- name: Copying over start-odl
+  register: opendaylight_config_start_odl
+  template:
+    src: "{{ role_path }}/templates/start-odl.j2"
+    dest: "{{ node_config_directory }}/opendaylight/start-odl"
+  notify:
+    - Restart opendaylight container
+
+- name: Copying over jetty.xml
+  register: opendaylight_config_jetty
+  template:
+    src: "{{ role_path }}/templates/jetty.xml.j2"
+    dest: "{{ node_config_directory }}/{{ item }}/jetty.xml"
+  with_items:
+    - "opendaylight"
+  notify:
+    - Restart opendaylight container
+
+- name: Copying over org.apache.karaf.features.cfg
+  register: opendaylight_config_features
+  template:
+    src: "{{ role_path }}/templates/org.apache.karaf.features.cfg.j2"
+    dest: "{{ node_config_directory }}/{{ item }}/org.apache.karaf.features.cfg"
+  with_items:
+    - "opendaylight"
+  notify:
+    - Restart opendaylight container
+
+- name: Copying over org.opendaylight.ovsdb.library.cfg
+  register: opendaylight_config_ovsdb
+  template:
+    src: "{{ role_path }}/templates/org.opendaylight.ovsdb.library.cfg.j2"
+    dest: "{{ node_config_directory }}/{{ item }}/org.opendaylight.ovsdb.library.cfg"
+  with_items:
+    - "opendaylight"
+  notify:
+    - Restart opendaylight container
+
+- name: Copying over tomcat-server.xml
+  template:
+    src: "{{ role_path }}/templates/tomcat-server.xml.j2"
+    dest: "{{ node_config_directory }}/{{ item }}/tomcat-server.xml"
+  register: opendaylight_config_tomcat
+  with_items:
+    - "opendaylight"
+  notify:
+    - Restart opendaylight container
+
+- name: Copying over org.ops4j.pax.logging.cfg.j2
+  template:
+    src: "{{ role_path }}/templates/org.ops4j.pax.logging.cfg.j2"
+    dest: "{{ node_config_directory }}/{{ item }}/org.ops4j.pax.logging.cfg"
+  register: opendaylight_config_logging
+  with_items:
+    - "opendaylight"
+  notify:
+    - Restart opendaylight container
+
+- name: Copying over netvirt-impl-config_netvirt-impl-config.xml
+  template:
+    src: "{{ role_path }}/templates/netvirt-impl-config_netvirt-impl-config.xml.j2"
+    dest: "{{ node_config_directory }}/{{ item }}/netvirt-impl-config_netvirt-impl-config.xml"
+  register: opendaylight_config_netvirt
+  with_items:
+    - "opendaylight"
+  notify:
+    - Restart opendaylight container
+
+- name: Copying over netvirt-aclservice-config.xml
+  template:
+    src: "{{ role_path }}/templates/netvirt-aclservice-config.xml.j2"
+    dest: "{{ node_config_directory }}/{{ item }}/netvirt-aclservice-config.xml"
+  register: opendaylight_config_netvirt_acl
+  with_items:
+    - "opendaylight"
+  notify:
+    - Restart opendaylight container
+
+- name: Copying over setenv
+  template:
+    src: "{{ role_path }}/templates/setenv.j2"
+    dest: "{{ node_config_directory }}/{{ item }}/setenv"
+  register: opendaylight_config_env
+  with_items:
+    - "opendaylight"
+  notify:
+    - Restart opendaylight container
+
+- name: Copying over akka.conf
+  template:
+    src: "{{ role_path }}/templates/akka.conf.j2"
+    dest: "{{ node_config_directory }}/opendaylight/akka.conf"
+  with_first_found:
+    - "{{ node_custom_config }}/opendaylight/{{ inventory_hostname }}/akka.conf"
+    - "{{ node_custom_config }}/opendaylight/akka.conf"
+    - "akka.conf.j2"
+  register: opendaylight_config_akka
+  notify:
+    - Restart opendaylight container
+
+- name: Copying over modules.conf
+  template:
+    src: "{{ role_path }}/templates/modules.conf.j2"
+    dest: "{{ node_config_directory }}/opendaylight/modules.conf"
+  with_first_found:
+    - "{{ node_custom_config }}/opendaylight/{{ inventory_hostname }}/modules.conf"
+    - "{{ node_custom_config }}/opendaylight/modules.conf"
+    - "modules.conf.j2"
+  register: opendaylight_config_modules
+  notify:
+    - Restart opendaylight container
+
+- name: Copying over module-shards.conf
+  template:
+    src: "{{ role_path }}/templates/module-shards.conf.j2"
+    dest: "{{ node_config_directory }}/opendaylight/module-shards.conf"
+  with_first_found:
+    - "{{ node_custom_config }}/opendaylight/{{ inventory_hostname }}/module-shards.conf"
+    - "{{ node_custom_config }}/opendaylight/module-shards.conf"
+    - "module-shards.conf.j2"
+  register: opendaylight_config_module_shards
+  notify:
+    - Restart opendaylight container
+
+- name: Check opendaylight containers
+  kolla_docker:
+    action: "compare_container"
+    common_options: "{{ docker_common_options }}"
+    name: "{{ item.value.container_name }}"
+    image: "{{ item.value.image }}"
+    privileged: "{{ item.value.privileged | default(False) }}"
+    volumes: "{{ item.value.volumes }}"
+  register: check_opendaylight_containers
+  when:
+    - action != "config"
+    - item.value.enabled | bool
+    - item.value.host_in_groups | bool
+  with_dict: "{{ opendaylight_services }}"
+  notify:
+    - "Restart {{ item.key }} container"
diff --git a/ansible/roles/opendaylight/tasks/deploy.yml b/ansible/roles/opendaylight/tasks/deploy.yml
new file mode 100644
index 0000000000..5aac9f5a7f
--- /dev/null
+++ b/ansible/roles/opendaylight/tasks/deploy.yml
@@ -0,0 +1,5 @@
+---
+- include: config.yml
+
+- name: Flush Handlers
+  meta: flush_handlers
diff --git a/ansible/roles/opendaylight/tasks/main.yml b/ansible/roles/opendaylight/tasks/main.yml
new file mode 100644
index 0000000000..b017e8b4ad
--- /dev/null
+++ b/ansible/roles/opendaylight/tasks/main.yml
@@ -0,0 +1,2 @@
+---
+- include: "{{ action }}.yml"
diff --git a/ansible/roles/opendaylight/tasks/precheck.yml b/ansible/roles/opendaylight/tasks/precheck.yml
new file mode 100644
index 0000000000..260e774f4e
--- /dev/null
+++ b/ansible/roles/opendaylight/tasks/precheck.yml
@@ -0,0 +1,111 @@
+---
+- name: Get container facts
+  kolla_container_facts:
+    name:
+      - opendaylight
+  register: container_facts
+
+- name: Checking free port for opendaylight_clustering
+  wait_for:
+    host: "{{ hostvars[inventory_hostname]['ansible_' + api_interface]['ipv4']['address'] }}"
+    port: "{{ opendaylight_clustering_port }}"
+    connect_timeout: 1
+    state: stopped
+  when:
+    - inventory_hostname in groups['opendaylight']
+    - container_facts['opendaylight'] is not defined
+
+- name: Checking free port for opendaylight_restconf
+  wait_for:
+    host: "{{ hostvars[inventory_hostname]['ansible_' + api_interface]['ipv4']['address'] }}"
+    port: "{{ opendaylight_restconf_port }}"
+    connect_timeout: 1
+    state: stopped
+  when:
+    - inventory_hostname in groups['opendaylight']
+    - container_facts['opendaylight'] is not defined
+
+- name: Checking free port for opendaylight_restconf_backup
+  wait_for:
+    host: "{{ hostvars[inventory_hostname]['ansible_' + api_interface]['ipv4']['address'] }}"
+    port: "{{ opendaylight_restconf_port_backup }}"
+    connect_timeout: 1
+    state: stopped
+  when:
+    - inventory_hostname in groups['opendaylight']
+    - container_facts['opendaylight'] is not defined
+
+- name: Checking free port for opendaylight_karaf_ssh
+  wait_for:
+    host: "{{ hostvars[inventory_hostname]['ansible_' + api_interface]['ipv4']['address'] }}"
+    port: "{{ opendaylight_karaf_ssh_port }}"
+    connect_timeout: 1
+    state: stopped
+  when:
+    - inventory_hostname in groups['opendaylight']
+    - container_facts['opendaylight'] is not defined
+
+- name: Checking free port for opendaylight_openflow
+  wait_for:
+    host: "{{ hostvars[inventory_hostname]['ansible_' + api_interface]['ipv4']['address'] }}"
+    port: "{{ opendaylight_openflow_port }}"
+    connect_timeout: 1
+    state: stopped
+  when:
+    - inventory_hostname in groups['opendaylight']
+    - container_facts['opendaylight'] is not defined
+
+- name: Checking free port for opendaylight_ovsdb
+  wait_for:
+    host: "{{ hostvars[inventory_hostname]['ansible_' + api_interface]['ipv4']['address'] }}"
+    port: "{{ opendaylight_ovsdb_port }}"
+    connect_timeout: 1
+    state: stopped
+  when:
+    - inventory_hostname in groups['opendaylight']
+    - container_facts['opendaylight'] is not defined
+
+- name: Checking free port for opendaylight_jetty_conf_port
+  wait_for:
+    host: "{{ hostvars[inventory_hostname]['ansible_' + api_interface]['ipv4']['address'] }}"
+    port: "{{ opendaylight_jetty_conf_port }}"
+    connect_timeout: 1
+    state: stopped
+  when:
+    - inventory_hostname in groups['opendaylight']
+    - container_facts['opendaylight'] is not defined
+
+- name: Checking free port for opendaylight_jetty_conf2_port
+  wait_for:
+    host: "{{ hostvars[inventory_hostname]['ansible_' + api_interface]['ipv4']['address'] }}"
+    port: "{{ opendaylight_jetty_conf2_port }}"
+    connect_timeout: 1
+    state: stopped
+  when:
+    - inventory_hostname in groups['opendaylight']
+    - container_facts['opendaylight'] is not defined
+
+- name: Checking free port for opendaylight_tomcat_port
+  wait_for:
+    host: "{{ hostvars[inventory_hostname]['ansible_' + api_interface]['ipv4']['address'] }}"
+    port: "{{ opendaylight_tomcat_port }}"
+    connect_timeout: 1
+    state: stopped
+  when:
+    - inventory_hostname in groups['opendaylight']
+    - container_facts['opendaylight'] is not defined
+
+- name: Checking free port for opendaylight_tomcat_redirect_port
+  wait_for:
+    host: "{{ hostvars[inventory_hostname]['ansible_' + api_interface]['ipv4']['address'] }}"
+    port: "{{ opendaylight_tomcat_redirect_port }}"
+    connect_timeout: 1
+    state: stopped
+  when:
+    - inventory_hostname in groups['opendaylight']
+    - container_facts['opendaylight'] is not defined
+
+- name: Checking available opendaylight nodes in inventory
+  fail:
+    msg: "Either 1 or 3 nodes required in inventory for OpenDaylight clustering"
+  when: groups['opendaylight'] | length == 2
diff --git a/ansible/roles/opendaylight/tasks/pull.yml b/ansible/roles/opendaylight/tasks/pull.yml
new file mode 100644
index 0000000000..76a1bd6b6a
--- /dev/null
+++ b/ansible/roles/opendaylight/tasks/pull.yml
@@ -0,0 +1,10 @@
+---
+- name: Pulling opendaylight image
+  kolla_docker:
+    action: "pull_image"
+    common_options: "{{ docker_common_options }}"
+    image: "{{ item.value.image }}"
+  when:
+    - item.value.enabled | bool
+    - item.value.host_in_groups | bool
+  with_dict: "{{ opendaylight_services }}"
diff --git a/ansible/roles/opendaylight/tasks/reconfigure.yml b/ansible/roles/opendaylight/tasks/reconfigure.yml
new file mode 100644
index 0000000000..e078ef1318
--- /dev/null
+++ b/ansible/roles/opendaylight/tasks/reconfigure.yml
@@ -0,0 +1,2 @@
+---
+- include: deploy.yml
diff --git a/ansible/roles/opendaylight/tasks/upgrade.yml b/ansible/roles/opendaylight/tasks/upgrade.yml
new file mode 100644
index 0000000000..5aac9f5a7f
--- /dev/null
+++ b/ansible/roles/opendaylight/tasks/upgrade.yml
@@ -0,0 +1,5 @@
+---
+- include: config.yml
+
+- name: Flush Handlers
+  meta: flush_handlers
diff --git a/ansible/roles/opendaylight/templates/akka.conf.j2 b/ansible/roles/opendaylight/templates/akka.conf.j2
new file mode 100644
index 0000000000..22d63e566f
--- /dev/null
+++ b/ansible/roles/opendaylight/templates/akka.conf.j2
@@ -0,0 +1,33 @@
+
+odl-cluster-data {
+  akka {
+    remote {
+      artery {
+          enabled = off
+          canonical.hostname = "{{ hostvars[inventory_hostname]['ansible_' + hostvars[inventory_hostname]['api_interface']]['ipv4']['address'] }}"
+          canonical.port = {{ opendaylight_clustering_port }}
+        }
+      netty.tcp {
+        hostname = "{{ hostvars[inventory_hostname]['ansible_' + hostvars[inventory_hostname]['api_interface']]['ipv4']['address'] }}"
+        port = {{ opendaylight_clustering_port }}
+      }
+    }
+
+    cluster {
+      seed-nodes = [{% for host in groups['opendaylight'] %}"akka.tcp://opendaylight-cluster-data@{{ hostvars[host]['ansible_' + hostvars[host]['api_interface']]['ipv4']['address'] }}:{{ opendaylight_clustering_port }}"{% if not loop.last %},{% endif %}{% endfor %}]
+
+      roles = [
+        "{{ hostvars[inventory_hostname]['ansible_hostname'] }}"
+      ]
+
+    }
+
+    persistence {
+
+      journal {
+        leveldb {
+        }
+      }
+    }
+  }
+}
diff --git a/ansible/roles/opendaylight/templates/custom.properties.j2 b/ansible/roles/opendaylight/templates/custom.properties.j2
new file mode 100644
index 0000000000..5f8c90cfea
--- /dev/null
+++ b/ansible/roles/opendaylight/templates/custom.properties.j2
@@ -0,0 +1,47 @@
+org.osgi.framework.system.packages.extra=org.apache.karaf.branding,sun.reflect,sun.reflect.misc,sun.misc,sun.nio.ch,com.sun.media.sound
+
+osgi.hook.configurators.include=org.eclipse.virgo.kernel.equinox.extensions.hooks.ExtensionsHookConfigurator
+
+
+org.eclipse.gemini.web.tomcat.config.path=configuration/tomcat-server.xml
+org.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH=true
+
+karaf.framework=equinox
+
+karaf.delay.console=true
+
+org.apache.karaf.security.providers = org.bouncycastle.jce.provider.BouncyCastleProvider
+
+org.apache.aries.blueprint.preemptiveShutdown=false
+
+netconf.config.persister.active=1
+
+netconf.config.persister.1.storageAdapterClass=org.opendaylight.controller.config.persist.storage.file.xml.XmlFileStorageAdapter
+netconf.config.persister.1.properties.fileStorage=etc/opendaylight/current/controller.currentconfig.xml
+netconf.config.persister.1.properties.numberOfBackups=1
+
+logback.configurationFile=configuration/logback.xml
+
+container.profile = Container
+
+connection.scheme = ANY_CONTROLLER_ONE_MASTER
+
+ovsdb.l3.arp.responder.disabled=no
+
+secureChannelEnabled=false
+controllerKeyStore=
+controllerKeyStorePassword=
+controllerTrustStore=
+controllerTrustStorePassword=
+
+enableStrongPasswordCheck = false
+
+java.util.logging.config.file=configuration/tomcat-logging.properties
+
+hosttracker.keyscheme=IP
+
+lisp.mappingMerge = false
+
+lisp.smr = true
+
+lisp.elpPolicy = default
diff --git a/ansible/roles/opendaylight/templates/jetty.xml.j2 b/ansible/roles/opendaylight/templates/jetty.xml.j2
new file mode 100644
index 0000000000..4e4cf897ca
--- /dev/null
+++ b/ansible/roles/opendaylight/templates/jetty.xml.j2
@@ -0,0 +1,90 @@
+<?xml version="1.0"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License.  You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied.  See the License for the
+ specific language governing permissions and limitations
+ under the License.
+-->
+<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//
+DTD Configure//EN" "http://jetty.mortbay.org/configure.dtd">
+
+<Configure class="org.eclipse.jetty.server.Server">
+    <Call name="addConnector">
+        <Arg>
+            <New class="org.eclipse.jetty.server.nio.SelectChannelConnector">
+                <Set name="host">
+                    <Property name="jetty.host" />
+                </Set>
+                <Set name="port">
+                    <Property name="jetty.port" default="{{ opendaylight_restconf_port }}" />
+                </Set>
+                <Set name="maxIdleTime">300000</Set>
+                <Set name="Acceptors">2</Set>
+                <Set name="statsOn">false</Set>
+                <Set name="confidentialPort">{{ opendaylight_jetty_conf_port }}</Set>
+                <Set name="lowResourcesConnections">20000</Set>
+                <Set name="lowResourcesMaxIdleTime">5000</Set>
+            </New>
+        </Arg>
+    </Call>
+    <Call name="addConnector">
+      <Arg>
+        <New class="org.eclipse.jetty.server.nio.SelectChannelConnector">
+          <Set name="host">
+            <Property name="jetty.host" />
+          </Set>
+          <Set name="port">
+            <Property name="jetty.port" default="{{ opendaylight_restconf_port_backup }}" />
+          </Set>
+          <Set name="maxIdleTime">300000</Set>
+          <Set name="Acceptors">2</Set>
+          <Set name="statsOn">false</Set>
+          <Set name="confidentialPort">{{ opendaylight_jetty_conf2_port }}</Set>
+          <Set name="lowResourcesConnections">20000</Set>
+          <Set name="lowResourcesMaxIdleTime">5000</Set>
+        </New>
+      </Arg>
+    </Call>
+
+    <Call name="addBean">
+        <Arg>
+            <New class="org.eclipse.jetty.plus.jaas.JAASLoginService">
+                <Set name="name">karaf</Set>
+                <Set name="loginModuleName">karaf</Set>
+                <Set name="roleClassNames">
+                    <Array type="java.lang.String">
+                        <Item>org.apache.karaf.jaas.boot.principal.RolePrincipal
+                        </Item>
+                    </Array>
+                </Set>
+            </New>
+        </Arg>
+    </Call>
+    <Call name="addBean">
+        <Arg>
+            <New class="org.eclipse.jetty.plus.jaas.JAASLoginService">
+                <Set name="name">default</Set>
+                <Set name="loginModuleName">karaf</Set>
+                <Set name="roleClassNames">
+                    <Array type="java.lang.String">
+                        <Item>org.apache.karaf.jaas.boot.principal.RolePrincipal
+                        </Item>
+                    </Array>
+                </Set>
+            </New>
+        </Arg>
+    </Call>
+
+</Configure>
diff --git a/ansible/roles/opendaylight/templates/module-shards.conf.j2 b/ansible/roles/opendaylight/templates/module-shards.conf.j2
new file mode 100644
index 0000000000..4fe1f999ff
--- /dev/null
+++ b/ansible/roles/opendaylight/templates/module-shards.conf.j2
@@ -0,0 +1,59 @@
+module-shards = [
+    {
+        name = "default"
+        shards = [
+            {
+                name="default"
+                replicas = [
+                    {% for host in groups['opendaylight'] %}
+                    "{{ hostvars[host]['ansible_hostname'] }}"{% if not loop.last %},
+                    {% endif %}
+                    {% endfor %}
+                ]
+            }
+        ]
+    },
+    {
+        name = "topology"
+        shards = [
+            {
+                name="topology"
+                replicas = [
+                    {% for host in groups['opendaylight'] %}
+                    "{{ hostvars[host]['ansible_hostname'] }}"{% if not loop.last %},
+                    {% endif %}
+                    {% endfor %}
+                ]
+            }
+        ]
+    },
+    {
+        name = "inventory"
+        shards = [
+            {
+                name="inventory"
+                replicas = [
+                    {% for host in groups['opendaylight'] %}
+                    "{{ hostvars[host]['ansible_hostname'] }}"{% if not loop.last %},
+                    {% endif %}
+                    {% endfor %}
+                ]
+            }
+        ]
+    },
+         {
+             name = "toaster"
+             shards = [
+                 {
+                     name="toaster"
+                     replicas = [
+                        {% for host in groups['opendaylight'] %}
+                        "{{ hostvars[host]['ansible_hostname'] }}"{% if not loop.last %},
+                        {% endif %}
+                        {% endfor %}
+                     ]
+                 }
+             ]
+         }
+
+]
diff --git a/ansible/roles/opendaylight/templates/modules.conf.j2 b/ansible/roles/opendaylight/templates/modules.conf.j2
new file mode 100644
index 0000000000..5b0711ea50
--- /dev/null
+++ b/ansible/roles/opendaylight/templates/modules.conf.j2
@@ -0,0 +1,20 @@
+modules = [
+    {
+        name = "inventory"
+        namespace = "urn:opendaylight:inventory"
+        shard-strategy = "module"
+    },
+
+    {
+        name = "topology"
+        namespace = "urn:TBD:params:xml:ns:yang:network-topology"
+        shard-strategy = "module"
+    },
+
+    {
+        name = "toaster"
+        namespace = "http://netconfcentral.org/ns/toaster"
+        shard-strategy = "module"
+    }
+
+]
diff --git a/ansible/roles/opendaylight/templates/netvirt-aclservice-config.xml.j2 b/ansible/roles/opendaylight/templates/netvirt-aclservice-config.xml.j2
new file mode 100644
index 0000000000..398719570b
--- /dev/null
+++ b/ansible/roles/opendaylight/templates/netvirt-aclservice-config.xml.j2
@@ -0,0 +1,4 @@
+<aclservice-config xmlns="urn:opendaylight:netvirt:aclservice-config">
+  <security-group-mode>{{ opendaylight_acl_impl }}</security-group-mode>
+  <default-behavior>deny</default-behavior>
+</aclservice-config>
diff --git a/ansible/roles/opendaylight/templates/netvirt-impl-config_netvirt-impl-config.xml.j2 b/ansible/roles/opendaylight/templates/netvirt-impl-config_netvirt-impl-config.xml.j2
new file mode 100644
index 0000000000..9e22e427a3
--- /dev/null
+++ b/ansible/roles/opendaylight/templates/netvirt-impl-config_netvirt-impl-config.xml.j2
@@ -0,0 +1,7 @@
+{% if enable_opendaylight_legacy_netvirt_conntrack | bool %}
+<netvirt-impl-config xmlns="urn:opendaylight:params:xml:ns:yang:netvirt:impl:config">
+  <conntrack-enabled>
+    true
+  </conntrack-enabled>
+</netvirt-impl-config>
+{% endif %}
diff --git a/ansible/roles/opendaylight/templates/opendaylight.json.j2 b/ansible/roles/opendaylight/templates/opendaylight.json.j2
new file mode 100644
index 0000000000..e475b9cb15
--- /dev/null
+++ b/ansible/roles/opendaylight/templates/opendaylight.json.j2
@@ -0,0 +1,90 @@
+{
+    "command": "start-odl",
+    "config_files": [
+        {
+            "source": "{{ container_config_directory }}/start-odl",
+            "dest": "/usr/local/bin/start-odl",
+            "owner": "odl",
+            "perm": "0655"
+        },
+        {
+            "source": "{{ container_config_directory }}/org.apache.karaf.features.cfg",
+            "dest": "/opt/opendaylight/etc/org.apache.karaf.features.cfg",
+            "owner": "odl",
+            "perm": "0600"
+        },
+        {
+            "source": "{{ container_config_directory }}/org.opendaylight.ovsdb.library.cfg",
+            "dest": "/opt/opendaylight/etc/org.opendaylight.ovsdb.library.cfg",
+            "owner": "odl",
+            "perm": "0600"
+        },
+        {
+            "source": "{{ container_config_directory }}/tomcat-server.xml",
+            "dest": "/opt/opendaylight/configuration/tomcat-server.xml",
+            "owner": "odl",
+            "perm": "0600"
+        },
+        {
+            "source": "{{ container_config_directory }}/jetty.xml",
+            "dest": "/opt/opendaylight/etc/jetty.xml",
+            "owner": "odl",
+            "perm": "0600"
+        },
+        {
+            "source": "{{ container_config_directory }}/org.ops4j.pax.logging.cfg",
+            "dest": "/opt/opendaylight/etc/org.ops4j.pax.logging.cfg",
+            "owner": "odl",
+            "perm": "0600"
+        },
+        {
+            "source": "/var/lib/kolla/config_files/custom.properties",
+            "dest": "/opt/opendaylight/etc/custom.properties",
+            "owner": "odl",
+            "perm": "0600"
+        },
+        {
+            "source": "{{ container_config_directory }}/netvirt-impl-config_netvirt-impl-config.xml",
+            "dest": "/opt/opendaylight/etc/opendaylight/datastore/initial/config/netvirt-impl-config_netvirt-impl-config.xml",
+            "owner": "odl",
+            "perm": "0600"
+        },
+        {
+            "source": "{{ container_config_directory }}/netvirt-aclservice-config.xml",
+            "dest": "/opt/opendaylight/etc/opendaylight/datastore/initial/config/netvirt-aclservice-config.xml",
+            "owner": "odl",
+            "perm": "0600"
+        },
+        {
+            "source": "{{ container_config_directory }}/setenv",
+            "dest": "/opt/opendaylight/bin/setenv",
+            "owner": "odl",
+            "perm": "0600"
+        },
+        {
+            "source": "{{ container_config_directory }}/akka.conf",
+            "dest": "/opt/opendaylight/configuration/initial/akka.conf",
+            "owner": "odl",
+            "perm": "0600"
+        },
+        {
+            "source": "{{ container_config_directory }}/modules.conf",
+            "dest": "/opt/opendaylight/configuration/initial/modules.conf",
+            "owner": "odl",
+            "perm": "0600"
+        },
+        {
+            "source": "{{ container_config_directory }}/module-shards.conf",
+            "dest": "/opt/opendaylight/configuration/initial/module-shards.conf",
+            "owner": "odl",
+            "perm": "0600"
+        }
+    ],
+    "permissions": [
+        {
+            "path": "/var/log/kolla/opendaylight",
+            "owner": "odl:odl",
+            "recurse": true
+        }
+    ]
+}
diff --git a/ansible/roles/opendaylight/templates/org.apache.karaf.features.cfg.j2 b/ansible/roles/opendaylight/templates/org.apache.karaf.features.cfg.j2
new file mode 100644
index 0000000000..ba2dfcd513
--- /dev/null
+++ b/ansible/roles/opendaylight/templates/org.apache.karaf.features.cfg.j2
@@ -0,0 +1,24 @@
+################################################################################
+#
+#    Licensed to the Apache Software Foundation (ASF) under one or more
+#    contributor license agreements.  See the NOTICE file distributed with
+#    this work for additional information regarding copyright ownership.
+#    The ASF licenses this file to You under the Apache License, Version 2.0
+#    (the "License"); you may not use this file except in compliance with
+#    the License.  You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS,
+#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#    See the License for the specific language governing permissions and
+#    limitations under the License.
+#
+################################################################################
+
+featuresRepositories = mvn:org.apache.karaf.features/standard/3.0.8/xml/features,mvn:org.apache.karaf.features/enterprise/3.0.8/xml/features,mvn:org.ops4j.pax.web/pax-web-features/3.2.9/xml/features,mvn:org.apache.karaf.features/spring/3.0.8/xml/features,mvn:org.opendaylight.integration/features-integration-index/{{ opendaylight_release }}/xml/features
+
+featuresBoot=config,standard,region,package,kar,ssh,management{% if not opendaylight_features == '' %},{% endif %}{{ opendaylight_features }}
+
+featuresBootAsynchronous=false
diff --git a/ansible/roles/opendaylight/templates/org.opendaylight.ovsdb.library.cfg.j2 b/ansible/roles/opendaylight/templates/org.opendaylight.ovsdb.library.cfg.j2
new file mode 100644
index 0000000000..d5cae1b52f
--- /dev/null
+++ b/ansible/roles/opendaylight/templates/org.opendaylight.ovsdb.library.cfg.j2
@@ -0,0 +1,7 @@
+ovsdb-listener-port = {{ opendaylight_ovsdb_port }}
+
+use-ssl = false
+
+json-rpc-decoder-max-frame-length = 100000
+
+ovsdb-rpc-task-timeout = 1000
diff --git a/ansible/roles/opendaylight/templates/org.ops4j.pax.logging.cfg.j2 b/ansible/roles/opendaylight/templates/org.ops4j.pax.logging.cfg.j2
new file mode 100644
index 0000000000..d2aa291f4b
--- /dev/null
+++ b/ansible/roles/opendaylight/templates/org.ops4j.pax.logging.cfg.j2
@@ -0,0 +1,52 @@
+################################################################################
+#
+#    Licensed to the Apache Software Foundation (ASF) under one or more
+#    contributor license agreements.  See the NOTICE file distributed with
+#    this work for additional information regarding copyright ownership.
+#    The ASF licenses this file to You under the Apache License, Version 2.0
+#    (the "License"); you may not use this file except in compliance with
+#    the License.  You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS,
+#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#    See the License for the specific language governing permissions and
+#    limitations under the License.
+#
+################################################################################
+
+log4j.rootLogger=INFO, async, osgi:*
+log4j.throwableRenderer=org.apache.log4j.OsgiThrowableRenderer
+
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} | %-5.5p | %-16.16t | %-32.32c{1} | %X{bundle.id} - %X{bundle.name} - %X{bundle.version} | %m%n
+
+log4j.appender.async=org.apache.log4j.AsyncAppender
+log4j.appender.async.appenders=out
+
+log4j.appender.out=org.apache.log4j.RollingFileAppender
+log4j.appender.out.layout=org.apache.log4j.PatternLayout
+log4j.appender.out.layout.ConversionPattern=%d{ISO8601} | %-5.5p | %-16.16t | %-32.32c{1} | %X{bundle.id} - %X{bundle.name} - %X{bundle.version} | %m%n
+log4j.appender.out.file=/var/log/kolla/opendaylight/karaf.log
+log4j.appender.out.append=true
+log4j.appender.out.maxFileSize=1MB
+log4j.appender.out.maxBackupIndex=10
+
+log4j.appender.sift=org.apache.log4j.sift.MDCSiftingAppender
+log4j.appender.sift.key=bundle.name
+log4j.appender.sift.default=karaf
+log4j.appender.sift.appender=org.apache.log4j.FileAppender
+log4j.appender.sift.appender.layout=org.apache.log4j.PatternLayout
+log4j.appender.sift.appender.layout.ConversionPattern=%d{ISO8601} | %-5.5p | %-16.16t | %-32.32c{1} | %m%n
+log4j.appender.sift.appender.file=/var/log/kolla/opendaylight/$\\{bundle.name\\}.log
+log4j.appender.sift.appender.append=true
+
+log4j.appender.syslog=org.apache.log4j.net.SyslogAppender
+log4j.appender.syslog.layout=org.apache.log4j.PatternLayout
+log4j.appender.syslog.layout.ConversionPattern=%d{ISO8601} | %-5.5p | %-16.16t | %-32.32c{1} | %X{bundle.id} - %X{bundle.name} - %X{bundle.version} | %m%n
+log4j.appender.syslog.syslogHost=127.0.0.1
+log4J.appender.syslog.facility=KARAF
+log4j.appender.syslog.facilityPrinting=false
diff --git a/ansible/roles/opendaylight/templates/org.ops4j.pax.url.mvn.cfg.j2 b/ansible/roles/opendaylight/templates/org.ops4j.pax.url.mvn.cfg.j2
new file mode 100644
index 0000000000..37e9e24206
--- /dev/null
+++ b/ansible/roles/opendaylight/templates/org.ops4j.pax.url.mvn.cfg.j2
@@ -0,0 +1,34 @@
+################################################################################
+#
+#    Licensed to the Apache Software Foundation (ASF) under one or more
+#    contributor license agreements.  See the NOTICE file distributed with
+#    this work for additional information regarding copyright ownership.
+#    The ASF licenses this file to You under the Apache License, Version 2.0
+#    (the "License"); you may not use this file except in compliance with
+#    the License.  You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS,
+#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#    See the License for the specific language governing permissions and
+#    limitations under the License.
+#
+################################################################################
+
+org.ops4j.pax.url.mvn.settings=/var/lib/odl/.m2/settings.xml
+
+org.ops4j.pax.url.mvn.localRepository=${karaf.home}/${karaf.default.repository}
+
+org.ops4j.pax.url.mvn.useFallbackRepositories=false
+
+org.ops4j.pax.url.mvn.defaultLocalRepoAsRemote=false
+
+org.ops4j.pax.url.mvn.repositories= \
+file:${karaf.home}/${karaf.default.repository}@id=system.repository, \
+file:${karaf.data}/kar@id=kar.repository@multi, \
+http://repo1.maven.org/maven2@id=central, \
+http://repository.springsource.com/maven/bundles/release@id=spring.ebr.release, \
+http://repository.springsource.com/maven/bundles/external@id=spring.ebr.external, \
+http://zodiac.springsource.com/maven/bundles/release@id=gemini
diff --git a/ansible/roles/opendaylight/templates/setenv.j2 b/ansible/roles/opendaylight/templates/setenv.j2
new file mode 100644
index 0000000000..7376186e18
--- /dev/null
+++ b/ansible/roles/opendaylight/templates/setenv.j2
@@ -0,0 +1,24 @@
+#!/bin/sh
+#
+#    Licensed to the Apache Software Foundation (ASF) under one or more
+#    contributor license agreements.  See the NOTICE file distributed with
+#    this work for additional information regarding copyright ownership.
+#    The ASF licenses this file to You under the Apache License, Version 2.0
+#    (the "License"); you may not use this file except in compliance with
+#    the License.  You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS,
+#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#    See the License for the specific language governing permissions and
+#    limitations under the License.
+#
+
+if [ "x$JAVA_MAX_PERM_MEM" = "x" ]; then
+    export JAVA_MAX_PERM_MEM="512m"
+fi
+if [ "x$JAVA_MAX_MEM" = "x" ]; then
+    export JAVA_MAX_MEM="8g"
+fi
diff --git a/ansible/roles/opendaylight/templates/start-odl.j2 b/ansible/roles/opendaylight/templates/start-odl.j2
new file mode 100644
index 0000000000..e039b1c655
--- /dev/null
+++ b/ansible/roles/opendaylight/templates/start-odl.j2
@@ -0,0 +1,3 @@
+#!/usr/bin/env bash
+java -jar /opt/opendaylight/bin/aaa-cli-jar.jar --dbd /opt/opendaylight/ --nu admin -p {{ opendaylight_password }}
+/opt/opendaylight/bin/karaf
diff --git a/ansible/roles/opendaylight/templates/tomcat-server.xml.j2 b/ansible/roles/opendaylight/templates/tomcat-server.xml.j2
new file mode 100644
index 0000000000..1d199d591e
--- /dev/null
+++ b/ansible/roles/opendaylight/templates/tomcat-server.xml.j2
@@ -0,0 +1,46 @@
+<?xml version='1.0' encoding='utf-8'?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<Server>
+  <!--APR library loader. Documentation at /docs/apr.html -->
+  <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
+  <!--Initialize Jasper prior to webapps are loaded. Documentation at /docs/jasper-howto.html -->
+  <Listener className="org.apache.catalina.core.JasperListener" />
+  <!-- Prevent memory leaks due to use of particular java/javax APIs-->
+  <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
+  <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
+  <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
+
+  <Service name="Catalina">
+    <Connector port="{{ opendaylight_tomcat_port }}" protocol="HTTP/1.1"
+               connectionTimeout="20000"
+               redirectPort="{{ opendaylight_tomcat_redirect_port }}" />
+
+    <Engine name="Catalina" defaultHost="localhost">
+      <Host name="localhost" appBase=""
+            unpackWARs="false" autoDeploy="false"
+            deployOnStartup="false" createDirs="false">
+            <Realm className="org.opendaylight.controller.karafsecurity.ControllerCustomRealm" />
+            <Valve className="org.apache.catalina.authenticator.SingleSignOn" />
+            <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
+                        prefix="web_access_log_" suffix=".txt" resolveHosts="false"
+                        rotatable="true" fileDateFormat="yyyy-MM"
+                        pattern="%{yyyy-MM-dd HH:mm:ss.SSS z}t - [%a] - %r"/>
+      </Host>
+    </Engine>
+  </Service>
+</Server>
diff --git a/ansible/roles/openvswitch/handlers/main.yml b/ansible/roles/openvswitch/handlers/main.yml
index 155536d836..d631076ea1 100644
--- a/ansible/roles/openvswitch/handlers/main.yml
+++ b/ansible/roles/openvswitch/handlers/main.yml
@@ -17,6 +17,8 @@
     - service.host_in_groups | bool
     - config_json | changed
       or openvswitch_db_container | changed
+      or openvswitch_start_ovsdb_server | changed
+
   notify:
     - Waiting for openvswitch_db service to be ready
     - Ensuring OVS bridge is properly setup
@@ -60,3 +62,4 @@
     - service.host_in_groups | bool
     - config_json | changed
       or openvswitch_vswitchd_container | changed
+      or openvswitch_start_ovs | changed
diff --git a/ansible/roles/openvswitch/tasks/config.yml b/ansible/roles/openvswitch/tasks/config.yml
index 0a24c63f4f..fb5dff2381 100644
--- a/ansible/roles/openvswitch/tasks/config.yml
+++ b/ansible/roles/openvswitch/tasks/config.yml
@@ -21,6 +21,32 @@
   notify:
     - "Restart {{ item.key }} container"
 
+- name: Copying over start-ovs file for openvswitch-vswitchd
+  vars:
+    service: "{{ openvswitch_services['openvswitch-vswitchd'] }}"
+  template:
+    src: "{{ role_path }}/templates/start-ovs.j2"
+    dest: "{{ node_config_directory }}/openvswitch-vswitchd/start-ovs"
+  register: openvswitch_start_ovs
+  when:
+    - inventory_hostname in groups[service.group]
+    - service.enabled | bool
+  notify:
+    - "Restart openvswitch-vswitchd container"
+
+- name: Copying over start-ovsdb-server files for openvswitch-db-server
+  vars:
+    service: "{{ openvswitch_services['openvswitch-db-server'] }}"
+  template:
+    src: "{{ role_path }}/templates/start-ovsdb-server.j2"
+    dest: "{{ node_config_directory }}/openvswitch-db-server/start-ovsdb-server"
+  register: openvswitch_start_ovsdb_server
+  when:
+    - inventory_hostname in groups[service.group]
+    - service.enabled | bool
+  notify:
+    - "Restart openvswitch-db-server container"
+
 - name: Check openvswitch containers
   kolla_docker:
     action: "compare_container"
diff --git a/ansible/roles/openvswitch/templates/openvswitch-db-server.json.j2 b/ansible/roles/openvswitch/templates/openvswitch-db-server.json.j2
index fdeea72ecb..955131cf81 100644
--- a/ansible/roles/openvswitch/templates/openvswitch-db-server.json.j2
+++ b/ansible/roles/openvswitch/templates/openvswitch-db-server.json.j2
@@ -1,4 +1,11 @@
 {
     "command": "start-ovsdb-server {{ api_interface_address }}",
-    "config_files": []
+    "config_files": [
+        {
+            "source": "{{ container_config_directory }}/start-ovsdb-server",
+            "dest": "/usr/local/bin/start-ovsdb-server",
+            "owner": "root",
+            "perm": "0655"
+        }
+    ]
 }
diff --git a/ansible/roles/openvswitch/templates/openvswitch-vswitchd.json.j2 b/ansible/roles/openvswitch/templates/openvswitch-vswitchd.json.j2
index 97308d886b..0c75c355f6 100644
--- a/ansible/roles/openvswitch/templates/openvswitch-vswitchd.json.j2
+++ b/ansible/roles/openvswitch/templates/openvswitch-vswitchd.json.j2
@@ -1,4 +1,11 @@
 {
-    "command": "/usr/sbin/ovs-vswitchd unix:/run/openvswitch/db.sock -vconsole:emer -vsyslog:err -vfile:info --mlockall --log-file=/var/log/kolla/openvswitch/ovs-vswitchd.log",
-    "config_files": []
+    "command": "start-ovs",
+    "config_files": [
+        {
+            "source": "{{ container_config_directory }}/start-ovs",
+            "dest": "/usr/local/bin/start-ovs",
+            "owner": "root",
+            "perm": "0655"
+        }
+    ]
 }
diff --git a/ansible/roles/openvswitch/templates/start-ovs.j2 b/ansible/roles/openvswitch/templates/start-ovs.j2
new file mode 100644
index 0000000000..d6dd723b68
--- /dev/null
+++ b/ansible/roles/openvswitch/templates/start-ovs.j2
@@ -0,0 +1,10 @@
+#!/usr/bin/env bash
+{% if enable_opendaylight | bool %}
+/usr/bin/ovs-vsctl --no-wait -- set-manager ptcp:{{ ovsdb_port }}:{{ hostvars[inventory_hostname]['ansible_' + hostvars[inventory_hostname]['api_interface']]['ipv4']['address'] }} tcp:{{ kolla_internal_vip_address }}:{{ opendaylight_haproxy_ovsdb_port }}
+/usr/bin/ovs-vsctl --no-wait -- set Open_vSwitch . other_config:local_ip={{ hostvars[inventory_hostname]['ansible_' + hostvars[inventory_hostname]['tunnel_interface']]['ipv4']['address'] }}
+/usr/bin/ovs-vsctl --no-wait -- set Open_vSwitch . other_config:provider_mappings=physnet1:{{ neutron_bridge_name }}
+/usr/bin/ovs-vsctl --no-wait -- set Open_vSwitch . external_ids:system-id=`cat /proc/sys/kernel/random/uuid`
+/usr/bin/ovs-vsctl --no-wait -- set Open_vSwitch . external_ids:odl_os_hostconfig_config_odl_l2='{"supported_vnic_types": [{"vnic_type": "normal", "vif_type": "ovs", "vif_details": {} }], "allowed_network_types": [{{ opendaylight_allowed_network_types }}], "datapath_types": ["netdev", "system"], "bridge_mappings": {"physnet1":"{{ neutron_bridge_name }}"} }'
+/usr/bin/ovs-vsctl --no-wait -- set Open_vSwitch . external_ids:odl_os_hostconfig_hostid="{{ hostvars[inventory_hostname]['ansible_hostname'] }}"
+{% endif %}
+/usr/sbin/ovs-vswitchd unix:/run/openvswitch/db.sock -vconsole:emer -vsyslog:err -vfile:info --mlockall --log-file=/var/log/kolla/openvswitch/ovs-vswitchd.log
diff --git a/ansible/roles/openvswitch/templates/start-ovsdb-server.j2 b/ansible/roles/openvswitch/templates/start-ovsdb-server.j2
new file mode 100644
index 0000000000..803e85660e
--- /dev/null
+++ b/ansible/roles/openvswitch/templates/start-ovsdb-server.j2
@@ -0,0 +1,33 @@
+#!/bin/bash
+
+# NOTE: (sbezverk) ovs_bridge and ovs_ext_intf variables get initialized only when
+# this script is executed for kubernetes deployment. With Ansible deployment, only
+# ovsdb-server gets launched and then the following workflow step will create
+# an external bridge and plug an external interface. With Kubernetes we want to
+# leverage its dynamic nature of automatic scaling up and down. It means all
+# activities related to creating initial bridge, plugging external interface
+# must be done by DaemonSet launched container.
+
+ovsdb_ip=$1
+ovs_bridge=$2
+ovs_ext_intf=$3
+
+printf "Argument p_out is %s\n" "$p_out"
+printf "Argument arg_1 is %s\n" "$arg_1"
+
+# NOTE: (sbezverk) The reason for introducing this script is to be able
+# to launch ovsdb-server and to create the initial external bridge in one step.
+# It is required in order to be able to use DaemonSet.
+
+if [ ! -e $ovs_bridge  ] && [ ! -e $ovs_ext_intf  ]; then
+# NOTE: (sbezverk) This part is executed only by kubernetes deployment.
+# Creating external bridge
+    /usr/sbin/ovsdb-server /var/lib/openvswitch/conf.db --remote=punix:/var/run/openvswitch/db.sock --run="ovs-vsctl --no-wait --db=unix:/var/run/openvswitch/db.sock add-br $ovs_bridge"
+# Plug the external interface into the external bridge.
+    /usr/sbin/ovsdb-server /var/lib/openvswitch/conf.db --remote=punix:/var/run/openvswitch/db.sock --run="ovs-vsctl --no-wait --db=unix:/var/run/openvswitch/db.sock add-port $ovs_bridge $ovs_ext_intf"
+# Run ovsdb server process
+    /usr/sbin/ovsdb-server /var/lib/openvswitch/conf.db -vconsole:emer -vsyslog:err -vfile:info --remote=punix:/var/run/openvswitch/db.sock --remote=ptcp:6640 --log-file=/var/log/kolla/openvswitch/ovsdb-server.log
+else
+# NOTE: (sbezverk) This part is executed only by kolla-ansible deployment.
+    /usr/sbin/ovsdb-server /var/lib/openvswitch/conf.db -vconsole:emer -vsyslog:err -vfile:info --remote=punix:/run/openvswitch/db.sock --remote=ptcp:{{ ovsdb_port }}:$ovsdb_ip --remote=db:Open_vSwitch,Open_vSwitch,manager_options --log-file=/var/log/kolla/openvswitch/ovsdb-server.log
+fi
diff --git a/ansible/site.yml b/ansible/site.yml
index 0ed2edc28c..50cbce1fa0 100644
--- a/ansible/site.yml
+++ b/ansible/site.yml
@@ -308,6 +308,14 @@
         tags: nova,
         when: enable_nova | bool }
 
+- name: Apply role opendaylight
+  gather_facts: false
+  hosts: opendaylight
+  roles:
+    - { role: opendaylight,
+        tags: opendaylight,
+        when: enable_opendaylight | bool }
+
 - name: Apply role openvswitch
   hosts:
     - openvswitch
diff --git a/etc/kolla/globals.yml b/etc/kolla/globals.yml
index 5227f9da79..f0a4b65963 100644
--- a/etc/kolla/globals.yml
+++ b/etc/kolla/globals.yml
@@ -86,7 +86,7 @@ kolla_internal_vip_address: "10.10.10.254"
 # addresses for that reason.
 #neutron_external_interface: "eth1"
 
-# Valid options are [ openvswitch, linuxbridge, vmware_nsxv, vmware_dvs ]
+# Valid options are [ openvswitch, linuxbridge, vmware_nsxv, vmware_dvs, opendaylight ]
 #neutron_plugin_agent: "openvswitch"
 
 
@@ -107,6 +107,12 @@ kolla_internal_vip_address: "10.10.10.254"
 #kolla_external_fqdn_cert: "{{ node_config_directory }}/certificates/haproxy.pem"
 
 
+###############################
+# OpenDaylight
+###############################
+#enable_opendaylight_qos: "no"
+#enable_opendaylight_l3: "yes"
+
 ####################
 # OpenStack options
 ####################
@@ -184,6 +190,7 @@ kolla_internal_vip_address: "10.10.10.254"
 #enable_neutron_sfc: "no"
 #enable_nova_serialconsole_proxy: "no"
 #enable_octavia: "no"
+#enable_opendaylight: "no"
 #enable_openvswitch: "{{ neutron_plugin_agent != 'linuxbridge' }}"
 #enable_osprofiler: "no"
 #enable_panko: "no"
diff --git a/etc/kolla/passwords.yml b/etc/kolla/passwords.yml
index 822c32e642..b126c5a131 100644
--- a/etc/kolla/passwords.yml
+++ b/etc/kolla/passwords.yml
@@ -22,6 +22,11 @@ database_password:
 # This should only be set if you require a password for your Docker registry
 docker_registry_password:
 
+######################
+# OpenDaylight options
+######################
+opendaylight_password:
+
 ####################
 # OpenStack options
 ####################
diff --git a/releasenotes/notes/opendaylight-role-b1787bc458da5bc4.yaml b/releasenotes/notes/opendaylight-role-b1787bc458da5bc4.yaml
new file mode 100644
index 0000000000..b7c0ae0f89
--- /dev/null
+++ b/releasenotes/notes/opendaylight-role-b1787bc458da5bc4.yaml
@@ -0,0 +1,3 @@
+---
+features:
+  - Add OpenDaylight role
\ No newline at end of file