NFVBENCH-153 Add support for python3
[nfvbench.git] / nfvbench / chaining.py
index 3350299..a8d6295 100644 (file)
@@ -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
@@ -182,18 +180,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)
 
@@ -352,24 +367,21 @@ 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 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 +410,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 +443,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.'
@@ -445,9 +460,11 @@ class ChainVnf(object):
             tg_mac2 = remote_mac_pair[1]
 
             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
@@ -466,10 +483,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 +610,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 +673,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 +703,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 +784,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:
@@ -1053,6 +1102,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
@@ -1185,7 +1244,8 @@ 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 +
+        # 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
         while instances:
@@ -1317,7 +1377,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:
@@ -1419,7 +1479,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 []
 
@@ -1429,5 +1488,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()