diff --git a/ansible/roles/neutron/defaults/main.yml b/ansible/roles/neutron/defaults/main.yml
index 7f3cad6fab..2efce3c0d3 100644
--- a/ansible/roles/neutron/defaults/main.yml
+++ b/ansible/roles/neutron/defaults/main.yml
@@ -19,7 +19,7 @@ neutron_services:
     privileged: True
     host_in_groups: >-
       {{
-      ( inventory_hostname in groups['compute']
+      ( (inventory_hostname in groups['compute'] and nova_compute_virt_type != 'xenapi')
       or (enable_manila | bool and inventory_hostname in groups['manila-share'])
       or inventory_hostname in groups['neutron-dhcp-agent']
       or inventory_hostname in groups['neutron-l3-agent']
@@ -40,6 +40,21 @@ neutron_services:
       - "/lib/modules:/lib/modules:ro"
       - "/run:/run:shared"
       - "kolla_logs:/var/log/kolla/"
+  neutron-openvswitch-agent-xenapi:
+    container_name: "neutron_openvswitch_agent_xenapi"
+    image: "{{ neutron_openvswitch_agent_image_full }}"
+    enabled: "{{ neutron_plugin_agent == 'openvswitch' and nova_compute_virt_type == 'xenapi' }}"
+    privileged: True
+    host_in_groups: >-
+      {{
+       inventory_hostname in groups['compute']
+      }}
+    volumes:
+      - "{{ node_config_directory }}/neutron-openvswitch-agent-xenapi/:{{ container_config_directory }}/:ro"
+      - "/etc/localtime:/etc/localtime:ro"
+      - "/lib/modules:/lib/modules:ro"
+      - "/run:/run:shared"
+      - "kolla_logs:/var/log/kolla/"
   neutron-linuxbridge-agent:
     container_name: "neutron_linuxbridge_agent"
     image: "{{  neutron_linuxbridge_agent_image_full }}"
diff --git a/ansible/roles/neutron/handlers/main.yml b/ansible/roles/neutron/handlers/main.yml
index 60b38a6f25..04c4db4c61 100644
--- a/ansible/roles/neutron/handlers/main.yml
+++ b/ansible/roles/neutron/handlers/main.yml
@@ -56,6 +56,32 @@
       or policy_json | changed
       or neutron_openvswitch_agent_container | changed
 
+- name: Restart neutron-openvswitch-agent-xenapi container
+  vars:
+    service_name: "neutron-openvswitch-agent-xenapi"
+    service: "{{ neutron_services[service_name] }}"
+    config_json: "{{ neutron_config_jsons.results|selectattr('item.key', 'equalto', service_name)|first }}"
+    neutron_conf: "{{ neutron_confs.results|selectattr('item.key', 'equalto', service_name)|first }}"
+    neutron_ml2_conf: "{{ neutron_ml2_confs.results|selectattr('item.key', 'equalto', service_name)|first }}"
+    policy_json: "{{ policy_jsons.results|selectattr('item.key', 'equalto', service_name)|first }}"
+    neutron_openvswitch_agent_xenapi_container: "{{ check_neutron_containers.results|selectattr('item.key', 'equalto', service_name)|first }}"
+  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
+    - config_json | changed
+      or neutron_conf | changed
+      or neutron_ml2_conf | changed
+      or policy_json | changed
+      or neutron_openvswitch_agent_xenapi_container | changed
+
 - name: Restart fake neutron-openvswitch-agent container
   vars:
     service_name: "neutron-openvswitch-agent"
diff --git a/ansible/roles/neutron/tasks/config.yml b/ansible/roles/neutron/tasks/config.yml
index 04c9f9e8dd..f44353947e 100644
--- a/ansible/roles/neutron/tasks/config.yml
+++ b/ansible/roles/neutron/tasks/config.yml
@@ -51,6 +51,7 @@
       - "neutron-linuxbridge-agent"
       - "neutron-metadata-agent"
       - "neutron-openvswitch-agent"
+      - "neutron-openvswitch-agent-xenapi"
       - "neutron-server"
       - "neutron-lbaas-agent"
       - "neutron-vpnaas-agent"
@@ -141,6 +142,31 @@
   notify:
     - "Restart {{ item.key }} container"
 
+- name: Copying over ml2_conf.ini for XenAPI
+  become: true
+  vars:
+    service_name: "{{ item.key }}"
+    services_need_ml2_conf_ini:
+      - "neutron-openvswitch-agent-xenapi"
+    os_xenapi_variables: "{{ lookup('file', xenapi_facts_file) | from_json }}"
+  merge_configs:
+    sources:
+      - "{{ role_path }}/templates/ml2_conf.ini.j2"
+      - "{{ role_path }}/templates/ml2_conf_xenapi.ini.j2"
+      - "{{ node_custom_config }}/neutron/ml2_conf.ini"
+      - "{{ node_custom_config }}/neutron/{{ inventory_hostname }}/ml2_conf.ini"
+      - "{{ node_custom_config }}/neutron/{{ service_name }}/ml2_conf.ini"
+    dest: "{{ node_config_directory }}/{{ service_name }}/ml2_conf.ini"
+    mode: "0660"
+  register: neutron_ml2_confs
+  when:
+    - item.key in services_need_ml2_conf_ini
+    - item.value.enabled | bool
+    - item.value.host_in_groups | bool
+  with_dict: "{{ neutron_services }}"
+  notify:
+    - "Restart {{ item.key }} container"
+
 - name: Copying over dhcp_agent.ini
   become: true
   vars:
@@ -326,6 +352,7 @@
       - "neutron-linuxbridge-agent"
       - "neutron-metadata-agent"
       - "neutron-openvswitch-agent"
+      - "neutron-openvswitch-agent-xenapi"
       - "neutron-server"
       - "neutron-lbaas-agent"
       - "neutron-vpnaas-agent"
diff --git a/ansible/roles/neutron/templates/ml2_conf.ini.j2 b/ansible/roles/neutron/templates/ml2_conf.ini.j2
index e8b6828d5e..c92385994c 100644
--- a/ansible/roles/neutron/templates/ml2_conf.ini.j2
+++ b/ansible/roles/neutron/templates/ml2_conf.ini.j2
@@ -58,7 +58,9 @@ firewall_driver = neutron.agent.linux.iptables_firewall.IptablesFirewallDriver
 {% if neutron_plugin_agent == "openvswitch" or neutron_plugin_agent == "opendaylight" %}
 [agent]
 tunnel_types = vxlan
+{% if nova_compute_virt_type != 'xenapi' %}
 l2_population = true
+{% endif %}
 arp_responder = true
 
 {% if enable_neutron_dvr | bool %}
diff --git a/ansible/roles/neutron/templates/ml2_conf_xenapi.ini.j2 b/ansible/roles/neutron/templates/ml2_conf_xenapi.ini.j2
new file mode 100644
index 0000000000..423b163b5c
--- /dev/null
+++ b/ansible/roles/neutron/templates/ml2_conf_xenapi.ini.j2
@@ -0,0 +1,19 @@
+# ml2_conf.ini
+[DEFAULT]
+# Use service_name as the log file name for neutron-openvswitch-agent-xenapi,
+# so that it will use a different log file from neutron-openvswitch-agent.
+log_file = {{ service_name }}.log
+host = {{ os_xenapi_variables.dom0_hostname }}
+
+[agent]
+root_helper_daemon = xenapi_root_helper
+root_helper =
+
+[ovs]
+of_listen_address = {{ hostvars[inventory_hostname]['ansible_' + os_xenapi_variables.domu_himn_eth]["ipv4"]["address"] }}
+ovsdb_connection = tcp:{{ xenserver_himn_ip }}:{{ ovsdb_port }}
+
+[xenapi]
+connection_password = {{ xenserver_password }}
+connection_username = {{ xenserver_username }}
+connection_url = {{ xenserver_connect_protocol }}://{{ xenserver_himn_ip }}
diff --git a/ansible/roles/neutron/templates/neutron-openvswitch-agent-xenapi.json.j2 b/ansible/roles/neutron/templates/neutron-openvswitch-agent-xenapi.json.j2
new file mode 100644
index 0000000000..e5dfd784c7
--- /dev/null
+++ b/ansible/roles/neutron/templates/neutron-openvswitch-agent-xenapi.json.j2
@@ -0,0 +1,31 @@
+{
+    "command": "neutron-openvswitch-agent --config-file /etc/neutron/neutron.conf --config-file /etc/neutron/plugins/ml2/ml2_conf.ini",
+    "config_files": [
+        {
+            "source": "{{ container_config_directory }}/neutron.conf",
+            "dest": "/etc/neutron/neutron.conf",
+            "owner": "neutron",
+            "perm": "0600"
+        },
+        {
+            "source": "{{ container_config_directory }}/ml2_conf.ini",
+            "dest": "/etc/neutron/plugins/ml2/ml2_conf.ini",
+            "owner": "neutron",
+            "perm": "0600"
+        },
+        {
+            "source": "{{ container_config_directory }}/policy.json",
+            "dest": "/etc/neutron/policy.json",
+            "owner": "neutron",
+            "perm": "0600",
+            "optional": true
+        }
+    ],
+    "permissions": [
+        {
+            "path": "/var/log/kolla/neutron",
+            "owner": "neutron:neutron",
+            "recurse": true
+        }
+    ]
+}