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