Merge "tc011: make parameters to be able to config"
[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.network_ports = attrs.get("network_ports", {})
212         self.ports = {}
213
214         self.floating_ip = None
215         self.floating_ip_assoc = None
216         if "floating_ip" in attrs:
217             self.floating_ip = {}
218             self.floating_ip_assoc = {}
219
220         if self.floating_ip is not None:
221             ext_net = Network.find_external_network()
222             assert ext_net is not None
223             self.floating_ip["external_network"] = ext_net
224
225         self._image = None
226         if "image" in attrs:
227             self._image = attrs["image"]
228
229         self._flavor = None
230         if "flavor" in attrs:
231             self._flavor = attrs["flavor"]
232
233         self.user_data = attrs.get('user_data', '')
234
235         Server.list.append(self)
236
237     @property
238     def image(self):
239         """returns a server's image name"""
240         if self._image:
241             return self._image
242         else:
243             return self._context.image
244
245     @property
246     def flavor(self):
247         """returns a server's flavor name"""
248         if self._flavor:
249             return self._flavor
250         else:
251             return self._context.flavor
252
253     def _add_instance(self, template, server_name, networks, scheduler_hints):
254         """adds to the template one server and corresponding resources"""
255         port_name_list = []
256         for network in networks:
257             # if explicit mapping skip unused networks
258             if self.network_ports:
259                 try:
260                     port = self.network_ports[network.name]
261                 except KeyError:
262                     # no port for this network
263                     continue
264             # otherwise add a port for every network with port name as network name
265             else:
266                 port = network.name
267             port_name = "{0}-{1}-port".format(server_name, port)
268             self.ports[network.name] = {"stack_name": port_name, "port": port}
269             # we can't use secgroups if port_security_enabled is False
270             if network.port_security_enabled is False:
271                 sec_group_id = None
272             else:
273                 # if port_security_enabled is None we still need to add to secgroup
274                 sec_group_id = self.secgroup_name
275             # don't refactor to pass in network object, that causes JSON
276             # circular ref encode errors
277             template.add_port(port_name, network.stack_name, network.subnet_stack_name,
278                               network.vnic_type, sec_group_id=sec_group_id,
279                               provider=network.provider,
280                               allowed_address_pairs=network.allowed_address_pairs)
281             port_name_list.append(port_name)
282
283             if self.floating_ip:
284                 external_network = self.floating_ip["external_network"]
285                 if network.has_route_to(external_network):
286                     self.floating_ip["stack_name"] = server_name + "-fip"
287                     template.add_floating_ip(self.floating_ip["stack_name"],
288                                              external_network,
289                                              port_name,
290                                              network.router.stack_if_name,
291                                              sec_group_id)
292                     self.floating_ip_assoc["stack_name"] = \
293                         server_name + "-fip-assoc"
294                     template.add_floating_ip_association(
295                         self.floating_ip_assoc["stack_name"],
296                         self.floating_ip["stack_name"],
297                         port_name)
298         if self.flavor:
299             if isinstance(self.flavor, dict):
300                 self.flavor["name"] = \
301                     self.flavor.setdefault("name", self.stack_name + "-flavor")
302                 template.add_flavor(**self.flavor)
303                 self.flavor_name = self.flavor["name"]
304             else:
305                 self.flavor_name = self.flavor
306
307         if self.volume:
308             if isinstance(self.volume, dict):
309                 self.volume["name"] = \
310                     self.volume.setdefault("name", server_name + "-volume")
311                 template.add_volume(**self.volume)
312                 template.add_volume_attachment(server_name, self.volume["name"],
313                                                mountpoint=self.volume_mountpoint)
314             else:
315                 template.add_volume_attachment(server_name, self.volume,
316                                                mountpoint=self.volume_mountpoint)
317
318         template.add_server(server_name, self.image, flavor=self.flavor_name,
319                             flavors=self.context.flavors,
320                             ports=port_name_list,
321                             user=self.user,
322                             key_name=self.keypair_name,
323                             user_data=self.user_data,
324                             scheduler_hints=scheduler_hints)
325
326     def add_to_template(self, template, networks, scheduler_hints=None):
327         """adds to the template one or more servers (instances)"""
328         if self.instances == 1:
329             server_name = self.stack_name
330             self._add_instance(template, server_name, networks,
331                                scheduler_hints=scheduler_hints)
332         else:
333             # TODO(hafe) fix or remove, no test/sample for this
334             for i in range(self.instances):
335                 server_name = "%s-%d" % (self.stack_name, i)
336                 self._add_instance(template, server_name, networks,
337                                    scheduler_hints=scheduler_hints)
338
339
340 def update_scheduler_hints(scheduler_hints, added_servers, placement_group):
341     """update scheduler hints from server's placement configuration
342     TODO: this code is openstack specific and should move somewhere else
343     """
344     if placement_group.policy == "affinity":
345         if "same_host" in scheduler_hints:
346             host_list = scheduler_hints["same_host"]
347         else:
348             host_list = scheduler_hints["same_host"] = []
349     else:
350         if "different_host" in scheduler_hints:
351             host_list = scheduler_hints["different_host"]
352         else:
353             host_list = scheduler_hints["different_host"] = []
354
355     for name in added_servers:
356         if name in placement_group.members:
357             host_list.append({'get_resource': name})