Merge "Bugfix: ha test case criteria pass when sla not pass"
[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.enable_dhcp = attrs.get('enable_dhcp', 'true')
108         self.router = None
109         self.physical_network = attrs.get('physical_network', 'physnet1')
110         self.provider = attrs.get('provider')
111         self.segmentation_id = attrs.get('segmentation_id')
112         self.network_type = attrs.get('network_type')
113         self.port_security_enabled = attrs.get('port_security_enabled')
114         self.vnic_type = attrs.get('vnic_type', 'normal')
115         self.allowed_address_pairs = attrs.get('allowed_address_pairs', [])
116         try:
117             # we require 'null' or '' to disable setting gateway_ip
118             self.gateway_ip = attrs['gateway_ip']
119         except KeyError:
120             # default to explicit None
121             self.gateway_ip = None
122         else:
123             # null is None in YAML, so we have to convert back to string
124             if self.gateway_ip is None:
125                 self.gateway_ip = "null"
126
127         if "external_network" in attrs:
128             self.router = Router("router", self.name,
129                                  context, attrs["external_network"])
130
131         Network.list.append(self)
132
133     def has_route_to(self, network_name):
134         """determines if this network has a route to the named network"""
135         if self.router and self.router.external_gateway_info == network_name:
136             return True
137         return False
138
139     @staticmethod
140     def find_by_route_to(external_network):
141         """finds a network that has a route to the specified network"""
142         for network in Network.list:
143             if network.has_route_to(external_network):
144                 return network
145
146     @staticmethod
147     def find_external_network():
148         """return the name of an external network some network in this
149         context has a route to
150         """
151         for network in Network.list:
152             if network.router:
153                 return network.router.external_gateway_info
154         return None
155
156
157 class Server(Object):     # pragma: no cover
158     """Class that represents a server in the logical model"""
159     list = []
160
161     def __init__(self, name, context, attrs):
162         super(Server, self).__init__(name, context)
163         self.stack_name = self.name + "." + context.name
164         self.keypair_name = context.keypair_name
165         self.secgroup_name = context.secgroup_name
166         self.user = context.user
167         self.context = context
168         self.public_ip = None
169         self.private_ip = None
170         self.user_data = ''
171         self.interfaces = {}
172
173         if attrs is None:
174             attrs = {}
175
176         self.placement_groups = []
177         placement = attrs.get("placement", [])
178         placement = placement if isinstance(placement, list) else [placement]
179         for p in placement:
180             pg = PlacementGroup.get(p)
181             if not pg:
182                 raise ValueError("server '%s', placement '%s' is invalid" %
183                                  (name, p))
184             self.placement_groups.append(pg)
185             pg.add_member(self.stack_name)
186
187         self.volume = None
188         if "volume" in attrs:
189             self.volume = attrs.get("volume")
190
191         self.volume_mountpoint = None
192         if "volume_mountpoint" in attrs:
193             self.volume_mountpoint = attrs.get("volume_mountpoint")
194
195         # support servergroup attr
196         self.server_group = None
197         sg = attrs.get("server_group")
198         if sg:
199             server_group = ServerGroup.get(sg)
200             if not server_group:
201                 raise ValueError("server '%s', server_group '%s' is invalid" %
202                                  (name, sg))
203             self.server_group = server_group
204             server_group.add_member(self.stack_name)
205
206         self.instances = 1
207         if "instances" in attrs:
208             self.instances = attrs["instances"]
209
210         # dict with key network name, each item is a dict with port name and ip
211         self.ports = {}
212
213         self.floating_ip = None
214         self.floating_ip_assoc = None
215         if "floating_ip" in attrs:
216             self.floating_ip = {}
217             self.floating_ip_assoc = {}
218
219         if self.floating_ip is not None:
220             ext_net = Network.find_external_network()
221             assert ext_net is not None
222             self.floating_ip["external_network"] = ext_net
223
224         self._image = None
225         if "image" in attrs:
226             self._image = attrs["image"]
227
228         self._flavor = None
229         if "flavor" in attrs:
230             self._flavor = attrs["flavor"]
231
232         self.user_data = attrs.get('user_data', '')
233
234         Server.list.append(self)
235
236     @property
237     def image(self):
238         """returns a server's image name"""
239         if self._image:
240             return self._image
241         else:
242             return self._context.image
243
244     @property
245     def flavor(self):
246         """returns a server's flavor name"""
247         if self._flavor:
248             return self._flavor
249         else:
250             return self._context.flavor
251
252     def _add_instance(self, template, server_name, networks, scheduler_hints):
253         """adds to the template one server and corresponding resources"""
254         port_name_list = []
255         for network in networks:
256             port_name = server_name + "-" + network.name + "-port"
257             self.ports[network.name] = {"stack_name": port_name}
258             # we can't use secgroups if port_security_enabled is False
259             if network.port_security_enabled is False:
260                 sec_group_id = None
261             else:
262                 # if port_security_enabled is None we still need to add to secgroup
263                 sec_group_id = self.secgroup_name
264             # don't refactor to pass in network object, that causes JSON
265             # circular ref encode errors
266             template.add_port(port_name, network.stack_name, network.subnet_stack_name,
267                               network.vnic_type, sec_group_id=sec_group_id,
268                               provider=network.provider,
269                               allowed_address_pairs=network.allowed_address_pairs)
270             port_name_list.append(port_name)
271
272             if self.floating_ip:
273                 external_network = self.floating_ip["external_network"]
274                 if network.has_route_to(external_network):
275                     self.floating_ip["stack_name"] = server_name + "-fip"
276                     template.add_floating_ip(self.floating_ip["stack_name"],
277                                              external_network,
278                                              port_name,
279                                              network.router.stack_if_name,
280                                              sec_group_id)
281                     self.floating_ip_assoc["stack_name"] = \
282                         server_name + "-fip-assoc"
283                     template.add_floating_ip_association(
284                         self.floating_ip_assoc["stack_name"],
285                         self.floating_ip["stack_name"],
286                         port_name)
287         if self.flavor:
288             if isinstance(self.flavor, dict):
289                 self.flavor["name"] = \
290                     self.flavor.setdefault("name", self.stack_name + "-flavor")
291                 template.add_flavor(**self.flavor)
292                 self.flavor_name = self.flavor["name"]
293             else:
294                 self.flavor_name = self.flavor
295
296         if self.volume:
297             if isinstance(self.volume, dict):
298                 self.volume["name"] = \
299                     self.volume.setdefault("name", server_name + "-volume")
300                 template.add_volume(**self.volume)
301                 template.add_volume_attachment(server_name, self.volume["name"],
302                                                mountpoint=self.volume_mountpoint)
303             else:
304                 template.add_volume_attachment(server_name, self.volume,
305                                                mountpoint=self.volume_mountpoint)
306
307         template.add_server(server_name, self.image, flavor=self.flavor_name,
308                             flavors=self.context.flavors,
309                             ports=port_name_list,
310                             user=self.user,
311                             key_name=self.keypair_name,
312                             user_data=self.user_data,
313                             scheduler_hints=scheduler_hints)
314
315     def add_to_template(self, template, networks, scheduler_hints=None):
316         """adds to the template one or more servers (instances)"""
317         if self.instances == 1:
318             server_name = self.stack_name
319             self._add_instance(template, server_name, networks,
320                                scheduler_hints=scheduler_hints)
321         else:
322             # TODO(hafe) fix or remove, no test/sample for this
323             for i in range(self.instances):
324                 server_name = "%s-%d" % (self.stack_name, i)
325                 self._add_instance(template, server_name, networks,
326                                    scheduler_hints=scheduler_hints)
327
328
329 def update_scheduler_hints(scheduler_hints, added_servers, placement_group):
330     """update scheduler hints from server's placement configuration
331     TODO: this code is openstack specific and should move somewhere else
332     """
333     if placement_group.policy == "affinity":
334         if "same_host" in scheduler_hints:
335             host_list = scheduler_hints["same_host"]
336         else:
337             host_list = scheduler_hints["same_host"] = []
338     else:
339         if "different_host" in scheduler_hints:
340             host_list = scheduler_hints["different_host"]
341         else:
342             host_list = scheduler_hints["different_host"] = []
343
344     for name in added_servers:
345         if name in placement_group.members:
346             host_list.append({'get_resource': name})