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 network.network_type)
168 template.add_subnet(network.subnet_stack_name, network.stack_name,
174 template.add_router(network.router.stack_name,
175 network.router.external_gateway_info,
176 network.subnet_stack_name)
177 template.add_router_interface(network.router.stack_if_name,
178 network.router.stack_name,
179 network.subnet_stack_name)
181 # create a list of servers sorted by increasing no of placement groups
182 list_of_servers = sorted(self.servers,
183 key=lambda s: len(s.placement_groups))
186 # add servers with scheduler hints derived from placement groups
189 # create list of servers with availability policy
190 availability_servers = []
191 for server in list_of_servers:
192 for pg in server.placement_groups:
193 if pg.policy == "availability":
194 availability_servers.append(server)
197 for server in availability_servers:
198 if isinstance(server.flavor, dict):
200 self.flavors.add(server.flavor["name"])
202 self.flavors.add(h_join(server.stack_name, "flavor"))
204 # add servers with availability policy
206 for server in availability_servers:
208 for pg in server.placement_groups:
209 update_scheduler_hints(scheduler_hints, added_servers, pg)
210 # workaround for openstack nova bug, check JIRA: YARDSTICK-200
212 if len(availability_servers) == 2:
213 if not scheduler_hints["different_host"]:
214 scheduler_hints.pop("different_host", None)
215 server.add_to_template(template,
216 list(self.networks.values()),
219 scheduler_hints["different_host"] = \
220 scheduler_hints["different_host"][0]
221 server.add_to_template(template,
222 list(self.networks.values()),
225 server.add_to_template(template,
226 list(self.networks.values()),
228 added_servers.append(server.stack_name)
230 # create list of servers with affinity policy
231 affinity_servers = []
232 for server in list_of_servers:
233 for pg in server.placement_groups:
234 if pg.policy == "affinity":
235 affinity_servers.append(server)
238 # add servers with affinity policy
239 for server in affinity_servers:
240 if server.stack_name in added_servers:
243 for pg in server.placement_groups:
244 update_scheduler_hints(scheduler_hints, added_servers, pg)
245 server.add_to_template(template, list(self.networks.values()),
247 added_servers.append(server.stack_name)
250 for sg in self.server_groups:
251 template.add_server_group(sg.name, sg.policy)
253 # add remaining servers with no placement group configured
254 for server in list_of_servers:
255 # TODO placement_group and server_group should combine
256 if not server.placement_groups:
258 # affinity/anti-aff server group
259 sg = server.server_group
261 scheduler_hints["group"] = {'get_resource': sg.name}
262 server.add_to_template(template,
263 list(self.networks.values()),
266 def get_neutron_info(self):
267 if not self.neutron_client:
268 self.neutron_client = get_neutron_client()
270 networks = self.neutron_client.list_networks()
271 for network in self.networks.values():
272 for neutron_net in networks['networks']:
273 if neutron_net['name'] == network.stack_name:
274 network.segmentation_id = neutron_net.get('provider:segmentation_id')
275 # we already have physical_network
276 # network.physical_network = neutron_net.get('provider:physical_network')
277 network.network_type = neutron_net.get('provider:network_type')
278 network.neutron_info = neutron_net
281 """deploys template into a stack using cloud"""
282 print("Deploying context '%s'" % self.name)
284 heat_template = HeatTemplate(self.name, self.template_file,
285 self.heat_parameters)
287 if self.template_file is None:
288 self._add_resources_to_template(heat_template)
291 self.stack = heat_template.create(block=True,
292 timeout=self.heat_timeout)
293 except KeyboardInterrupt:
294 raise SystemExit("\nStack create interrupted")
296 LOG.exception("stack failed")
297 # let the other failures happen, we want stack trace
300 # TODO: use Neutron to get segmentation-id
301 self.get_neutron_info()
303 # copy some vital stack output into server objects
304 for server in self.servers:
306 self.add_server_port(server)
308 if server.floating_ip:
310 self.stack.outputs[server.floating_ip["stack_name"]]
312 print("Context '%s' deployed" % self.name)
314 def add_server_port(self, server):
315 # TODO(hafe) can only handle one internal network for now
316 port = next(iter(server.ports.values()))
317 server.private_ip = self.stack.outputs[port["stack_name"]]
318 server.interfaces = {}
319 for network_name, port in server.ports.items():
320 server.interfaces[network_name] = self.make_interface_dict(
321 network_name, port['stack_name'], self.stack.outputs)
323 def make_interface_dict(self, network_name, stack_name, outputs):
324 private_ip = outputs[stack_name]
325 mac_address = outputs[h_join(stack_name, "mac_address")]
326 output_subnet_cidr = outputs[h_join(self.name, network_name,
329 output_subnet_gateway = outputs[h_join(self.name, network_name,
330 'subnet', 'gateway_ip')]
333 "private_ip": private_ip,
334 "subnet_id": outputs[h_join(stack_name, "subnet_id")],
335 "subnet_cidr": output_subnet_cidr,
336 "network": str(ipaddress.ip_network(output_subnet_cidr).network_address),
337 "netmask": str(ipaddress.ip_network(output_subnet_cidr).netmask),
338 "gateway_ip": output_subnet_gateway,
339 "mac_address": mac_address,
340 "device_id": outputs[h_join(stack_name, "device_id")],
341 "network_id": outputs[h_join(stack_name, "network_id")],
342 "network_name": network_name,
343 # to match vnf_generic
344 "local_mac": mac_address,
345 "local_ip": private_ip,
346 "vld_id": self.networks[network_name].vld_id,
350 """undeploys stack from cloud"""
352 print("Undeploying context '%s'" % self.name)
355 print("Context '%s' undeployed" % self.name)
357 if os.path.exists(self.key_filename):
359 os.remove(self.key_filename)
360 os.remove(self.key_filename + ".pub")
362 LOG.exception("Key filename %s", self.key_filename)
364 super(HeatContext, self).undeploy()
367 def generate_routing_table(server):
370 "network": intf["network"],
371 "netmask": intf["netmask"],
373 # We have to encode a None gateway as '' for Jinja2 to YAML conversion
374 "gateway": intf["gateway_ip"] if intf["gateway_ip"] else '',
376 for name, intf in server.interfaces.items()
380 def _get_server(self, attr_name):
381 """lookup server info by name from context
382 attr_name: either a name for a server created by yardstick or a dict
383 with attribute name mapping when using external heat templates
385 key_filename = pkg_resources.resource_filename(
386 'yardstick.resources',
387 h_join('files/yardstick_key', get_short_key_uuid(self.key_uuid)))
389 if isinstance(attr_name, collections.Mapping):
390 node_name, cname = self.split_name(attr_name['name'])
391 if cname is None or cname != self.name:
394 # Create a dummy server instance for holding the *_ip attributes
395 server = Server(node_name, self, {})
396 server.public_ip = self.stack.outputs.get(
397 attr_name.get("public_ip_attr", object()), None)
399 server.private_ip = self.stack.outputs.get(
400 attr_name.get("private_ip_attr", object()), None)
402 server = self._server_map.get(attr_name, None)
407 "user": server.context.user,
408 "key_filename": key_filename,
409 "private_ip": server.private_ip,
410 "interfaces": server.interfaces,
411 "routing_table": self.generate_routing_table(server),
412 # empty IPv6 routing table
415 # Target server may only have private_ip
417 result["ip"] = server.public_ip
421 def _get_network(self, attr_name):
422 if not isinstance(attr_name, collections.Mapping):
423 network = self.networks.get(attr_name, None)
426 # Don't generalize too much Just support vld_id
427 vld_id = attr_name.get('vld_id', {})
428 network_iter = (n for n in self.networks.values() if n.vld_id == vld_id)
429 network = next(network_iter, None)
435 "name": network.name,
436 "vld_id": network.vld_id,
437 "segmentation_id": network.segmentation_id,
438 "network_type": network.network_type,
439 "physical_network": network.physical_network,