X-Git-Url: https://gerrit.opnfv.org/gerrit/gitweb?a=blobdiff_plain;f=nfvbench%2Fchaining.py;h=b9ed48bea80c50089f18c497babc82f66b6055fd;hb=423f360415e2834dd8de065434023b822e2ca3f8;hp=60f3832431b76b7bee543571b4fbf056722bb889;hpb=7439c895c0ad2960712910ab6b72042884ce2a41;p=nfvbench.git diff --git a/nfvbench/chaining.py b/nfvbench/chaining.py index 60f3832..b9ed48b 100644 --- a/nfvbench/chaining.py +++ b/nfvbench/chaining.py @@ -54,13 +54,16 @@ from neutronclient.neutron import client as neutronclient from novaclient.client import Client from attrdict import AttrDict -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 +# L3 traffic edge networks are at the end of networks list +EDGE_LEFT = -2 +EDGE_RIGHT = -1 # Name of the VM config file NFVBENCH_CFG_FILENAME = 'nfvbenchvm.conf' # full pathame of the VM config in the VM @@ -74,8 +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.""" @@ -131,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 @@ -174,18 +176,39 @@ class ChainVnfPort(object): """Get the MAC address for this port.""" return self.port['mac_address'] + def get_ip(self): + """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) @@ -222,6 +245,8 @@ class ChainNetwork(object): self.reuse = False self.network = None self.vlan = None + if manager.config.l3_router and hasattr(network_config, 'router_name'): + self.router_name = network_config.router_name try: self._setup(network_config, lookup_only) except Exception: @@ -294,7 +319,7 @@ class ChainNetwork(object): 'network': { 'name': self.name, 'admin_state_up': True - } + } } if network_config.network_type: body['network']['provider:network_type'] = network_config.network_type @@ -342,24 +367,30 @@ class ChainNetwork(object): :return: VNI ID for this network """ - if 'vxlan' not in self.network['provider:network_type']: - raise ChainException('Trying to retrieve VNI for non VXLAN network') + + 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) @@ -388,6 +419,8 @@ 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 self.reuse = False @@ -397,7 +430,10 @@ class ChainVnf(object): try: # the vnf_id is conveniently also the starting index in networks # for the left and right networks associated to this VNF - self._setup(networks[vnf_id:vnf_id + 2]) + if self.manager.config.l3_router: + self._setup(networks[vnf_id:vnf_id + 4]) + else: + self._setup(networks[vnf_id:vnf_id + 2]) except Exception: LOG.error("Error creating VNF %s", self.name) self.delete() @@ -406,25 +442,81 @@ class ChainVnf(object): def _get_vm_config(self, remote_mac_pair): config = self.manager.config devices = self.manager.generator_config.devices + + if config.l3_router: + tg_gateway1_ip = self.routers[LEFT].ports[1]['fixed_ips'][0][ + 'ip_address'] # router edge ip left + tg_gateway2_ip = self.routers[RIGHT].ports[1]['fixed_ips'][0][ + 'ip_address'] # router edge ip right + tg_mac1 = self.routers[LEFT].ports[1]['mac_address'] # router edge mac left + 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.__get_network_mask( + self.manager.config.edge_networks.left.cidr) + # edge cidr mask right + vnf_gateway2_cidr = \ + 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.' + 'Please update your config file with: vm_forwarder: vpp') + else: + tg_gateway1_ip = devices[LEFT].tg_gateway_ip_addrs + tg_gateway2_ip = devices[RIGHT].tg_gateway_ip_addrs + 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.__get_network_mask( + self.manager.config.internal_networks.left.cidr) + g2cidr = devices[RIGHT].get_gw_ip( + 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: content = boot_script.read() - g1cidr = devices[LEFT].get_gw_ip(self.chain.chain_id) + '/8' - g2cidr = devices[RIGHT].get_gw_ip(self.chain.chain_id) + '/8' vm_config = { 'forwarder': config.vm_forwarder, 'intf_mac1': self.ports[LEFT].get_mac(), 'intf_mac2': self.ports[RIGHT].get_mac(), - 'tg_gateway1_ip': devices[LEFT].tg_gateway_ip_addrs, - 'tg_gateway2_ip': devices[RIGHT].tg_gateway_ip_addrs, + 'tg_gateway1_ip': tg_gateway1_ip, + 'tg_gateway2_ip': tg_gateway2_ip, 'tg_net1': devices[LEFT].ip_addrs, 'tg_net2': devices[RIGHT].ip_addrs, - 'vnf_gateway1_cidr': g1cidr, - 'vnf_gateway2_cidr': g2cidr, - 'tg_mac1': remote_mac_pair[0], - 'tg_mac2': remote_mac_pair[1] + 'vnf_gateway1_cidr': vnf_gateway1_cidr, + 'vnf_gateway2_cidr': vnf_gateway2_cidr, + 'tg_mac1': tg_mac1, + 'tg_mac2': tg_mac2, + '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. @@ -504,46 +596,100 @@ class ChainVnf(object): # Check if we can reuse an instance with same name for instance in self.manager.existing_instances: if instance.name == self.name: + instance_left = LEFT + instance_right = RIGHT + # In case of L3 traffic instance use edge networks + if self.manager.config.l3_router: + instance_left = EDGE_LEFT + instance_right = EDGE_RIGHT # Verify that other instance characteristics match if instance.flavor['id'] != flavor_id: self._reuse_exception('Flavor mismatch') if instance.status != "ACTIVE": self._reuse_exception('Matching instance is not in ACTIVE state') # The 2 networks for this instance must also be reused - if not networks[LEFT].reuse: - self._reuse_exception('network %s is new' % networks[LEFT].name) - if not networks[RIGHT].reuse: - self._reuse_exception('network %s is new' % networks[RIGHT].name) + if not networks[instance_left].reuse: + self._reuse_exception('network %s is new' % networks[instance_left].name) + if not networks[instance_right].reuse: + self._reuse_exception('network %s is new' % networks[instance_right].name) # instance.networks have the network names as keys: # {'nfvbench-rnet0': ['192.168.2.10'], 'nfvbench-lnet0': ['192.168.1.8']} - if networks[LEFT].name not in instance.networks: + if networks[instance_left].name not in instance.networks: self._reuse_exception('Left network mismatch') - if networks[RIGHT].name not in instance.networks: + if networks[instance_right].name not in instance.networks: self._reuse_exception('Right network mismatch') self.reuse = True 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 - self.ports = [ChainVnfPort(self.name + '-' + str(index), - self, - networks[index], - self._get_vnic_type(index)) for index in [0, 1]] + if self.manager.config.l3_router: + for index in [0, 1]: + self.ports.append(ChainVnfPort(self.name + '-' + str(index), + self, + networks[index + 2], + self._get_vnic_type(index))) + else: + 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 if not self.reuse: self._get_idle_networks_ports() + # Create neutron routers for L3 traffic use case + if self.manager.config.l3_router and self.manager.openstack: + internal_nets = networks[:2] + if self.manager.config.service_chain == ChainType.PVP: + edge_nets = networks[2:] + else: + edge_nets = networks[3:] + subnets_left = [internal_nets[0], edge_nets[0]] + routes_left = [{'destination': self.manager.config.traffic_generator.ip_addrs[0], + 'nexthop': self.manager.config.traffic_generator.tg_gateway_ip_addrs[ + 0]}, + {'destination': self.manager.config.traffic_generator.ip_addrs[1], + 'nexthop': self.ports[0].get_ip()}] + self.routers.append( + ChainRouter(self.manager, edge_nets[0].router_name, subnets_left, routes_left)) + subnets_right = [internal_nets[1], edge_nets[1]] + routes_right = [{'destination': self.manager.config.traffic_generator.ip_addrs[0], + 'nexthop': self.ports[1].get_ip()}, + {'destination': self.manager.config.traffic_generator.ip_addrs[1], + 'nexthop': self.manager.config.traffic_generator.tg_gateway_ip_addrs[ + 1]}] + self.routers.append( + ChainRouter(self.manager, edge_nets[1].router_name, subnets_right, routes_right)) + # Overload gateway_ips property with router ip address for ARP and traffic calls + self.manager.generator_config.devices[LEFT].set_gw_ip( + self.routers[LEFT].ports[0]['fixed_ips'][0]['ip_address']) # router edge ip left) + self.manager.generator_config.devices[RIGHT].set_gw_ip( + self.routers[RIGHT].ports[0]['fixed_ips'][0]['ip_address']) # router edge ip right) + # if no reuse, actual vm creation is deferred after all ports in the chain are created # since we need to know the next mac in a multi-vnf chain 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']}) @@ -570,8 +716,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() @@ -651,6 +797,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: @@ -658,6 +806,7 @@ class ChainVnf(object): for network in self.idle_networks: network.delete() + class Chain(object): """A class to manage a single chain. @@ -719,7 +868,8 @@ class Chain(object): def get_length(self): """Get the number of VNF in the chain.""" - return len(self.networks) - 1 + # Take into account 2 edge networks for routers + return len(self.networks) - 3 if self.manager.config.l3_router else len(self.networks) - 1 def _get_remote_mac_pairs(self): """Get the list of remote mac pairs for every VNF in the chain. @@ -790,6 +940,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. @@ -965,6 +1129,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 @@ -1088,12 +1262,16 @@ class ChainManager(object): LOG.info('Image %s successfully uploaded.', self.image_name) self.image_instance = self.comp.find_image(self.image_name) + # image multiqueue property must be set according to the vif_multiqueue_size + # config value (defaults to 1 or disabled) + self.comp.image_set_multiqueue(self.image_instance, self.config.vif_multiqueue_size > 1) + def _ensure_instances_active(self): instances = [] for chain in self.chains: instances.extend(chain.get_instances()) initial_instance_count = len(instances) - max_retries = (self.config.check_traffic_time_sec + + 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 while instances: @@ -1151,6 +1329,10 @@ class ChainManager(object): net_cfg = [int_nets.left, int_nets.right] else: net_cfg = [int_nets.left, int_nets.middle, int_nets.right] + if self.config.l3_router: + edge_nets = self.config.edge_networks + net_cfg.append(edge_nets.left) + net_cfg.append(edge_nets.right) networks = [] try: for cfg in net_cfg: @@ -1221,7 +1403,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: @@ -1266,6 +1448,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. @@ -1323,7 +1517,6 @@ class ChainManager(object): if hypervisor: LOG.info('Found hypervisor for EXT chain: %s', hypervisor.hypervisor_hostname) return[':' + hypervisor.hypervisor_hostname] - # no openstack = no chains return [] @@ -1333,5 +1526,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()