Add OVN support

Install docs:
https://docs.openstack.org/networking-ovn/latest/install/index.html

Partial Prod: PROD-15003

Co-Authored-By: Elena Ezhova <eezhova@mirantis.com>
Change-Id: I0a7023f3e14aae6d5ec5efe8f117ebc9d0ed302d
diff --git a/.kitchen.yml b/.kitchen.yml
index 1051363..2481b6c 100644
--- a/.kitchen.yml
+++ b/.kitchen.yml
@@ -93,6 +93,11 @@
       pillars-from-files:
         neutron.sls: tests/pillar/compute_qos.sls
 
+  - name: compute_ovn
+    provisioner:
+      pillars-from-files:
+        neutron.sls: tests/pillar/compute_ovn.sls
+
   - name: control_cluster
     provisioner:
       pillars-from-files:
@@ -128,6 +133,11 @@
       pillars-from-files:
         neutron.sls: tests/pillar/control_qos.sls
 
+  - name: control_ovn
+    provisioner:
+      pillars-from-files:
+        neutron.sls: tests/pillar/control_ovn.sls
+
   - name: gateway_dvr
     provisioner:
       pillars-from-files:
diff --git a/.travis.yml b/.travis.yml
index 779286f..cc2ce87 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -32,6 +32,7 @@
     - PLATFORM=trevorj/salty-whales:xenial SUITE=compute_qos_sriov
     - PLATFORM=trevorj/salty-whales:trusty SUITE=compute_qos
     - PLATFORM=trevorj/salty-whales:xenial SUITE=compute_qos
+    - PLATFORM=trevorj/salty-whales:xenial SUITE=compute_ovn
     - PLATFORM=trevorj/salty-whales:trusty SUITE=control_cluster
     - PLATFORM=trevorj/salty-whales:xenial SUITE=control_cluster
     - PLATFORM=trevorj/salty-whales:trusty SUITE=control_dvr
@@ -44,6 +45,7 @@
     - PLATFORM=trevorj/salty-whales:xenial SUITE=control_fwaas_v1
     - PLATFORM=trevorj/salty-whales:trusty SUITE=control_qos
     - PLATFORM=trevorj/salty-whales:xenial SUITE=control_qos
+    - PLATFORM=trevorj/salty-whales:xenial SUITE=control_ovn
     - PLATFORM=trevorj/salty-whales:trusty SUITE=gateway_dvr
     - PLATFORM=trevorj/salty-whales:xenial SUITE=gateway_dvr
     - PLATFORM=trevorj/salty-whales:trusty SUITE=gateway_legacy
diff --git a/README.rst b/README.rst
index 3b161e5..57153ed 100644
--- a/README.rst
+++ b/README.rst
@@ -591,6 +591,7 @@
               driver: openvswitch
 
 Advanced Neutron Features (DPDK, SR-IOV)
+----------------------------------------
 
 Neutron OVS DPDK
 
@@ -648,6 +649,7 @@
               driver: openvswitch
 
 Neutron with VLAN-aware-VMs
+---------------------------
 
 .. code-block:: yaml
 
@@ -661,6 +663,34 @@
       gateway:
         vlan_aware_vms: true
 
+Neutron with OVN
+----------------
+
+Control node:
+
+.. code-block:: yaml
+
+    neutron:
+      server:
+        backend:
+          engine: ovn
+          mechanism:
+            ovn:
+              driver: ovn
+          tenant_network_types: "geneve,flat"
+
+Compute node:
+
+.. code-block:: yaml
+
+    neutron:
+      compute:
+        local_ip: 10.2.0.105
+        controller_vip: 10.1.0.101
+        external_access: false
+        backend:
+          engine: ovn
+
 Neutron Server
 --------------
 
@@ -692,7 +722,6 @@
           user: admin
           password: password
 
-
 Neutron Keystone region
 
 .. code-block:: yaml
diff --git a/metadata/service/compute/ovn/single.yml b/metadata/service/compute/ovn/single.yml
new file mode 100644
index 0000000..9bfb54d
--- /dev/null
+++ b/metadata/service/compute/ovn/single.yml
@@ -0,0 +1,17 @@
+applications:
+- neutron
+classes:
+- service.neutron.support
+parameters:
+  _param:
+    ovn_external_bridge: br-floating
+  neutron:
+    compute:
+      enabled: true
+      version: ${_param:neutron_version}
+      local_ip: ${_param:tenant_address}
+      controller_vip: ${_param:cluster_vip_address}
+      external_access: true
+      external_bridge: ${_param:ovn_external_bridge}
+      backend:
+        engine: ovn
diff --git a/metadata/service/control/cluster.yml b/metadata/service/control/cluster.yml
index c69a8f8..701b367 100644
--- a/metadata/service/control/cluster.yml
+++ b/metadata/service/control/cluster.yml
@@ -11,6 +11,7 @@
       dns_domain: novalocal
       vlan_aware_vms: false
       version: ${_param:neutron_version}
+      controller_vip: ${_param:cluster_vip_address}
       bind:
         address: ${_param:cluster_local_address}
         port: 9696
diff --git a/metadata/service/control/single.yml b/metadata/service/control/single.yml
index a47f680..149d37a 100644
--- a/metadata/service/control/single.yml
+++ b/metadata/service/control/single.yml
@@ -15,6 +15,7 @@
       qos: false
       vlan_aware_vms: false
       version: ${_param:neutron_version}
+      controller_vip: ${_param:single_address}
       bind:
         address: ${_param:single_address}
         port: 9696
diff --git a/neutron/compute.sls b/neutron/compute.sls
index 6829780..c6a1df5 100644
--- a/neutron/compute.sls
+++ b/neutron/compute.sls
@@ -1,6 +1,7 @@
 {% from "neutron/map.jinja" import compute, fwaas, system_cacerts_file with context %}
 {%- if compute.enabled %}
 
+{% if compute.backend.engine == "ml2" %}
 neutron_compute_packages:
   pkg.installed:
   - names: {{ compute.pkgs }}
@@ -132,4 +133,52 @@
 {%- endif %}
 {%- endif %}
 
+{%- elif compute.backend.engine == "ovn" %}
+
+ovn_packages:
+  pkg.installed:
+  - names: {{ compute.pkgs_ovn }}
+
+{%- if not grains.get('noservices', False) %}
+
+remote_ovsdb_access:
+  cmd.run:
+  - name: "ovs-vsctl set open .
+  external-ids:ovn-remote=tcp:{{ compute.controller_vip }}:6642"
+
+enable_overlays:
+  cmd.run:
+  - name: "ovs-vsctl set open . external-ids:ovn-encap-type=geneve,vxlan"
+
+configure_local_endpoint:
+  cmd.run:
+  - name: "ovs-vsctl set open .
+  external-ids:ovn-encap-ip={{ compute.local_ip }}"
+
+{%- if compute.get('external_access', True) %}
+
+set_bridge_external_id:
+  cmd.run:
+  - name: "ovs-vsctl --no-wait br-set-external-id
+   {{ compute.external_bridge }} bridge-id {{ compute.external_bridge }}"
+
+set_bridge_mapping:
+  cmd.run:
+  - name: "ovs-vsctl set open .
+   external-ids:ovn-bridge-mappings=physnet1:{{ compute.external_bridge }}"
+
+{%- endif %}
+
+ovn_services:
+  service.running:
+  - names: {{ compute.services_ovn }}
+  - enable: true
+  {%- if grains.get('noservices') %}
+  - onlyif: /bin/false
+  {%- endif %}
+  - require:
+    - pkg: ovn_packages
+
+{%- endif %}
+{%- endif %}
 {%- endif %}
diff --git a/neutron/files/ocata/ml2_conf.ini b/neutron/files/ocata/ml2_conf.ini
index 8f5e8dd..3056ae5 100644
--- a/neutron/files/ocata/ml2_conf.ini
+++ b/neutron/files/ocata/ml2_conf.ini
@@ -120,7 +120,6 @@
 # List of network type driver entrypoints to be loaded from the
 # neutron.ml2.type_drivers namespace. (list value)
 #type_drivers = local,flat,vlan,gre,vxlan,geneve
-type_drivers = local,flat,vlan,gre,vxlan
 
 # Ordered list of network_types to allocate as tenant networks. The default
 # value 'local' is useful for single-box testing but provides no connectivity
@@ -203,6 +202,7 @@
 # Comma-separated list of <vni_min>:<vni_max> tuples enumerating ranges of
 # Geneve VNI IDs that are available for tenant network allocation (list value)
 #vni_ranges =
+vni_ranges = {{ server.get('geneve', {}).vni_ranges|default('1:65536') }}
 
 # Geneve encapsulation header size is dynamic, this value is used to calculate
 # the maximum MTU for the driver. This is the sum of the sizes of the outer ETH
@@ -210,6 +210,7 @@
 # which is the size of the Geneve header without any additional option headers.
 # (integer value)
 #max_header_size = 30
+max_header_size = 38
 
 
 [ml2_type_gre]
@@ -284,3 +285,10 @@
 # Use ipset to speed-up the iptables based security groups. Enabling ipset
 # support requires that ipset is installed on L2 agent node. (boolean value)
 #enable_ipset = true
+
+{%- if server.backend.engine == "ovn" %}
+[ovn]
+ovn_nb_connection = tcp:{{ server.controller_vip }}:6641
+ovn_sb_connection = tcp:{{ server.controller_vip }}:6642
+ovn_l3_scheduler = leastloaded
+{%- endif %}
diff --git a/neutron/files/ocata/neutron-server b/neutron/files/ocata/neutron-server
index 54f6ceb..04830ca 100644
--- a/neutron/files/ocata/neutron-server
+++ b/neutron/files/ocata/neutron-server
@@ -7,7 +7,7 @@
 # neutron.conf
 #NEUTRON_PLUGIN_CONFIG="/etc/neutron/plugins/openvswitch/ovs_neutron_plugin.ini"
 
-{%- if server.backend.engine == "ml2" %}
+{%- if server.backend.engine in ["ml2", "ovn"] %}
 NEUTRON_PLUGIN_CONFIG="/etc/neutron/plugins/ml2/ml2_conf.ini"
 {%- endif %}
 
diff --git a/neutron/files/ocata/neutron-server.conf.Debian b/neutron/files/ocata/neutron-server.conf.Debian
index 049544b..74e155c 100644
--- a/neutron/files/ocata/neutron-server.conf.Debian
+++ b/neutron/files/ocata/neutron-server.conf.Debian
@@ -39,11 +39,18 @@
 core_plugin = neutron_plugin_contrail.plugins.opencontrail.contrail_plugin.NeutronPluginContrailCoreV2
 
 service_plugins = neutron_plugin_contrail.plugins.opencontrail.loadbalancer.v2.plugin.LoadBalancerPluginV2
-{% elif server.backend.engine == "ml2" %}
+
+{% elif server.backend.engine in ["ml2", "ovn"] %}
 
 core_plugin = neutron.plugins.ml2.plugin.Ml2Plugin
 
-service_plugins =neutron.services.l3_router.l3_router_plugin.L3RouterPlugin,neutron.services.metering.metering_plugin.MeteringPlugin
+{% if server.backend.engine == "ml2" %}
+{% set l3_plugin = 'neutron.services.l3_router.l3_router_plugin.L3RouterPlugin' %}
+{% elif server.backend.engine == "ovn" %}
+{% set l3_plugin = 'networking_ovn.l3.l3_ovn.OVNL3RouterPlugin' %}
+{% endif %}
+
+service_plugins ={{ l3_plugin }}, neutron.services.metering.metering_plugin.MeteringPlugin
 {%- if server.lbaas is defined -%},lbaasv2{%- endif -%}
 {%- if fwaas.get('enabled', False) -%},{{ fwaas[fwaas.api_version]['service_plugin'] }}{%- endif -%}
 {%- if server.get('qos', 'True') -%},neutron.services.qos.qos_plugin.QoSPlugin{%- endif -%}
@@ -838,7 +845,7 @@
 # Deprecated group/name - [DEFAULT]/sql_connection
 # Deprecated group/name - [DATABASE]/sql_connection
 # Deprecated group/name - [sql]/connection
-{% if server.backend.engine == "ml2" %}
+{% if server.backend.engine in ["ml2", "ovn"] %}
 connection = {{ server.database.engine }}+pymysql://{{ server.database.user }}:{{ server.database.password }}@{{ server.database.host }}/{{ server.database.name }}?charset=utf8
 {% else %}
 connection = sqlite:////var/lib/neutron/neutron.sqlite
diff --git a/neutron/map.jinja b/neutron/map.jinja
index eef262f..9631d1b 100644
--- a/neutron/map.jinja
+++ b/neutron/map.jinja
@@ -6,7 +6,9 @@
 {% set compute = salt['grains.filter_by']({
     'Debian': {
         'pkgs': ['neutron-openvswitch-agent', 'openvswitch-switch', 'python-pycadf'],
+        'pkgs_ovn': ['ovn-common', 'ovn-host'],
         'services': ['neutron-openvswitch-agent'],
+        'services_ovn': ['ovn-host'],
         'dpdk': false,
         'audit': {
           'enabled': false
@@ -14,7 +16,9 @@
     },
     'RedHat': {
         'pkgs': ['openstack-neutron-openvswitch', 'openvswitch', 'python-pycadf'],
+        'pkgs_ovn': ['openvswitch-ovn'],
         'services': ['neutron-openvswitch-agent'],
+        'services_ovn': ['ovn-host'],
         'dpdk': false,
         'audit': {
           'enabled': false
@@ -38,8 +42,10 @@
 {% set server = salt['grains.filter_by']({
     'Debian': {
         'pkgs': ['neutron-server','python-neutron-lbaas', 'gettext-base', 'python-pycadf'],
+        'pkgs_ovn': ['python-networking-ovn', 'ovn-common', 'ovn-central'],
         'pkgs_ml2': ['neutron-plugin-ml2'],
         'services': ['neutron-server'],
+        'services_ovn': ['ovn-central'],
         'notification': False,
         'dpdk': false,
         'cors': {},
@@ -50,7 +56,9 @@
     'RedHat': {
         'pkgs_ml2': ['openstack-neutron-ml2', 'python-pycadf'],
         'pkgs': ['openstack-neutron'],
+        'pkgs_ovn': ['openvswitch-ovn', 'python-networking-ovn'],
         'services': ['neutron-server'],
+        'services_ovn': ['ovn-central'],
         'notification': False,
         'dpdk': false,
         'cors': {},
diff --git a/neutron/server.sls b/neutron/server.sls
index 79e5aba..6de0f4a 100644
--- a/neutron/server.sls
+++ b/neutron/server.sls
@@ -72,7 +72,7 @@
 
 {%- endif %}
 
-{% if server.backend.engine == "ml2" %}
+{% if server.backend.engine in ["ml2", "ovn"] %}
 
 /etc/neutron/plugins/ml2/ml2_conf.ini:
   file.managed:
@@ -154,6 +154,41 @@
 
 {%- endif %}
 
+{%- if server.backend.engine == "ovn" %}
+
+ovn_packages:
+  pkg.installed:
+  - names: {{ server.pkgs_ovn }}
+
+{%- if not grains.get('noservices', False) %}
+
+remote_ovsdb_access:
+  cmd.run:
+  - name: "ovs-appctl -t ovsdb-server ovsdb-server/add-remote
+  ptcp:6640:{{ server.controller_vip }}"
+
+open_ovs_port:
+  iptables.append:
+    - table: filter
+    - chain: INPUT
+    - jump: ACCEPT
+    - dport: 6640
+    - proto: tcp
+    - save: True
+
+ovn_services:
+  service.running:
+  - names: {{ server.services_ovn }}
+  - enable: true
+  {%- if grains.get('noservices') %}
+  - onlyif: /bin/false
+  {%- endif %}
+  - require:
+    - pkg: ovn_packages
+
+{%- endif %}
+{%- endif %}
+
 {%- if server.backend.engine == "midonet" %}
 
 /etc/neutron/plugins/midonet/midonet.ini:
diff --git a/tests/pillar/compute_ovn.sls b/tests/pillar/compute_ovn.sls
new file mode 100644
index 0000000..0f4b580
--- /dev/null
+++ b/tests/pillar/compute_ovn.sls
@@ -0,0 +1,9 @@
+neutron:
+  compute:
+    enabled: true
+    version: ocata
+    local_ip: 10.2.0.105
+    controller_vip: 10.1.0.101
+    external_access: false
+    backend:
+      engine: ovn
diff --git a/tests/pillar/control_ovn.sls b/tests/pillar/control_ovn.sls
new file mode 100644
index 0000000..cd79174
--- /dev/null
+++ b/tests/pillar/control_ovn.sls
@@ -0,0 +1,57 @@
+neutron:
+  server:
+    enabled: true
+    version: ocata
+    backend:
+      engine: ovn
+      external_mtu: 1500
+      mechanism:
+        ovn:
+          driver: ovn
+      tenant_network_types: "geneve,flat"
+    controller_vip: 172.16.10.101
+    dvr: false
+    l3_ha: false
+    dns_domain: novalocal
+    global_physnet_mtu: 1500
+    bind:
+      address: 172.16.10.101
+      port: 9696
+    compute:
+      host: 127.0.0.1
+      password: workshop
+      region: RegionOne
+      tenant: service
+      user: nova
+    database:
+      engine: mysql
+      host: 127.0.0.1
+      name: neutron
+      password: workshop
+      port: 3306
+      user: neutron
+    identity:
+      engine: keystone
+      host: 127.0.0.1
+      password: workshop
+      port: 35357
+      region: RegionOne
+      tenant: service
+      user: neutron
+      endpoint_type: internal
+    message_queue:
+      engine: rabbitmq
+      host: 127.0.0.1
+      password: workshop
+      port: 5672
+      user: openstack
+      virtual_host: /openstack
+
+linux:
+  system:
+    enabled: true
+    repo:
+      mirantis_openstack_ocata:
+        source: "deb http://mirror.fuel-infra.org/mcp-repos/ocata/xenial ocata main"
+        architectures: amd64
+        key_url: "http://mirror.fuel-infra.org/mcp-repos/ocata/xenial/archive-mcpocata.key"