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