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
18 from collections import OrderedDict
23 from yardstick.benchmark.contexts.base import Context
24 from yardstick.benchmark.contexts.model import Network
25 from yardstick.benchmark.contexts.model import PlacementGroup, ServerGroup
26 from yardstick.benchmark.contexts.model import Server
27 from yardstick.benchmark.contexts.model import update_scheduler_hints
28 from yardstick.common.openstack_utils import get_neutron_client
29 from yardstick.orchestrator.heat import HeatTemplate, get_short_key_uuid
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"
55 self.networks = OrderedDict()
56 self.heat_timeout = None
58 self.placement_groups = []
59 self.server_groups = []
60 self.keypair_name = None
61 self.secgroup_name = None
68 self.template_file = None
69 self.heat_parameters = None
70 self.neutron_client = None
71 # generate an uuid to identify yardstick_key
72 # the first 8 digits of the uuid will be used
73 self.key_uuid = uuid.uuid4()
74 self.heat_timeout = None
75 self.key_filename = ''.join(
76 [consts.YARDSTICK_ROOT_PATH, 'yardstick/resources/files/yardstick_key-',
77 get_short_key_uuid(self.key_uuid)])
78 super(HeatContext, self).__init__()
81 def assign_external_network(networks):
82 sorted_networks = sorted(networks.items())
83 external_network = os.environ.get("EXTERNAL_NETWORK", "net04_ext")
85 have_external_network = any(net.get("external_network") for net in networks.values())
86 if sorted_networks and not have_external_network:
87 # no external net defined, assign it to first network using os.environ
88 sorted_networks[0][1]["external_network"] = external_network
90 return sorted_networks
92 def init(self, attrs):
93 self.check_environment()
94 """initializes itself from the supplied arguments"""
95 self.name = attrs["name"]
97 self._user = attrs.get("user")
99 self.template_file = attrs.get("heat_template")
100 if self.template_file:
101 self.heat_parameters = attrs.get("heat_parameters")
104 self.keypair_name = h_join(self.name, "key")
105 self.secgroup_name = h_join(self.name, "secgroup")
107 self._image = attrs.get("image")
109 self._flavor = attrs.get("flavor")
111 self.heat_timeout = attrs.get("timeout", DEFAULT_HEAT_TIMEOUT)
113 self.placement_groups = [PlacementGroup(name, self, pg_attrs["policy"])
114 for name, pg_attrs in attrs.get(
115 "placement_groups", {}).items()]
117 self.server_groups = [ServerGroup(name, self, sg_attrs["policy"])
118 for name, sg_attrs in attrs.get(
119 "server_groups", {}).items()]
121 # we have to do this first, because we are injecting external_network
123 sorted_networks = self.assign_external_network(attrs["networks"])
125 self.networks = OrderedDict(
126 (name, Network(name, self, net_attrs)) for name, net_attrs in
129 for name, server_attrs in sorted(attrs["servers"].items()):
130 server = Server(name, self, server_attrs)
131 self.servers.append(server)
132 self._server_map[server.dn] = server
135 SSH.gen_keys(self.key_filename)
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.key_uuid)
175 template.add_security_group(self.secgroup_name)
177 for network in self.networks.values():
178 template.add_network(network.stack_name,
179 network.physical_network,
181 network.segmentation_id,
182 network.port_security_enabled,
183 network.network_type)
184 template.add_subnet(network.subnet_stack_name, network.stack_name,
190 template.add_router(network.router.stack_name,
191 network.router.external_gateway_info,
192 network.subnet_stack_name)
193 template.add_router_interface(network.router.stack_if_name,
194 network.router.stack_name,
195 network.subnet_stack_name)
197 # create a list of servers sorted by increasing no of placement groups
198 list_of_servers = sorted(self.servers,
199 key=lambda s: len(s.placement_groups))
202 # add servers with scheduler hints derived from placement groups
205 # create list of servers with availability policy
206 availability_servers = []
207 for server in list_of_servers:
208 for pg in server.placement_groups:
209 if pg.policy == "availability":
210 availability_servers.append(server)
213 for server in availability_servers:
214 if isinstance(server.flavor, dict):
216 self.flavors.add(server.flavor["name"])
218 self.flavors.add(h_join(server.stack_name, "flavor"))
220 # add servers with availability policy
222 for server in availability_servers:
224 for pg in server.placement_groups:
225 update_scheduler_hints(scheduler_hints, added_servers, pg)
226 # workaround for openstack nova bug, check JIRA: YARDSTICK-200
228 if len(availability_servers) == 2:
229 if not scheduler_hints["different_host"]:
230 scheduler_hints.pop("different_host", None)
231 server.add_to_template(template,
232 list(self.networks.values()),
235 scheduler_hints["different_host"] = \
236 scheduler_hints["different_host"][0]
237 server.add_to_template(template,
238 list(self.networks.values()),
241 server.add_to_template(template,
242 list(self.networks.values()),
244 added_servers.append(server.stack_name)
246 # create list of servers with affinity policy
247 affinity_servers = []
248 for server in list_of_servers:
249 for pg in server.placement_groups:
250 if pg.policy == "affinity":
251 affinity_servers.append(server)
254 # add servers with affinity policy
255 for server in affinity_servers:
256 if server.stack_name in added_servers:
259 for pg in server.placement_groups:
260 update_scheduler_hints(scheduler_hints, added_servers, pg)
261 server.add_to_template(template, list(self.networks.values()),
263 added_servers.append(server.stack_name)
266 for sg in self.server_groups:
267 template.add_server_group(sg.name, sg.policy)
269 # add remaining servers with no placement group configured
270 for server in list_of_servers:
271 # TODO placement_group and server_group should combine
272 if not server.placement_groups:
274 # affinity/anti-aff server group
275 sg = server.server_group
277 scheduler_hints["group"] = {'get_resource': sg.name}
278 server.add_to_template(template,
279 list(self.networks.values()),
282 def get_neutron_info(self):
283 if not self.neutron_client:
284 self.neutron_client = get_neutron_client()
286 networks = self.neutron_client.list_networks()
287 for network in self.networks.values():
288 for neutron_net in networks['networks']:
289 if neutron_net['name'] == network.stack_name:
290 network.segmentation_id = neutron_net.get('provider:segmentation_id')
291 # we already have physical_network
292 # network.physical_network = neutron_net.get('provider:physical_network')
293 network.network_type = neutron_net.get('provider:network_type')
294 network.neutron_info = neutron_net
297 """deploys template into a stack using cloud"""
298 print("Deploying context '%s'" % self.name)
300 heat_template = HeatTemplate(self.name, self.template_file,
301 self.heat_parameters)
303 if self.template_file is None:
304 self._add_resources_to_template(heat_template)
307 self.stack = heat_template.create(block=True,
308 timeout=self.heat_timeout)
309 except KeyboardInterrupt:
310 raise SystemExit("\nStack create interrupted")
312 LOG.exception("stack failed")
313 # let the other failures happen, we want stack trace
316 # TODO: use Neutron to get segmentation-id
317 self.get_neutron_info()
319 # copy some vital stack output into server objects
320 for server in self.servers:
322 self.add_server_port(server)
324 if server.floating_ip:
326 self.stack.outputs[server.floating_ip["stack_name"]]
328 print("Context '%s' deployed" % self.name)
330 def add_server_port(self, server):
331 # TODO(hafe) can only handle one internal network for now
332 port = next(iter(server.ports.values()))
333 server.private_ip = self.stack.outputs[port["stack_name"]]
334 server.interfaces = {}
335 for network_name, port in server.ports.items():
336 server.interfaces[network_name] = self.make_interface_dict(
337 network_name, port['stack_name'], self.stack.outputs)
339 def make_interface_dict(self, network_name, stack_name, outputs):
340 private_ip = outputs[stack_name]
341 mac_address = outputs[h_join(stack_name, "mac_address")]
342 output_subnet_cidr = outputs[h_join(self.name, network_name,
345 output_subnet_gateway = outputs[h_join(self.name, network_name,
346 'subnet', 'gateway_ip')]
349 "private_ip": private_ip,
350 "subnet_id": outputs[h_join(stack_name, "subnet_id")],
351 "subnet_cidr": output_subnet_cidr,
352 "network": str(ipaddress.ip_network(output_subnet_cidr).network_address),
353 "netmask": str(ipaddress.ip_network(output_subnet_cidr).netmask),
354 "gateway_ip": output_subnet_gateway,
355 "mac_address": mac_address,
356 "device_id": outputs[h_join(stack_name, "device_id")],
357 "network_id": outputs[h_join(stack_name, "network_id")],
358 "network_name": network_name,
359 # to match vnf_generic
360 "local_mac": mac_address,
361 "local_ip": private_ip,
362 "vld_id": self.networks[network_name].vld_id,
366 """undeploys stack from cloud"""
368 print("Undeploying context '%s'" % self.name)
371 print("Context '%s' undeployed" % self.name)
373 if os.path.exists(self.key_filename):
375 os.remove(self.key_filename)
376 os.remove(self.key_filename + ".pub")
378 LOG.exception("Key filename %s", self.key_filename)
380 super(HeatContext, self).undeploy()
383 def generate_routing_table(server):
386 "network": intf["network"],
387 "netmask": intf["netmask"],
389 # We have to encode a None gateway as '' for Jinja2 to YAML conversion
390 "gateway": intf["gateway_ip"] if intf["gateway_ip"] else '',
392 for name, intf in server.interfaces.items()
396 def _get_server(self, attr_name):
397 """lookup server info by name from context
398 attr_name: either a name for a server created by yardstick or a dict
399 with attribute name mapping when using external heat templates
401 key_filename = pkg_resources.resource_filename(
402 'yardstick.resources',
403 h_join('files/yardstick_key', get_short_key_uuid(self.key_uuid)))
405 if isinstance(attr_name, collections.Mapping):
406 node_name, cname = self.split_name(attr_name['name'])
407 if cname is None or cname != self.name:
410 # Create a dummy server instance for holding the *_ip attributes
411 server = Server(node_name, self, {})
412 server.public_ip = self.stack.outputs.get(
413 attr_name.get("public_ip_attr", object()), None)
415 server.private_ip = self.stack.outputs.get(
416 attr_name.get("private_ip_attr", object()), None)
418 server = self._server_map.get(attr_name, None)
423 "user": server.context.user,
424 "key_filename": key_filename,
425 "private_ip": server.private_ip,
426 "interfaces": server.interfaces,
427 "routing_table": self.generate_routing_table(server),
428 # empty IPv6 routing table
431 # Target server may only have private_ip
433 result["ip"] = server.public_ip
437 def _get_network(self, attr_name):
438 if not isinstance(attr_name, collections.Mapping):
439 network = self.networks.get(attr_name, None)
442 # Don't generalize too much Just support vld_id
443 vld_id = attr_name.get('vld_id', {})
444 network_iter = (n for n in self.networks.values() if n.vld_id == vld_id)
445 network = next(network_iter, None)
451 "name": network.name,
452 "vld_id": network.vld_id,
453 "segmentation_id": network.segmentation_id,
454 "network_type": network.network_type,
455 "physical_network": network.physical_network,