diff --git a/ansible/group_vars/all.yml b/ansible/group_vars/all.yml
index ef6ccc7d6b4dcdae63390134f3e68fd38c2bc393..992eb4b2d91549dbb2b7867969bef001a442db32 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 154abf5e6f60455638529ddb08b91fedd05029cc..f9c6de918bea49599bfdd0a52b787d979afd4a58 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 8b57b3d4d1607287e2fcd11fee1149be5a52791a..c319219ce3d07fbc330f329d35c3ab9cb5446b0e 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 49c7e029d4efd9eacc82df564f5d266372e8c5f1..bdde3f92da776f4d079c69f2b823d7a34f8c2d11 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 f24ab2b4aa95a4ddc3dc89434cfcf6ddc92f2475..e52d60a692cd615b8c86a795f4aac94b6f50a3ea 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 71c40967ccba3f89dce1401dc19a4ea63c44e055..240482db440bea1de26a624513ea70b5b1dfa77c 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 054b8faa43b55fa293c90197be5756eb70ca4209..279423ba3a2b9fbf017e3bf863a02398213d8609 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 076e9a5eaed650776b10784377daec21160b2c89..9f83c71f9da1cb3d2f99905357d64f7acc88340d 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 919c7422c6f70e85accd5542850e6656cd511e87..d1aa1d8681c83b9884ab21a7357c19e8aee4ee2e 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 0000000000000000000000000000000000000000..f2d35bda472d8233705807a33b3684ba09e8c41f
--- /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 0000000000000000000000000000000000000000..0a5f9eec70eae0f61486226c0b3bb0ae0f235750
--- /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 0000000000000000000000000000000000000000..6b4fff8fef6f81d35b73a30c90de45639db41cc9
--- /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 0000000000000000000000000000000000000000..ed97d539c095cf1413af30cc23dea272095b97dd
--- /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 0000000000000000000000000000000000000000..06e49573375d47ddec0e8a3934741d7192a0b894
--- /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 0000000000000000000000000000000000000000..5aac9f5a7f854e831b74bae654cce2a9b0dd2861
--- /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 0000000000000000000000000000000000000000..b017e8b4ad9edbc10e43b690b269d460e5b9b1f4
--- /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 0000000000000000000000000000000000000000..260e774f4ee7cdb490b54def0b848575b99e8d82
--- /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 0000000000000000000000000000000000000000..76a1bd6b6a8e24b7972baa353caed47c7c58b2d3
--- /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 0000000000000000000000000000000000000000..e078ef1318f52fd9e74ccb13343015761312ca52
--- /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 0000000000000000000000000000000000000000..5aac9f5a7f854e831b74bae654cce2a9b0dd2861
--- /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 0000000000000000000000000000000000000000..22d63e566fdd6ece7e62fb8f2f99754cde238eec
--- /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 0000000000000000000000000000000000000000..5f8c90cfeaff619c128559f5d0f40f0e1339cdee
--- /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 0000000000000000000000000000000000000000..4e4cf897ca3e54cc9f4163d2b136d244d0d3677e
--- /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 0000000000000000000000000000000000000000..4fe1f999ff8089697edc19acf893ad2a38281bc0
--- /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 0000000000000000000000000000000000000000..5b0711ea50cbae355536a363e41c49fa0df02d6e
--- /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 0000000000000000000000000000000000000000..398719570bc13f10f8b3b56669669dbc319d467b
--- /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 0000000000000000000000000000000000000000..9e22e427a34e26b9097adb9bab44c7cb603c6bc3
--- /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 0000000000000000000000000000000000000000..e475b9cb15b84d24de65d68bd8bc7de85c7fb6ec
--- /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 0000000000000000000000000000000000000000..ba2dfcd5133278bf67088cd30e2334405326983d
--- /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 0000000000000000000000000000000000000000..d5cae1b52fbfed510f3f7d330fd3207a1cfb0321
--- /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 0000000000000000000000000000000000000000..d2aa291f4b388946d1c577cefb8cd03126511e77
--- /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 0000000000000000000000000000000000000000..37e9e24206d831f95988398529cb17e6dbc71a17
--- /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 0000000000000000000000000000000000000000..7376186e1836e894dfb3a8bc22b1e3860ab74f85
--- /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 0000000000000000000000000000000000000000..e039b1c65566cd622ca47cdbc312666dbb317c96
--- /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 0000000000000000000000000000000000000000..1d199d591ebe82daa05124fa9bedf5dd199eaf64
--- /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 155536d836120ca38df3cb75cde23cfbdd655f03..d631076ea1d370cb681d193d36be8b319bd54714 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 0a24c63f4fa517103b6bc6f8ff13914d87a1f2b9..fb5dff238108c045e2494a8d55f7565bf56ae353 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 fdeea72ecbc3cdc1bcd1b4236f7a4bb17c8265b9..955131cf81a60a44eecbcd0616d6c17c094b74f9 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 97308d886bd424114549fdee64293442a197a319..0c75c355f621f143c2442d448bb2d7791f52155a 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 0000000000000000000000000000000000000000..d6dd723b688b5b08cca71d5983750212ef22ddda
--- /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 0000000000000000000000000000000000000000..803e85660e4314722da9c9d66f5f30635a3b9cab
--- /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 0ed2edc28ced2ebe1b6e1826bf79d83a29b5437a..50cbce1fa04d4c15649dd35f55f3e0aa38cd2664 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 5227f9da79c92d024b5cc091c822b6c419eccb6f..f0a4b659630fc6b90dc3cc72f03b3cb0065d8b9f 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 822c32e642b6b7acc009901b8ff04b73b116de97..b126c5a1314d6a555ad9764544989e0ae4cb7039 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 0000000000000000000000000000000000000000..b7c0ae0f89ee62bf094a35fc007c34440f5fe095
--- /dev/null
+++ b/releasenotes/notes/opendaylight-role-b1787bc458da5bc4.yaml
@@ -0,0 +1,3 @@
+---
+features:
+  - Add OpenDaylight role
\ No newline at end of file