4873afde3e679e6b4993a7dfdda4984dd8db977f
[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
152         if attrs is None:
153             attrs = {}
154
155         self.placement_groups = []
156         placement = attrs.get("placement", [])
157         placement = placement if isinstance(placement, list) else [placement]
158         for p in placement:
159             pg = PlacementGroup.get(p)
160             if not pg:
161                 raise ValueError("server '%s', placement '%s' is invalid" %
162                                  (name, p))
163             self.placement_groups.append(pg)
164             pg.add_member(self.stack_name)
165
166         # support servergroup attr
167         self.server_group = None
168         sg = attrs.get("server_group")
169         if sg:
170             server_group = ServerGroup.get(sg)
171             if not server_group:
172                 raise ValueError("server '%s', server_group '%s' is invalid" %
173                                  (name, sg))
174             self.server_group = server_group
175             server_group.add_member(self.stack_name)
176
177         self.instances = 1
178         if "instances" in attrs:
179             self.instances = attrs["instances"]
180
181         # dict with key network name, each item is a dict with port name and ip
182         self.ports = {}
183
184         self.floating_ip = None
185         self.floating_ip_assoc = None
186         if "floating_ip" in attrs:
187             self.floating_ip = {}
188             self.floating_ip_assoc = {}
189
190         if self.floating_ip is not None:
191             ext_net = Network.find_external_network()
192             assert ext_net is not None
193             self.floating_ip["external_network"] = ext_net
194
195         self._image = None
196         if "image" in attrs:
197             self._image = attrs["image"]
198
199         self._flavor = None
200         if "flavor" in attrs:
201             self._flavor = attrs["flavor"]
202
203         Server.list.append(self)
204
205     @property
206     def image(self):
207         """returns a server's image name"""
208         if self._image:
209             return self._image
210         else:
211             return self._context.image
212
213     @property
214     def flavor(self):
215         """returns a server's flavor name"""
216         if self._flavor:
217             return self._flavor
218         else:
219             return self._context.flavor
220
221     def _add_instance(self, template, server_name, networks, scheduler_hints):
222         """adds to the template one server and corresponding resources"""
223         port_name_list = []
224         for network in networks:
225             port_name = server_name + "-" + network.name + "-port"
226             self.ports[network.name] = {"stack_name": port_name}
227             template.add_port(port_name, network.stack_name,
228                               network.subnet_stack_name,
229                               sec_group_id=self.secgroup_name)
230             port_name_list.append(port_name)
231
232             if self.floating_ip:
233                 external_network = self.floating_ip["external_network"]
234                 if network.has_route_to(external_network):
235                     self.floating_ip["stack_name"] = server_name + "-fip"
236                     template.add_floating_ip(self.floating_ip["stack_name"],
237                                              external_network,
238                                              port_name,
239                                              network.router.stack_if_name,
240                                              self.secgroup_name)
241                     self.floating_ip_assoc["stack_name"] = \
242                         server_name + "-fip-assoc"
243                     template.add_floating_ip_association(
244                         self.floating_ip_assoc["stack_name"],
245                         self.floating_ip["stack_name"],
246                         port_name)
247
248         template.add_server(server_name, self.image, self.flavor,
249                             ports=port_name_list,
250                             user=self.user,
251                             key_name=self.keypair_name,
252                             scheduler_hints=scheduler_hints)
253
254     def add_to_template(self, template, networks, scheduler_hints=None):
255         """adds to the template one or more servers (instances)"""
256         if self.instances == 1:
257             server_name = self.stack_name
258             self._add_instance(template, server_name, networks,
259                                scheduler_hints=scheduler_hints)
260         else:
261             # TODO(hafe) fix or remove, no test/sample for this
262             for i in range(self.instances):
263                 server_name = "%s-%d" % (self.stack_name, i)
264                 self._add_instance(template, server_name, networks,
265                                    scheduler_hints=scheduler_hints)
266
267
268 def update_scheduler_hints(scheduler_hints, added_servers, placement_group):
269     """ update scheduler hints from server's placement configuration
270     TODO: this code is openstack specific and should move somewhere else
271     """
272     if placement_group.policy == "affinity":
273         if "same_host" in scheduler_hints:
274             host_list = scheduler_hints["same_host"]
275         else:
276             host_list = scheduler_hints["same_host"] = []
277     else:
278         if "different_host" in scheduler_hints:
279             host_list = scheduler_hints["different_host"]
280         else:
281             host_list = scheduler_hints["different_host"] = []
282
283     for name in added_servers:
284         if name in placement_group.members:
285             host_list.append({'get_resource': name})