0396865544f59949b5de0d8427370cb4efdeec6b
[yardstick.git] / yardstick / benchmark / contexts / model.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 """ Logical model
11
12 """
13 from __future__ import absolute_import
14 from six.moves import range
15
16
17 class Object(object):
18     """Base class for classes in the logical model
19     Contains common attributes and methods
20     """
21
22     def __init__(self, name, context):
23         # model identities and reference
24         self.name = name
25         self._context = context
26
27         # stack identities
28         self.stack_name = None
29         self.stack_id = None
30
31     @property
32     def dn(self):
33         """returns distinguished name for object"""
34         return self.name + "." + self._context.name
35
36
37 class PlacementGroup(Object):
38     """Class that represents a placement group in the logical model
39     Concept comes from the OVF specification. Policy should be one of
40     "availability" or "affinity (there are more but they are not supported)"
41     """
42     map = {}
43
44     def __init__(self, name, context, policy):
45         if policy not in ["affinity", "availability"]:
46             raise ValueError("placement group '%s', policy '%s' is not valid" %
47                              (name, policy))
48         self.name = name
49         self.members = set()
50         self.stack_name = context.name + "-" + name
51         self.policy = policy
52         PlacementGroup.map[name] = self
53
54     def add_member(self, name):
55         self.members.add(name)
56
57     @staticmethod
58     def get(name):
59         return PlacementGroup.map.get(name)
60
61
62 class ServerGroup(Object):     # pragma: no cover
63     """Class that represents a server group in the logical model
64     Policy should be one of "anti-affinity" or "affinity"
65     """
66     map = {}
67
68     def __init__(self, name, context, policy):
69         super(ServerGroup, self).__init__(name, context)
70         if policy not in {"affinity", "anti-affinity"}:
71             raise ValueError("server group '%s', policy '%s' is not valid" %
72                              (name, policy))
73         self.name = name
74         self.members = set()
75         self.stack_name = context.name + "-" + name
76         self.policy = policy
77         ServerGroup.map[name] = self
78
79     def add_member(self, name):
80         self.members.add(name)
81
82     @staticmethod
83     def get(name):
84         return ServerGroup.map.get(name)
85
86
87 class Router(Object):
88     """Class that represents a router in the logical model"""
89
90     def __init__(self, name, network_name, context, external_gateway_info):
91         super(Router, self).__init__(name, context)
92
93         self.stack_name = context.name + "-" + network_name + "-" + self.name
94         self.stack_if_name = self.stack_name + "-if0"
95         self.external_gateway_info = external_gateway_info
96
97
98 class Network(Object):
99     """Class that represents a network in the logical model"""
100     list = []
101
102     def __init__(self, name, context, attrs):
103         super(Network, self).__init__(name, context)
104         self.stack_name = context.name + "-" + self.name
105         self.subnet_stack_name = self.stack_name + "-subnet"
106         self.subnet_cidr = attrs.get('cidr', '10.0.1.0/24')
107         self.router = None
108
109         if "external_network" in attrs:
110             self.router = Router("router", self.name,
111                                  context, attrs["external_network"])
112
113         Network.list.append(self)
114
115     def has_route_to(self, network_name):
116         """determines if this network has a route to the named network"""
117         if self.router and self.router.external_gateway_info == network_name:
118             return True
119         return False
120
121     @staticmethod
122     def find_by_route_to(external_network):
123         """finds a network that has a route to the specified network"""
124         for network in Network.list:
125             if network.has_route_to(external_network):
126                 return network
127
128     @staticmethod
129     def find_external_network():
130         """return the name of an external network some network in this
131         context has a route to"""
132         for network in Network.list:
133             if network.router:
134                 return network.router.external_gateway_info
135         return None
136
137
138 class Server(Object):     # pragma: no cover
139     """Class that represents a server in the logical model"""
140     list = []
141
142     def __init__(self, name, context, attrs):
143         super(Server, self).__init__(name, context)
144         self.stack_name = self.name + "." + context.name
145         self.keypair_name = context.keypair_name
146         self.secgroup_name = context.secgroup_name
147         self.user = context.user
148         self.context = context
149         self.public_ip = None
150         self.private_ip = None
151         self.user_data = ''
152
153         if attrs is None:
154             attrs = {}
155
156         self.placement_groups = []
157         placement = attrs.get("placement", [])
158         placement = placement if type(placement) is list else [placement]
159         for p in placement:
160             pg = PlacementGroup.get(p)
161             if not pg:
162                 raise ValueError("server '%s', placement '%s' is invalid" %
163                                  (name, p))
164             self.placement_groups.append(pg)
165             pg.add_member(self.stack_name)
166
167         # support servergroup attr
168         self.server_group = None
169         sg = attrs.get("server_group")
170         if (sg):
171             server_group = ServerGroup.get(sg)
172             if not server_group:
173                 raise ValueError("server '%s', server_group '%s' is invalid" %
174                                  (name, sg))
175             self.server_group = server_group
176             server_group.add_member(self.stack_name)
177
178         self.instances = 1
179         if "instances" in attrs:
180             self.instances = attrs["instances"]
181
182         # dict with key network name, each item is a dict with port name and ip
183         self.ports = {}
184
185         self.floating_ip = None
186         self.floating_ip_assoc = None
187         if "floating_ip" in attrs:
188             self.floating_ip = {}
189             self.floating_ip_assoc = {}
190
191         if self.floating_ip is not None:
192             ext_net = Network.find_external_network()
193             assert ext_net is not None
194             self.floating_ip["external_network"] = ext_net
195
196         self._image = None
197         if "image" in attrs:
198             self._image = attrs["image"]
199
200         self._flavor = None
201         if "flavor" in attrs:
202             self._flavor = attrs["flavor"]
203
204         self.user_data = attrs.get('user_data', '')
205
206         Server.list.append(self)
207
208     @property
209     def image(self):
210         """returns a server's image name"""
211         if self._image:
212             return self._image
213         else:
214             return self._context.image
215
216     @property
217     def flavor(self):
218         """returns a server's flavor name"""
219         if self._flavor:
220             return self._flavor
221         else:
222             return self._context.flavor
223
224     def _add_instance(self, template, server_name, networks, scheduler_hints):
225         """adds to the template one server and corresponding resources"""
226         port_name_list = []
227         for network in networks:
228             port_name = server_name + "-" + network.name + "-port"
229             self.ports[network.name] = {"stack_name": port_name}
230             template.add_port(port_name, network.stack_name,
231                               network.subnet_stack_name,
232                               sec_group_id=self.secgroup_name)
233             port_name_list.append(port_name)
234
235             if self.floating_ip:
236                 external_network = self.floating_ip["external_network"]
237                 if network.has_route_to(external_network):
238                     self.floating_ip["stack_name"] = server_name + "-fip"
239                     template.add_floating_ip(self.floating_ip["stack_name"],
240                                              external_network,
241                                              port_name,
242                                              network.router.stack_if_name,
243                                              self.secgroup_name)
244                     self.floating_ip_assoc["stack_name"] = \
245                         server_name + "-fip-assoc"
246                     template.add_floating_ip_association(
247                         self.floating_ip_assoc["stack_name"],
248                         self.floating_ip["stack_name"],
249                         port_name)
250
251         template.add_server(server_name, self.image, self.flavor,
252                             ports=port_name_list,
253                             user=self.user,
254                             key_name=self.keypair_name,
255                             user_data=self.user_data,
256                             scheduler_hints=scheduler_hints)
257
258     def add_to_template(self, template, networks, scheduler_hints=None):
259         """adds to the template one or more servers (instances)"""
260         if self.instances == 1:
261             server_name = self.stack_name
262             self._add_instance(template, server_name, networks,
263                                scheduler_hints=scheduler_hints)
264         else:
265             # TODO(hafe) fix or remove, no test/sample for this
266             for i in range(self.instances):
267                 server_name = "%s-%d" % (self.stack_name, i)
268                 self._add_instance(template, server_name, networks,
269                                    scheduler_hints=scheduler_hints)
270
271
272 def update_scheduler_hints(scheduler_hints, added_servers, placement_group):
273     """ update scheduler hints from server's placement configuration
274     TODO: this code is openstack specific and should move somewhere else
275     """
276     if placement_group.policy == "affinity":
277         if "same_host" in scheduler_hints:
278             host_list = scheduler_hints["same_host"]
279         else:
280             host_list = scheduler_hints["same_host"] = []
281     else:
282         if "different_host" in scheduler_hints:
283             host_list = scheduler_hints["different_host"]
284         else:
285             host_list = scheduler_hints["different_host"] = []
286
287     for name in added_servers:
288         if name in placement_group.members:
289             host_list.append({'get_resource': name})