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