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 ##############################################################################
16 from yardstick.orchestrator.heat import HeatTemplate
20 '''Base class for classes in the logical model
21 Contains common attributes and methods
23 def __init__(self, name, context):
24 # model identities and reference
26 self._context = context
29 self.stack_name = None
34 '''returns distinguished name for object'''
35 return self.name + "." + self._context.name
38 class PlacementGroup(Object):
39 '''Class that represents a placement group in the logical model
40 Concept comes from the OVF specification. Policy should be one of
41 "availability" or "affinity (there are more but they are not supported)"
45 def __init__(self, name, context, policy):
46 if policy not in ["affinity", "availability"]:
47 raise ValueError("placement group '%s', policy '%s' is not valid" %
51 self.stack_name = context.name + "-" + name
53 PlacementGroup.map[name] = self
55 def add_member(self, name):
56 self.members.add(name)
60 if name in PlacementGroup.map:
61 return PlacementGroup.map[name]
67 '''Class that represents a router in the logical model'''
68 def __init__(self, name, network_name, context, external_gateway_info):
69 super(Router, self).__init__(name, context)
71 self.stack_name = context.name + "-" + network_name + "-" + self.name
72 self.stack_if_name = self.stack_name + "-if0"
73 self.external_gateway_info = external_gateway_info
76 class Network(Object):
77 '''Class that represents a network in the logical model'''
80 def __init__(self, name, context, attrs):
81 super(Network, self).__init__(name, context)
82 self.stack_name = context.name + "-" + self.name
83 self.subnet_stack_name = self.stack_name + "-subnet"
84 self.subnet_cidr = attrs.get('cidr', '10.0.1.0/24')
87 if "external_network" in attrs:
88 self.router = Router("router", self.name,
89 context, attrs["external_network"])
91 Network.list.append(self)
93 def has_route_to(self, network_name):
94 '''determines if this network has a route to the named network'''
95 if self.router and self.router.external_gateway_info == network_name:
100 def find_by_route_to(external_network):
101 '''finds a network that has a route to the specified network'''
102 for network in Network.list:
103 if network.has_route_to(external_network):
107 def find_external_network():
108 '''return the name of an external network some network in this
109 context has a route to'''
110 for network in Network.list:
112 return network.router.external_gateway_info
116 class Server(Object):
117 '''Class that represents a server in the logical model'''
120 def __init__(self, name, context, attrs):
121 super(Server, self).__init__(name, context)
122 self.stack_name = context.name + "-" + self.name
123 self.keypair_name = context.keypair_name
124 self.secgroup_name = context.secgroup_name
125 self.context = context
126 self.public_ip = None
127 self.private_ip = None
132 self.placement_groups = []
133 placement = attrs.get("placement", [])
134 placement = placement if type(placement) is list else [placement]
136 pg = PlacementGroup.get(p)
138 raise ValueError("server '%s', placement '%s' is invalid" %
140 self.placement_groups.append(pg)
141 pg.add_member(self.stack_name)
144 if "instances" in attrs:
145 self.instances = attrs["instances"]
147 # dict with key network name, each item is a dict with port name and ip
150 self.floating_ip = None
151 if "floating_ip" in attrs:
152 self.floating_ip = {}
154 if self.floating_ip is not None:
155 ext_net = Network.find_external_network()
156 assert ext_net is not None
157 self.floating_ip["external_network"] = ext_net
161 self._image = attrs["image"]
164 if "flavor" in attrs:
165 self._flavor = attrs["flavor"]
167 Server.list.append(self)
171 '''returns a server's image name'''
175 return self._context.image
179 '''returns a server's flavor name'''
183 return self._context.flavor
185 def _add_instance(self, template, server_name, networks, scheduler_hints):
186 '''adds to the template one server and corresponding resources'''
188 for network in networks:
189 port_name = server_name + "-" + network.name + "-port"
190 self.ports[network.name] = {"stack_name": port_name}
191 template.add_port(port_name, network.stack_name,
192 network.subnet_stack_name,
193 sec_group_id=self.secgroup_name)
194 port_name_list.append(port_name)
197 external_network = self.floating_ip["external_network"]
198 if network.has_route_to(external_network):
199 self.floating_ip["stack_name"] = server_name + "-fip"
200 template.add_floating_ip(self.floating_ip["stack_name"],
203 network.router.stack_if_name,
206 template.add_server(server_name, self.image, self.flavor,
207 ports=port_name_list,
208 key_name=self.keypair_name,
209 scheduler_hints=scheduler_hints)
211 def add_to_template(self, template, networks, scheduler_hints=None):
212 '''adds to the template one or more servers (instances)'''
213 if self.instances == 1:
214 server_name = "%s-%s" % (template.name, self.name)
215 self._add_instance(template, server_name, networks,
216 scheduler_hints=scheduler_hints)
218 for i in range(self.instances):
219 server_name = "%s-%s-%d" % (template.name, self.name, i)
220 self._add_instance(template, server_name, networks,
221 scheduler_hints=scheduler_hints)
224 def update_scheduler_hints(scheduler_hints, added_servers, placement_group):
225 ''' update scheduler hints from server's placement configuration
226 TODO: this code is openstack specific and should move somewhere else
228 if placement_group.policy == "affinity":
229 if "same_host" in scheduler_hints:
230 host_list = scheduler_hints["same_host"]
232 host_list = scheduler_hints["same_host"] = []
234 if "different_host" in scheduler_hints:
235 host_list = scheduler_hints["different_host"]
237 host_list = scheduler_hints["different_host"] = []
239 for name in added_servers:
240 if name in placement_group.members:
241 host_list.append({'get_resource': name})
244 class Context(object):
245 '''Class that represents a context in the logical model'''
253 self.placement_groups = []
254 self.keypair_name = None
255 self.secgroup_name = None
256 self._server_map = {}
260 self.template_file = None
261 self.heat_parameters = None
262 Context.list.append(self)
264 def init(self, attrs):
265 '''initializes itself from the supplied arguments'''
266 self.name = attrs["name"]
269 self._user = attrs["user"]
271 if "heat_template" in attrs:
272 self.template_file = attrs["heat_template"]
273 self.heat_parameters = attrs.get("heat_parameters", None)
276 self.keypair_name = self.name + "-key"
277 self.secgroup_name = self.name + "-secgroup"
280 self._image = attrs["image"]
282 if "flavor" in attrs:
283 self._flavor = attrs["flavor"]
285 if "placement_groups" in attrs:
286 for name, pgattrs in attrs["placement_groups"].items():
287 pg = PlacementGroup(name, self, pgattrs["policy"])
288 self.placement_groups.append(pg)
290 for name, netattrs in attrs["networks"].items():
291 network = Network(name, self, netattrs)
292 self.networks.append(network)
294 for name, serverattrs in attrs["servers"].items():
295 server = Server(name, self, serverattrs)
296 self.servers.append(server)
297 self._server_map[server.dn] = server
301 '''returns application's default image name'''
306 '''returns application's default flavor name'''
311 '''return login user name corresponding to image'''
314 def _add_resources_to_template(self, template):
315 '''add to the template the resources represented by this context'''
316 template.add_keypair(self.keypair_name)
317 template.add_security_group(self.secgroup_name)
319 for network in self.networks:
320 template.add_network(network.stack_name)
321 template.add_subnet(network.subnet_stack_name, network.stack_name,
325 template.add_router(network.router.stack_name,
326 network.router.external_gateway_info,
327 network.subnet_stack_name)
328 template.add_router_interface(network.router.stack_if_name,
329 network.router.stack_name,
330 network.subnet_stack_name)
332 # create a list of servers sorted by increasing no of placement groups
333 list_of_servers = sorted(self.servers,
334 key=lambda s: len(s.placement_groups))
337 # add servers with scheduler hints derived from placement groups
340 # create list of servers with availability policy
341 availability_servers = []
342 for server in list_of_servers:
343 for pg in server.placement_groups:
344 if pg.policy == "availability":
345 availability_servers.append(server)
348 # add servers with availability policy
350 for server in availability_servers:
352 for pg in server.placement_groups:
353 update_scheduler_hints(scheduler_hints, added_servers, pg)
354 server.add_to_template(template, self.networks, scheduler_hints)
355 added_servers.append(server.stack_name)
357 # create list of servers with affinity policy
358 affinity_servers = []
359 for server in list_of_servers:
360 for pg in server.placement_groups:
361 if pg.policy == "affinity":
362 affinity_servers.append(server)
365 # add servers with affinity policy
366 for server in affinity_servers:
367 if server.stack_name in added_servers:
370 for pg in server.placement_groups:
371 update_scheduler_hints(scheduler_hints, added_servers, pg)
372 server.add_to_template(template, self.networks, scheduler_hints)
373 added_servers.append(server.stack_name)
375 # add remaining servers with no placement group configured
376 for server in list_of_servers:
377 if len(server.placement_groups) == 0:
378 server.add_to_template(template, self.networks, {})
381 '''deploys template into a stack using cloud'''
382 print "Deploying context '%s'" % self.name
384 heat_template = HeatTemplate(self.name, self.template_file,
385 self.heat_parameters)
387 if self.template_file is None:
388 self._add_resources_to_template(heat_template)
391 self.stack = heat_template.create()
392 except KeyboardInterrupt:
393 sys.exit("\nStack create interrupted")
394 except RuntimeError as err:
395 sys.exit("error: failed to deploy stack: '%s'" % err.args)
396 except Exception as err:
397 sys.exit("error: failed to deploy stack: '%s'" % err)
399 # copy some vital stack output into server objects
400 for server in self.servers:
401 if len(server.ports) > 0:
402 # TODO(hafe) can only handle one internal network for now
403 port = server.ports.values()[0]
404 server.private_ip = self.stack.outputs[port["stack_name"]]
406 if server.floating_ip:
408 self.stack.outputs[server.floating_ip["stack_name"]]
410 print "Context '%s' deployed" % self.name
413 '''undeploys stack from cloud'''
415 print "Undeploying context '%s'" % self.name
418 print "Context '%s' undeployed" % self.name
421 def get_server_by_name(dn):
422 '''lookup server object by DN
424 dn is a distinguished name including the context name'''
426 raise ValueError("dn '%s' is malformed" % dn)
428 for context in Context.list:
429 if dn in context._server_map:
430 return context._server_map[dn]
435 def get_context_by_name(name):
436 for context in Context.list:
437 if name == context.name:
442 def get_server(attr_name):
443 '''lookup server object by name from context
444 attr_name: either a name for a server created by yardstick or a dict
445 with attribute name mapping when using external heat templates
447 if type(attr_name) is dict:
448 cname = attr_name["name"].split(".")[1]
449 context = Context.get_context_by_name(cname)
451 raise ValueError("context not found for server '%s'" %
456 if "public_ip_attr" in attr_name:
457 public_ip = context.stack.outputs[attr_name["public_ip_attr"]]
458 if "private_ip_attr" in attr_name:
459 private_ip = context.stack.outputs[
460 attr_name["private_ip_attr"]]
462 # Create a dummy server instance for holding the *_ip attributes
463 server = Server(attr_name["name"].split(".")[0], context, {})
464 server.public_ip = public_ip
465 server.private_ip = private_ip
468 return Context.get_server_by_name(attr_name)