summaryrefslogtreecommitdiffstats
path: root/nfvbench/chaining.py
diff options
context:
space:
mode:
Diffstat (limited to 'nfvbench/chaining.py')
-rw-r--r--nfvbench/chaining.py151
1 files changed, 137 insertions, 14 deletions
diff --git a/nfvbench/chaining.py b/nfvbench/chaining.py
index e5a9f0a..a5ae680 100644
--- a/nfvbench/chaining.py
+++ b/nfvbench/chaining.py
@@ -399,8 +399,7 @@ class ChainVnf(object):
self._reuse_exception('Left network mismatch')
if networks[RIGHT].name not in instance.networks:
self._reuse_exception('Right network mismatch')
- # Other checks not performed (yet)
- # check if az and compute node match
+
self.reuse = True
self.instance = instance
LOG.info('Reusing existing instance %s on %s',
@@ -413,17 +412,13 @@ class ChainVnf(object):
# 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 get_az(self):
- """Get the AZ associated to this VNF."""
- return self.manager.az[0]
-
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]
vm_config = self._get_vm_config(remote_mac_pair)
- az = self.get_az()
+ az = self.manager.placer.get_required_az()
server = self.manager.comp.create_server(self.name,
self.manager.image_instance,
self.manager.flavor.flavor,
@@ -435,8 +430,38 @@ class ChainVnf(object):
config_drive=True,
files={NFVBENCH_CFG_VM_PATHNAME: vm_config})
if server:
- LOG.info('Created instance %s on %s', self.name, az)
self.instance = server
+ if self.manager.placer.is_resolved():
+ LOG.info('Created instance %s on %s', self.name, az)
+ else:
+ # the location is undetermined at this point
+ # self.get_hypervisor_name() will return None
+ LOG.info('Created instance %s - waiting for placement resolution...', self.name)
+ # 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
+ retry = 0
+ for retry in range(max_retries):
+ status = self.get_status()
+ if status == 'ACTIVE':
+ hyp_name = self.get_hypervisor_name()
+ LOG.info('Instance %s is active and has been placed on %s',
+ self.name, hyp_name)
+ self.manager.placer.register_full_name(hyp_name)
+ break
+ if status == 'ERROR':
+ raise ChainException('Instance %s creation error: %s' %
+ (self.name,
+ self.instance.fault['message']))
+ LOG.info('Waiting for instance %s to become active (retry %d/%d)...',
+ self.name, retry + 1, max_retries + 1)
+ time.sleep(config.generic_poll_sec)
+ else:
+ # timing out
+ LOG.error('Instance %s creation timed out', self.name)
+ raise ChainException('Instance %s creation timed out' % self.name)
self.reuse = False
else:
raise ChainException('Unable to create instance: %s' % (self.name))
@@ -514,6 +539,9 @@ class Chain(object):
self.instances.append(ChainVnf(self,
chain_instance_index,
self.networks))
+ # at this point new VNFs are not created yet but
+ # verify that all discovered VNFs are on the same hypervisor
+ self._check_hypervisors()
# now that all VNF ports are created we need to calculate the
# left/right remote MAC for each VNF in the chain
# before actually creating the VNF itself
@@ -525,6 +553,25 @@ class Chain(object):
self.delete()
raise
+ def _check_hypervisors(self):
+ common_hypervisor = None
+ for instance in self.instances:
+ # get the full hypervizor name (az:compute)
+ hname = instance.get_hypervisor_name()
+ if hname:
+ if common_hypervisor:
+ if hname != common_hypervisor:
+ raise ChainException('Discovered instances on different hypervisors:'
+ ' %s and %s' % (hname, common_hypervisor))
+ else:
+ common_hypervisor = hname
+ if common_hypervisor:
+ # check that the common hypervisor name matchs the requested hypervisor name
+ # and set the name to be used by all future instances (if any)
+ if not self.manager.placer.register_full_name(common_hypervisor):
+ raise ChainException('Discovered hypervisor placement %s is incompatible' %
+ common_hypervisor)
+
def get_length(self):
"""Get the number of VNF in the chain."""
return len(self.networks) - 1
@@ -629,6 +676,84 @@ class Chain(object):
network.delete()
+class InstancePlacer(object):
+ """A class to manage instance placement for all VNFs in all chains.
+
+ A full az string is made of 2 parts AZ and hypervisor.
+ The placement is resolved when both parts az and hypervisor names are known.
+ """
+
+ def __init__(self, req_az, req_hyp):
+ """Create a new instance placer.
+
+ req_az: requested AZ (can be None or empty if no preference)
+ req_hyp: requested hypervisor name (can be None of empty if no preference)
+ can be any of 'nova:', 'comp1', 'nova:comp1'
+ if it is a list, only the first item is used (backward compatibility in config)
+
+ req_az is ignored if req_hyp has an az part
+ all other parts beyond the first 2 are ignored in req_hyp
+ """
+ # if passed a list just pick the first item
+ if req_hyp and isinstance(req_hyp, list):
+ req_hyp = req_hyp[0]
+ # only pick first part of az
+ if req_az and ':' in req_az:
+ req_az = req_az.split(':')[0]
+ if req_hyp:
+ # check if requested hypervisor string has an AZ part
+ split_hyp = req_hyp.split(':')
+ if len(split_hyp) > 1:
+ # override the AZ part and hypervisor part
+ req_az = split_hyp[0]
+ req_hyp = split_hyp[1]
+ self.requested_az = req_az if req_az else ''
+ self.requested_hyp = req_hyp if req_hyp else ''
+ # Nova can accept AZ only (e.g. 'nova:', use any hypervisor in that AZ)
+ # or hypervisor only (e.g. ':comp1')
+ # or both (e.g. 'nova:comp1')
+ if req_az:
+ self.required_az = req_az + ':' + self.requested_hyp
+ else:
+ # no ":" needed
+ self.required_az = self.requested_hyp if req_hyp else ''
+ # placement is resolved when both AZ and hypervisor names are known and set
+ self.resolved = self.requested_az != '' and self.requested_hyp != ''
+
+ def get_required_az(self):
+ """Return the required az (can be resolved or not)."""
+ return self.required_az
+
+ def register_full_name(self, discovered_az):
+ """Verify compatibility and register a discovered hypervisor full name.
+
+ discovered_az: a discovered AZ in az:hypervisor format
+ return: True if discovered_az is compatible and set
+ False if discovered_az is not compatible
+ """
+ if self.resolved:
+ return discovered_az == self.required_az
+
+ # must be in full az format
+ split_daz = discovered_az.split(':')
+ if len(split_daz) != 2:
+ return False
+ if self.requested_az and self.requested_az != split_daz[0]:
+ return False
+ if self.requested_hyp and self.requested_hyp != split_daz[1]:
+ return False
+ self.required_az = discovered_az
+ self.resolved = True
+ return True
+
+ def is_resolved(self):
+ """Check if the full AZ is resolved.
+
+ return: True if resolved
+ """
+ return self.resolved
+
+
class ChainManager(object):
"""A class for managing all chains for a given run.
@@ -663,6 +788,7 @@ class ChainManager(object):
config = self.config
self.openstack = (chain_runner.cred is not None) and not config.l2_loopback
self.chain_count = config.service_chain_count
+ self.az = None
if self.openstack:
# openstack only
session = chain_runner.cred.get_session()
@@ -672,14 +798,9 @@ class ChainManager(object):
self.comp = compute.Compute(self.nova_client,
self.glance_client,
config)
- self.az = None
try:
if config.service_chain != ChainType.EXT:
- # we need to find 1 hypervisor
- az_list = self.comp.get_enabled_az_host_list(1)
- if not az_list:
- raise ChainException('No matching hypervisor found')
- self.az = az_list
+ self.placer = InstancePlacer(config.availability_zone, config.compute_nodes)
self._setup_image()
self.flavor = ChainFlavor(config.flavor_type, config.flavor, self.comp)
# Get list of all existing instances to check if some instances can be reused
@@ -785,6 +906,8 @@ class ChainManager(object):
for instance in instances:
status = instance.get_status()
if status == 'ACTIVE':
+ LOG.info('Instance %s is ACTIVE on %s',
+ instance.name, instance.get_hypervisor_name())
continue
if status == 'ERROR':
raise ChainException('Instance %s creation error: %s' %