Browse Source

Support of ERSPAN for tap as a service

Change-Id: I6f7bcc447ce6b7b27cdf50f0a7586daf1e1acd32
changes/71/675671/5
jb 2 years ago
parent
commit
ec44f89058
  1. 49
      README_taas.txt
  2. 2
      devstack/plugin.sh
  3. 4
      devstack/settings
  4. 479
      networking_vpp/agent/taas_vpp_agent.py
  5. 45
      networking_vpp/agent/vpp.py
  6. 224
      networking_vpp/taas_vpp.py

49
README_taas.txt

@ -8,8 +8,11 @@ For this purpose, networking-vpp implements a driver for the extension
neutron/tap-as-a-service
(https://opendev.org/x/tap-as-a-service/).
================================================================================
1. Installation
- Install the neutron extension: openstack/tap-as-a-service
NB: In order to use the ERSPAN mode, you need to use the "customized version" of tap-as-a-service that supports ERSPAN.
It is available here: https://github.com/jbeuque/tap-as-a-service
- Update the ml2 plugin configuration files
Add the following lines to the ML2 configuration
@ -26,11 +29,57 @@ Add the following lines to the ML2 configuration on the Neutron server:
[service_providers]
service_provider = TAAS:TAAS:networking_vpp.taas_vpp.TaasEtcdDriver:default
- ERSPAN mode
In order to use the ERSPAN mode for tap as a service, the parameters esp_physnet and esp_src_cidr have to
be added in the ml2 plugin configuration file. esp_src_cidr is the source address of the ERSpan tunnels for the
compute node. esp_physnet is the physical interface to be used for the ERSPAN external mode.
[ml2_vpp]
physnets = physnet1:TenGigabitEthernet9/0/0,physnet2:TenGigabitEtherneta/0/0
esp_physnet = physnet2
esp_src_cidr = 10.1.2.10/24
================================================================================
2. Usage
See the documentation of Tap as a service
(https://opendev.org/x/tap-as-a-service/). This
implements the standard service API.
----------------------
ERSPAN mode:
* In order to use the ERSPAN mode, you need to use the "customized version" of tap-as-a-service that supports ERSPAN.
usage: neutron tap-service-create [-h] [-f {json,shell,table,value,yaml}]
[-c COLUMN] [--max-width <integer>]
[--fit-width] [--print-empty] [--noindent]
[--prefix PREFIX] [--tenant-id TENANT_ID]
[--name NAME] [--description DESCRIPTION]
[--port PORT]
[--erspan_dst_ip ERSPAN_DST_IP]
tap-service examples:
a/ ERSPAN external mode
The destination is outside of Openstack. The tap service doesn't have any port_id but an erspan destination IP address.
neutron tap-service-create --erspan_dst_ip 10.1.2.3
b/ ERSPAN internal mode
The destination is a port of an openstack VM. The tap service has both a port_id and an erspan destination IP address.
neutron tap-service-create --port 837e40d1-6458-442e-8386-bf6799f1d89f --erspan_dst_ip 10.1.2.3
----------------------
usage: neutron tap-flow-create [-h] [-f {json,shell,table,value,yaml}]
[-c COLUMN] [--max-width <integer>]
[--fit-width] [--print-empty] [--noindent]
[--prefix PREFIX] [--tenant-id TENANT_ID]
[--name NAME] [--description DESCRIPTION]
--port SOURCE_PORT --tap-service TAP_SERVICE
--direction DIRECTION
[--erspan_session_id ERSPAN_SESSION_ID]
tap-flow example:
neutron tap-flow-create --port 0f576381-e461-4807-bb11-5d57624b30a0 --tap-service 51d17137-241d-47db-a28b-d62d0e631205 --erspan_session_id 234 --direction BOTH
================================================================================
3. Known Limitations
- Live migration is not supported by this version.
- vxlan support is currently preliminary.

2
devstack/plugin.sh

@ -77,6 +77,8 @@ function configure_networking_vpp {
else
iniset /$Q_PLUGIN_CONF_FILE ml2_vpp jwt_signing False
fi
iniset /$Q_PLUGIN_CONF_FILE ml2_vpp esp_src_cidr $ESP_SRC_CIDR
iniset /$Q_PLUGIN_CONF_FILE ml2_vpp esp_physnet $ESP_PHYSNET
}

4
devstack/settings

@ -69,3 +69,7 @@ JWT_MAX_DURATION=${JWT_MAX_DURATION:-0}
# Layer3 Router/HA Settings
# Provide a hostname or a comma separated list of L3 hostnames
L3_HOSTS=${L3_HOSTS:-localhost}
# ERSPAN configuration
ESP_SRC_CIDR=${ESP_SRC_CIDR:-}
ESP_PHYSNET=${ESP_PHYSNET:-}

479
networking_vpp/agent/taas_vpp_agent.py

@ -14,15 +14,29 @@
# under the License.
import etcd
from ipaddress import ip_address
from ipaddress import ip_network
from networking_vpp.constants import LEADIN
from networking_vpp import etcdutils
from networking_vpp.extension import VPPAgentExtensionBase
import neutron.agent.linux.ip_lib as ip_lib
from oslo_config import cfg
from oslo_log import log as logging
from oslo_serialization import jsonutils
import six
LOG = logging.getLogger(__name__)
def ipnet(ip):
return ip_network(six.text_type(ip))
def ipaddr(ip):
return ip_address(six.text_type(ip))
class TaasServiceAgentWatcher(etcdutils.EtcdChangeWatcher):
"""Watch for changes in TaaS flow destinations."""
@ -59,7 +73,10 @@ class TaasServiceAgentWatcher(etcdutils.EtcdChangeWatcher):
try:
port_info = jsonutils.loads(self.etcd_client.read(port_path).value)
physnet = port_info['physnet']
network_type = port_info['network_type']
if data['dest_type'] == 'ERSPAN_INT':
network_type = 'vlan'
else:
network_type = port_info['network_type']
# Tapped packets for this destination will be put on their own
# overlay
@ -98,8 +115,9 @@ class TaasServiceAgentWatcher(etcdutils.EtcdChangeWatcher):
# Since we want all packets regardless of MAC to go to the other
# end, we want the bridge to flood
self.vppf.vpp.bridge_enable_flooding(
bridge_data['bridge_domain_id'])
if data['dest_type'] != 'ERSPAN_INT':
self.vppf.vpp.bridge_enable_flooding(
bridge_data['bridge_domain_id'])
# Tapped interfaces feed this bridge; this interface will
# receive the results.
@ -181,10 +199,58 @@ class TaasFlowAgentWatcher(etcdutils.EtcdChangeWatcher):
etcd_helper.ensure_dir(self._node_key_space)
etcd_helper.ensure_dir(self._state_key_space)
self.iputils = ip_lib.IPWrapper()
# ERSPan IP address/prefix len
self.esp_src_cidr = cfg.CONF.ml2_vpp.esp_src_cidr
if self.esp_src_cidr is not None and self.esp_src_cidr != '':
(self.esp_src_addr,
self.esp_plen) = self.esp_src_cidr.split('/')
# Name of the ERspan physnet
self.esp_physnet = cfg.CONF.ml2_vpp.esp_physnet
super(TaasFlowAgentWatcher, self).__init__(self.etcd_client,
self.path,
self._node_key_space)
def _ensure_ext_link(self):
"""Ensures that the EXT link interface is present and configured.
The ext_link is used for ERPSAN_EXT mode to reach the external tap
service.
The physical interface of ext_link is specified by the paramter
esp_phynet of the configuration. The address of ext_link is given by
the parameter esp_src_cidr of the configuration.
Returns:-
The name and the software_if_index of the EXT link or None in case
of error.
"""
intf, if_physnet = self.vppf.get_if_for_physnet(self.esp_physnet)
LOG.debug('Setting EXT attachment interface: %s',
intf)
if if_physnet is None:
LOG.error('Cannot create a EXT network because the esp_'
'physnet config value:%s is broken. Make sure this '
'value is set to a valid physnet name used as the '
'EXT interface',
self.esp_physnet)
return
self.vppf.vpp.ifup(if_physnet)
LOG.debug('Configuring EXT ip address %s on '
'interface %s', self.esp_src_cidr, intf)
physnet_ip_addrs = self.vppf.vpp.get_interface_ip_addresses(if_physnet)
LOG.debug('Exising IP addresses %s', str(physnet_ip_addrs))
cidr = (ipaddr(self.esp_src_addr),
int(self.esp_plen))
if cidr not in physnet_ip_addrs:
self.vppf.vpp.set_interface_address(
sw_if_index=if_physnet,
is_ipv6=1 if ipnet(self.esp_src_addr).version == 6 else 0,
address_length=int(self.esp_plen),
address=self.vppf._pack_address(self.esp_src_addr)
)
return (intf, if_physnet)
def _find_port_idx(self, port_id, host):
port_path = (LEADIN + '/state/' + host + '/ports/' +
str(port_id))
@ -235,6 +301,51 @@ class TaasFlowAgentWatcher(etcdutils.EtcdChangeWatcher):
is_ipv6,
vni)
def _create_erspan_tunnel(self, src_adr, dst_adr, session_id):
"""Create a tunnel to a remote destination VTEP."""
if ip_network(six.text_type(src_adr)).version == 6:
is_ipv6 = 1
else:
is_ipv6 = 0
if ip_network(six.text_type(dst_adr)).version == 6:
is_ipv6d = 1
else:
is_ipv6d = 0
if is_ipv6 != is_ipv6d:
LOG.error('Cannot create an erspan tunnel because the IP version'
' of src_adr and dst_adr are different',
self.src_adr, self.dst_adr)
return None
dst_adrp = self.vppf._pack_address(dst_adr)
src_adrp = self.vppf._pack_address(src_adr)
esptuns = self.vppf.vpp.get_erspan_tunnels()
if is_ipv6 == 0:
kadr = dst_adrp + "\x00" * 12
else:
kadr = dst_adrp
tidx = esptuns.get((int(session_id), kadr))
if tidx is not None:
return tidx
idx = self.vppf.vpp.create_erspan_tunnel(
src_adrp,
dst_adrp,
is_ipv6,
int(session_id))
return idx
def _delete_erspan_tunnel(self, src_adr, dst_adr, session_id):
"""Remove a VXLAN tunnel from VPP."""
if ip_network(six.text_type(src_adr)).version == 6:
is_ipv6 = 1
else:
is_ipv6 = 0
self.vppf.vpp.delete_erspan_tunnel(
self.vppf._pack_address(src_adr),
self.vppf._pack_address(dst_adr),
is_ipv6,
int(session_id))
def _get_remote_addr(self, port_mac):
self.vppf.load_gpe_mappings()
remote_ip = ''
@ -264,11 +375,8 @@ class TaasFlowAgentWatcher(etcdutils.EtcdChangeWatcher):
data = jsonutils.loads(value)
# data = value
taas_id = data['taas_id']
direction = data['tap_flow']['direction']
ts_host = data['ts_host']
tf_host = data['tf_host']
# Check Span direction
direction = data['tap_flow']['direction']
DIR_RX = 1
DIR_TX = 2
DIR_TX_RX = 3
@ -279,109 +387,216 @@ class TaasFlowAgentWatcher(etcdutils.EtcdChangeWatcher):
else:
direction = DIR_TX_RX
tap_srv_id = data['tap_flow']['tap_service_id']
ts_path = (LEADIN + '/state_taas/' + ts_host +
'/taas_service/' + tap_srv_id)
try:
ts_info = jsonutils.loads(self.etcd_client.read(ts_path).value)
network_type = ts_info['service_bridge']['network_type']
physnet = ts_info['service_bridge']['physnet']
if network_type != 'vxlan':
# Check Destination type
if 'dest_type' in data:
dest_type = data['dest_type']
else:
dest_type = 'Port'
if dest_type == 'ERSPAN_EXT' or dest_type == 'ERSPAN_INT':
tap_srv_id = data['tap_flow']['tap_service_id']
ts_host = ''
if dest_type == 'ERSPAN_INT':
ts_host = data['ts_host']
ts_path = (LEADIN + '/state_taas/' + ts_host +
'/taas_service/' + tap_srv_id)
else:
ts_path = (LEADIN + '/global' +
'/taas_service/' + tap_srv_id)
try:
ts_info = jsonutils.loads(self.etcd_client.read(ts_path).value)
if dest_type == 'ERSPAN_INT':
physnet = ts_info['service_bridge']['physnet']
esp_dst_addr = \
ts_info['ts']['tap_service']['erspan_dst_ip']
else:
physnet = self.esp_physnet
esp_dst_addr = ts_info['tap_service']['erspan_dst_ip']
network_type = 'vlan'
esp_src_addr = self.esp_src_addr
# esp_dst_addr = ts_info['tap_service']['erspan_dst_ip']
esp_session_id = data['tap_flow']['erspan_session_id']
esp_plen = self.esp_plen
if ip_network(six.text_type(esp_dst_addr)).version == 6:
esp_isv6 = 1
else:
esp_isv6 = 0
# Check if tap flow and tap service are located in the same node
# Local Span
if ts_host == tf_host:
# Create the ERSpan tunnel
tun_idx = self._create_erspan_tunnel(
esp_src_addr, esp_dst_addr, esp_session_id)
tf_host = data['tf_host']
source_port_idx = self._find_port_idx(
data['tap_flow']['source_port'], tf_host)
srv_port_idx = ts_info['port']['iface_idx']
# Local Span
dst_idx = srv_port_idx
# Mirror the src port to the ERSpan tunnel
self.vppf.vpp.enable_port_mirroring(source_port_idx,
srv_port_idx,
tun_idx,
direction)
if dest_type == 'ERSPAN_EXT':
self._ensure_ext_link()
loop_idx = -1
elif dest_type == 'ERSPAN_INT':
# Create or find the TF bridge
bridge_data = self.vppf.ensure_network_on_host(
physnet,
network_type,
data['taas_id'])
# Connect the tunnel to the bridge
self.vppf.vpp.add_to_bridge(
bridge_data['bridge_domain_id'],
tun_idx)
# Create the loopback intf as BVI for TF bridge
loop_idx = self.vppf.vpp.get_bridge_bvi(
bridge_data['bridge_domain_id'])
if loop_idx is None:
loop_idx = self.vppf.vpp.create_loopback()
self.vppf.vpp.set_loopback_bridge_bvi(
loop_idx, bridge_data['bridge_domain_id'])
self.vppf.vpp.set_interface_vrf(loop_idx, 0, esp_isv6)
self.vppf.vpp.set_interface_ip(
loop_idx,
self.vppf._pack_address(esp_src_addr),
int(esp_plen),
esp_isv6)
self.vppf.vpp.add_ip_route(
0, self.vppf._pack_address(esp_dst_addr),
int(esp_plen), None, loop_idx, esp_isv6, False)
self.vppf.vpp.ifup(loop_idx)
# Activate the ERspan tunnel
self.vppf.vpp.ifup(tun_idx)
# Set the tap_flow state in etcd
data = {"tf": data,
"dst_idx": tun_idx,
"port_idx": source_port_idx,
'dst_idx': dst_idx,
'span_mode': 0, # Local
'tfn': True
'span_mode': 3, # ERSPAN
'dst_adr': esp_dst_addr,
'session_id': esp_session_id,
'loop_idx': loop_idx,
'physnet': physnet,
'ts_host': ts_host
}
# Remote Span via vlan
elif network_type == 'vlan':
if self._host == tf_host:
source_port_idx = self._find_port_idx(
data['tap_flow']['source_port'], tf_host)
# get/create a numbered bridge domain for the service
service_bridge = self.vppf.ensure_network_on_host(
physnet, network_type, taas_id)
service_bridge_id = service_bridge['bridge_domain_id']
self.vppf.vpp.bridge_enable_flooding(service_bridge_id)
self.etcd_client.write(self._state_key_space +
'/%s' % flow_id,
jsonutils.dumps(data))
# Remote Span
srv_uplink_idx = service_bridge['if_uplink_idx']
dst_idx = srv_uplink_idx
except etcd.EtcdKeyNotFound:
pass
else:
taas_id = data['taas_id']
ts_host = data['ts_host']
tf_host = data['tf_host']
tap_srv_id = data['tap_flow']['tap_service_id']
ts_path = (LEADIN + '/state_taas/' + ts_host +
'/taas_service/' + tap_srv_id)
try:
ts_info = jsonutils.loads(self.etcd_client.read(ts_path).value)
network_type = ts_info['service_bridge']['network_type']
physnet = ts_info['service_bridge']['physnet']
if network_type != 'vxlan':
network_type = 'vlan'
# Check if tapflow and tapservice are located in the same node
# Local Span
if ts_host == tf_host:
source_port_idx = self._find_port_idx(
data['tap_flow']['source_port'], tf_host)
srv_port_idx = ts_info['port']['iface_idx']
# Local Span
dst_idx = srv_port_idx
self.vppf.vpp.enable_port_mirroring(source_port_idx,
srv_uplink_idx,
srv_port_idx,
direction)
# Set the tap_flow state in etcd
data = {"tf": data,
"service_bridge": service_bridge,
"port_idx": source_port_idx,
'dst_idx': dst_idx,
'span_mode': 1, # vlan
'span_mode': 0, # Local
'tfn': True
}
# Remote Span via vlan
elif network_type == 'vlan':
if self._host == tf_host:
source_port_idx = self._find_port_idx(
data['tap_flow']['source_port'], tf_host)
# get/create a numbered bridge domain for the service
service_bridge = self.vppf.ensure_network_on_host(
physnet, network_type, taas_id)
service_bridge_id = service_bridge['bridge_domain_id']
self.vppf.vpp.bridge_enable_flooding(service_bridge_id)
# Remote Span
srv_uplink_idx = service_bridge['if_uplink_idx']
dst_idx = srv_uplink_idx
self.vppf.vpp.enable_port_mirroring(source_port_idx,
srv_uplink_idx,
direction)
# Set the tap_flow state in etcd
data = {"tf": data,
"service_bridge": service_bridge,
"port_idx": source_port_idx,
'dst_idx': dst_idx,
'span_mode': 1, # vlan
'tfn': True
}
else:
# Set the tap_flow state in etcd
data = {"tf": data,
'span_mode': 1, # vlan
'tfn': False
}
# Remote Span via vxlan
else:
if self._host == tf_host:
dst_adr = self._get_remote_addr(data['ts_port_mac'])
else:
dst_adr = self._get_remote_addr(data['port_mac'])
source_port_idx = self._find_port_idx(
data['tap_flow']['source_port'], tf_host)
vni = taas_id
tun_idx = self._create_vxlan_tunnel(dst_adr, vni)
# source_port_idx = -1
if self._host == tf_host:
tfn = True
self.vppf.vpp.cross_connect(tun_idx, 0)
self.vppf.vpp.enable_port_mirroring(source_port_idx,
tun_idx,
direction)
else:
tfn = False
bd_idx = taas_id + 64000
self.vppf.vpp.add_to_bridge(bd_idx,
tun_idx)
self.vppf.vpp.ifup(tun_idx)
# Set the tap_flow state in etcd
data = {"tf": data,
'span_mode': 1, # vlan
'tfn': False
"dst_idx": tun_idx,
"port_idx": source_port_idx,
'span_mode': 2, # vxlan
'tfn': tfn,
'dst_adr': dst_adr,
'vni': vni
}
# Remote Span via vxlan
else:
if self._host == tf_host:
dst_adr = self._get_remote_addr(data['ts_port_mac'])
else:
dst_adr = self._get_remote_addr(data['port_mac'])
source_port_idx = self._find_port_idx(
data['tap_flow']['source_port'], tf_host)
vni = taas_id
tun_idx = self._create_vxlan_tunnel(dst_adr, vni)
# source_port_idx = -1
if self._host == tf_host:
tfn = True
self.vppf.vpp.cross_connect(tun_idx, 0)
self.vppf.vpp.enable_port_mirroring(source_port_idx,
tun_idx,
direction)
else:
tfn = False
bd_idx = taas_id + 64000
self.vppf.vpp.add_to_bridge(bd_idx,
tun_idx)
self.vppf.vpp.ifup(tun_idx)
# Set the tap_flow state in etcd
data = {"tf": data,
"dst_idx": tun_idx,
"port_idx": source_port_idx,
'span_mode': 2, # vxlan
'tfn': tfn,
'dst_adr': dst_adr,
'vni': vni
}
self.etcd_client.write(self._state_key_space +
'/%s' % flow_id,
jsonutils.dumps(data))
except etcd.EtcdKeyNotFound:
pass
self.etcd_client.write(self._state_key_space +
'/%s' % flow_id,
jsonutils.dumps(data))
except etcd.EtcdKeyNotFound:
pass
def removed(self, key):
# Removing key == desire to unbind
@ -392,39 +607,67 @@ class TaasFlowAgentWatcher(etcdutils.EtcdChangeWatcher):
self.etcd_client.read(taas_path).value)
span_mode = tap_flow_info['span_mode']
tfn = tap_flow_info['tfn']
if tfn:
dst_idx = tap_flow_info['dst_idx']
source_port_idx = tap_flow_info['port_idx']
self.vppf.vpp.disable_port_mirroring(source_port_idx,
dst_idx)
if span_mode == 1 and tfn:
service_bridge = tap_flow_info['service_bridge']
physnet = service_bridge['physnet']
net_type = service_bridge['network_type']
seg_id = service_bridge['segmentation_id']
# check if the local service bridge needs to be removed
spans = self.vppf.vpp.dump_port_mirroring()
cnt = 0
for sp in spans:
if sp.sw_if_index_to == dst_idx:
cnt += 1
if cnt == 0:
self.vppf.delete_network_on_host(physnet, net_type, seg_id)
elif span_mode == 2: # vxlan
taas_id = tap_flow_info['tf']['taas_id']
tf_host = tap_flow_info['tf']['tf_host']
tf_nb = self._get_num_flow(tf_host, taas_id)
if tf_nb == 0:
if tfn is False:
self.vppf.vpp.delete_from_bridge(
tap_flow_info['dst_idx'])
vni = tap_flow_info['vni']
dst_adr = tap_flow_info['dst_adr']
self._delete_vxlan_tunnel(dst_adr, vni)
if span_mode == 3: # ERSPAN
dest_type = tap_flow_info['tf']['dest_type']
dst_idx = tap_flow_info['dst_idx']
source_port_idx = tap_flow_info['port_idx']
self.vppf.vpp.disable_port_mirroring(source_port_idx,
dst_idx)
if dest_type == 'ERSPAN_INT':
self.vppf.vpp.delete_from_bridge(dst_idx)
taas_id = tap_flow_info['tf']['taas_id']
tf_host = tap_flow_info['tf']['tf_host']
ts_host = tap_flow_info['ts_host']
session_id = tap_flow_info['session_id']
dst_addr = tap_flow_info['dst_adr']
src_addr = self.esp_src_addr
self._delete_erspan_tunnel(src_addr, dst_addr, session_id)
if dest_type == 'ERSPAN_INT':
tf_nb = self._get_num_flow(tf_host, taas_id)
if tf_nb == 0:
loop_idx = tap_flow_info['loop_idx']
self.vppf.vpp.delete_loopback(loop_idx)
if tf_host != ts_host:
physnet = tap_flow_info['physnet']
net_type = 'vlan'
seg_id = taas_id
self.vppf.delete_network_on_host(
physnet, net_type, seg_id)
else:
tfn = tap_flow_info['tfn']
if tfn:
dst_idx = tap_flow_info['dst_idx']
source_port_idx = tap_flow_info['port_idx']
self.vppf.vpp.disable_port_mirroring(source_port_idx,
dst_idx)
if span_mode == 1 and tfn:
service_bridge = tap_flow_info['service_bridge']
physnet = service_bridge['physnet']
net_type = service_bridge['network_type']
seg_id = service_bridge['segmentation_id']
# check if the local service bridge needs to be removed
spans = self.vppf.vpp.dump_port_mirroring()
cnt = 0
for sp in spans:
if sp.sw_if_index_to == dst_idx:
cnt += 1
if cnt == 0:
self.vppf.delete_network_on_host(
physnet, net_type, seg_id)
elif span_mode == 2: # vxlan
taas_id = tap_flow_info['tf']['taas_id']
tf_host = tap_flow_info['tf']['tf_host']
tf_nb = self._get_num_flow(tf_host, taas_id)
if tf_nb == 0:
if tfn is False:
self.vppf.vpp.delete_from_bridge(
tap_flow_info['dst_idx'])
vni = tap_flow_info['vni']
dst_adr = tap_flow_info['dst_adr']
self._delete_vxlan_tunnel(dst_adr, vni)
self.etcd_client.delete(self._state_key_space +
'/%s' % flow_id)

45
networking_vpp/agent/vpp.py

@ -1491,13 +1491,14 @@ class VPPInterface(object):
state=direction,
is_l2=is_l2)
def disable_port_mirroring(self, source_idx, dest_idx):
def disable_port_mirroring(self, source_idx, dest_idx, is_l2=1):
self.LOG.debug("Disable span from %d to %d",
source_idx, dest_idx)
self.call_vpp('sw_interface_span_enable_disable',
sw_if_index_from=source_idx,
sw_if_index_to=dest_idx,
state=0)
state=0,
is_l2=is_l2)
def dump_port_mirroring(self):
self.LOG.debug("Dump span")
@ -1545,3 +1546,43 @@ class VPPInterface(object):
for tun in t:
tuns[(tun.vni, tun.dst_address,)] = tun.sw_if_index
return tuns
TUNNEL_TYPE_ERSPAN = 2
def create_erspan_tunnel(self, src_addr, dst_addr, is_ipv6, session_id):
self.LOG.debug("Create ERSPAN tunnel session_id: %d", session_id)
# Device instance (ifidx) is selected for us (~0)
t = self.call_vpp('gre_add_del_tunnel',
is_add=1,
is_ipv6=is_ipv6,
tunnel_type=self.TUNNEL_TYPE_ERPSAN,
instance=0xffffffff,
src_address=src_addr,
dst_address=dst_addr,
outer_fib_id=0,
session_id=session_id)
return t.sw_if_index
def delete_erspan_tunnel(self, src_addr, dst_addr, is_ipv6, session_id):
self.LOG.debug("Delete ERSPAN tunnel session_id: %d", session_id)
self.call_vpp('gre_add_del_tunnel',
is_add=0,
is_ipv6=is_ipv6,
tunnel_type=self.TUNNEL_TYPE_ERPSAN,
instance=0xffffffff,
src_address=src_addr,
dst_address=dst_addr,
outer_fib_id=0,
session_id=session_id)
def get_erspan_tunnels(self):
"""Get the list of existing erspan tunnels in this node
Tunnels returned as a hash: (session_id, dest) => tunnel ifidx
"""
t = self.call_vpp('gre_tunnel_dump', sw_if_index=0xffffffff)
tuns = {}
for tun in t:
if tun.tunnel_type == self.TUNNEL_TYPE_ERSPAN:
tuns[(tun.session_id, tun.dst_address,)] = tun.sw_if_index
return tuns

224
networking_vpp/taas_vpp.py

@ -64,6 +64,7 @@ class FeatureTaasService(etcdutils.EtcdChangeWatcher):
"description": "",
"tenant_id": "",
"project_id": "",
"dest_type": "Port",
"port_id": "",
"id": "",
"name": ""},
@ -96,6 +97,23 @@ class FeatureTaasService(etcdutils.EtcdChangeWatcher):
"created_at": "",
"binding:vnic_type": ""}}
The following etcd key structure is created by this class:
path : LEADIN/global/taas_service/<taas_service_id>
{"tap_service":
{"status": "PENDING_ACTIVE",
"description": "",
"tenant_id": "",
"project_id": "",
"dest_type": "ERSPAN",
"port_id": "",
"erspan_src_addr": "",
"erspan_dst_addr": "",
"erspan_network_id": "",
"id": "",
"name": ""},
}
The following etcd key structure is read by this class:
path : LEADIN/state_taas/<hostname>/taas_service/<taas_service_id>
{"port":
@ -149,6 +167,7 @@ class FeatureTaasService(etcdutils.EtcdChangeWatcher):
path = 'taas_service'
nodes_key_space = LEADIN + '/nodes'
global_key_space = LEADIN + '/global'
def __init__(self, service_plugin, etcd_client, name, watch_path):
# The service_plugin is an instance of
@ -160,6 +179,10 @@ class FeatureTaasService(etcdutils.EtcdChangeWatcher):
return (self.nodes_key_space +
'/' + host + '/' + self.path + '/' + str(uuid))
def _build_etcd_global_path(self, uuid):
return (self.global_key_space +
'/' + self.path + '/' + str(uuid))
def added(self, key, value):
"""Called when the etcd tap service state key has been created."""
LOG.info('FeatureTaasService set %s %s', key, str(value))
@ -184,10 +207,25 @@ class FeatureTaasService(etcdutils.EtcdChangeWatcher):
Create a key in etcd nodes/<computeNode>/taas_service to request
the compute node to create a tap service
"""
host = port['binding:host_id']
service_id = taas_data['tap_service']['id']
EtcdJournalHelper.etcd_write(
self._build_etcd_nodes_path(host, service_id), taas_data)
LOG.debug("tap_service create called")
# If there is no port for the tap_service it means we are in
# ERSPAN EXT Mode. There is no compute node associated with the tap
# service. Thus the tap service is put in active mode immediately.
# On the other hand, if there is a port, we have to wait for the
# compute node to answer via ectd before putting the tap_service
# in active mode.
if port is not None:
host = port['binding:host_id']
service_id = taas_data['tap_service']['id']
EtcdJournalHelper.etcd_write(
self._build_etcd_nodes_path(host, service_id), taas_data)
else:
service_id = taas_data['tap_service']['id']
LOG.debug("tap_service service_id=%s" % service_id)
LOG.debug("tap_service taas_data=%s" % taas_data)
taas_data['tap_service']['status'] = constants.ACTIVE
EtcdJournalHelper.etcd_write(
self._build_etcd_global_path(service_id), taas_data)
def delete(self, host, taas_data):
"""Server to compute node - deletion request.
@ -196,8 +234,12 @@ class FeatureTaasService(etcdutils.EtcdChangeWatcher):
the compute node to delete the tap service
"""
service_id = taas_data['tap_service']['id']
EtcdJournalHelper.etcd_write(
self._build_etcd_nodes_path(host, service_id), None)
if host is not None:
EtcdJournalHelper.etcd_write(
self._build_etcd_nodes_path(host, service_id), None)
else:
EtcdJournalHelper.etcd_write(
self._build_etcd_global_path(service_id), None)
class FeatureTaasFlow(etcdutils.EtcdChangeWatcher):
@ -367,6 +409,7 @@ class TaasEtcdDriver(service_drivers.TaasBaseDriver):
etcd_client = self.client_factory.client()
etcd_helper = etcdutils.EtcdHelper(etcd_client)
etcd_helper.ensure_dir(LEADIN + '/state_taas')
etcd_helper.ensure_dir(LEADIN + '/global/taas_service')
self.taas_service = FeatureTaasService(service_plugin,
self.client_factory.client(),
@ -398,6 +441,7 @@ class TaasEtcdDriver(service_drivers.TaasBaseDriver):
This message includes taas_id that is added vlan_range_start to
so that the vpp taas agent can use taas_id as VLANID.
"""
LOG.debug("create_tap_service_precommit called.")
# by default, the status is ACTIVE: wait for creation...
context.tap_service['status'] = constants.PENDING_CREATE
@ -412,20 +456,44 @@ class TaasEtcdDriver(service_drivers.TaasBaseDriver):
context.tap_service})
ts = context.tap_service
tap_id_association = context.tap_id_association
# taas_vlan_id = (tap_id_association['taas_id'] +
# cfg.CONF.taas.vlan_range_start)
taas_vlan_id = tap_id_association['taas_id']
port = self.service_plugin._get_port_details(context._plugin_context,
ts['port_id'])
if taas_vlan_id > cfg.CONF.taas.vlan_range_end:
raise taas_ex.TapServiceLimitReached()
# The field port_id always exists as it's a field in the SQL database.
# For ERSPAN EXT mode, it is set to an empty string
# If the port_id is empty, we're in ERSPAN Ext mode.
# If port_id is not empty and erspan_dst_ip is defined, we're in
# ERSPAN Internal mode.
# Otherwise we're in normal port tapping mode.
if ts['port_id'] == '':
dest_type = 'ERSPAN_EXT'
elif 'erspan_dst_ip' in ts and ts['erspan_dst_ip'] != '':
dest_type = 'ERSPAN_INT'
else:
dest_type = 'Port'
if dest_type != 'ERSPAN_EXT':
taas_vlan_id = tap_id_association['taas_id']
port = self.service_plugin._get_port_details(
context._plugin_context,
ts['port_id'])
if taas_vlan_id > cfg.CONF.taas.vlan_range_end:
raise taas_ex.TapServiceLimitReached()
msg = {"tap_service": ts,
"taas_id": taas_vlan_id,
"port": port}
msg = {"tap_service": ts,
"taas_id": taas_vlan_id,
"port": port,
"dest_type": dest_type}
self.taas_service.create(port, msg)
else:
msg = {"tap_service": ts,
"dest_type": dest_type}
self.taas_service.create(None, msg)
self.service_plugin.update_tap_service(context._plugin_context,
ts['id'],
{'tap_service':
context.tap_service})
self.taas_service.create(port, msg)
return
def create_tap_service_postcommit(self, context):
@ -439,24 +507,35 @@ class TaasEtcdDriver(service_drivers.TaasBaseDriver):
so that the vpp taas agent can use taas_id as VLANID.
"""
ts = context.tap_service
tap_id_association = context.tap_id_association
# taas_vlan_id = (tap_id_association['taas_id'] +
# cfg.CONF.taas.vlan_range_start)
taas_vlan_id = tap_id_association['taas_id']
try:
port = self.service_plugin._get_port_details(
context._plugin_context,
ts['port_id'])
host = port['binding:host_id']
except n_exc.PortNotFound:
# if not found, we just pass to None
port = None
if ts['port_id'] == '':
dest_type = 'ERSPAN_EXT'
elif 'erspan_dst_ip' in ts and ts['erspan_dst_ip'] != '':
dest_type = 'ERSPAN_INT'
else:
dest_type = 'Port'
if dest_type != 'ERSPAN_EXT':
tap_id_association = context.tap_id_association
# taas_vlan_id = (tap_id_association['taas_id'] +
# cfg.CONF.taas.vlan_range_start)
taas_vlan_id = tap_id_association['taas_id']
try:
port = self.service_plugin._get_port_details(
context._plugin_context,
ts['port_id'])
host = port['binding:host_id']
except n_exc.PortNotFound:
# if not found, we just pass to None
port = None
host = None
msg = {"tap_service": ts,
"taas_id": taas_vlan_id,
"port": port}
else:
msg = {"tap_service": ts}
host = None
msg = {"tap_service": ts,
"taas_id": taas_vlan_id,
"port": port}
self.taas_service.delete(host, msg)
return
@ -466,6 +545,7 @@ class TaasEtcdDriver(service_drivers.TaasBaseDriver):
def create_tap_flow_precommit(self, context):
"""Send tap flow creation message to agent."""
LOG.debug("create_tap_flow_precommit called.")
tf = context.tap_flow
tf['status'] = constants.PENDING_CREATE
taas_id = self._get_taas_id(context._plugin_context, tf)
@ -484,23 +564,40 @@ class TaasEtcdDriver(service_drivers.TaasBaseDriver):
# has been added in order for the node to create the vxlan tunnel.
ts = self.service_plugin.get_tap_service(context._plugin_context,
tf['tap_service_id'])
ts_port = self.service_plugin._get_port_details(
context._plugin_context,
ts['port_id'])
ts_host = ts_port['binding:host_id']
ts_port_mac = ts_port['mac_address']
# This status will be set in the callback
msg = {"tap_flow": tf,
"port_mac": port_mac,
"taas_id": taas_id,
"port": port,
"ts_port_mac": ts_port_mac,
"tf_host": tf_host,
"ts_host": ts_host}
self.taas_flow.create(port, msg)
if ts_host != tf_host:
self.taas_flow.create(ts_port, msg)
if ts['port_id'] == '':
dest_type = 'ERSPAN_EXT'
elif 'erspan_dst_ip' in ts and ts['erspan_dst_ip'] != '':
dest_type = 'ERSPAN_INT'
else:
dest_type = 'Port'
if dest_type == 'Port' or dest_type == 'ERSPAN_INT':
ts_port = self.service_plugin._get_port_details(
context._plugin_context,
ts['port_id'])
ts_host = ts_port['binding:host_id']
ts_port_mac = ts_port['mac_address']
# This status will be set in the callback
msg = {"tap_flow": tf,
"port_mac": port_mac,
"taas_id": taas_id,
"port": port,
"ts_port_mac": ts_port_mac,
"tf_host": tf_host,
"ts_host": ts_host,
"dest_type": dest_type}
self.taas_flow.create(port, msg)
if dest_type == 'Port' and ts_host != tf_host:
self.taas_flow.create(ts_port, msg)
else:
msg = {"tap_flow": tf,
"port_mac": port_mac,
"taas_id": taas_id,
"port": port,
"tf_host": tf_host,
"dest_type": dest_type}
self.taas_flow.create(port, msg)
return
def create_tap_flow_postcommit(self, context):
@ -518,14 +615,24 @@ class TaasEtcdDriver(service_drivers.TaasBaseDriver):
# Find the host of the tap service
ts = self.service_plugin.get_tap_service(context._plugin_context,
tf['tap_service_id'])
ts_port = self.service_plugin._get_port_details(
context._plugin_context,
ts['port_id'])
ts_host = ts_port['binding:host_id']
self.taas_flow.delete(host, tf['id'])
if ts_host != host:
self.taas_flow.delete(ts_host, tf['id'])
if ts['port_id'] == '':
dest_type = 'ERSPAN_EXT'
elif 'erspan_dst_ip' in ts and ts['erspan_dst_ip'] != '':
dest_type = 'ERSPAN_INT'
else:
dest_type = 'Port'
if dest_type == 'Port' or dest_type == 'ERSPAN_INT':
ts_port = self.service_plugin._get_port_details(
context._plugin_context,
ts['port_id'])
ts_host = ts_port['binding:host_id']
self.taas_flow.delete(host, tf['id'])
if dest_type == 'Port' and ts_host != host:
self.taas_flow.delete(ts_host, tf['id'])
else:
self.taas_flow.delete(host, tf['id'])
return
def delete_tap_flow_postcommit(self, context):
@ -538,6 +645,7 @@ class TaasVPPDriverExtension(MechDriverExtensionBase):
pass
def run(self, communicator):
LOG.debug("####TaasVPPDriverExtension run called.")
self.etcdJournalHelper = \
EtcdJournalHelper(
communicator,

Loading…
Cancel
Save