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 ##############################################################################
17 from yardstick.orchestrator.heat import HeatTemplate
21 '''Base class for classes in the logical model
22 Contains common attributes and methods
24 def __init__(self, name, context):
25 # model identities and reference
27 self._context = context
30 self.stack_name = None
35 '''returns distinguished name for object'''
36 return self.name + "." + self._context.name
39 class PlacementGroup(Object):
40 '''Class that represents a placement group in the logical model
41 Concept comes from the OVF specification. Policy should be one of
42 "availability" or "affinity (there are more but they are not supported)"
46 def __init__(self, name, context, policy):
47 if policy not in ["affinity", "availability"]:
48 raise ValueError("placement group '%s', policy '%s' is not valid" %
52 self.stack_name = context.name + "-" + name
54 PlacementGroup.map[name] = self
56 def add_member(self, name):
57 self.members.add(name)
61 if name in PlacementGroup.map:
62 return PlacementGroup.map[name]
68 '''Class that represents a router in the logical model'''
69 def __init__(self, name, network_name, context, external_gateway_info):
70 super(Router, self).__init__(name, context)
72 self.stack_name = context.name + "-" + network_name + "-" + self.name
73 self.stack_if_name = self.stack_name + "-if0"
74 self.external_gateway_info = external_gateway_info
77 class Network(Object):
78 '''Class that represents a network in the logical model'''
81 def __init__(self, name, context, attrs):
82 super(Network, self).__init__(name, context)
83 self.stack_name = context.name + "-" + self.name
84 self.subnet_stack_name = self.stack_name + "-subnet"
85 self.subnet_cidr = attrs.get('cidr', '10.0.1.0/24')
88 if "external_network" in attrs:
89 self.router = Router("router", self.name,
90 context, attrs["external_network"])
92 Network.list.append(self)
94 def has_route_to(self, network_name):
95 '''determines if this network has a route to the named network'''
96 if self.router and self.router.external_gateway_info == network_name:
101 def find_by_route_to(external_network):
102 '''finds a network that has a route to the specified network'''
103 for network in Network.list:
104 if network.has_route_to(external_network):
108 def find_external_network():
109 '''return the name of an external network some network in this
110 context has a route to'''
111 for network in Network.list:
113 return network.router.external_gateway_info
117 class Server(Object):
118 '''Class that represents a server in the logical model'''
121 def __init__(self, name, context, attrs):
122 super(Server, self).__init__(name, context)
123 self.stack_name = context.name + "-" + self.name
124 self.keypair_name = context.keypair_name
125 self.secgroup_name = context.secgroup_name
126 self.context = context
131 self.placement_groups = []
132 placement = attrs.get("placement", [])
133 placement = placement if type(placement) is list else [placement]
135 pg = PlacementGroup.get(p)
137 raise ValueError("server '%s', placement '%s' is invalid" %
139 self.placement_groups.append(pg)
140 pg.add_member(self.stack_name)
143 if "instances" in attrs:
144 self.instances = attrs["instances"]
146 # dict with key network name, each item is a dict with port name and ip
149 self.floating_ip = None
150 if "floating_ip" in attrs:
151 self.floating_ip = {}
153 if self.floating_ip is not None:
154 ext_net = Network.find_external_network()
155 assert ext_net is not None
156 self.floating_ip["external_network"] = ext_net
160 self._image = attrs["image"]
163 if "flavor" in attrs:
164 self._flavor = attrs["flavor"]
166 Server.list.append(self)
170 '''returns a server's image name'''
174 return self._context.image
178 '''returns a server's flavor name'''
182 return self._context.flavor
184 def _add_instance(self, template, server_name, networks, scheduler_hints):
185 '''adds to the template one server and corresponding resources'''
187 for network in networks:
188 port_name = server_name + "-" + network.name + "-port"
189 self.ports[network.name] = {"stack_name": port_name}
190 template.add_port(port_name, network.stack_name,
191 network.subnet_stack_name,
192 sec_group_id=self.secgroup_name)
193 port_name_list.append(port_name)
196 external_network = self.floating_ip["external_network"]
197 if network.has_route_to(external_network):
198 self.floating_ip["stack_name"] = server_name + "-fip"
199 template.add_floating_ip(self.floating_ip["stack_name"],
202 network.router.stack_if_name,
205 template.add_server(server_name, self.image, self.flavor,
206 ports=port_name_list,
207 key_name=self.keypair_name,
208 scheduler_hints=scheduler_hints)
210 def add_to_template(self, template, networks, scheduler_hints=None):
211 '''adds to the template one or more servers (instances)'''
212 if self.instances == 1:
213 server_name = "%s-%s" % (template.name, self.name)
214 self._add_instance(template, server_name, networks,
215 scheduler_hints=scheduler_hints)
217 for i in range(self.instances):
218 server_name = "%s-%s-%d" % (template.name, self.name, i)
219 self._add_instance(template, server_name, networks,
220 scheduler_hints=scheduler_hints)
223 def update_scheduler_hints(scheduler_hints, added_servers, placement_group):
224 ''' update scheduler hints from server's placement configuration
225 TODO: this code is openstack specific and should move somewhere else
227 if placement_group.policy == "affinity":
228 if "same_host" in scheduler_hints:
229 host_list = scheduler_hints["same_host"]
231 host_list = scheduler_hints["same_host"] = []
233 if "different_host" in scheduler_hints:
234 host_list = scheduler_hints["different_host"]
236 host_list = scheduler_hints["different_host"] = []
238 for name in added_servers:
239 if name in placement_group.members:
240 host_list.append({'get_resource': name})
243 class Context(object):
244 '''Class that represents a context in the logical model'''
252 self.placement_groups = []
253 self.keypair_name = None
254 self.secgroup_name = None
255 self._server_map = {}
259 Context.list.append(self)
261 def init(self, attrs):
262 '''initializes itself from the supplied arguments'''
263 self.name = attrs["name"]
264 self.keypair_name = self.name + "-key"
265 self.secgroup_name = self.name + "-secgroup"
268 self._image = attrs["image"]
270 if "flavor" in attrs:
271 self._flavor = attrs["flavor"]
274 self._user = attrs["user"]
276 if "placement_groups" in attrs:
277 for name, pgattrs in attrs["placement_groups"].items():
278 pg = PlacementGroup(name, self, pgattrs["policy"])
279 self.placement_groups.append(pg)
281 for name, netattrs in attrs["networks"].items():
282 network = Network(name, self, netattrs)
283 self.networks.append(network)
285 for name, serverattrs in attrs["servers"].items():
286 server = Server(name, self, serverattrs)
287 self.servers.append(server)
288 self._server_map[server.dn] = server
292 '''returns application's default image name'''
297 '''returns application's default flavor name'''
302 '''return login user name corresponding to image'''
305 def _add_resources_to_template(self, template):
306 '''add to the template the resources represented by this context'''
307 template.add_keypair(self.keypair_name)
308 template.add_security_group(self.secgroup_name)
310 for network in self.networks:
311 template.add_network(network.stack_name)
312 template.add_subnet(network.subnet_stack_name, network.stack_name,
316 template.add_router(network.router.stack_name,
317 network.router.external_gateway_info,
318 network.subnet_stack_name)
319 template.add_router_interface(network.router.stack_if_name,
320 network.router.stack_name,
321 network.subnet_stack_name)
323 # create a list of servers sorted by increasing no of placement groups
324 list_of_servers = sorted(self.servers,
325 key=lambda s: len(s.placement_groups))
328 # add servers with scheduler hints derived from placement groups
331 # create list of servers with availability policy
332 availability_servers = []
333 for server in list_of_servers:
334 for pg in server.placement_groups:
335 if pg.policy == "availability":
336 availability_servers.append(server)
339 # add servers with availability policy
341 for server in availability_servers:
343 for pg in server.placement_groups:
344 update_scheduler_hints(scheduler_hints, added_servers, pg)
345 server.add_to_template(template, self.networks, scheduler_hints)
346 added_servers.append(server.stack_name)
348 # create list of servers with affinity policy
349 affinity_servers = []
350 for server in list_of_servers:
351 for pg in server.placement_groups:
352 if pg.policy == "affinity":
353 affinity_servers.append(server)
356 # add servers with affinity policy
357 for server in affinity_servers:
358 if server.stack_name in added_servers:
361 for pg in server.placement_groups:
362 update_scheduler_hints(scheduler_hints, added_servers, pg)
363 server.add_to_template(template, self.networks, scheduler_hints)
364 added_servers.append(server.stack_name)
366 # add remaining servers with no placement group configured
367 for server in list_of_servers:
368 if len(server.placement_groups) == 0:
369 server.add_to_template(template, self.networks, {})
372 '''deploys template into a stack using cloud'''
373 print "Deploying context as stack '%s' using auth_url %s" % (
374 self.name, os.environ.get('OS_AUTH_URL'))
376 template = HeatTemplate(self.name)
377 self._add_resources_to_template(template)
380 self.stack = template.create()
381 except KeyboardInterrupt:
382 sys.exit("\nStack create interrupted")
383 except RuntimeError as err:
384 sys.exit("error: failed to deploy stack: '%s'" % err.args)
385 except Exception as err:
386 sys.exit("error: failed to deploy stack: '%s'" % err)
388 # Iterate the servers in this context and copy out needed info
389 for server in self.servers:
390 for port in server.ports.itervalues():
391 port["ipaddr"] = self.stack.outputs[port["stack_name"]]
393 if server.floating_ip:
394 server.floating_ip["ipaddr"] = \
395 self.stack.outputs[server.floating_ip["stack_name"]]
397 print "Context deployed"
400 '''undeploys stack from cloud'''
402 print "Undeploying context (stack) '%s'" % self.name
405 print "Context undeployed"
409 '''lookup server object by DN
411 dn is a distinguished name including the context name'''
413 raise ValueError("dn '%s' is malformed" % dn)
415 for context in Context.list:
416 if dn in context._server_map:
417 return context._server_map[dn]