1 ##############################################################################
2 # Copyright (c) 2015 Ericsson AB and others.
4 # All rights reserved. This program and the accompanying materials
5 # are made available under the terms of the Apache License, Version 2.0
6 # which accompanies this distribution, and is available at
7 # http://www.apache.org/licenses/LICENSE-2.0
8 ##############################################################################
10 from __future__ import absolute_import
11 from __future__ import print_function
17 from collections import OrderedDict
22 from yardstick.benchmark.contexts.base import Context
23 from yardstick.benchmark.contexts.model import Network
24 from yardstick.benchmark.contexts.model import PlacementGroup, ServerGroup
25 from yardstick.benchmark.contexts.model import Server
26 from yardstick.benchmark.contexts.model import update_scheduler_hints
27 from yardstick.common import exceptions as y_exc
28 from yardstick.common.openstack_utils import get_shade_client
29 from yardstick.orchestrator.heat import HeatStack
30 from yardstick.orchestrator.heat import HeatTemplate
31 from yardstick.common import constants as consts
32 from yardstick.common import utils
33 from yardstick.common.utils import source_env
34 from yardstick.ssh import SSH
36 LOG = logging.getLogger(__name__)
38 DEFAULT_HEAT_TIMEOUT = 3600
41 def join_args(sep, *args):
49 class HeatContext(Context):
50 """Class that represents a context in the logical model"""
52 __context_type__ = "Heat"
56 self.networks = OrderedDict()
57 self.heat_timeout = None
59 self.placement_groups = []
60 self.server_groups = []
61 self.keypair_name = None
62 self.secgroup_name = None
69 self.template_file = None
70 self.heat_parameters = None
71 self.shade_client = None
72 self.heat_timeout = None
73 self.key_filename = None
74 super(HeatContext, self).__init__()
77 def assign_external_network(networks):
78 sorted_networks = sorted(networks.items())
79 external_network = os.environ.get("EXTERNAL_NETWORK", "net04_ext")
81 have_external_network = any(net.get("external_network") for net in networks.values())
82 if not have_external_network:
83 # try looking for mgmt network first
85 networks['mgmt']["external_network"] = external_network
88 # otherwise assign it to first network using os.environ
89 sorted_networks[0][1]["external_network"] = external_network
91 return sorted_networks
93 def init(self, attrs):
94 """Initializes itself from the supplied arguments"""
95 super(HeatContext, self).init(attrs)
97 self.check_environment()
98 self._user = attrs.get("user")
100 self.template_file = attrs.get("heat_template")
102 self.heat_timeout = attrs.get("timeout", DEFAULT_HEAT_TIMEOUT)
103 if self.template_file:
104 self.heat_parameters = attrs.get("heat_parameters")
107 self.keypair_name = h_join(self.name, "key")
108 self.secgroup_name = h_join(self.name, "secgroup")
110 self._image = attrs.get("image")
112 self._flavor = attrs.get("flavor")
114 self.placement_groups = [PlacementGroup(name, self, pg_attrs["policy"])
115 for name, pg_attrs in attrs.get(
116 "placement_groups", {}).items()]
118 self.server_groups = [ServerGroup(name, self, sg_attrs["policy"])
119 for name, sg_attrs in attrs.get(
120 "server_groups", {}).items()]
122 # we have to do this first, because we are injecting external_network
124 sorted_networks = self.assign_external_network(attrs["networks"])
126 self.networks = OrderedDict(
127 (name, Network(name, self, net_attrs)) for name, net_attrs in
130 for name, server_attrs in sorted(attrs["servers"].items()):
131 server = Server(name, self, server_attrs)
132 self.servers.append(server)
133 self._server_map[server.dn] = server
137 def check_environment(self):
139 os.environ['OS_AUTH_URL']
142 source_env(consts.OPENRC)
144 if e.errno != errno.EEXIST:
145 LOG.error('OPENRC file not found')
148 LOG.error('OS_AUTH_URL not found')
152 """returns application's default image name"""
157 """returns application's default flavor name"""
162 """return login user name corresponding to image"""
165 def _add_resources_to_template(self, template):
166 """add to the template the resources represented by this context"""
169 if isinstance(self.flavor, dict):
170 flavor = self.flavor.setdefault("name", self.name + "-flavor")
171 template.add_flavor(**self.flavor)
172 self.flavors.add(flavor)
174 template.add_keypair(self.keypair_name, self.name)
175 template.add_security_group(self.secgroup_name)
177 for network in self.networks.values():
178 # Using existing network
179 if network.is_existing():
181 template.add_network(network.stack_name,
182 network.physical_network,
184 network.segmentation_id,
185 network.port_security_enabled,
186 network.network_type)
187 template.add_subnet(network.subnet_stack_name, network.stack_name,
193 template.add_router(network.router.stack_name,
194 network.router.external_gateway_info,
195 network.subnet_stack_name)
196 template.add_router_interface(network.router.stack_if_name,
197 network.router.stack_name,
198 network.subnet_stack_name)
200 # create a list of servers sorted by increasing no of placement groups
201 list_of_servers = sorted(self.servers,
202 key=lambda s: len(s.placement_groups))
205 # add servers with scheduler hints derived from placement groups
208 # create list of servers with availability policy
209 availability_servers = []
210 for server in list_of_servers:
211 for pg in server.placement_groups:
212 if pg.policy == "availability":
213 availability_servers.append(server)
216 for server in availability_servers:
217 if isinstance(server.flavor, dict):
219 self.flavors.add(server.flavor["name"])
221 self.flavors.add(h_join(server.stack_name, "flavor"))
223 # add servers with availability policy
225 for server in availability_servers:
227 for pg in server.placement_groups:
228 update_scheduler_hints(scheduler_hints, added_servers, pg)
229 # workaround for openstack nova bug, check JIRA: YARDSTICK-200
231 if len(availability_servers) == 2:
232 if not scheduler_hints["different_host"]:
233 scheduler_hints.pop("different_host", None)
234 server.add_to_template(template,
235 list(self.networks.values()),
238 scheduler_hints["different_host"] = \
239 scheduler_hints["different_host"][0]
240 server.add_to_template(template,
241 list(self.networks.values()),
244 server.add_to_template(template,
245 list(self.networks.values()),
247 added_servers.append(server.stack_name)
249 # create list of servers with affinity policy
250 affinity_servers = []
251 for server in list_of_servers:
252 for pg in server.placement_groups:
253 if pg.policy == "affinity":
254 affinity_servers.append(server)
257 # add servers with affinity policy
258 for server in affinity_servers:
259 if server.stack_name in added_servers:
262 for pg in server.placement_groups:
263 update_scheduler_hints(scheduler_hints, added_servers, pg)
264 server.add_to_template(template, list(self.networks.values()),
266 added_servers.append(server.stack_name)
269 for sg in self.server_groups:
270 template.add_server_group(sg.name, sg.policy)
272 # add remaining servers with no placement group configured
273 for server in list_of_servers:
274 # TODO placement_group and server_group should combine
275 if not server.placement_groups:
277 # affinity/anti-aff server group
278 sg = server.server_group
280 scheduler_hints["group"] = {'get_resource': sg.name}
281 server.add_to_template(template,
282 list(self.networks.values()),
285 def get_neutron_info(self):
286 if not self.shade_client:
287 self.shade_client = get_shade_client()
289 networks = self.shade_client.list_networks()
290 for network in self.networks.values():
291 for neutron_net in (net for net in networks if net.name == network.stack_name):
292 network.segmentation_id = neutron_net.get('provider:segmentation_id')
293 # we already have physical_network
294 # network.physical_network = neutron_net.get('provider:physical_network')
295 network.network_type = neutron_net.get('provider:network_type')
296 network.neutron_info = neutron_net
298 def _create_new_stack(self, heat_template):
300 return heat_template.create(block=True,
301 timeout=self.heat_timeout)
302 except KeyboardInterrupt:
303 raise y_exc.StackCreationInterrupt
305 LOG.exception("stack failed")
306 # let the other failures happen, we want stack trace
309 def _retrieve_existing_stack(self, stack_name):
310 stack = HeatStack(stack_name)
314 LOG.warning("Stack %s does not exist", self.name)
318 """deploys template into a stack using cloud"""
319 LOG.info("Deploying context '%s' START", self.name)
321 self.key_filename = ''.join(
322 [consts.YARDSTICK_ROOT_PATH,
323 'yardstick/resources/files/yardstick_key-',
325 # Permissions may have changed since creation; this can be fixed. If we
326 # overwrite the file, we lose future access to VMs using this key.
327 # As long as the file exists, even if it is unreadable, keep it intact
328 if not os.path.exists(self.key_filename):
329 SSH.gen_keys(self.key_filename)
331 heat_template = HeatTemplate(self.name, self.template_file,
332 self.heat_parameters)
334 if self.template_file is None:
335 self._add_resources_to_template(heat_template)
337 if self._flags.no_setup:
338 # Try to get an existing stack, returns a stack or None
339 self.stack = self._retrieve_existing_stack(self.name)
341 self.stack = self._create_new_stack(heat_template)
344 self.stack = self._create_new_stack(heat_template)
346 # TODO: use Neutron to get segmentation-id
347 self.get_neutron_info()
349 # copy some vital stack output into server objects
350 for server in self.servers:
352 self.add_server_port(server)
354 if server.floating_ip:
356 self.stack.outputs[server.floating_ip["stack_name"]]
358 LOG.info("Deploying context '%s' DONE", self.name)
361 def _port_net_is_existing(port_info):
362 net_flags = port_info.get('net_flags', {})
363 return net_flags.get(consts.IS_EXISTING)
366 def _port_net_is_public(port_info):
367 net_flags = port_info.get('net_flags', {})
368 return net_flags.get(consts.IS_PUBLIC)
370 def add_server_port(self, server):
371 server_ports = server.ports.values()
372 for server_port in server_ports:
373 port_info = server_port[0]
374 port_ip = self.stack.outputs[port_info["stack_name"]]
375 port_net_is_existing = self._port_net_is_existing(port_info)
376 port_net_is_public = self._port_net_is_public(port_info)
377 if port_net_is_existing and (port_net_is_public or
378 len(server_ports) == 1):
379 server.public_ip = port_ip
380 if not server.private_ip or len(server_ports) == 1:
381 server.private_ip = port_ip
383 server.interfaces = {}
384 for network_name, ports in server.ports.items():
386 # port['port'] is either port name from mapping or default network_name
387 if self._port_net_is_existing(port):
389 server.interfaces[port['port']] = self.make_interface_dict(network_name,
393 server.override_ip(network_name, port)
395 def make_interface_dict(self, network_name, port, stack_name, outputs):
396 private_ip = outputs[stack_name]
397 mac_address = outputs[h_join(stack_name, "mac_address")]
398 # these are attributes of the network, not the port
399 output_subnet_cidr = outputs[h_join(self.name, network_name,
402 # these are attributes of the network, not the port
403 output_subnet_gateway = outputs[h_join(self.name, network_name,
404 'subnet', 'gateway_ip')]
407 # add default port name
409 "private_ip": private_ip,
410 "subnet_id": outputs[h_join(stack_name, "subnet_id")],
411 "subnet_cidr": output_subnet_cidr,
412 "network": str(ipaddress.ip_network(output_subnet_cidr).network_address),
413 "netmask": str(ipaddress.ip_network(output_subnet_cidr).netmask),
414 "gateway_ip": output_subnet_gateway,
415 "mac_address": mac_address,
416 "device_id": outputs[h_join(stack_name, "device_id")],
417 "network_id": outputs[h_join(stack_name, "network_id")],
418 # this should be == vld_id for NSB tests
419 "network_name": network_name,
420 # to match vnf_generic
421 "local_mac": mac_address,
422 "local_ip": private_ip,
425 def _delete_key_file(self):
427 utils.remove_file(self.key_filename)
428 utils.remove_file(self.key_filename + ".pub")
430 LOG.exception("There was an error removing the key file %s",
434 """undeploys stack from cloud"""
435 if self._flags.no_teardown:
436 LOG.info("Undeploying context '%s' SKIP", self.name)
440 LOG.info("Undeploying context '%s' START", self.name)
443 LOG.info("Undeploying context '%s' DONE", self.name)
445 self._delete_key_file()
447 super(HeatContext, self).undeploy()
450 def generate_routing_table(server):
453 "network": intf["network"],
454 "netmask": intf["netmask"],
456 # We have to encode a None gateway as '' for Jinja2 to YAML conversion
457 "gateway": intf["gateway_ip"] if intf["gateway_ip"] else '',
459 for name, intf in server.interfaces.items()
463 def _get_server(self, attr_name):
464 """lookup server info by name from context
465 attr_name: either a name for a server created by yardstick or a dict
466 with attribute name mapping when using external heat templates
468 if isinstance(attr_name, collections.Mapping):
469 node_name, cname = self.split_name(attr_name['name'])
470 if cname is None or cname != self.name:
473 # Create a dummy server instance for holding the *_ip attributes
474 server = Server(node_name, self, {})
475 server.public_ip = self.stack.outputs.get(
476 attr_name.get("public_ip_attr", object()), None)
478 server.private_ip = self.stack.outputs.get(
479 attr_name.get("private_ip_attr", object()), None)
482 server = self._server_map[attr_name]
484 attr_name_no_suffix = attr_name.split("-")[0]
485 server = self._server_map.get(attr_name_no_suffix, None)
489 pkey = pkg_resources.resource_string(
490 'yardstick.resources',
491 h_join('files/yardstick_key', self.name)).decode('utf-8')
494 "user": server.context.user,
496 "private_ip": server.private_ip,
497 "interfaces": server.interfaces,
498 "routing_table": self.generate_routing_table(server),
499 # empty IPv6 routing table
501 # we want to save the contex name so we can generate pod.yaml
504 # Target server may only have private_ip
506 result["ip"] = server.public_ip
510 def _get_network(self, attr_name):
511 if not isinstance(attr_name, collections.Mapping):
512 network = self.networks.get(attr_name, None)
515 # Only take the first key, value
516 key, value = next(iter(attr_name.items()), (None, None))
519 network_iter = (n for n in self.networks.values() if getattr(n, key) == value)
520 network = next(network_iter, None)
526 "name": network.name,
527 "segmentation_id": network.segmentation_id,
528 "network_type": network.network_type,
529 "physical_network": network.physical_network,