X-Git-Url: https://gerrit.opnfv.org/gerrit/gitweb?a=blobdiff_plain;f=yardstick%2Fbenchmark%2Fcontexts%2Fheat.py;h=d5349eab580c361bae3a02456b80c56b15317c84;hb=deccb02a8adabe609c2e903ce9f60e8b39db6c29;hp=4c7f05236c27265e57cc131bc718735106c7e8a5;hpb=604905d36270e83a8ac2ffb80c2ca53830fca262;p=yardstick.git diff --git a/yardstick/benchmark/contexts/heat.py b/yardstick/benchmark/contexts/heat.py index 4c7f05236..d5349eab5 100644 --- a/yardstick/benchmark/contexts/heat.py +++ b/yardstick/benchmark/contexts/heat.py @@ -13,22 +13,26 @@ 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 from yardstick.benchmark.contexts.base import Context from yardstick.benchmark.contexts.model import Network -from yardstick.benchmark.contexts.model import PlacementGroup +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,56 +42,74 @@ 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 = [] self.keypair_name = None self.secgroup_name = None 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)]) super(HeatContext, self).__init__() + def assign_external_network(self, networks): + sorted_networks = sorted(networks.items()) + external_network = os.environ.get("EXTERNAL_NETWORK", "net04_ext") + + 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 + + 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"] - if "user" in attrs: - self._user = attrs["user"] + self._user = attrs.get("user") - if "heat_template" in attrs: - self.template_file = attrs["heat_template"] - self.heat_parameters = attrs.get("heat_parameters", None) + self.template_file = attrs.get("heat_template") + if self.template_file: + self.heat_parameters = attrs.get("heat_parameters") return self.keypair_name = self.name + "-key" self.secgroup_name = self.name + "-secgroup" - if "image" in attrs: - self._image = attrs["image"] + self._image = attrs.get("image") + + self._flavor = attrs.get("flavor") - if "flavor" in attrs: - self._flavor = attrs["flavor"] + self.heat_timeout = attrs.get("timeout", DEFAULT_HEAT_TIMEOUT) - if "placement_groups" in attrs: - for name, pgattrs in attrs["placement_groups"].items(): - pg = PlacementGroup(name, self, pgattrs["policy"]) - self.placement_groups.append(pg) + self.placement_groups = [PlacementGroup(name, self, pgattrs["policy"]) + for name, pgattrs in attrs.get( + "placement_groups", {}).items()] - for name, netattrs in attrs["networks"].items(): - network = Network(name, self, netattrs) - self.networks.append(network) + self.server_groups = [ServerGroup(name, self, sgattrs["policy"]) + for name, sgattrs in attrs.get( + "server_groups", {}).items()] - for name, serverattrs in attrs["servers"].items(): + # we have to do this first, because we are injecting external_network + # into the dict + self.assign_external_network(attrs["networks"]) + + for name, serverattrs in sorted(attrs["servers"].items()): server = Server(name, self, serverattrs) self.servers.append(server) self._server_map[server.dn] = server @@ -98,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): @@ -117,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, @@ -149,33 +183,38 @@ 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 len(scheduler_hints["different_host"]) == 0: + 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) - added_servers.append(server.stack_name) else: scheduler_hints["different_host"] = \ scheduler_hints["different_host"][0] server.add_to_template(template, - self.networks, + list(self.networks.values()), scheduler_hints) - added_servers.append(server.stack_name) else: server.add_to_template(template, - self.networks, + list(self.networks.values()), scheduler_hints) - added_servers.append(server.stack_name) + added_servers.append(server.stack_name) # create list of servers with affinity policy affinity_servers = [] @@ -192,13 +231,40 @@ 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 + for sg in self.server_groups: + template.add_server_group(sg.name, sg.policy) + # add remaining servers with no placement group configured for server in list_of_servers: - if len(server.placement_groups) == 0: - server.add_to_template(template, self.networks, {}) + # TODO placement_group and server_group should combine + if not server.placement_groups: + scheduler_hints = {} + # affinity/anti-aff server group + sg = server.server_group + if sg: + scheduler_hints["group"] = {'get_resource': sg.name} + server.add_to_template(template, + 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""" @@ -211,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 = \ @@ -232,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: @@ -247,6 +348,21 @@ class HeatContext(Context): except OSError: LOG.exception("Key filename %s", self.key_filename) + 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 @@ -256,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 @@ -273,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 @@ -284,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