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 = self.name + "." + context.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 = self.stack_name
215 self._add_instance(template, server_name, networks,
216 scheduler_hints=scheduler_hints)
218 # TODO(hafe) fix or remove, no test/sample for this
219 for i in range(self.instances):
220 server_name = "%s-%d" % (self.stack_name, i)
221 self._add_instance(template, server_name, networks,
222 scheduler_hints=scheduler_hints)
225 def update_scheduler_hints(scheduler_hints, added_servers, placement_group):
226 ''' update scheduler hints from server's placement configuration
227 TODO: this code is openstack specific and should move somewhere else
229 if placement_group.policy == "affinity":
230 if "same_host" in scheduler_hints:
231 host_list = scheduler_hints["same_host"]
233 host_list = scheduler_hints["same_host"] = []
235 if "different_host" in scheduler_hints:
236 host_list = scheduler_hints["different_host"]
238 host_list = scheduler_hints["different_host"] = []
240 for name in added_servers:
241 if name in placement_group.members:
242 host_list.append({'get_resource': name})
245 class Context(object):
246 '''Class that represents a context in the logical model'''
254 self.placement_groups = []
255 self.keypair_name = None
256 self.secgroup_name = None
257 self._server_map = {}
261 self.template_file = None
262 self.heat_parameters = None
263 Context.list.append(self)
265 def init(self, attrs):
266 '''initializes itself from the supplied arguments'''
267 self.name = attrs["name"]
270 self._user = attrs["user"]
272 if "heat_template" in attrs:
273 self.template_file = attrs["heat_template"]
274 self.heat_parameters = attrs.get("heat_parameters", None)
277 self.keypair_name = self.name + "-key"
278 self.secgroup_name = self.name + "-secgroup"
281 self._image = attrs["image"]
283 if "flavor" in attrs:
284 self._flavor = attrs["flavor"]
286 if "placement_groups" in attrs:
287 for name, pgattrs in attrs["placement_groups"].items():
288 pg = PlacementGroup(name, self, pgattrs["policy"])
289 self.placement_groups.append(pg)
291 for name, netattrs in attrs["networks"].items():
292 network = Network(name, self, netattrs)
293 self.networks.append(network)
295 for name, serverattrs in attrs["servers"].items():
296 server = Server(name, self, serverattrs)
297 self.servers.append(server)
298 self._server_map[server.dn] = server
302 '''returns application's default image name'''
307 '''returns application's default flavor name'''
312 '''return login user name corresponding to image'''
315 def _add_resources_to_template(self, template):
316 '''add to the template the resources represented by this context'''
317 template.add_keypair(self.keypair_name)
318 template.add_security_group(self.secgroup_name)
320 for network in self.networks:
321 template.add_network(network.stack_name)
322 template.add_subnet(network.subnet_stack_name, network.stack_name,
326 template.add_router(network.router.stack_name,
327 network.router.external_gateway_info,
328 network.subnet_stack_name)
329 template.add_router_interface(network.router.stack_if_name,
330 network.router.stack_name,
331 network.subnet_stack_name)
333 # create a list of servers sorted by increasing no of placement groups
334 list_of_servers = sorted(self.servers,
335 key=lambda s: len(s.placement_groups))
338 # add servers with scheduler hints derived from placement groups
341 # create list of servers with availability policy
342 availability_servers = []
343 for server in list_of_servers:
344 for pg in server.placement_groups:
345 if pg.policy == "availability":
346 availability_servers.append(server)
349 # add servers with availability policy
351 for server in availability_servers:
353 for pg in server.placement_groups:
354 update_scheduler_hints(scheduler_hints, added_servers, pg)
355 server.add_to_template(template, self.networks, scheduler_hints)
356 added_servers.append(server.stack_name)
358 # create list of servers with affinity policy
359 affinity_servers = []
360 for server in list_of_servers:
361 for pg in server.placement_groups:
362 if pg.policy == "affinity":
363 affinity_servers.append(server)
366 # add servers with affinity policy
367 for server in affinity_servers:
368 if server.stack_name in added_servers:
371 for pg in server.placement_groups:
372 update_scheduler_hints(scheduler_hints, added_servers, pg)
373 server.add_to_template(template, self.networks, scheduler_hints)
374 added_servers.append(server.stack_name)
376 # add remaining servers with no placement group configured
377 for server in list_of_servers:
378 if len(server.placement_groups) == 0:
379 server.add_to_template(template, self.networks, {})
382 '''deploys template into a stack using cloud'''
383 print "Deploying context '%s'" % self.name
385 heat_template = HeatTemplate(self.name, self.template_file,
386 self.heat_parameters)
388 if self.template_file is None:
389 self._add_resources_to_template(heat_template)
392 self.stack = heat_template.create()
393 except KeyboardInterrupt:
394 sys.exit("\nStack create interrupted")
395 except RuntimeError as err:
396 sys.exit("error: failed to deploy stack: '%s'" % err.args)
397 except Exception as err:
398 sys.exit("error: failed to deploy stack: '%s'" % err)
400 # copy some vital stack output into server objects
401 for server in self.servers:
402 if len(server.ports) > 0:
403 # TODO(hafe) can only handle one internal network for now
404 port = server.ports.values()[0]
405 server.private_ip = self.stack.outputs[port["stack_name"]]
407 if server.floating_ip:
409 self.stack.outputs[server.floating_ip["stack_name"]]
411 print "Context '%s' deployed" % self.name
414 '''undeploys stack from cloud'''
416 print "Undeploying context '%s'" % self.name
419 print "Context '%s' undeployed" % self.name
422 def get_server_by_name(dn):
423 '''lookup server object by DN
425 dn is a distinguished name including the context name'''
427 raise ValueError("dn '%s' is malformed" % dn)
429 for context in Context.list:
430 if dn in context._server_map:
431 return context._server_map[dn]
436 def get_context_by_name(name):
437 for context in Context.list:
438 if name == context.name:
443 def get_server(attr_name):
444 '''lookup server object by name from context
445 attr_name: either a name for a server created by yardstick or a dict
446 with attribute name mapping when using external heat templates
448 if type(attr_name) is dict:
449 cname = attr_name["name"].split(".")[1]
450 context = Context.get_context_by_name(cname)
452 raise ValueError("context not found for server '%s'" %
457 if "public_ip_attr" in attr_name:
458 public_ip = context.stack.outputs[attr_name["public_ip_attr"]]
459 if "private_ip_attr" in attr_name:
460 private_ip = context.stack.outputs[
461 attr_name["private_ip_attr"]]
463 # Create a dummy server instance for holding the *_ip attributes
464 server = Server(attr_name["name"].split(".")[0], context, {})
465 server.public_ip = public_ip
466 server.private_ip = private_ip
469 return Context.get_server_by_name(attr_name)