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.openstack_utils import get_neutron_client
28 from yardstick.orchestrator.heat import HeatTemplate, get_short_key_uuid
29 from yardstick.common.constants import YARDSTICK_ROOT_PATH
30 from yardstick.ssh import SSH
32 LOG = logging.getLogger(__name__)
34 DEFAULT_HEAT_TIMEOUT = 3600
37 def join_args(sep, *args):
45 class HeatContext(Context):
46 """Class that represents a context in the logical model"""
48 __context_type__ = "Heat"
53 self.networks = OrderedDict()
54 self.heat_timeout = None
56 self.placement_groups = []
57 self.server_groups = []
58 self.keypair_name = None
59 self.secgroup_name = None
66 self.template_file = None
67 self.heat_parameters = None
68 self.neutron_client = None
69 # generate an uuid to identify yardstick_key
70 # the first 8 digits of the uuid will be used
71 self.key_uuid = uuid.uuid4()
72 self.heat_timeout = None
73 self.key_filename = ''.join(
74 [YARDSTICK_ROOT_PATH, 'yardstick/resources/files/yardstick_key-',
75 get_short_key_uuid(self.key_uuid)])
76 super(HeatContext, self).__init__()
79 def assign_external_network(networks):
80 sorted_networks = sorted(networks.items())
81 external_network = os.environ.get("EXTERNAL_NETWORK", "net04_ext")
83 have_external_network = any(net.get("external_network") for net in networks.values())
84 if sorted_networks and not have_external_network:
85 # no external net defined, assign it to first network using os.environ
86 sorted_networks[0][1]["external_network"] = external_network
88 return sorted_networks
90 def init(self, attrs):
91 """initializes itself from the supplied arguments"""
92 self.name = attrs["name"]
94 self._user = attrs.get("user")
96 self.template_file = attrs.get("heat_template")
97 if self.template_file:
98 self.heat_parameters = attrs.get("heat_parameters")
101 self.keypair_name = h_join(self.name, "key")
102 self.secgroup_name = h_join(self.name, "secgroup")
104 self._image = attrs.get("image")
106 self._flavor = attrs.get("flavor")
108 self.heat_timeout = attrs.get("timeout", DEFAULT_HEAT_TIMEOUT)
110 self.placement_groups = [PlacementGroup(name, self, pg_attrs["policy"])
111 for name, pg_attrs in attrs.get(
112 "placement_groups", {}).items()]
114 self.server_groups = [ServerGroup(name, self, sg_attrs["policy"])
115 for name, sg_attrs in attrs.get(
116 "server_groups", {}).items()]
118 # we have to do this first, because we are injecting external_network
120 sorted_networks = self.assign_external_network(attrs["networks"])
122 self.networks = OrderedDict(
123 (name, Network(name, self, net_attrs)) for name, net_attrs in
126 for name, server_attrs in sorted(attrs["servers"].items()):
127 server = Server(name, self, server_attrs)
128 self.servers.append(server)
129 self._server_map[server.dn] = server
132 SSH.gen_keys(self.key_filename)
136 """returns application's default image name"""
141 """returns application's default flavor name"""
146 """return login user name corresponding to image"""
149 def _add_resources_to_template(self, template):
150 """add to the template the resources represented by this context"""
153 if isinstance(self.flavor, dict):
154 flavor = self.flavor.setdefault("name", self.name + "-flavor")
155 template.add_flavor(**self.flavor)
156 self.flavors.add(flavor)
158 template.add_keypair(self.keypair_name, self.key_uuid)
159 template.add_security_group(self.secgroup_name)
161 for network in self.networks.values():
162 template.add_network(network.stack_name,
163 network.physical_network,
165 network.segmentation_id,
166 network.port_security_enabled)
167 template.add_subnet(network.subnet_stack_name, network.stack_name,
173 template.add_router(network.router.stack_name,
174 network.router.external_gateway_info,
175 network.subnet_stack_name)
176 template.add_router_interface(network.router.stack_if_name,
177 network.router.stack_name,
178 network.subnet_stack_name)
180 # create a list of servers sorted by increasing no of placement groups
181 list_of_servers = sorted(self.servers,
182 key=lambda s: len(s.placement_groups))
185 # add servers with scheduler hints derived from placement groups
188 # create list of servers with availability policy
189 availability_servers = []
190 for server in list_of_servers:
191 for pg in server.placement_groups:
192 if pg.policy == "availability":
193 availability_servers.append(server)
196 for server in availability_servers:
197 if isinstance(server.flavor, dict):
199 self.flavors.add(server.flavor["name"])
201 self.flavors.add(h_join(server.stack_name, "flavor"))
203 # add servers with availability policy
205 for server in availability_servers:
207 for pg in server.placement_groups:
208 update_scheduler_hints(scheduler_hints, added_servers, pg)
209 # workaround for openstack nova bug, check JIRA: YARDSTICK-200
211 if len(availability_servers) == 2:
212 if not scheduler_hints["different_host"]:
213 scheduler_hints.pop("different_host", None)
214 server.add_to_template(template,
215 list(self.networks.values()),
218 scheduler_hints["different_host"] = \
219 scheduler_hints["different_host"][0]
220 server.add_to_template(template,
221 list(self.networks.values()),
224 server.add_to_template(template,
225 list(self.networks.values()),
227 added_servers.append(server.stack_name)
229 # create list of servers with affinity policy
230 affinity_servers = []
231 for server in list_of_servers:
232 for pg in server.placement_groups:
233 if pg.policy == "affinity":
234 affinity_servers.append(server)
237 # add servers with affinity policy
238 for server in affinity_servers:
239 if server.stack_name in added_servers:
242 for pg in server.placement_groups:
243 update_scheduler_hints(scheduler_hints, added_servers, pg)
244 server.add_to_template(template, list(self.networks.values()),
246 added_servers.append(server.stack_name)
249 for sg in self.server_groups:
250 template.add_server_group(sg.name, sg.policy)
252 # add remaining servers with no placement group configured
253 for server in list_of_servers:
254 # TODO placement_group and server_group should combine
255 if not server.placement_groups:
257 # affinity/anti-aff server group
258 sg = server.server_group
260 scheduler_hints["group"] = {'get_resource': sg.name}
261 server.add_to_template(template,
262 list(self.networks.values()),
265 def get_neutron_info(self):
266 if not self.neutron_client:
267 self.neutron_client = get_neutron_client()
269 networks = self.neutron_client.list_networks()
270 for network in self.networks.values():
271 for neutron_net in networks['networks']:
272 if neutron_net['name'] == network.stack_name:
273 network.segmentation_id = neutron_net.get('provider:segmentation_id')
274 # we already have physical_network
275 # network.physical_network = neutron_net.get('provider:physical_network')
276 network.network_type = neutron_net.get('provider:network_type')
277 network.neutron_info = neutron_net
280 """deploys template into a stack using cloud"""
281 print("Deploying context '%s'" % self.name)
283 heat_template = HeatTemplate(self.name, self.template_file,
284 self.heat_parameters)
286 if self.template_file is None:
287 self._add_resources_to_template(heat_template)
290 self.stack = heat_template.create(block=True,
291 timeout=self.heat_timeout)
292 except KeyboardInterrupt:
293 raise SystemExit("\nStack create interrupted")
295 LOG.exception("stack failed")
296 # let the other failures happen, we want stack trace
299 # TODO: use Neutron to get segmentation-id
300 self.get_neutron_info()
302 # copy some vital stack output into server objects
303 for server in self.servers:
305 self.add_server_port(server)
307 if server.floating_ip:
309 self.stack.outputs[server.floating_ip["stack_name"]]
311 print("Context '%s' deployed" % self.name)
313 def add_server_port(self, server):
314 # TODO(hafe) can only handle one internal network for now
315 port = next(iter(server.ports.values()))
316 server.private_ip = self.stack.outputs[port["stack_name"]]
317 server.interfaces = {}
318 for network_name, port in server.ports.items():
319 server.interfaces[network_name] = self.make_interface_dict(
320 network_name, port['stack_name'], self.stack.outputs)
322 def make_interface_dict(self, network_name, stack_name, outputs):
323 private_ip = outputs[stack_name]
324 mac_address = outputs[h_join(stack_name, "mac_address")]
325 output_subnet_cidr = outputs[h_join(self.name, network_name,
328 output_subnet_gateway = outputs[h_join(self.name, network_name,
329 'subnet', 'gateway_ip')]
332 "private_ip": private_ip,
333 "subnet_id": outputs[h_join(stack_name, "subnet_id")],
334 "subnet_cidr": output_subnet_cidr,
335 "network": str(ipaddress.ip_network(output_subnet_cidr).network_address),
336 "netmask": str(ipaddress.ip_network(output_subnet_cidr).netmask),
337 "gateway_ip": output_subnet_gateway,
338 "mac_address": mac_address,
339 "device_id": outputs[h_join(stack_name, "device_id")],
340 "network_id": outputs[h_join(stack_name, "network_id")],
341 "network_name": network_name,
342 # to match vnf_generic
343 "local_mac": mac_address,
344 "local_ip": private_ip,
345 "vld_id": self.networks[network_name].vld_id,
349 """undeploys stack from cloud"""
351 print("Undeploying context '%s'" % self.name)
354 print("Context '%s' undeployed" % self.name)
356 if os.path.exists(self.key_filename):
358 os.remove(self.key_filename)
359 os.remove(self.key_filename + ".pub")
361 LOG.exception("Key filename %s", self.key_filename)
363 super(HeatContext, self).undeploy()
366 def generate_routing_table(server):
369 "network": intf["network"],
370 "netmask": intf["netmask"],
372 # We have to encode a None gateway as '' for Jinja2 to YAML conversion
373 "gateway": intf["gateway_ip"] if intf["gateway_ip"] else '',
375 for name, intf in server.interfaces.items()
379 def _get_server(self, attr_name):
380 """lookup server info by name from context
381 attr_name: either a name for a server created by yardstick or a dict
382 with attribute name mapping when using external heat templates
384 key_filename = pkg_resources.resource_filename(
385 'yardstick.resources',
386 h_join('files/yardstick_key', get_short_key_uuid(self.key_uuid)))
388 if isinstance(attr_name, collections.Mapping):
389 node_name, cname = self.split_name(attr_name['name'])
390 if cname is None or cname != self.name:
393 # Create a dummy server instance for holding the *_ip attributes
394 server = Server(node_name, self, {})
395 server.public_ip = self.stack.outputs.get(
396 attr_name.get("public_ip_attr", object()), None)
398 server.private_ip = self.stack.outputs.get(
399 attr_name.get("private_ip_attr", object()), None)
401 server = self._server_map.get(attr_name, None)
406 "user": server.context.user,
407 "key_filename": key_filename,
408 "private_ip": server.private_ip,
409 "interfaces": server.interfaces,
410 "routing_table": self.generate_routing_table(server),
411 # empty IPv6 routing table
414 # Target server may only have private_ip
416 result["ip"] = server.public_ip
420 def _get_network(self, attr_name):
421 if not isinstance(attr_name, collections.Mapping):
422 network = self.networks.get(attr_name, None)
425 # Don't generalize too much Just support vld_id
426 vld_id = attr_name.get('vld_id', {})
427 network_iter = (n for n in self.networks.values() if n.vld_id == vld_id)
428 network = next(network_iter, None)
434 "name": network.name,
435 "vld_id": network.vld_id,
436 "segmentation_id": network.segmentation_id,
437 "network_type": network.network_type,
438 "physical_network": network.physical_network,