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
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.constants import YARDSTICK_ROOT_PATH
32 LOG = logging.getLogger(__name__)
34 DEFAULT_HEAT_TIMEOUT = 3600
37 class HeatContext(Context):
38 """Class that represents a context in the logical model"""
40 __context_type__ = "Heat"
45 self.networks = OrderedDict()
47 self.placement_groups = []
48 self.server_groups = []
49 self.keypair_name = None
50 self.secgroup_name = None
56 self.template_file = None
57 self.heat_parameters = None
58 self.neutron_client = None
59 # generate an uuid to identify yardstick_key
60 # the first 8 digits of the uuid will be used
61 self.key_uuid = uuid.uuid4()
62 self.heat_timeout = None
63 self.key_filename = ''.join(
64 [YARDSTICK_ROOT_PATH, 'yardstick/resources/files/yardstick_key-',
65 get_short_key_uuid(self.key_uuid)])
66 super(HeatContext, self).__init__()
68 def assign_external_network(self, networks):
69 sorted_networks = sorted(networks.items())
70 external_network = os.environ.get("EXTERNAL_NETWORK", "net04_ext")
72 have_external_network = any(net.get("external_network") for net in networks.values())
73 if sorted_networks and not have_external_network:
74 # no external net defined, assign it to first network using os.environ
75 sorted_networks[0][1]["external_network"] = external_network
77 self.networks = OrderedDict((name, Network(name, self, attrs))
78 for name, attrs in sorted_networks)
80 def init(self, attrs):
81 """initializes itself from the supplied arguments"""
82 self.name = attrs["name"]
84 self._user = attrs.get("user")
86 self.template_file = attrs.get("heat_template")
87 if self.template_file:
88 self.heat_parameters = attrs.get("heat_parameters")
91 self.keypair_name = self.name + "-key"
92 self.secgroup_name = self.name + "-secgroup"
94 self._image = attrs.get("image")
96 self._flavor = attrs.get("flavor")
98 self.heat_timeout = attrs.get("timeout", DEFAULT_HEAT_TIMEOUT)
100 self.placement_groups = [PlacementGroup(name, self, pgattrs["policy"])
101 for name, pgattrs in attrs.get(
102 "placement_groups", {}).items()]
104 self.server_groups = [ServerGroup(name, self, sgattrs["policy"])
105 for name, sgattrs in attrs.get(
106 "server_groups", {}).items()]
108 # we have to do this first, because we are injecting external_network
110 self.assign_external_network(attrs["networks"])
112 for name, serverattrs in sorted(attrs["servers"].items()):
113 server = Server(name, self, serverattrs)
114 self.servers.append(server)
115 self._server_map[server.dn] = server
117 rsa_key = paramiko.RSAKey.generate(bits=2048, progress_func=None)
118 rsa_key.write_private_key_file(self.key_filename)
119 print("Writing %s ..." % self.key_filename)
120 with open(self.key_filename + ".pub", "w") as pubkey_file:
122 "%s %s\n" % (rsa_key.get_name(), rsa_key.get_base64()))
126 """returns application's default image name"""
131 """returns application's default flavor name"""
136 """return login user name corresponding to image"""
139 def _add_resources_to_template(self, template):
140 """add to the template the resources represented by this context"""
143 if isinstance(self.flavor, dict):
144 flavor = self.flavor.setdefault("name", self.name + "-flavor")
145 template.add_flavor(**self.flavor)
146 self.flavors.add(flavor)
148 template.add_keypair(self.keypair_name, self.key_uuid)
149 template.add_security_group(self.secgroup_name)
151 for network in self.networks.values():
152 template.add_network(network.stack_name,
153 network.physical_network,
155 network.segmentation_id,
156 network.port_security_enabled)
157 template.add_subnet(network.subnet_stack_name, network.stack_name,
163 template.add_router(network.router.stack_name,
164 network.router.external_gateway_info,
165 network.subnet_stack_name)
166 template.add_router_interface(network.router.stack_if_name,
167 network.router.stack_name,
168 network.subnet_stack_name)
170 # create a list of servers sorted by increasing no of placement groups
171 list_of_servers = sorted(self.servers,
172 key=lambda s: len(s.placement_groups))
175 # add servers with scheduler hints derived from placement groups
178 # create list of servers with availability policy
179 availability_servers = []
180 for server in list_of_servers:
181 for pg in server.placement_groups:
182 if pg.policy == "availability":
183 availability_servers.append(server)
186 for server in availability_servers:
187 if isinstance(server.flavor, dict):
189 self.flavors.add(server.flavor["name"])
191 self.flavors.add(server.stack_name + "-flavor")
193 # add servers with availability policy
195 for server in availability_servers:
197 for pg in server.placement_groups:
198 update_scheduler_hints(scheduler_hints, added_servers, pg)
199 # workaround for openstack nova bug, check JIRA: YARDSTICK-200
201 if len(availability_servers) == 2:
202 if not scheduler_hints["different_host"]:
203 scheduler_hints.pop("different_host", None)
204 server.add_to_template(template,
205 list(self.networks.values()),
208 scheduler_hints["different_host"] = \
209 scheduler_hints["different_host"][0]
210 server.add_to_template(template,
211 list(self.networks.values()),
214 server.add_to_template(template,
215 list(self.networks.values()),
217 added_servers.append(server.stack_name)
219 # create list of servers with affinity policy
220 affinity_servers = []
221 for server in list_of_servers:
222 for pg in server.placement_groups:
223 if pg.policy == "affinity":
224 affinity_servers.append(server)
227 # add servers with affinity policy
228 for server in affinity_servers:
229 if server.stack_name in added_servers:
232 for pg in server.placement_groups:
233 update_scheduler_hints(scheduler_hints, added_servers, pg)
234 server.add_to_template(template, list(self.networks.values()),
236 added_servers.append(server.stack_name)
239 for sg in self.server_groups:
240 template.add_server_group(sg.name, sg.policy)
242 # add remaining servers with no placement group configured
243 for server in list_of_servers:
244 # TODO placement_group and server_group should combine
245 if not server.placement_groups:
247 # affinity/anti-aff server group
248 sg = server.server_group
250 scheduler_hints["group"] = {'get_resource': sg.name}
251 server.add_to_template(template,
252 list(self.networks.values()),
255 def get_neutron_info(self):
256 if not self.neutron_client:
257 self.neutron_client = get_neutron_client()
259 networks = self.neutron_client.list_networks()
260 for network in self.networks.values():
261 for neutron_net in networks['networks']:
262 if neutron_net['name'] == network.stack_name:
263 network.segmentation_id = neutron_net.get('provider:segmentation_id')
264 # we already have physical_network
265 # network.physical_network = neutron_net.get('provider:physical_network')
266 network.network_type = neutron_net.get('provider:network_type')
267 network.neutron_info = neutron_net
270 """deploys template into a stack using cloud"""
271 print("Deploying context '%s'" % self.name)
273 heat_template = HeatTemplate(self.name, self.template_file,
274 self.heat_parameters)
276 if self.template_file is None:
277 self._add_resources_to_template(heat_template)
280 self.stack = heat_template.create(block=True,
281 timeout=self.heat_timeout)
282 except KeyboardInterrupt:
283 raise SystemExit("\nStack create interrupted")
285 LOG.exception("stack failed")
286 # let the other failures happen, we want stack trace
289 # TODO: use Neutron to get segementation-id
290 self.get_neutron_info()
292 # copy some vital stack output into server objects
293 for server in self.servers:
295 self.add_server_port(server)
297 if server.floating_ip:
299 self.stack.outputs[server.floating_ip["stack_name"]]
301 print("Context '%s' deployed" % self.name)
303 def add_server_port(self, server):
304 # TODO(hafe) can only handle one internal network for now
305 port = next(iter(server.ports.values()))
306 server.private_ip = self.stack.outputs[port["stack_name"]]
307 server.interfaces = {}
308 for network_name, port in server.ports.items():
309 server.interfaces[network_name] = self.make_interface_dict(
310 network_name, port['stack_name'], self.stack.outputs)
312 def make_interface_dict(self, network_name, stack_name, outputs):
313 private_ip = outputs[stack_name]
314 mac_addr = outputs[stack_name + "-mac_address"]
315 subnet_cidr_key = "-".join([self.name, network_name, 'subnet', 'cidr'])
316 gateway_key = "-".join([self.name, network_name, 'subnet', 'gateway_ip'])
317 subnet_cidr = outputs[subnet_cidr_key]
318 subnet_ip = ipaddress.ip_network(subnet_cidr)
320 "private_ip": private_ip,
321 "subnet_id": outputs[stack_name + "-subnet_id"],
322 "subnet_cidr": subnet_cidr,
323 "network": str(subnet_ip.network_address),
324 "netmask": str(subnet_ip.netmask),
325 "gateway_ip": outputs[gateway_key],
326 "mac_address": mac_addr,
327 "device_id": outputs[stack_name + "-device_id"],
328 "network_id": outputs[stack_name + "-network_id"],
329 "network_name": network_name,
330 # to match vnf_generic
331 "local_mac": mac_addr,
332 "local_ip": private_ip,
333 "vld_id": self.networks[network_name].vld_id,
337 """undeploys stack from cloud"""
339 print("Undeploying context '%s'" % self.name)
342 print("Context '%s' undeployed" % self.name)
344 if os.path.exists(self.key_filename):
346 os.remove(self.key_filename)
347 os.remove(self.key_filename + ".pub")
349 LOG.exception("Key filename %s", self.key_filename)
351 super(HeatContext, self).undeploy()
354 def generate_routing_table(server):
357 "network": intf["network"],
358 "netmask": intf["netmask"],
360 "gateway": intf["gateway_ip"],
362 for name, intf in server.interfaces.items()
366 def _get_server(self, attr_name):
367 """lookup server info by name from context
368 attr_name: either a name for a server created by yardstick or a dict
369 with attribute name mapping when using external heat templates
371 key_filename = pkg_resources.resource_filename(
372 'yardstick.resources',
373 'files/yardstick_key-' + get_short_key_uuid(self.key_uuid))
375 if not isinstance(attr_name, collections.Mapping):
376 server = self._server_map.get(attr_name, None)
379 cname = attr_name["name"].split(".")[1]
380 if cname != self.name:
385 if "public_ip_attr" in attr_name:
386 public_ip = self.stack.outputs[attr_name["public_ip_attr"]]
387 if "private_ip_attr" in attr_name:
388 private_ip = self.stack.outputs[
389 attr_name["private_ip_attr"]]
391 # Create a dummy server instance for holding the *_ip attributes
392 server = Server(attr_name["name"].split(".")[0], self, {})
393 server.public_ip = public_ip
394 server.private_ip = private_ip
400 "user": server.context.user,
401 "key_filename": key_filename,
402 "private_ip": server.private_ip,
403 "interfaces": server.interfaces,
404 "routing_table": self.generate_routing_table(server),
405 # empty IPv6 routing table
408 # Target server may only have private_ip
410 result["ip"] = server.public_ip
414 def _get_network(self, attr_name):
415 if not isinstance(attr_name, collections.Mapping):
416 network = self.networks.get(attr_name, None)
419 # Don't generalize too much Just support vld_id
420 vld_id = attr_name.get('vld_id')
424 network = next((n for n in self.networks.values() if
425 getattr(n, "vld_id", None) == vld_id), None)
431 "name": network.name,
432 "vld_id": network.vld_id,
433 "segmentation_id": network.segmentation_id,
434 "network_type": network.network_type,
435 "physical_network": network.physical_network,