X-Git-Url: https://gerrit.opnfv.org/gerrit/gitweb?a=blobdiff_plain;f=nfvbench%2Fchaining.py;h=d6f67f9bc003984a42bd6551c8e9a9575c596e03;hb=1c9894ba0ccb31d64c5b2d15c700b98e332e0672;hp=3350299b2c8acf828013429efe57cb2f10e20b8b;hpb=4453818e3af2143e099a5f578c4a73b25abbfe58;p=nfvbench.git diff --git a/nfvbench/chaining.py b/nfvbench/chaining.py index 3350299..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. @@ -352,24 +385,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) @@ -398,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 @@ -430,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.' @@ -441,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, @@ -466,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. @@ -576,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 @@ -628,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']}) @@ -656,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() @@ -737,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: @@ -862,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. @@ -878,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. @@ -1053,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 @@ -1105,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}$" @@ -1119,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}" @@ -1185,7 +1295,7 @@ class ChainManager(object): 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: @@ -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()