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