X-Git-Url: https://gerrit.opnfv.org/gerrit/gitweb?a=blobdiff_plain;f=yardstick%2Fbenchmark%2Fcontexts%2Fheat.py;h=d5349eab580c361bae3a02456b80c56b15317c84;hb=deccb02a8adabe609c2e903ce9f60e8b39db6c29;hp=571a769ebac718da24600ea2f03f1495223ef1d6;hpb=6f541e757283b528d8736e314b2490f28d3c2563;p=yardstick.git diff --git a/yardstick/benchmark/contexts/heat.py b/yardstick/benchmark/contexts/heat.py index 571a769eb..d5349eab5 100644 --- a/yardstick/benchmark/contexts/heat.py +++ b/yardstick/benchmark/contexts/heat.py @@ -13,9 +13,10 @@ from __future__ import print_function import collections import logging import os -import sys import uuid +from collections import OrderedDict +import ipaddress import paramiko import pkg_resources @@ -24,11 +25,14 @@ from yardstick.benchmark.contexts.model import Network from yardstick.benchmark.contexts.model import PlacementGroup, ServerGroup from yardstick.benchmark.contexts.model import Server from yardstick.benchmark.contexts.model import update_scheduler_hints +from yardstick.common.openstack_utils import get_neutron_client from yardstick.orchestrator.heat import HeatTemplate, get_short_key_uuid -from yardstick.definitions import YARDSTICK_ROOT_PATH +from yardstick.common.constants import YARDSTICK_ROOT_PATH LOG = logging.getLogger(__name__) +DEFAULT_HEAT_TIMEOUT = 3600 + class HeatContext(Context): """Class that represents a context in the logical model""" @@ -38,7 +42,7 @@ class HeatContext(Context): def __init__(self): self.name = None self.stack = None - self.networks = [] + self.networks = OrderedDict() self.servers = [] self.placement_groups = [] self.server_groups = [] @@ -47,12 +51,15 @@ class HeatContext(Context): self._server_map = {} self._image = None self._flavor = None + self.flavors = set() self._user = None self.template_file = None self.heat_parameters = None + self.neutron_client = None # generate an uuid to identify yardstick_key # the first 8 digits of the uuid will be used self.key_uuid = uuid.uuid4() + self.heat_timeout = None self.key_filename = ''.join( [YARDSTICK_ROOT_PATH, 'yardstick/resources/files/yardstick_key-', get_short_key_uuid(self.key_uuid)]) @@ -61,14 +68,16 @@ class HeatContext(Context): def assign_external_network(self, networks): sorted_networks = sorted(networks.items()) external_network = os.environ.get("EXTERNAL_NETWORK", "net04_ext") - have_external_network = [(name, net) - for name, net in sorted_networks if - net.get("external_network")] - # no external net defined, assign it to first network usig os.environ + + have_external_network = any(net.get("external_network") for net in networks.values()) if sorted_networks and not have_external_network: + # no external net defined, assign it to first network using os.environ sorted_networks[0][1]["external_network"] = external_network - def init(self, attrs): # pragma: no cover + self.networks = OrderedDict((name, Network(name, self, attrs)) + for name, attrs in sorted_networks) + + def init(self, attrs): """initializes itself from the supplied arguments""" self.name = attrs["name"] @@ -86,6 +95,8 @@ class HeatContext(Context): self._flavor = attrs.get("flavor") + self.heat_timeout = attrs.get("timeout", DEFAULT_HEAT_TIMEOUT) + self.placement_groups = [PlacementGroup(name, self, pgattrs["policy"]) for name, pgattrs in attrs.get( "placement_groups", {}).items()] @@ -94,12 +105,11 @@ class HeatContext(Context): for name, sgattrs in attrs.get( "server_groups", {}).items()] + # we have to do this first, because we are injecting external_network + # into the dict self.assign_external_network(attrs["networks"]) - self.networks = [Network(name, self, netattrs) for name, netattrs in - sorted(attrs["networks"].items())] - - for name, serverattrs in attrs["servers"].items(): + for name, serverattrs in sorted(attrs["servers"].items()): server = Server(name, self, serverattrs) self.servers.append(server) self._server_map[server.dn] = server @@ -110,7 +120,6 @@ class HeatContext(Context): with open(self.key_filename + ".pub", "w") as pubkey_file: pubkey_file.write( "%s %s\n" % (rsa_key.get_name(), rsa_key.get_base64())) - del rsa_key @property def image(self): @@ -129,13 +138,26 @@ class HeatContext(Context): def _add_resources_to_template(self, template): """add to the template the resources represented by this context""" + + if self.flavor: + if isinstance(self.flavor, dict): + flavor = self.flavor.setdefault("name", self.name + "-flavor") + template.add_flavor(**self.flavor) + self.flavors.add(flavor) + template.add_keypair(self.keypair_name, self.key_uuid) template.add_security_group(self.secgroup_name) - for network in self.networks: - template.add_network(network.stack_name) + for network in self.networks.values(): + template.add_network(network.stack_name, + network.physical_network, + network.provider, + network.segmentation_id, + network.port_security_enabled) template.add_subnet(network.subnet_stack_name, network.stack_name, - network.subnet_cidr) + network.subnet_cidr, + network.enable_dhcp, + network.gateway_ip) if network.router: template.add_router(network.router.stack_name, @@ -161,29 +183,36 @@ class HeatContext(Context): availability_servers.append(server) break + for server in availability_servers: + if isinstance(server.flavor, dict): + try: + self.flavors.add(server.flavor["name"]) + except KeyError: + self.flavors.add(server.stack_name + "-flavor") + # add servers with availability policy added_servers = [] for server in availability_servers: scheduler_hints = {} for pg in server.placement_groups: update_scheduler_hints(scheduler_hints, added_servers, pg) - # workround for openstack nova bug, check JIRA: YARDSTICK-200 + # workaround for openstack nova bug, check JIRA: YARDSTICK-200 # for details if len(availability_servers) == 2: if not scheduler_hints["different_host"]: scheduler_hints.pop("different_host", None) server.add_to_template(template, - self.networks, + list(self.networks.values()), scheduler_hints) else: scheduler_hints["different_host"] = \ scheduler_hints["different_host"][0] server.add_to_template(template, - self.networks, + list(self.networks.values()), scheduler_hints) else: server.add_to_template(template, - self.networks, + list(self.networks.values()), scheduler_hints) added_servers.append(server.stack_name) @@ -202,7 +231,8 @@ class HeatContext(Context): scheduler_hints = {} for pg in server.placement_groups: update_scheduler_hints(scheduler_hints, added_servers, pg) - server.add_to_template(template, self.networks, scheduler_hints) + server.add_to_template(template, list(self.networks.values()), + scheduler_hints) added_servers.append(server.stack_name) # add server group @@ -219,7 +249,22 @@ class HeatContext(Context): if sg: scheduler_hints["group"] = {'get_resource': sg.name} server.add_to_template(template, - self.networks, scheduler_hints) + list(self.networks.values()), + scheduler_hints) + + def get_neutron_info(self): + if not self.neutron_client: + self.neutron_client = get_neutron_client() + + networks = self.neutron_client.list_networks() + for network in self.networks.values(): + for neutron_net in networks['networks']: + if neutron_net['name'] == network.stack_name: + network.segmentation_id = neutron_net.get('provider:segmentation_id') + # we already have physical_network + # network.physical_network = neutron_net.get('provider:physical_network') + network.network_type = neutron_net.get('provider:network_type') + network.neutron_info = neutron_net def deploy(self): """deploys template into a stack using cloud""" @@ -232,20 +277,22 @@ class HeatContext(Context): self._add_resources_to_template(heat_template) try: - self.stack = heat_template.create() + self.stack = heat_template.create(block=True, + timeout=self.heat_timeout) except KeyboardInterrupt: - sys.exit("\nStack create interrupted") - except RuntimeError as err: - sys.exit("error: failed to deploy stack: '%s'" % err.args) - except Exception as err: - sys.exit("error: failed to deploy stack: '%s'" % err) + raise SystemExit("\nStack create interrupted") + except: + LOG.exception("stack failed") + # let the other failures happen, we want stack trace + raise + + # TODO: use Neutron to get segementation-id + self.get_neutron_info() # copy some vital stack output into server objects for server in self.servers: if server.ports: - # TODO(hafe) can only handle one internal network for now - port = next(iter(server.ports.values())) - server.private_ip = self.stack.outputs[port["stack_name"]] + self.add_server_port(server) if server.floating_ip: server.public_ip = \ @@ -253,6 +300,39 @@ class HeatContext(Context): print("Context '%s' deployed" % self.name) + def add_server_port(self, server): + # TODO(hafe) can only handle one internal network for now + port = next(iter(server.ports.values())) + server.private_ip = self.stack.outputs[port["stack_name"]] + server.interfaces = {} + for network_name, port in server.ports.items(): + server.interfaces[network_name] = self.make_interface_dict( + network_name, port['stack_name'], self.stack.outputs) + + def make_interface_dict(self, network_name, stack_name, outputs): + private_ip = outputs[stack_name] + mac_addr = outputs[stack_name + "-mac_address"] + subnet_cidr_key = "-".join([self.name, network_name, 'subnet', 'cidr']) + gateway_key = "-".join([self.name, network_name, 'subnet', 'gateway_ip']) + subnet_cidr = outputs[subnet_cidr_key] + subnet_ip = ipaddress.ip_network(subnet_cidr) + return { + "private_ip": private_ip, + "subnet_id": outputs[stack_name + "-subnet_id"], + "subnet_cidr": subnet_cidr, + "network": str(subnet_ip.network_address), + "netmask": str(subnet_ip.netmask), + "gateway_ip": outputs[gateway_key], + "mac_address": mac_addr, + "device_id": outputs[stack_name + "-device_id"], + "network_id": outputs[stack_name + "-network_id"], + "network_name": network_name, + # to match vnf_generic + "local_mac": mac_addr, + "local_ip": private_ip, + "vld_id": self.networks[network_name].vld_id, + } + def undeploy(self): """undeploys stack from cloud""" if self.stack: @@ -270,6 +350,19 @@ class HeatContext(Context): super(HeatContext, self).undeploy() + @staticmethod + def generate_routing_table(server): + routes = [ + { + "network": intf["network"], + "netmask": intf["netmask"], + "if": name, + "gateway": intf["gateway_ip"], + } + for name, intf in server.interfaces.items() + ] + return routes + def _get_server(self, attr_name): """lookup server info by name from context attr_name: either a name for a server created by yardstick or a dict @@ -279,7 +372,10 @@ class HeatContext(Context): 'yardstick.resources', 'files/yardstick_key-' + get_short_key_uuid(self.key_uuid)) - if isinstance(attr_name, collections.Mapping): + if not isinstance(attr_name, collections.Mapping): + server = self._server_map.get(attr_name, None) + + else: cname = attr_name["name"].split(".")[1] if cname != self.name: return None @@ -296,10 +392,6 @@ class HeatContext(Context): server = Server(attr_name["name"].split(".")[0], self, {}) server.public_ip = public_ip server.private_ip = private_ip - else: - if attr_name not in self._server_map: - return None - server = self._server_map[attr_name] if server is None: return None @@ -307,10 +399,39 @@ class HeatContext(Context): result = { "user": server.context.user, "key_filename": key_filename, - "private_ip": server.private_ip + "private_ip": server.private_ip, + "interfaces": server.interfaces, + "routing_table": self.generate_routing_table(server), + # empty IPv6 routing table + "nd_route_tbl": [], } # Target server may only have private_ip if server.public_ip: result["ip"] = server.public_ip return result + + def _get_network(self, attr_name): + if not isinstance(attr_name, collections.Mapping): + network = self.networks.get(attr_name, None) + + else: + # Don't generalize too much Just support vld_id + vld_id = attr_name.get('vld_id') + if vld_id is None: + return None + + network = next((n for n in self.networks.values() if + getattr(n, "vld_id", None) == vld_id), None) + + if network is None: + return None + + result = { + "name": network.name, + "vld_id": network.vld_id, + "segmentation_id": network.segmentation_id, + "network_type": network.network_type, + "physical_network": network.physical_network, + } + return result