Merge "cachestat: use raw strings to escape \d"
[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         self.physical_network = attrs.get('physical_network', 'physnet1')
109         self.provider = attrs.get('provider', None)
110
111         if "external_network" in attrs:
112             self.router = Router("router", self.name,
113                                  context, attrs["external_network"])
114
115         Network.list.append(self)
116
117     def has_route_to(self, network_name):
118         """determines if this network has a route to the named network"""
119         if self.router and self.router.external_gateway_info == network_name:
120             return True
121         return False
122
123     @staticmethod
124     def find_by_route_to(external_network):
125         """finds a network that has a route to the specified network"""
126         for network in Network.list:
127             if network.has_route_to(external_network):
128                 return network
129
130     @staticmethod
131     def find_external_network():
132         """return the name of an external network some network in this
133         context has a route to"""
134         for network in Network.list:
135             if network.router:
136                 return network.router.external_gateway_info
137         return None
138
139
140 class Server(Object):     # pragma: no cover
141     """Class that represents a server in the logical model"""
142     list = []
143
144     def __init__(self, name, context, attrs):
145         super(Server, self).__init__(name, context)
146         self.stack_name = self.name + "." + context.name
147         self.keypair_name = context.keypair_name
148         self.secgroup_name = context.secgroup_name
149         self.user = context.user
150         self.context = context
151         self.public_ip = None
152         self.private_ip = None
153
154         if attrs is None:
155             attrs = {}
156
157         self.placement_groups = []
158         placement = attrs.get("placement", [])
159         placement = placement if isinstance(placement, list) else [placement]
160         for p in placement:
161             pg = PlacementGroup.get(p)
162             if not pg:
163                 raise ValueError("server '%s', placement '%s' is invalid" %
164                                  (name, p))
165             self.placement_groups.append(pg)
166             pg.add_member(self.stack_name)
167
168         # support servergroup attr
169         self.server_group = None
170         sg = attrs.get("server_group")
171         if sg:
172             server_group = ServerGroup.get(sg)
173             if not server_group:
174                 raise ValueError("server '%s', server_group '%s' is invalid" %
175                                  (name, sg))
176             self.server_group = server_group
177             server_group.add_member(self.stack_name)
178
179         self.instances = 1
180         if "instances" in attrs:
181             self.instances = attrs["instances"]
182
183         # dict with key network name, each item is a dict with port name and ip
184         self.ports = {}
185
186         self.floating_ip = None
187         self.floating_ip_assoc = None
188         if "floating_ip" in attrs:
189             self.floating_ip = {}
190             self.floating_ip_assoc = {}
191
192         if self.floating_ip is not None:
193             ext_net = Network.find_external_network()
194             assert ext_net is not None
195             self.floating_ip["external_network"] = ext_net
196
197         self._image = None
198         if "image" in attrs:
199             self._image = attrs["image"]
200
201         self._flavor = None
202         if "flavor" in attrs:
203             self._flavor = attrs["flavor"]
204
205         Server.list.append(self)
206
207     @property
208     def image(self):
209         """returns a server's image name"""
210         if self._image:
211             return self._image
212         else:
213             return self._context.image
214
215     @property
216     def flavor(self):
217         """returns a server's flavor name"""
218         if self._flavor:
219             return self._flavor
220         else:
221             return self._context.flavor
222
223     def _add_instance(self, template, server_name, networks, scheduler_hints):
224         """adds to the template one server and corresponding resources"""
225         port_name_list = []
226         for network in networks:
227             port_name = server_name + "-" + network.name + "-port"
228             self.ports[network.name] = {"stack_name": port_name}
229             template.add_port(port_name, network.stack_name,
230                               network.subnet_stack_name,
231                               sec_group_id=self.secgroup_name,
232                               provider=network.provider)
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                             scheduler_hints=scheduler_hints)
256
257     def add_to_template(self, template, networks, scheduler_hints=None):
258         """adds to the template one or more servers (instances)"""
259         if self.instances == 1:
260             server_name = self.stack_name
261             self._add_instance(template, server_name, networks,
262                                scheduler_hints=scheduler_hints)
263         else:
264             # TODO(hafe) fix or remove, no test/sample for this
265             for i in range(self.instances):
266                 server_name = "%s-%d" % (self.stack_name, i)
267                 self._add_instance(template, server_name, networks,
268                                    scheduler_hints=scheduler_hints)
269
270
271 def update_scheduler_hints(scheduler_hints, added_servers, placement_group):
272     """ update scheduler hints from server's placement configuration
273     TODO: this code is openstack specific and should move somewhere else
274     """
275     if placement_group.policy == "affinity":
276         if "same_host" in scheduler_hints:
277             host_list = scheduler_hints["same_host"]
278         else:
279             host_list = scheduler_hints["same_host"] = []
280     else:
281         if "different_host" in scheduler_hints:
282             host_list = scheduler_hints["different_host"]
283         else:
284             host_list = scheduler_hints["different_host"] = []
285
286     for name in added_servers:
287         if name in placement_group.members:
288             host_list.append({'get_resource': name})