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_neutron_client
29 from yardstick.orchestrator.heat import HeatTemplate
30 from yardstick.common import constants as consts
31 from yardstick.common.utils import source_env
32 from yardstick.ssh import SSH
34 LOG = logging.getLogger(__name__)
36 DEFAULT_HEAT_TIMEOUT = 3600
39 def join_args(sep, *args):
47 class HeatContext(Context):
48 """Class that represents a context in the logical model"""
50 __context_type__ = "Heat"
54 self.networks = OrderedDict()
55 self.heat_timeout = None
57 self.placement_groups = []
58 self.server_groups = []
59 self.keypair_name = None
60 self.secgroup_name = None
67 self.template_file = None
68 self.heat_parameters = None
69 self.neutron_client = None
70 self.heat_timeout = None
71 self.key_filename = None
72 super(HeatContext, self).__init__()
75 def assign_external_network(networks):
76 sorted_networks = sorted(networks.items())
77 external_network = os.environ.get("EXTERNAL_NETWORK", "net04_ext")
79 have_external_network = any(net.get("external_network") for net in networks.values())
80 if not have_external_network:
81 # try looking for mgmt network first
83 networks['mgmt']["external_network"] = external_network
86 # otherwise assign it to first network using os.environ
87 sorted_networks[0][1]["external_network"] = external_network
89 return sorted_networks
91 def init(self, attrs):
92 """Initializes itself from the supplied arguments"""
93 super(HeatContext, self).init(attrs)
95 self.check_environment()
96 self._user = attrs.get("user")
98 self.template_file = attrs.get("heat_template")
100 self.heat_timeout = attrs.get("timeout", DEFAULT_HEAT_TIMEOUT)
101 if self.template_file:
102 self.heat_parameters = attrs.get("heat_parameters")
105 self.keypair_name = h_join(self.name, "key")
106 self.secgroup_name = h_join(self.name, "secgroup")
108 self._image = attrs.get("image")
110 self._flavor = attrs.get("flavor")
112 self.placement_groups = [PlacementGroup(name, self, pg_attrs["policy"])
113 for name, pg_attrs in attrs.get(
114 "placement_groups", {}).items()]
116 self.server_groups = [ServerGroup(name, self, sg_attrs["policy"])
117 for name, sg_attrs in attrs.get(
118 "server_groups", {}).items()]
120 # we have to do this first, because we are injecting external_network
122 sorted_networks = self.assign_external_network(attrs["networks"])
124 self.networks = OrderedDict(
125 (name, Network(name, self, net_attrs)) for name, net_attrs in
128 for name, server_attrs in sorted(attrs["servers"].items()):
129 server = Server(name, self, server_attrs)
130 self.servers.append(server)
131 self._server_map[server.dn] = server
135 self.key_filename = ''.join(
136 [consts.YARDSTICK_ROOT_PATH,
137 'yardstick/resources/files/yardstick_key-',
139 # Permissions may have changed since creation; this can be fixed. If we
140 # overwrite the file, we lose future access to VMs using this key.
141 # As long as the file exists, even if it is unreadable, keep it intact
142 if not os.path.exists(self.key_filename):
143 SSH.gen_keys(self.key_filename)
145 def check_environment(self):
147 os.environ['OS_AUTH_URL']
150 source_env(consts.OPENRC)
152 if e.errno != errno.EEXIST:
153 LOG.error('OPENRC file not found')
156 LOG.error('OS_AUTH_URL not found')
160 """returns application's default image name"""
165 """returns application's default flavor name"""
170 """return login user name corresponding to image"""
173 def _add_resources_to_template(self, template):
174 """add to the template the resources represented by this context"""
177 if isinstance(self.flavor, dict):
178 flavor = self.flavor.setdefault("name", self.name + "-flavor")
179 template.add_flavor(**self.flavor)
180 self.flavors.add(flavor)
182 template.add_keypair(self.keypair_name, self.name)
183 template.add_security_group(self.secgroup_name)
185 for network in self.networks.values():
186 template.add_network(network.stack_name,
187 network.physical_network,
189 network.segmentation_id,
190 network.port_security_enabled,
191 network.network_type)
192 template.add_subnet(network.subnet_stack_name, network.stack_name,
198 template.add_router(network.router.stack_name,
199 network.router.external_gateway_info,
200 network.subnet_stack_name)
201 template.add_router_interface(network.router.stack_if_name,
202 network.router.stack_name,
203 network.subnet_stack_name)
205 # create a list of servers sorted by increasing no of placement groups
206 list_of_servers = sorted(self.servers,
207 key=lambda s: len(s.placement_groups))
210 # add servers with scheduler hints derived from placement groups
213 # create list of servers with availability policy
214 availability_servers = []
215 for server in list_of_servers:
216 for pg in server.placement_groups:
217 if pg.policy == "availability":
218 availability_servers.append(server)
221 for server in availability_servers:
222 if isinstance(server.flavor, dict):
224 self.flavors.add(server.flavor["name"])
226 self.flavors.add(h_join(server.stack_name, "flavor"))
228 # add servers with availability policy
230 for server in availability_servers:
232 for pg in server.placement_groups:
233 update_scheduler_hints(scheduler_hints, added_servers, pg)
234 # workaround for openstack nova bug, check JIRA: YARDSTICK-200
236 if len(availability_servers) == 2:
237 if not scheduler_hints["different_host"]:
238 scheduler_hints.pop("different_host", None)
239 server.add_to_template(template,
240 list(self.networks.values()),
243 scheduler_hints["different_host"] = \
244 scheduler_hints["different_host"][0]
245 server.add_to_template(template,
246 list(self.networks.values()),
249 server.add_to_template(template,
250 list(self.networks.values()),
252 added_servers.append(server.stack_name)
254 # create list of servers with affinity policy
255 affinity_servers = []
256 for server in list_of_servers:
257 for pg in server.placement_groups:
258 if pg.policy == "affinity":
259 affinity_servers.append(server)
262 # add servers with affinity policy
263 for server in affinity_servers:
264 if server.stack_name in added_servers:
267 for pg in server.placement_groups:
268 update_scheduler_hints(scheduler_hints, added_servers, pg)
269 server.add_to_template(template, list(self.networks.values()),
271 added_servers.append(server.stack_name)
274 for sg in self.server_groups:
275 template.add_server_group(sg.name, sg.policy)
277 # add remaining servers with no placement group configured
278 for server in list_of_servers:
279 # TODO placement_group and server_group should combine
280 if not server.placement_groups:
282 # affinity/anti-aff server group
283 sg = server.server_group
285 scheduler_hints["group"] = {'get_resource': sg.name}
286 server.add_to_template(template,
287 list(self.networks.values()),
290 def get_neutron_info(self):
291 if not self.neutron_client:
292 self.neutron_client = get_neutron_client()
294 networks = self.neutron_client.list_networks()
295 for network in self.networks.values():
296 for neutron_net in networks['networks']:
297 if neutron_net['name'] == network.stack_name:
298 network.segmentation_id = neutron_net.get('provider:segmentation_id')
299 # we already have physical_network
300 # network.physical_network = neutron_net.get('provider:physical_network')
301 network.network_type = neutron_net.get('provider:network_type')
302 network.neutron_info = neutron_net
304 def _create_new_stack(self, heat_template):
306 return heat_template.create(block=True,
307 timeout=self.heat_timeout)
308 except KeyboardInterrupt:
309 raise y_exc.StackCreationInterrupt
311 LOG.exception("stack failed")
312 # let the other failures happen, we want stack trace
316 """deploys template into a stack using cloud"""
317 LOG.info("Deploying context '%s' START", self.name)
319 heat_template = HeatTemplate(self.name, self.template_file,
320 self.heat_parameters)
322 if self.template_file is None:
323 self._add_resources_to_template(heat_template)
325 self.stack = self._create_new_stack(heat_template)
327 # TODO: use Neutron to get segmentation-id
328 self.get_neutron_info()
330 # copy some vital stack output into server objects
331 for server in self.servers:
333 self.add_server_port(server)
335 if server.floating_ip:
337 self.stack.outputs[server.floating_ip["stack_name"]]
339 LOG.info("Deploying context '%s' DONE", self.name)
341 def add_server_port(self, server):
342 # use private ip from first port in first network
344 private_port = next(iter(server.ports.values()))[0]
346 LOG.exception("Unable to find first private port in %s", server.ports)
348 server.private_ip = self.stack.outputs[private_port["stack_name"]]
349 server.interfaces = {}
350 for network_name, ports in server.ports.items():
352 # port['port'] is either port name from mapping or default network_name
353 server.interfaces[port['port']] = self.make_interface_dict(network_name,
357 server.override_ip(network_name, port)
359 def make_interface_dict(self, network_name, port, stack_name, outputs):
360 private_ip = outputs[stack_name]
361 mac_address = outputs[h_join(stack_name, "mac_address")]
362 # these are attributes of the network, not the port
363 output_subnet_cidr = outputs[h_join(self.name, network_name,
366 # these are attributes of the network, not the port
367 output_subnet_gateway = outputs[h_join(self.name, network_name,
368 'subnet', 'gateway_ip')]
371 # add default port name
373 "private_ip": private_ip,
374 "subnet_id": outputs[h_join(stack_name, "subnet_id")],
375 "subnet_cidr": output_subnet_cidr,
376 "network": str(ipaddress.ip_network(output_subnet_cidr).network_address),
377 "netmask": str(ipaddress.ip_network(output_subnet_cidr).netmask),
378 "gateway_ip": output_subnet_gateway,
379 "mac_address": mac_address,
380 "device_id": outputs[h_join(stack_name, "device_id")],
381 "network_id": outputs[h_join(stack_name, "network_id")],
382 # this should be == vld_id for NSB tests
383 "network_name": network_name,
384 # to match vnf_generic
385 "local_mac": mac_address,
386 "local_ip": private_ip,
390 """undeploys stack from cloud"""
392 LOG.info("Undeploying context '%s' START", self.name)
395 LOG.info("Undeploying context '%s' DONE", self.name)
397 if os.path.exists(self.key_filename):
399 os.remove(self.key_filename)
400 os.remove(self.key_filename + ".pub")
402 LOG.exception("Key filename %s", self.key_filename)
404 super(HeatContext, self).undeploy()
407 def generate_routing_table(server):
410 "network": intf["network"],
411 "netmask": intf["netmask"],
413 # We have to encode a None gateway as '' for Jinja2 to YAML conversion
414 "gateway": intf["gateway_ip"] if intf["gateway_ip"] else '',
416 for name, intf in server.interfaces.items()
420 def _get_server(self, attr_name):
421 """lookup server info by name from context
422 attr_name: either a name for a server created by yardstick or a dict
423 with attribute name mapping when using external heat templates
425 if isinstance(attr_name, collections.Mapping):
426 node_name, cname = self.split_name(attr_name['name'])
427 if cname is None or cname != self.name:
430 # Create a dummy server instance for holding the *_ip attributes
431 server = Server(node_name, self, {})
432 server.public_ip = self.stack.outputs.get(
433 attr_name.get("public_ip_attr", object()), None)
435 server.private_ip = self.stack.outputs.get(
436 attr_name.get("private_ip_attr", object()), None)
438 server = self._server_map.get(attr_name, None)
442 pkey = pkg_resources.resource_string(
443 'yardstick.resources',
444 h_join('files/yardstick_key', self.name)).decode('utf-8')
447 "user": server.context.user,
449 "private_ip": server.private_ip,
450 "interfaces": server.interfaces,
451 "routing_table": self.generate_routing_table(server),
452 # empty IPv6 routing table
454 # we want to save the contex name so we can generate pod.yaml
457 # Target server may only have private_ip
459 result["ip"] = server.public_ip
463 def _get_network(self, attr_name):
464 if not isinstance(attr_name, collections.Mapping):
465 network = self.networks.get(attr_name, None)
468 # Only take the first key, value
469 key, value = next(iter(attr_name.items()), (None, None))
472 network_iter = (n for n in self.networks.values() if getattr(n, key) == value)
473 network = next(network_iter, None)
479 "name": network.name,
480 "segmentation_id": network.segmentation_id,
481 "network_type": network.network_type,
482 "physical_network": network.physical_network,