Merge "Add spec cpu2006 test case"
[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         self.vld_id = attrs.get("vld_id")
131
132         Network.list.append(self)
133
134     def has_route_to(self, network_name):
135         """determines if this network has a route to the named network"""
136         if self.router and self.router.external_gateway_info == network_name:
137             return True
138         return False
139
140     @staticmethod
141     def find_by_route_to(external_network):
142         """finds a network that has a route to the specified network"""
143         for network in Network.list:
144             if network.has_route_to(external_network):
145                 return network
146
147     @staticmethod
148     def find_external_network():
149         """return the name of an external network some network in this
150         context has a route to
151         """
152         for network in Network.list:
153             if network.router:
154                 return network.router.external_gateway_info
155         return None
156
157
158 class Server(Object):     # pragma: no cover
159     """Class that represents a server in the logical model"""
160     list = []
161
162     def __init__(self, name, context, attrs):
163         super(Server, self).__init__(name, context)
164         self.stack_name = self.name + "." + context.name
165         self.keypair_name = context.keypair_name
166         self.secgroup_name = context.secgroup_name
167         self.user = context.user
168         self.context = context
169         self.public_ip = None
170         self.private_ip = None
171         self.user_data = ''
172         self.interfaces = {}
173
174         if attrs is None:
175             attrs = {}
176
177         self.placement_groups = []
178         placement = attrs.get("placement", [])
179         placement = placement if isinstance(placement, list) else [placement]
180         for p in placement:
181             pg = PlacementGroup.get(p)
182             if not pg:
183                 raise ValueError("server '%s', placement '%s' is invalid" %
184                                  (name, p))
185             self.placement_groups.append(pg)
186             pg.add_member(self.stack_name)
187
188         self.volume = None
189         if "volume" in attrs:
190             self.volume = attrs.get("volume")
191
192         self.volume_mountpoint = None
193         if "volume_mountpoint" in attrs:
194             self.volume_mountpoint = attrs.get("volume_mountpoint")
195
196         # support servergroup attr
197         self.server_group = None
198         sg = attrs.get("server_group")
199         if sg:
200             server_group = ServerGroup.get(sg)
201             if not server_group:
202                 raise ValueError("server '%s', server_group '%s' is invalid" %
203                                  (name, sg))
204             self.server_group = server_group
205             server_group.add_member(self.stack_name)
206
207         self.instances = 1
208         if "instances" in attrs:
209             self.instances = attrs["instances"]
210
211         # dict with key network name, each item is a dict with port name and ip
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             port_name = server_name + "-" + network.name + "-port"
258             self.ports[network.name] = {"stack_name": port_name}
259             # we can't use secgroups if port_security_enabled is False
260             if network.port_security_enabled is False:
261                 sec_group_id = None
262             else:
263                 # if port_security_enabled is None we still need to add to secgroup
264                 sec_group_id = self.secgroup_name
265             # don't refactor to pass in network object, that causes JSON
266             # circular ref encode errors
267             template.add_port(port_name, network.stack_name, network.subnet_stack_name,
268                               network.vnic_type, sec_group_id=sec_group_id,
269                               provider=network.provider,
270                               allowed_address_pairs=network.allowed_address_pairs)
271             port_name_list.append(port_name)
272
273             if self.floating_ip:
274                 external_network = self.floating_ip["external_network"]
275                 if network.has_route_to(external_network):
276                     self.floating_ip["stack_name"] = server_name + "-fip"
277                     template.add_floating_ip(self.floating_ip["stack_name"],
278                                              external_network,
279                                              port_name,
280                                              network.router.stack_if_name,
281                                              sec_group_id)
282                     self.floating_ip_assoc["stack_name"] = \
283                         server_name + "-fip-assoc"
284                     template.add_floating_ip_association(
285                         self.floating_ip_assoc["stack_name"],
286                         self.floating_ip["stack_name"],
287                         port_name)
288         if self.flavor:
289             if isinstance(self.flavor, dict):
290                 self.flavor["name"] = \
291                     self.flavor.setdefault("name", self.stack_name + "-flavor")
292                 template.add_flavor(**self.flavor)
293                 self.flavor_name = self.flavor["name"]
294             else:
295                 self.flavor_name = self.flavor
296
297         if self.volume:
298             if isinstance(self.volume, dict):
299                 self.volume["name"] = \
300                     self.volume.setdefault("name", server_name + "-volume")
301                 template.add_volume(**self.volume)
302                 template.add_volume_attachment(server_name, self.volume["name"],
303                                                mountpoint=self.volume_mountpoint)
304             else:
305                 template.add_volume_attachment(server_name, self.volume,
306                                                mountpoint=self.volume_mountpoint)
307
308         template.add_server(server_name, self.image, flavor=self.flavor_name,
309                             flavors=self.context.flavors,
310                             ports=port_name_list,
311                             user=self.user,
312                             key_name=self.keypair_name,
313                             user_data=self.user_data,
314                             scheduler_hints=scheduler_hints)
315
316     def add_to_template(self, template, networks, scheduler_hints=None):
317         """adds to the template one or more servers (instances)"""
318         if self.instances == 1:
319             server_name = self.stack_name
320             self._add_instance(template, server_name, networks,
321                                scheduler_hints=scheduler_hints)
322         else:
323             # TODO(hafe) fix or remove, no test/sample for this
324             for i in range(self.instances):
325                 server_name = "%s-%d" % (self.stack_name, i)
326                 self._add_instance(template, server_name, networks,
327                                    scheduler_hints=scheduler_hints)
328
329
330 def update_scheduler_hints(scheduler_hints, added_servers, placement_group):
331     """update scheduler hints from server's placement configuration
332     TODO: this code is openstack specific and should move somewhere else
333     """
334     if placement_group.policy == "affinity":
335         if "same_host" in scheduler_hints:
336             host_list = scheduler_hints["same_host"]
337         else:
338             host_list = scheduler_hints["same_host"] = []
339     else:
340         if "different_host" in scheduler_hints:
341             host_list = scheduler_hints["different_host"]
342         else:
343             host_list = scheduler_hints["different_host"] = []
344
345     for name in added_servers:
346         if name in placement_group.members:
347             host_list.append({'get_resource': name})