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