aboutsummaryrefslogtreecommitdiffstats
path: root/nfvbench/chaining.py
diff options
context:
space:
mode:
Diffstat (limited to 'nfvbench/chaining.py')
-rw-r--r--nfvbench/chaining.py208
1 files changed, 167 insertions, 41 deletions
diff --git a/nfvbench/chaining.py b/nfvbench/chaining.py
index 49d23b7..d6f67f9 100644
--- a/nfvbench/chaining.py
+++ b/nfvbench/chaining.py
@@ -54,10 +54,10 @@ from neutronclient.neutron import client as neutronclient
from novaclient.client import Client
from attrdict import AttrDict
-from chain_router import ChainRouter
-import compute
-from log import LOG
-from specs import ChainType
+from .chain_router import ChainRouter
+from . import compute
+from .log import LOG
+from .specs import ChainType
# Left and right index for network and port lists
LEFT = 0
RIGHT = 1
@@ -77,9 +77,6 @@ BOOT_SCRIPT_PATHNAME = os.path.join(os.path.dirname(os.path.abspath(__file__)),
class ChainException(Exception):
"""Exception while operating the chains."""
- pass
-
-
class NetworkEncaps(object):
"""Network encapsulation."""
@@ -135,6 +132,7 @@ class ChainVnfPort(object):
self.manager = vnf.manager
self.reuse = False
self.port = None
+ self.floating_ip = None
if vnf.instance:
# VNF instance is reused, we need to find an existing port that matches this instance
# and network
@@ -160,6 +158,10 @@ class ChainVnfPort(object):
'binding:vnic_type': vnic_type
}
}
+ subnet_id = chain_network.get_subnet_uuid()
+ if subnet_id:
+ body['port']['fixed_ips'] = [{'subnet_id': subnet_id}]
+
port = self.manager.neutron_client.create_port(body)
self.port = port['port']
LOG.info('Created port %s', name)
@@ -182,18 +184,35 @@ class ChainVnfPort(object):
"""Get the IP address for this port."""
return self.port['fixed_ips'][0]['ip_address']
+ def set_floating_ip(self, chain_network):
+ # create and add floating ip to port
+ try:
+ self.floating_ip = self.manager.neutron_client.create_floatingip({
+ 'floatingip': {
+ 'floating_network_id': chain_network.get_uuid(),
+ 'port_id': self.port['id'],
+ 'description': 'nfvbench floating ip for port:' + self.port['name'],
+ }})['floatingip']
+ LOG.info('Floating IP %s created and associated on port %s',
+ self.floating_ip['floating_ip_address'], self.name)
+ return self.floating_ip['floating_ip_address']
+ except Exception:
+ LOG.info('Failed to created and associated floating ip on port %s (ignored)', self.name)
+ return self.port['fixed_ips'][0]['ip_address']
+
def delete(self):
"""Delete this port instance."""
if self.reuse or not self.port:
return
- retry = 0
- while retry < self.manager.config.generic_retry_count:
+ for _ in range(0, self.manager.config.generic_retry_count):
try:
self.manager.neutron_client.delete_port(self.port['id'])
LOG.info("Deleted port %s", self.name)
+ if self.floating_ip:
+ self.manager.neutron_client.delete_floatingip(self.floating_ip['id'])
+ LOG.info("Deleted floating IP %s", self.floating_ip['description'])
return
except Exception:
- retry += 1
time.sleep(self.manager.config.generic_poll_sec)
LOG.error('Unable to delete port: %s', self.name)
@@ -225,11 +244,13 @@ class ChainNetwork(object):
self.name = self.name + suffix
self.segmentation_id = self._get_item(network_config.segmentation_id,
chain_id, auto_index=True)
+ self.subnet_name = self._get_item(network_config.subnet, chain_id)
self.physical_network = self._get_item(network_config.physical_network, chain_id)
self.reuse = False
self.network = None
self.vlan = None
+ self.router_name = None
if manager.config.l3_router and hasattr(network_config, 'router_name'):
self.router_name = network_config.router_name
try:
@@ -263,7 +284,7 @@ class ChainNetwork(object):
return item_field[index]
except IndexError:
raise ChainException("List %s is too short for chain index %d" %
- (str(item_field), index))
+ (str(item_field), index)) from IndexError
# single value is configured
if auto_index:
return item_field + index
@@ -336,6 +357,18 @@ class ChainNetwork(object):
"""
return self.network['id']
+ def get_subnet_uuid(self):
+ """
+ Extract UUID of this subnet network.
+
+ :return: UUID of this subnet network
+ """
+ for subnet in self.network['subnets']:
+ if self.subnet_name == self.manager.neutron_client \
+ .show_subnet(subnet)['subnet']['name']:
+ return subnet
+ return None
+
def get_vlan(self):
"""
Extract vlan for this network.
@@ -355,20 +388,27 @@ class ChainNetwork(object):
return self.network['provider:segmentation_id']
+ def get_mpls_inner_label(self):
+ """
+ Extract MPLS VPN Label for this network.
+
+ :return: MPLS VPN Label for this network
+ """
+
+ return self.network['provider:segmentation_id']
+
def delete(self):
"""Delete this network."""
if not self.reuse and self.network:
- retry = 0
- while retry < self.manager.config.generic_retry_count:
+ for retry in range(0, self.manager.config.generic_retry_count):
try:
self.manager.neutron_client.delete_network(self.network['id'])
LOG.info("Deleted network: %s", self.name)
return
except Exception:
- retry += 1
LOG.info('Error deleting network %s (retry %d/%d)...',
self.name,
- retry,
+ retry + 1,
self.manager.config.generic_retry_count)
time.sleep(self.manager.config.generic_poll_sec)
LOG.error('Unable to delete network: %s', self.name)
@@ -397,6 +437,7 @@ class ChainVnf(object):
# For example if 7 idle interfaces are requested, the corresp. ports will be
# at index 2 to 8
self.ports = []
+ self.management_port = None
self.routers = []
self.status = None
self.instance = None
@@ -429,10 +470,12 @@ class ChainVnf(object):
tg_mac2 = self.routers[RIGHT].ports[1]['mac_address'] # router edge mac right
# edge cidr mask left
vnf_gateway1_cidr = \
- self.ports[LEFT].get_ip() + self.manager.config.edge_networks.left.cidr[-3:]
+ self.ports[LEFT].get_ip() + self.__get_network_mask(
+ self.manager.config.edge_networks.left.cidr)
# edge cidr mask right
vnf_gateway2_cidr = \
- self.ports[RIGHT].get_ip() + self.manager.config.edge_networks.right.cidr[-3:]
+ self.ports[RIGHT].get_ip() + self.__get_network_mask(
+ self.manager.config.edge_networks.right.cidr)
if config.vm_forwarder != 'vpp':
raise ChainException(
'L3 router mode imply to set VPP as VM forwarder.'
@@ -440,18 +483,24 @@ class ChainVnf(object):
else:
tg_gateway1_ip = devices[LEFT].tg_gateway_ip_addrs
tg_gateway2_ip = devices[RIGHT].tg_gateway_ip_addrs
- tg_mac1 = remote_mac_pair[0]
- tg_mac2 = remote_mac_pair[1]
+ if not config.loop_vm_arp:
+ tg_mac1 = remote_mac_pair[0]
+ tg_mac2 = remote_mac_pair[1]
+ else:
+ tg_mac1 = ""
+ tg_mac2 = ""
g1cidr = devices[LEFT].get_gw_ip(
- self.chain.chain_id) + self.manager.config.internal_networks.left.cidr[-3:]
+ self.chain.chain_id) + self.__get_network_mask(
+ self.manager.config.internal_networks.left.cidr)
g2cidr = devices[RIGHT].get_gw_ip(
- self.chain.chain_id) + self.manager.config.internal_networks.right.cidr[-3:]
+ self.chain.chain_id) + self.__get_network_mask(
+ self.manager.config.internal_networks.right.cidr)
vnf_gateway1_cidr = g1cidr
vnf_gateway2_cidr = g2cidr
- with open(BOOT_SCRIPT_PATHNAME, 'r') as boot_script:
+ with open(BOOT_SCRIPT_PATHNAME, 'r', encoding="utf-8") as boot_script:
content = boot_script.read()
vm_config = {
'forwarder': config.vm_forwarder,
@@ -465,10 +514,27 @@ class ChainVnf(object):
'vnf_gateway2_cidr': vnf_gateway2_cidr,
'tg_mac1': tg_mac1,
'tg_mac2': tg_mac2,
- 'vif_mq_size': config.vif_multiqueue_size
+ 'vif_mq_size': config.vif_multiqueue_size,
+ 'num_mbufs': config.num_mbufs
}
+ if self.manager.config.use_management_port:
+ mgmt_ip = self.management_port.port['fixed_ips'][0]['ip_address']
+ mgmt_mask = self.__get_network_mask(self.manager.config.management_network.cidr)
+ vm_config['intf_mgmt_cidr'] = mgmt_ip + mgmt_mask
+ vm_config['intf_mgmt_ip_gw'] = self.manager.config.management_network.gateway
+ vm_config['intf_mac_mgmt'] = self.management_port.port['mac_address']
+ else:
+ # Interface management config left empty to avoid error in VM spawn
+ # if nfvbench config has values for management network but use_management_port=false
+ vm_config['intf_mgmt_cidr'] = ''
+ vm_config['intf_mgmt_ip_gw'] = ''
+ vm_config['intf_mac_mgmt'] = ''
return content.format(**vm_config)
+ @staticmethod
+ def __get_network_mask(network):
+ return '/' + network.split('/')[1]
+
def _get_vnic_type(self, port_index):
"""Get the right vnic type for given port indexself.
@@ -575,17 +641,28 @@ class ChainVnf(object):
self.instance = instance
LOG.info('Reusing existing instance %s on %s',
self.name, self.get_hypervisor_name())
+ # create management port if needed
+ if self.manager.config.use_management_port:
+ self.management_port = ChainVnfPort(self.name + '-mgmt', self,
+ self.manager.management_network, 'normal')
+ ip = self.management_port.port['fixed_ips'][0]['ip_address']
+ if self.manager.config.use_floating_ip:
+ ip = self.management_port.set_floating_ip(self.manager.floating_ip_network)
+ LOG.info("Management interface will be active using IP: %s, "
+ "and you can connect over SSH with login: nfvbench and password: nfvbench", ip)
# create or reuse/discover 2 ports per instance
if self.manager.config.l3_router:
- self.ports = [ChainVnfPort(self.name + '-' + str(index),
- self,
- networks[index + 2],
- self._get_vnic_type(index)) for index in [0, 1]]
+ for index in [0, 1]:
+ self.ports.append(ChainVnfPort(self.name + '-' + str(index),
+ self,
+ networks[index + 2],
+ self._get_vnic_type(index)))
else:
- self.ports = [ChainVnfPort(self.name + '-' + str(index),
- self,
- networks[index],
- self._get_vnic_type(index)) for index in [0, 1]]
+ for index in [0, 1]:
+ self.ports.append(ChainVnfPort(self.name + '-' + str(index),
+ self,
+ networks[index],
+ self._get_vnic_type(index)))
# create idle networks and ports only if instance is not reused
# if reused, we do not care about idle networks/ports
@@ -627,8 +704,10 @@ class ChainVnf(object):
def create_vnf(self, remote_mac_pair):
"""Create the VNF instance if it does not already exist."""
if self.instance is None:
- port_ids = [{'port-id': vnf_port.port['id']}
- for vnf_port in self.ports]
+ port_ids = []
+ if self.manager.config.use_management_port:
+ port_ids.append({'port-id': self.management_port.port['id']})
+ port_ids.extend([{'port-id': vnf_port.port['id']} for vnf_port in self.ports])
# add idle ports
for idle_port in self.idle_ports:
port_ids.append({'port-id': idle_port.port['id']})
@@ -655,8 +734,8 @@ class ChainVnf(object):
# here we MUST wait until this instance is resolved otherwise subsequent
# VNF creation can be placed in other hypervisors!
config = self.manager.config
- max_retries = (config.check_traffic_time_sec +
- config.generic_poll_sec - 1) / config.generic_poll_sec
+ max_retries = int((config.check_traffic_time_sec +
+ config.generic_poll_sec - 1) / config.generic_poll_sec)
retry = 0
for retry in range(max_retries):
status = self.get_status()
@@ -736,6 +815,8 @@ class ChainVnf(object):
if self.instance:
self.manager.comp.delete_server(self.instance)
LOG.info("Deleted instance %s", self.name)
+ if self.manager.config.use_management_port:
+ self.management_port.delete()
for port in self.ports:
port.delete()
for port in self.idle_ports:
@@ -861,7 +942,10 @@ class Chain(object):
if port_index:
# this will pick the last item in array
port_index = -1
- return self.networks[port_index].get_vlan()
+ # This string filters networks connected to TG, in case of
+ # l3-router feature we have 4 networks instead of 2
+ networks = [x for x in self.networks if not x.router_name]
+ return networks[port_index].get_vlan()
def get_vxlan(self, port_index):
"""Get the VXLAN id on a given port.
@@ -877,6 +961,20 @@ class Chain(object):
port_index = -1
return self.networks[port_index].get_vxlan()
+ def get_mpls_inner_label(self, port_index):
+ """Get the MPLS VPN Label on a given port.
+
+ port_index: left port is 0, right port is 1
+ return: the mpls_label_id or None if there is no mpls
+ """
+ # for port 1 we need to return the MPLS Label of the last network in the chain
+ # The networks array contains 2 networks for PVP [left, right]
+ # and 3 networks in the case of PVVP [left.middle,right]
+ if port_index:
+ # this will pick the last item in array
+ port_index = -1
+ return self.networks[port_index].get_mpls_inner_label()
+
def get_dest_mac(self, port_index):
"""Get the dest MAC on a given port.
@@ -1052,6 +1150,16 @@ class ChainManager(object):
self.flavor = ChainFlavor(config.flavor_type, config.flavor, self.comp)
# Get list of all existing instances to check if some instances can be reused
self.existing_instances = self.comp.get_server_list()
+ # If management port is requested for VMs, create management network (shared)
+ if self.config.use_management_port:
+ self.management_network = ChainNetwork(self, self.config.management_network,
+ None, False)
+ # If floating IP is used for management, create and share
+ # across chains the floating network
+ if self.config.use_floating_ip:
+ self.floating_ip_network = ChainNetwork(self,
+ self.config.floating_network,
+ None, False)
else:
# For EXT chains, the external_networks left and right fields in the config
# must be either a prefix string or a list of at least chain-count strings
@@ -1104,7 +1212,8 @@ class ChainManager(object):
self.vlans = [self._check_list('vlans[0]', self.config.vlans[0], re_vlan),
self._check_list('vlans[1]', self.config.vlans[1], re_vlan)]
except IndexError:
- raise ChainException('vlans parameter is mandatory. Set valid value in config file')
+ raise ChainException(
+ 'vlans parameter is mandatory. Set valid value in config file') from IndexError
def _get_dest_macs_from_config(self):
re_mac = "[0-9a-fA-F]{2}([-:])[0-9a-fA-F]{2}(\\1[0-9a-fA-F]{2}){4}$"
@@ -1118,6 +1227,8 @@ class ChainManager(object):
# if it is a single int or mac, make it a list of 1 int
if isinstance(ll, (int, str)):
ll = [ll]
+ else:
+ ll = list(ll)
for item in ll:
if not re.match(pattern, str(item)):
raise ChainException("Invalid format '{item}' specified in {fname}"
@@ -1184,7 +1295,6 @@ class ChainManager(object):
for chain in self.chains:
instances.extend(chain.get_instances())
initial_instance_count = len(instances)
- # Give additional 10 seconds per VM
max_retries = (self.config.check_traffic_time_sec + (initial_instance_count - 1) * 10 +
self.config.generic_poll_sec - 1) / self.config.generic_poll_sec
retry = 0
@@ -1231,6 +1341,7 @@ class ChainManager(object):
lookup_only = True
ext_net = self.config.external_networks
net_cfg = [AttrDict({'name': name,
+ 'subnet': None,
'segmentation_id': None,
'physical_network': None})
for name in [ext_net.left, ext_net.right]]
@@ -1317,7 +1428,7 @@ class ChainManager(object):
return: the hypervisor where the matching port runs or None if not found
"""
# _existing_ports is a dict of list of ports indexed by network id
- for port_list in self.get_existing_ports().values():
+ for port_list in list(self.get_existing_ports().values()):
for port in port_list:
try:
if port['mac_address'] == mac:
@@ -1362,6 +1473,18 @@ class ChainManager(object):
# no openstack
raise ChainException('VxLAN is only supported with OpenStack and with admin user')
+ def get_chain_mpls_inner_labels(self, port_index):
+ """Get the list of per chain MPLS VPN Labels on a given port.
+
+ port_index: left port is 0, right port is 1
+ return: a MPLSs ID list indexed by the chain index or None if no mpls
+ """
+ if self.chains and self.is_admin:
+ return [self.chains[chain_index].get_mpls_inner_label(port_index)
+ for chain_index in range(self.chain_count)]
+ # no openstack
+ raise ChainException('MPLS is only supported with OpenStack and with admin user')
+
def get_dest_macs(self, port_index):
"""Get the list of per chain dest MACs on a given port.
@@ -1418,8 +1541,7 @@ class ChainManager(object):
hypervisor = self.get_hypervisor_from_mac(dst_mac)
if hypervisor:
LOG.info('Found hypervisor for EXT chain: %s', hypervisor.hypervisor_hostname)
- return[':' + hypervisor.hypervisor_hostname]
-
+ return [':' + hypervisor.hypervisor_hostname]
# no openstack = no chains
return []
@@ -1429,5 +1551,9 @@ class ChainManager(object):
chain.delete()
for network in self.networks:
network.delete()
+ if self.config.use_management_port and hasattr(self, 'management_network'):
+ self.management_network.delete()
+ if self.config.use_floating_ip and hasattr(self, 'floating_ip_network'):
+ self.floating_ip_network.delete()
if self.flavor:
self.flavor.delete()