Merge "Bring in opendaylight support"
diff --git a/.kitchen.yml b/.kitchen.yml
index 843d75e..60bc140 100644
--- a/.kitchen.yml
+++ b/.kitchen.yml
@@ -133,6 +133,11 @@
       pillars-from-files:
         neutron.sls: tests/pillar/control_l2gw.sls
 
+  - name: control_opendaylight
+    provisioner:
+      pillars-from-files:
+        neutron.sls: tests/pillar/control_opendaylight.sls
+
   - name: control_single
     provisioner:
       pillars-from-files:
@@ -178,6 +183,11 @@
       pillars-from-files:
         neutron.sls: tests/pillar/gateway_legacy_l2gw.sls
 
+  - name: gateway_legacy_opendaylight
+    provisioner:
+      pillars-from-files:
+        neutron.sls: tests/pillar/gateway_legacy_opendaylight.sls
+
   - name: control_dns
     provisioner:
       pillars-from-files:
diff --git a/README.rst b/README.rst
index 0209dd6..17cc5b4 100644
--- a/README.rst
+++ b/README.rst
@@ -850,6 +850,57 @@
             ovsdb2: 10.164.4.33:6632
 
 
+OpenDaylight integration
+------------------------
+
+Control node:
+
+.. code-block:: yaml
+
+  neutron:
+    server:
+      backend:
+        opendaylight: true
+        router: odl-router_v2
+        host: 10.20.0.77
+        rest_api_port: 8282
+        user: admin
+        password: admin
+        ovsdb_connection: tcp:127.0.0.1:6639
+        enable_websocket: true
+        enable_dhcp_service: false
+        mechanism:
+          ovs:
+            driver: opendaylight_v2
+
+Network/Gateway node:
+
+.. code-block:: yaml
+
+  neutron:
+    gateway:
+      backend:
+        router: odl-router_v2
+        ovsdb_connection: tcp:127.0.0.1:6639
+      opendaylight:
+        ovsdb_server_iface: ptcp:6639:127.0.0.1
+        ovsdb_odl_iface: tcp:10.20.0.77:6640
+        tunnel_ip: 10.1.0.110
+        provider_mappings: physnet1:br-floating
+
+Compute node:
+
+.. code-block:: yaml
+
+  neutron:
+    compute:
+      opendaylight:
+        ovsdb_server_iface: ptcp:6639:127.0.0.1
+        ovsdb_odl_iface: tcp:10.20.0.77:6640
+        tunnel_ip: 10.1.0.105
+        provider_mappings: physnet1:br-floating
+
+
 Neutron Server
 --------------
 
diff --git a/metadata/service/compute/opendaylight/single.yml b/metadata/service/compute/opendaylight/single.yml
new file mode 100644
index 0000000..aa9f8a4
--- /dev/null
+++ b/metadata/service/compute/opendaylight/single.yml
@@ -0,0 +1,11 @@
+parameters:
+  _param:
+    provider_mappings: physnet1:br-floating
+    opendaylight_service_host: 127.0.0.1
+  neutron:
+    compute:
+      opendaylight:
+        ovsdb_server_iface: ptcp:6639:127.0.0.1
+        ovsdb_odl_iface: tcp:${_param:opendaylight_service_host}:6640
+        tunnel_ip: ${_param:tenant_address}
+        provider_mappings: ${_param:provider_mappings}
diff --git a/metadata/service/gateway/opendaylight/single.yml b/metadata/service/gateway/opendaylight/single.yml
new file mode 100644
index 0000000..36d6380
--- /dev/null
+++ b/metadata/service/gateway/opendaylight/single.yml
@@ -0,0 +1,17 @@
+classes:
+- service.neutron.gateway.single
+parameters:
+  _param:
+    opendaylight_router: odl-router_v2
+    provider_mappings: physnet1:br-floating
+    opendaylight_service_host: 127.0.0.1
+  neutron:
+    gateway:
+      backend:
+        router: ${_param:opendaylight_router}
+        ovsdb_connection: tcp:127.0.0.1:6639
+      opendaylight:
+        ovsdb_server_iface: ptcp:6639:127.0.0.1
+        ovsdb_odl_iface: tcp:${_param:opendaylight_service_host}:6640
+        tunnel_ip: ${_param:tenant_address}
+        provider_mappings: ${_param:provider_mappings}
diff --git a/neutron/compute.sls b/neutron/compute.sls
index f003a57..770f2ba 100644
--- a/neutron/compute.sls
+++ b/neutron/compute.sls
@@ -246,6 +246,11 @@
 {%- endif %}
 {%- endif %}
 
+{%- if compute.opendaylight is defined %}
+include:
+  - .opendaylight.client
+{%- endif %}
+
 {%- elif compute.backend.engine == "ovn" %}
 
 ovn_packages:
diff --git a/neutron/files/pike/dhcp_agent.ini b/neutron/files/pike/dhcp_agent.ini
index d327e64..0b2c60f 100644
--- a/neutron/files/pike/dhcp_agent.ini
+++ b/neutron/files/pike/dhcp_agent.ini
@@ -1,3 +1,8 @@
+{%- if pillar.neutron.gateway is defined %}
+{%- from "neutron/map.jinja" import gateway as neutron with context %}
+{%- else %}
+{%- from "neutron/map.jinja" import compute as neutron with context %}
+{%- endif %}
 [DEFAULT]
 
 #
@@ -48,6 +53,9 @@
 # this value will force the DHCP server to append specific host routes to the DHCP request. If this option is set, then the metadata service
 # will be activated for all the networks. (boolean value)
 #force_metadata = false
+{%- if neutron.backend.router is defined or neutron.force_metadata|default(False) %}
+force_metadata = True
+{%- endif %}
 
 # Allows for serving metadata requests coming from a dedicated metadata access network whose CIDR is 169.254.169.254/16 (or larger prefix),
 # and is connected to a Neutron router from which the VMs send metadata:1 request. In this case DHCP Option 121 will not be injected in VMs,
diff --git a/neutron/files/pike/ml2_conf.ini b/neutron/files/pike/ml2_conf.ini
index abff949..a9b8c97 100644
--- a/neutron/files/pike/ml2_conf.ini
+++ b/neutron/files/pike/ml2_conf.ini
@@ -134,7 +134,8 @@
 {%- for mechanism_name, mechanism in server.get('backend', {}).get('mechanism', []).items() %}
 {%- do mechanism_drivers.append(mechanism.get('driver')) if 'driver' in mechanism %}
 {%- endfor %}
-{%- if "vxlan" in server.backend.tenant_network_types %}
+{%- set opendaylight_enabled = true if 'opendaylight' in mechanism_drivers|join else false %}
+{%- if "vxlan" in server.backend.tenant_network_types and not opendaylight_enabled %}
 {%- do mechanism_drivers.append('l2population') %}
 {%- endif %}
 mechanism_drivers = {{ ','.join(mechanism_drivers) }}
@@ -311,3 +312,26 @@
 ovn_sb_connection = tcp:{{ server.controller_vip }}:6642
 ovn_l3_scheduler = leastloaded
 {%- endif %}
+
+{%- if opendaylight_enabled %}
+[ml2_odl]
+# HTTP URL of OpenDaylight REST interface. (string value)
+url = {{ server.backend.protocol|default('http') }}://{{ server.backend.host }}:{{ server.backend.rest_api_port }}/controller/nb/v2/neutron
+
+# HTTP username for authentication. (string value)
+username = {{ server.backend.user }}
+
+# HTTP password for authentication. (string value)
+password = {{ server.backend.password }}
+
+# Name of the controller to be used for port binding. (string value)
+port_binding_controller = pseudo-agentdb-binding
+
+# Enable websocket for pseudo-agent-port-binding. (boolean value)
+enable_websocket_pseudo_agentdb = {{ server.backend.enable_websocket|default('false') }}
+
+# Enables the networking-odl driver to supply special neutron ports of
+# "dhcp" type to OpenDaylight Controller for its use in providing DHCP
+# Service. (boolean value)
+enable_dhcp_service = {{ server.backend.enable_dhcp_service|default('false') }}
+{%- endif %}
diff --git a/neutron/files/pike/neutron-generic.conf.Debian b/neutron/files/pike/neutron-generic.conf.Debian
index 0d16a6d..a94a24b 100644
--- a/neutron/files/pike/neutron-generic.conf.Debian
+++ b/neutron/files/pike/neutron-generic.conf.Debian
@@ -37,7 +37,7 @@
 
 core_plugin = neutron.plugins.ml2.plugin.Ml2Plugin
 
-service_plugins =neutron.services.l3_router.l3_router_plugin.L3RouterPlugin,neutron.services.metering.metering_plugin.MeteringPlugin
+service_plugins = {{ neutron.backend.get('router', 'router')}}, metering
 
 {% endif %}
 
@@ -2088,3 +2088,8 @@
 # Sets the list of available ciphers. value should be a string in the OpenSSL
 # cipher list format. (string value)
 #ciphers = <None>
+
+{%- if neutron.backend.ovsdb_connection is defined %}
+[ovs]
+ovsdb_connection = {{ neutron.backend.ovsdb_connection }}
+{%- endif %}
diff --git a/neutron/files/pike/neutron-server.conf.Debian b/neutron/files/pike/neutron-server.conf.Debian
index 6164875..2daaebf 100644
--- a/neutron/files/pike/neutron-server.conf.Debian
+++ b/neutron/files/pike/neutron-server.conf.Debian
@@ -50,7 +50,7 @@
 {% set l3_plugin = 'networking_ovn.l3.l3_ovn.OVNL3RouterPlugin' %}
 {% endif %}
 
-service_plugins ={{ l3_plugin }}, neutron.services.metering.metering_plugin.MeteringPlugin
+service_plugins = {{ server.backend.get('router', l3_plugin)}},metering
 {%- 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 -%}
@@ -2278,3 +2278,8 @@
 password = {{ server.identity.password }}
 auth_url=http://{{ server.identity.host }}:35357
 {%- endif %}
+
+{%- if server.backend.ovsdb_connection is defined %}
+[ovs]
+ovsdb_connection = {{ server.backend.ovsdb_connection }}
+{%- endif %}
diff --git a/neutron/gateway.sls b/neutron/gateway.sls
index 9283813..ddcea6c 100644
--- a/neutron/gateway.sls
+++ b/neutron/gateway.sls
@@ -37,6 +37,7 @@
 /etc/neutron/dhcp_agent.ini:
   file.managed:
   - source: salt://neutron/files/{{ gateway.version }}/dhcp_agent.ini
+  - template: jinja
   - require:
     - pkg: neutron_gateway_packages
 
@@ -132,4 +133,9 @@
 {%- endif %}
 {%- endif %}
 
+{%- if gateway.opendaylight is defined %}
+include:
+  - .opendaylight.client
+{%- endif %}
+
 {%- endif %}
diff --git a/neutron/map.jinja b/neutron/map.jinja
index 19b19c2..c509b31 100644
--- a/neutron/map.jinja
+++ b/neutron/map.jinja
@@ -47,12 +47,20 @@
     },
 }, merge=pillar.neutron.get('compute', {}), base='BaseDefaults') %}
 
+{%- set opendaylight_enabled = pillar.neutron.gateway is defined and pillar.neutron.gateway.opendaylight is defined %}
+{%- set pkgs_list = ['neutron-dhcp-agent', 'openvswitch-common', 'neutron-metadata-agent'] %}
+{%- set services_list = ['neutron-metadata-agent', 'neutron-dhcp-agent'] %}
+{%- if not opendaylight_enabled %}
+{%- do pkgs_list.extend(['neutron-openvswitch-agent', 'neutron-l3-agent']) %}
+{%- do services_list.extend(['neutron-openvswitch-agent', 'neutron-l3-agent']) %}
+{%- endif %}
+
 {% set gateway = salt['grains.filter_by']({
     'BaseDefaults': default_params,
     'Debian': {
-        'pkgs': ['neutron-dhcp-agent', 'neutron-openvswitch-agent', 'neutron-l3-agent', 'openvswitch-common', 'neutron-metadata-agent'],
+        'pkgs': pkgs_list,
         'pkgs_l2gw_agent': ['neutron-l2gateway-agent'],
-        'services': ['neutron-openvswitch-agent', 'neutron-metadata-agent', 'neutron-l3-agent', 'neutron-dhcp-agent'],
+        'services': services_list,
         'dpdk': false,
         'logging': {
           'log_appender': false,
@@ -66,7 +74,7 @@
     'RedHat': {
         'pkgs': ['openstack-neutron-openvswitch'],
         'pkgs_l2gw_agent': ['openstack-neutron-l2gw-agent'],
-        'services': ['neutron-openvswitch-agent', 'neutron-metadata-agent', 'neutron-l3-agent', 'neutron-dhcp-agent'],
+        'services': services_list,
         'dpdk': false,
         'logging': {
           'log_appender': false,
diff --git a/neutron/opendaylight/client.sls b/neutron/opendaylight/client.sls
new file mode 100644
index 0000000..57e6bcc
--- /dev/null
+++ b/neutron/opendaylight/client.sls
@@ -0,0 +1,35 @@
+{%- if pillar.neutron.gateway is defined %}
+{%- from "neutron/map.jinja" import gateway as neutron with context %}
+{%- else %}
+{%- from "neutron/map.jinja" import compute as neutron with context %}
+{%- endif %}
+
+python-networking-odl:
+  pkg.installed
+
+{%- if not grains.get('noservices', False) %}
+
+ovs_set_manager:
+  cmd.run:
+  - name: 'ovs-vsctl set-manager {{ neutron.opendaylight.ovsdb_server_iface }} {{ neutron.opendaylight.ovsdb_odl_iface }}'
+  - unless: 'ovs-vsctl get-manager | fgrep -x {{ neutron.opendaylight.ovsdb_odl_iface }}'
+
+ovs_set_tunnel_endpoint:
+  cmd.run:
+  - name: 'ovs-vsctl set Open_vSwitch . other_config:local_ip={{ neutron.opendaylight.tunnel_ip }}'
+  - unless: 'ovs-vsctl get Open_vSwitch . other_config | fgrep local_ip="{{ neutron.opendaylight.tunnel_ip }}"'
+
+{%- if neutron.opendaylight.provider_mappings is defined %}
+ovs_set_provider_mappings:
+  cmd.run:
+  - name: 'ovs-vsctl set Open_vSwitch . other_config:provider_mappings={{ neutron.opendaylight.provider_mappings }}'
+  - unless: 'ovs-vsctl get Open_vSwitch . other_config | fgrep provider_mappings="{{ neutron.opendaylight.provider_mappings }}"'
+{%- endif %}
+
+neutron_odl_ovs_hostconfig:
+  cmd.run:
+  - name: 'neutron-odl-ovs-hostconfig --noovs_dpdk'
+  - require:
+    - pkg: python-networking-odl
+
+{%- endif %}
diff --git a/neutron/server.sls b/neutron/server.sls
index 92aa61e..7c13131 100644
--- a/neutron/server.sls
+++ b/neutron/server.sls
@@ -116,6 +116,13 @@
   - .services.l2gw
 {%- endif %}
 
+{%- if server.backend.get('opendaylight', False) %}
+python-networking-odl:
+  pkg.installed:
+  - require_in:
+    - pkg: neutron_server_packages
+{%- endif %}
+
 /etc/neutron/neutron.conf:
   file.managed:
   - source: salt://neutron/files/{{ server.version }}/neutron-server.conf.{{ grains.os_family }}
diff --git a/tests/pillar/control_opendaylight.sls b/tests/pillar/control_opendaylight.sls
new file mode 100644
index 0000000..174790e
--- /dev/null
+++ b/tests/pillar/control_opendaylight.sls
@@ -0,0 +1,69 @@
+neutron:
+  server:
+    api_workers: 2
+    rpc_workers: 2
+    rpc_state_report_workers: 2
+    bind:
+      address: 172.16.10.101
+      port: 9696
+    compute:
+      host: 127.0.0.1
+      password: unsegreto
+      region: RegionOne
+      tenant: service
+      user: nova
+    database:
+      engine: mysql
+      host: 127.0.0.1
+      name: neutron
+      password: unsegreto
+      port: 3306
+      user: neutron
+    dns_domain: novalocal
+    dvr: false
+    enabled: true
+    global_physnet_mtu: 1500
+    identity:
+      engine: keystone
+      host: 127.0.0.1
+      password: unsegreto
+      port: 35357
+      region: RegionOne
+      tenant: service
+      user: neutron
+      endpoint_type: internal
+    l3_ha: False
+    message_queue:
+      engine: rabbitmq
+      host: 127.0.0.1
+      password: unsegreto
+      port: 5672
+      user: openstack
+      virtual_host: /openstack
+    policy:
+      create_subnet: 'rule:admin_or_network_owner'
+      'get_network:queue_id': 'rule:admin_only'
+      'create_network:shared':
+    version: pike
+    backend:
+      engine: ml2
+      external_mtu: 1500
+      tenant_network_types: flat,vxlan
+      opendaylight: true
+      router: odl-router
+      host: 127.0.0.1
+      rest_api_port: 8282
+      user: admin
+      password: admin
+      ovsdb_connection: tcp:127.0.0.1:6639
+      mechanism:
+        ovs:
+          driver: opendaylight
+linux:
+  system:
+    enabled: true
+    repo:
+      mirantis_openstack_pike:
+        source: "deb http://mirror.fuel-infra.org/mcp-repos/pike/xenial pike main"
+        architectures: amd64
+        key_url: "http://mirror.fuel-infra.org/mcp-repos/pike/xenial/archive-mcppike.key"
diff --git a/tests/pillar/gateway_legacy_opendaylight.sls b/tests/pillar/gateway_legacy_opendaylight.sls
new file mode 100644
index 0000000..8bf6b26
--- /dev/null
+++ b/tests/pillar/gateway_legacy_opendaylight.sls
@@ -0,0 +1,40 @@
+neutron:
+  gateway:
+    agent_mode: legacy
+    backend:
+      engine: ml2
+      tenant_network_types: "flat,vxlan"
+      router: odl-router
+      ovsdb_connection: tcp:127.0.0.1:6639
+      mechanism:
+        ovs:
+          driver: opendaylight
+    dvr: false
+    enabled: true
+    external_access: True
+    local_ip: 10.1.0.110
+    message_queue:
+      engine: rabbitmq
+      host: 127.0.0.1
+      password: unsegreto
+      port: 5672
+      user: openstack
+      virtual_host: /openstack
+    metadata:
+      host: 127.0.0.1
+      password: unsegreto
+      workers: 2
+    version: pike
+    opendaylight:
+      ovsdb_server_iface: ptcp:6639:127.0.0.1
+      ovsdb_odl_iface: tcp:127.0.0.1:6640
+      tunnel_ip: 10.1.0.110
+      provider_mappings: physnet1:br-floating
+linux:
+  system:
+    enabled: true
+    repo:
+      mirantis_openstack_pike:
+        source: "deb http://mirror.fuel-infra.org/mcp-repos/pike/xenial pike main"
+        architectures: amd64
+        key_url: "http://mirror.fuel-infra.org/mcp-repos/pike/xenial/archive-mcppike.key"