#
# Copyright (C) 2014 eNovance SAS <licensing@enovance.com>
#
# Author: Emilien Macchi <emilien.macchi@enovance.com>
#
# Licensed 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.
#
# == Class: neutron::agents::ml2::ovs
#
# Setups OVS neutron agent when using ML2 plugin
#
# === Parameters
#
# [*package_ensure*]
#   (optional) The state of the package
#   Defaults to 'present'
#
# [*enabled*]
#   (required) Whether or not to enable the OVS Agent
#   Defaults to true
#
# [*manage_service*]
#   (optional) Whether to start/stop the service
#   Defaults to true
#
# [*bridge_uplinks*]
#   (optional) List of interfaces to connect to the bridge when doing
#   bridge mapping.
#   Defaults to empty list
#
# [*bridge_mappings*]
#   (optional) List of <physical_network>:<bridge>
#   Defaults to empty list
#
# [*ovsdb_timeout*]
#   (Optional) The timeout in seconds for OVSDB commands.
#   Defaults to $::os_service_default
#
# [*of_connect_timeout*]
#   (Optional) Timeout in seconds to wait for the local switch
#   connecting to the controller.
#   Defaults to $::os_service_default
#
# [*of_request_timeout*]
#   (Optional) Timeout in seconds to wait for a single OpenFlow request.
#   Defaults to $::os_service_default
#
# [*of_inactivity_probe*]
#   (Optional) The inactivity_probe interval in second for the local switch
#   connnection to the controller. A value of 0 disables inactivity probes.
#   Defaults to $::os_service_default
#
# [*integration_bridge*]
#   (optional) Integration bridge in OVS
#   Defaults to 'br-int'
#
# [*tunnel_types*]
#   (optional) List of types of tunnels to use when utilizing tunnels,
#   either 'gre' or 'vxlan'.
#   Defaults to empty list
#
# [*local_ip*]
#   (optional) Local IP address of VXLAN/GRE tunnel endpoints.
#   Required when enabling tunneling
#   Defaults to false
#
# [*tunnel_bridge*]
#   (optional) Bridge used to transport tunnels
#   Defaults to 'br-tun'
#
# [*vxlan_udp_port*]
#   (optional) The UDP port to use for VXLAN tunnels.
#   Defaults to '4789'
#
# [*polling_interval*]
#   (optional) The number of seconds the agent will wait between
#   polling for local device changes.
#   Defaults to $::os_service_default
#
# [*l2_population*]
#   (optional) Extension to use alongside ml2 plugin's l2population
#   mechanism driver.
#   Defaults to $::os_service_default
#
# [*arp_responder*]
#   (optional) Enable or not the ARP responder.
#   Recommanded when using l2 population mechanism driver.
#   Defaults to $::os_service_default
#
# [*firewall_driver*]
#   (optional) Firewall driver for realizing neutron security group function.
#   Defaults to 'iptables_hybrid'.
#
# [*enable_distributed_routing*]
#   (optional) Set to True on L2 agents to enable support
#   for distributed virtual routing.
#   Defaults to $::os_service_default
#
# [*drop_flows_on_start*]
#   (optional) Set to True to drop all flows during agent start for a clean
#   flow tables resetting
#   Defaults to false
#
# [*manage_vswitch*]
#   (optional) This boolean is used to indicate if this class should manage the
#   vswitch software installation and the ovs bridges/ports from the
#   $bridge_mappings parameter. If manage_vswitch is set to true, then we will
#   require the vswitch::ovs and configure the ovs bridges/ports using the
#   mappings provided as part of the $bridge_mappings parameters.
#   Defaults to true
#
# [*extensions*]
#   (optional) Extensions list to use
#   Defaults to $::os_service_default
#
# [*int_peer_patch_port*]
#   (optional) Peer patch port in integration bridge for tunnel bridge
#   Defaults to $::os_service_default
#
# [*tun_peer_patch_port*]
#   (optional) Peer patch port in tunnel bridge for integration bridge
#   Defaults to $::os_service_default
#
# [*datapath_type*]
#   (optional) Datapath type for ovs bridges
#   Defaults to $::os_service_default
#
# [*vhostuser_socket_dir*]
#   (optional) The vhost-user socket directory for OVS
#   Defaults to $::os_service_default
#
# [*purge_config*]
#   (optional) Whether to set only the specified config options
#   in the ovs config.
#   Defaults to false.
#
# [*enable_dpdk*]
#   (optional) Enable or not DPDK with OVS
#   Defaults to false.
#
# [*enable_security_group*]
#   (optional) Controls whether the agent supports security
#   groups or not.
#   Defaults to $::os_service_default
#
# [*permitted_ethertypes*]
#   (optional) List of additional ethernet types to be configured
#   on the firewall.
#   Defaults to $::os_service_default
#
# [*minimize_polling*]
#  (optional) Minimize polling by monitoring ovsdb for interface
#  changes. (boolean value)
#  Defaults to $::os_service_default
#
# [*tunnel_csum*]
#  (optional) Set or un-set the tunnel header checksum  on
#  outgoing IP packet carrying GRE/VXLAN tunnel.
#  Defaults to $::os_service_default
#
# [*bridge_mac_table_size*]
#   (optional) The maximum number of MAC addresses to learn on a bridge managed
#   by the Neutron OVS agent.
#   Defaults to $::os_service_default
#
# [*igmp_snooping_enable*]
#   (Optional) Enable IGMP snooping for integration bridge. If this
#   option is set to True, support for Internet Group Management
#   Protocol (IGMP) is enabled in integration bridge.
#   Setting this option to True will also enable Open vSwitch
#   mcast-snooping-disable-flood-unregistered flag. This option will
#   disable flooding of unregistered multicast packets to all ports.
#   The switch will send unregistered multicast packets only to ports
#   connected to multicast routers. This option is used by the ML2/OVS
#   mechanism driver for Neutron.
#   Defaults to $::os_service_default
#
# [*resource_provider_bandwidths*]
#   (optional) List of <bridge>:<egress_bw>:<ingress_bw>
#   Defaults to empty list
#
# [*resource_provider_hypervisors*]
#   (optional) List of <bridge>:<hypervisor>
#   Defaults to empty list
#
# [*explicitly_egress_direct*]
#  (optional) When set to True, the accepted egress unicast traffic will not
#  use action NORMAL. The accepted egress packets will be taken care of in the
#  final egress tables direct output flows for unicast traffic. (boolean value)
#  Defaults to $::os_service_default
#
# DEPRECATED
#
# [*ovsdb_interface*]
#   (optional) The interface for interacting with the OVSDB
#   Allowed values: vsctl, native
#   Defaults to undef
#
class neutron::agents::ml2::ovs (
  $package_ensure                = 'present',
  $enabled                       = true,
  $manage_service                = true,
  $extensions                    = $::os_service_default,
  $bridge_uplinks                = [],
  $bridge_mappings               = [],
  $ovsdb_timeout                 = $::os_service_default,
  $of_connect_timeout            = $::os_service_default,
  $of_request_timeout            = $::os_service_default,
  $of_inactivity_probe           = $::os_service_default,
  $integration_bridge            = 'br-int',
  $tunnel_types                  = [],
  $local_ip                      = false,
  $tunnel_bridge                 = 'br-tun',
  $vxlan_udp_port                = 4789,
  $polling_interval              = $::os_service_default,
  $l2_population                 = $::os_service_default,
  $arp_responder                 = $::os_service_default,
  $firewall_driver               = 'iptables_hybrid',
  $enable_distributed_routing    = $::os_service_default,
  $drop_flows_on_start           = false,
  $manage_vswitch                = true,
  $int_peer_patch_port           = $::os_service_default,
  $tun_peer_patch_port           = $::os_service_default,
  $datapath_type                 = $::os_service_default,
  $vhostuser_socket_dir          = $::os_service_default,
  $purge_config                  = false,
  $enable_dpdk                   = false,
  $enable_security_group         = $::os_service_default,
  $permitted_ethertypes          = $::os_service_default,
  $minimize_polling              = $::os_service_default,
  $tunnel_csum                   = $::os_service_default,
  $bridge_mac_table_size         = $::os_service_default,
  $igmp_snooping_enable          = $::os_service_default,
  $resource_provider_bandwidths  = [],
  $resource_provider_hypervisors = [],
  $explicitly_egress_direct      = $::os_service_default,
  # DEPRECATED
  $ovsdb_interface               = undef,
) {

  include neutron::deps
  include neutron::params

  if $enable_dpdk and ! $manage_vswitch {
    fail('Enabling DPDK without manage vswitch does not have any effect')
  }

  if $enable_dpdk and is_service_default($datapath_type) {
    fail('Datapath type for ovs agent must be set when DPDK is enabled')
  }

  if $enable_dpdk and is_service_default($vhostuser_socket_dir) {
    fail('vhost user socket directory for ovs agent must be set when DPDK is enabled')
  }

  if ! is_service_default($permitted_ethertypes) {
    validate_legacy(Array, 'validate_array', $permitted_ethertypes)
    neutron_agent_ovs {
      'securitygroup/permitted_ethertypes':   value => join($permitted_ethertypes, ',');
    }
  }

  if $manage_vswitch {
    if $enable_dpdk {
      require vswitch::dpdk
    } else {
      require vswitch::ovs
    }
  }

  validate_legacy(Array, 'validate_array', $tunnel_types)
  if size($tunnel_types) > 0 {
    $enable_tunneling = true
  } else {
    $enable_tunneling = false
  }

  if $enable_tunneling and ! $local_ip {
    fail('Local ip for ovs agent must be set when tunneling is enabled')
  }

  if ($enable_tunneling) and (!is_service_default($enable_distributed_routing)) and (!is_service_default($l2_population)) {
    if $enable_distributed_routing and ! $l2_population {
      fail('L2 population must be enabled when DVR and tunneling are enabled')
    }
  }

  resources { 'neutron_agent_ovs':
    purge => $purge_config,
  }

  if ($bridge_mappings != []) {
    # bridge_mappings are used to describe external networks that are
    # *directly* attached to this machine.
    # (This has nothing to do with VM-VM comms over neutron virtual networks.)
    # Typically, the network node - running L3 agent - will want one external
    # network (often this is on the control node) and the other nodes (all the
    # compute nodes) will want none at all.  The only other reason you will
    # want to add networks here is if you're using provider networks, in which
    # case you will name the network with bridge_mappings and add the server's
    # interfaces that are attached to that network with bridge_uplinks.
    # (The bridge names can be nearly anything, they just have to match between
    # mappings and uplinks; they're what the OVS switches will get named.)

    # Set config for bridges that we're going to create
    # The OVS neutron plugin will talk in terms of the networks in the bridge_mappings
    $br_map_str = join(any2array($bridge_mappings), ',')
    neutron_agent_ovs {
      'ovs/bridge_mappings': value => $br_map_str;
    }
    if ($manage_vswitch) {
      neutron::plugins::ovs::bridge{ $bridge_mappings:
        before => Service['neutron-ovs-agent-service'],
      }
      neutron::plugins::ovs::port{ $bridge_uplinks:
        before => Service['neutron-ovs-agent-service'],
      }
    }
  }

  if ($resource_provider_bandwidths != []) {
    $resource_provider_bandwidths_real = join(any2array($resource_provider_bandwidths), ',')
  } else {
    $resource_provider_bandwidths_real = $::os_service_default
  }

  if ($resource_provider_hypervisors != []){
    $resource_provider_hypervisors_real = join(any2array($resource_provider_hypervisors), ',')
  } else {
    $resource_provider_hypervisors_real = $::os_service_default
  }

  neutron_agent_ovs {
    'ovs/resource_provider_bandwidths':  value => $resource_provider_bandwidths_real;
    'ovs/resource_provider_hypervisors': value => $resource_provider_hypervisors_real;
  }

  # TODO(tobias.urdin): Remove in V release.
  if $ovsdb_interface != undef {
    warning('ovsdb_interface is deprecated and has no effect')
  }
  neutron_agent_ovs {
    'ovs/ovsdb_interface': ensure => absent;
    'ovs/of_interface': ensure => absent;
  }

  neutron_agent_ovs {
    'agent/polling_interval':               value => $polling_interval;
    'agent/l2_population':                  value => $l2_population;
    'agent/arp_responder':                  value => $arp_responder;
    'agent/enable_distributed_routing':     value => $enable_distributed_routing;
    'agent/drop_flows_on_start':            value => $drop_flows_on_start;
    'agent/extensions':                     value => join(any2array($extensions), ',');
    'agent/minimize_polling':               value => $minimize_polling;
    'agent/tunnel_csum':                    value => $tunnel_csum;
    'agent/explicitly_egress_direct':       value => $explicitly_egress_direct;
    'ovs/ovsdb_timeout':                    value => $ovsdb_timeout;
    'ovs/of_connect_timeout':               value => $of_connect_timeout;
    'ovs/of_request_timeout':               value => $of_request_timeout;
    'ovs/of_inactivity_probe':              value => $of_inactivity_probe;
    'ovs/integration_bridge':               value => $integration_bridge;
    'ovs/datapath_type':                    value => $datapath_type;
    'ovs/vhostuser_socket_dir':             value => $vhostuser_socket_dir;
    'securitygroup/enable_security_group':  value => $enable_security_group;
    'ovs/bridge_mac_table_size':            value => $bridge_mac_table_size;
    'ovs/igmp_snooping_enable':             value => $igmp_snooping_enable;
  }

  if $firewall_driver {
    neutron_agent_ovs { 'securitygroup/firewall_driver': value => $firewall_driver }
  } else {
    neutron_agent_ovs { 'securitygroup/firewall_driver': ensure => absent }
  }

  if $enable_tunneling {
    neutron_agent_ovs {
      'ovs/tunnel_bridge':         value => $tunnel_bridge;
      'ovs/local_ip':              value => $local_ip;
      'ovs/int_peer_patch_port':   value => $int_peer_patch_port;
      'ovs/tun_peer_patch_port':   value => $tun_peer_patch_port;
      'agent/tunnel_types':        value => join($tunnel_types, ',');
    }

    if 'vxlan' in $tunnel_types {
      validate_vxlan_udp_port($vxlan_udp_port)
      neutron_agent_ovs {
        'agent/vxlan_udp_port': value => $vxlan_udp_port;
      }
    }
  } else {
    neutron_agent_ovs {
      'ovs/tunnel_bridge':         ensure => absent;
      'ovs/local_ip':              ensure => absent;
      'ovs/int_peer_patch_port':   ensure => absent;
      'ovs/tun_peer_patch_port':   ensure => absent;
      'agent/tunnel_types':        ensure => absent;
    }
  }


  if $::neutron::params::ovs_agent_package {
    package { 'neutron-ovs-agent':
      ensure => $package_ensure,
      name   => $::neutron::params::ovs_agent_package,
      tag    => ['openstack', 'neutron-package'],
    }
  } else {
    # Some platforms (RedHat) do not provide a separate
    # neutron plugin ovs agent package. The configuration file for
    # the ovs agent is provided by the neutron ovs plugin package.
    if ! defined(Package['neutron-ovs-agent']) {
      package { 'neutron-ovs-agent':
        ensure => $package_ensure,
        name   => $::neutron::params::ovs_server_package,
        tag    => ['openstack', 'neutron-package'],
      }
    }
  }

  if $manage_service {
    if $enabled {
      $service_ensure = 'running'
    } else {
      $service_ensure = 'stopped'
    }
  }

  service { 'neutron-ovs-agent-service':
    ensure => $service_ensure,
    name   => $::neutron::params::ovs_agent_service,
    enable => $enabled,
    tag    => ['neutron-service', 'neutron-db-sync-service'],
  }

  if ($::osfamily == 'Redhat') {
    service { 'neutron-destroy-patch-ports-service':
      ensure => $service_ensure,
      name   => $::neutron::params::destroy_patch_ports_service,
      enable => $enabled,
      tag    => ['neutron-service'],
    }
  }

  if $::neutron::params::ovs_cleanup_service {
    service { 'ovs-cleanup-service':
      name    => $::neutron::params::ovs_cleanup_service,
      enable  => $enabled,
      # TODO: Remove this require once ovs-cleanup service
      # script is packaged in neutron-openvswitch package
      require => Anchor['neutron::install::end'],
    }
  }
}
