Merge "remove yardstick_key from the repo"
[yardstick.git] / yardstick / benchmark / contexts / heat.py
1 ##############################################################################
2 # Copyright (c) 2015 Ericsson AB and others.
3 #
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 ##############################################################################
9
10 import os
11 import sys
12 import pkg_resources
13 import paramiko
14
15 from yardstick.benchmark.contexts.base import Context
16 from yardstick.benchmark.contexts.model import Server
17 from yardstick.benchmark.contexts.model import PlacementGroup
18 from yardstick.benchmark.contexts.model import Network
19 from yardstick.benchmark.contexts.model import update_scheduler_hints
20 from yardstick.orchestrator.heat import HeatTemplate
21
22
23 class HeatContext(Context):
24     '''Class that represents a context in the logical model'''
25
26     __context_type__ = "Heat"
27
28     def __init__(self):
29         self.name = None
30         self.stack = None
31         self.networks = []
32         self.servers = []
33         self.placement_groups = []
34         self.keypair_name = None
35         self.secgroup_name = None
36         self._server_map = {}
37         self._image = None
38         self._flavor = None
39         self._user = None
40         self.template_file = None
41         self.heat_parameters = None
42         self.key_filename = 'yardstick/resources/files/yardstick_key'
43         super(self.__class__, self).__init__()
44
45     def init(self, attrs):
46         '''initializes itself from the supplied arguments'''
47         self.name = attrs["name"]
48
49         if "user" in attrs:
50             self._user = attrs["user"]
51
52         if "heat_template" in attrs:
53             self.template_file = attrs["heat_template"]
54             self.heat_parameters = attrs.get("heat_parameters", None)
55             return
56
57         self.keypair_name = self.name + "-key"
58         self.secgroup_name = self.name + "-secgroup"
59
60         if "image" in attrs:
61             self._image = attrs["image"]
62
63         if "flavor" in attrs:
64             self._flavor = attrs["flavor"]
65
66         if "placement_groups" in attrs:
67             for name, pgattrs in attrs["placement_groups"].items():
68                 pg = PlacementGroup(name, self, pgattrs["policy"])
69                 self.placement_groups.append(pg)
70
71         for name, netattrs in attrs["networks"].items():
72             network = Network(name, self, netattrs)
73             self.networks.append(network)
74
75         for name, serverattrs in attrs["servers"].items():
76             server = Server(name, self, serverattrs)
77             self.servers.append(server)
78             self._server_map[server.dn] = server
79
80         print "Generating RSA host key ..."
81         rsa_key = paramiko.RSAKey.generate(bits=2048, progress_func=None)
82         print "Writing yardstick_key ..."
83         rsa_key.write_private_key_file(self.key_filename)
84         print "Writing yardstick_key.pub ..."
85         open(self.key_filename + ".pub", "w").write("%s %s\n" %
86                                                     (rsa_key.get_name(),
87                                                      rsa_key.get_base64()))
88         del rsa_key
89         print "... done!"
90
91     @property
92     def image(self):
93         '''returns application's default image name'''
94         return self._image
95
96     @property
97     def flavor(self):
98         '''returns application's default flavor name'''
99         return self._flavor
100
101     @property
102     def user(self):
103         '''return login user name corresponding to image'''
104         return self._user
105
106     def _add_resources_to_template(self, template):
107         '''add to the template the resources represented by this context'''
108         template.add_keypair(self.keypair_name)
109         template.add_security_group(self.secgroup_name)
110
111         for network in self.networks:
112             template.add_network(network.stack_name)
113             template.add_subnet(network.subnet_stack_name, network.stack_name,
114                                 network.subnet_cidr)
115
116             if network.router:
117                 template.add_router(network.router.stack_name,
118                                     network.router.external_gateway_info,
119                                     network.subnet_stack_name)
120                 template.add_router_interface(network.router.stack_if_name,
121                                               network.router.stack_name,
122                                               network.subnet_stack_name)
123
124         # create a list of servers sorted by increasing no of placement groups
125         list_of_servers = sorted(self.servers,
126                                  key=lambda s: len(s.placement_groups))
127
128         #
129         # add servers with scheduler hints derived from placement groups
130         #
131
132         # create list of servers with availability policy
133         availability_servers = []
134         for server in list_of_servers:
135             for pg in server.placement_groups:
136                 if pg.policy == "availability":
137                     availability_servers.append(server)
138                     break
139
140         # add servers with availability policy
141         added_servers = []
142         for server in availability_servers:
143             scheduler_hints = {}
144             for pg in server.placement_groups:
145                 update_scheduler_hints(scheduler_hints, added_servers, pg)
146             # workround for openstack nova bug, check JIRA: YARDSTICK-200
147             # for details
148             if len(availability_servers) == 2:
149                 if len(scheduler_hints["different_host"]) == 0:
150                     scheduler_hints.pop("different_host", None)
151                     server.add_to_template(template,
152                                            self.networks,
153                                            scheduler_hints)
154                     added_servers.append(server.stack_name)
155                 else:
156                     scheduler_hints["different_host"] = \
157                         scheduler_hints["different_host"][0]
158                     server.add_to_template(template,
159                                            self.networks,
160                                            scheduler_hints)
161                     added_servers.append(server.stack_name)
162             else:
163                 server.add_to_template(template,
164                                        self.networks,
165                                        scheduler_hints)
166                 added_servers.append(server.stack_name)
167
168         # create list of servers with affinity policy
169         affinity_servers = []
170         for server in list_of_servers:
171             for pg in server.placement_groups:
172                 if pg.policy == "affinity":
173                     affinity_servers.append(server)
174                     break
175
176         # add servers with affinity policy
177         for server in affinity_servers:
178             if server.stack_name in added_servers:
179                 continue
180             scheduler_hints = {}
181             for pg in server.placement_groups:
182                 update_scheduler_hints(scheduler_hints, added_servers, pg)
183             server.add_to_template(template, self.networks, scheduler_hints)
184             added_servers.append(server.stack_name)
185
186         # add remaining servers with no placement group configured
187         for server in list_of_servers:
188             if len(server.placement_groups) == 0:
189                 server.add_to_template(template, self.networks, {})
190
191     def deploy(self):
192         '''deploys template into a stack using cloud'''
193         print "Deploying context '%s'" % self.name
194
195         heat_template = HeatTemplate(self.name, self.template_file,
196                                      self.heat_parameters)
197
198         if self.template_file is None:
199             self._add_resources_to_template(heat_template)
200
201         try:
202             self.stack = heat_template.create()
203         except KeyboardInterrupt:
204             sys.exit("\nStack create interrupted")
205         except RuntimeError as err:
206             sys.exit("error: failed to deploy stack: '%s'" % err.args)
207         except Exception as err:
208             sys.exit("error: failed to deploy stack: '%s'" % err)
209
210         # copy some vital stack output into server objects
211         for server in self.servers:
212             if len(server.ports) > 0:
213                 # TODO(hafe) can only handle one internal network for now
214                 port = server.ports.values()[0]
215                 server.private_ip = self.stack.outputs[port["stack_name"]]
216
217             if server.floating_ip:
218                 server.public_ip = \
219                     self.stack.outputs[server.floating_ip["stack_name"]]
220
221         print "Context '%s' deployed" % self.name
222
223     def undeploy(self):
224         '''undeploys stack from cloud'''
225         if self.stack:
226             print "Undeploying context '%s'" % self.name
227             self.stack.delete()
228             self.stack = None
229             print "Context '%s' undeployed" % self.name
230
231         if os.path.exists(self.key_filename):
232             try:
233                 os.remove(self.key_filename)
234                 os.remove(self.key_filename + ".pub")
235             except OSError, e:
236                 print ("Error: %s - %s." % (e.key_filename, e.strerror))
237
238     def _get_server(self, attr_name):
239         '''lookup server info by name from context
240         attr_name: either a name for a server created by yardstick or a dict
241         with attribute name mapping when using external heat templates
242         '''
243         key_filename = pkg_resources.resource_filename(
244             'yardstick.resources', 'files/yardstick_key')
245
246         if type(attr_name) is dict:
247             cname = attr_name["name"].split(".")[1]
248             if cname != self.name:
249                 return None
250
251             public_ip = None
252             private_ip = None
253             if "public_ip_attr" in attr_name:
254                 public_ip = self.stack.outputs[attr_name["public_ip_attr"]]
255             if "private_ip_attr" in attr_name:
256                 private_ip = self.stack.outputs[
257                     attr_name["private_ip_attr"]]
258
259             # Create a dummy server instance for holding the *_ip attributes
260             server = Server(attr_name["name"].split(".")[0], self, {})
261             server.public_ip = public_ip
262             server.private_ip = private_ip
263         else:
264             if attr_name not in self._server_map:
265                 return None
266             server = self._server_map[attr_name]
267
268         if server is None:
269             return None
270
271         result = {
272             "user": server.context.user,
273             "key_filename": key_filename,
274             "private_ip": server.private_ip
275         }
276         # Target server may only have private_ip
277         if server.public_ip:
278             result["ip"] = server.public_ip
279
280         return result