Heat: support create and attach volume in heat type context
[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')
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         self.volume = None
188         if "volume" in attrs:
189             self.volume = attrs.get("volume")
190
191         self.volume_mountpoint = None
192         if "volume_mountpoint" in attrs:
193             self.volume_mountpoint = attrs.get("volume_mountpoint")
194
195         # support servergroup attr
196         self.server_group = None
197         sg = attrs.get("server_group")
198         if sg:
199             server_group = ServerGroup.get(sg)
200             if not server_group:
201                 raise ValueError("server '%s', server_group '%s' is invalid" %
202                                  (name, sg))
203             self.server_group = server_group
204             server_group.add_member(self.stack_name)
205
206         self.instances = 1
207         if "instances" in attrs:
208             self.instances = attrs["instances"]
209
210         # dict with key network name, each item is a dict with port name and ip
211         self.ports = {}
212
213         self.floating_ip = None
214         self.floating_ip_assoc = None
215         if "floating_ip" in attrs:
216             self.floating_ip = {}
217             self.floating_ip_assoc = {}
218
219         if self.floating_ip is not None:
220             ext_net = Network.find_external_network()
221             assert ext_net is not None
222             self.floating_ip["external_network"] = ext_net
223
224         self._image = None
225         if "image" in attrs:
226             self._image = attrs["image"]
227
228         self._flavor = None
229         if "flavor" in attrs:
230             self._flavor = attrs["flavor"]
231
232         self.user_data = attrs.get('user_data', '')
233
234         Server.list.append(self)
235
236     @property
237     def image(self):
238         """returns a server's image name"""
239         if self._image:
240             return self._image
241         else:
242             return self._context.image
243
244     @property
245     def flavor(self):
246         """returns a server's flavor name"""
247         if self._flavor:
248             return self._flavor
249         else:
250             return self._context.flavor
251
252     def _add_instance(self, template, server_name, networks, scheduler_hints):
253         """adds to the template one server and corresponding resources"""
254         port_name_list = []
255         for network in networks:
256             port_name = server_name + "-" + network.name + "-port"
257             self.ports[network.name] = {"stack_name": port_name}
258             # we can't use secgroups if port_security_enabled is False
259             if network.port_security_enabled:
260                 sec_group_id = self.secgroup_name
261             else:
262                 sec_group_id = None
263             # don't refactor to pass in network object, that causes JSON
264             # circular ref encode errors
265             template.add_port(port_name, network.stack_name, network.subnet_stack_name,
266                               sec_group_id=sec_group_id, provider=network.provider,
267                               allowed_address_pairs=network.allowed_address_pairs)
268             port_name_list.append(port_name)
269
270             if self.floating_ip:
271                 external_network = self.floating_ip["external_network"]
272                 if network.has_route_to(external_network):
273                     self.floating_ip["stack_name"] = server_name + "-fip"
274                     template.add_floating_ip(self.floating_ip["stack_name"],
275                                              external_network,
276                                              port_name,
277                                              network.router.stack_if_name,
278                                              sec_group_id)
279                     self.floating_ip_assoc["stack_name"] = \
280                         server_name + "-fip-assoc"
281                     template.add_floating_ip_association(
282                         self.floating_ip_assoc["stack_name"],
283                         self.floating_ip["stack_name"],
284                         port_name)
285         if self.flavor:
286             if isinstance(self.flavor, dict):
287                 self.flavor["name"] = \
288                     self.flavor.setdefault("name", self.stack_name + "-flavor")
289                 template.add_flavor(**self.flavor)
290                 self.flavor_name = self.flavor["name"]
291             else:
292                 self.flavor_name = self.flavor
293
294         if self.volume:
295             if isinstance(self.volume, dict):
296                 self.volume["name"] = \
297                     self.volume.setdefault("name", server_name + "-volume")
298                 template.add_volume(**self.volume)
299                 template.add_volume_attachment(server_name, self.volume["name"],
300                                                mountpoint=self.volume_mountpoint)
301             else:
302                 template.add_volume_attachment(server_name, self.volume,
303                                                mountpoint=self.volume_mountpoint)
304
305         template.add_server(server_name, self.image, flavor=self.flavor_name,
306                             flavors=self.context.flavors,
307                             ports=port_name_list,
308                             user=self.user,
309                             key_name=self.keypair_name,
310                             user_data=self.user_data,
311                             scheduler_hints=scheduler_hints)
312
313     def add_to_template(self, template, networks, scheduler_hints=None):
314         """adds to the template one or more servers (instances)"""
315         if self.instances == 1:
316             server_name = self.stack_name
317             self._add_instance(template, server_name, networks,
318                                scheduler_hints=scheduler_hints)
319         else:
320             # TODO(hafe) fix or remove, no test/sample for this
321             for i in range(self.instances):
322                 server_name = "%s-%d" % (self.stack_name, i)
323                 self._add_instance(template, server_name, networks,
324                                    scheduler_hints=scheduler_hints)
325
326
327 def update_scheduler_hints(scheduler_hints, added_servers, placement_group):
328     """update scheduler hints from server's placement configuration
329     TODO: this code is openstack specific and should move somewhere else
330     """
331     if placement_group.policy == "affinity":
332         if "same_host" in scheduler_hints:
333             host_list = scheduler_hints["same_host"]
334         else:
335             host_list = scheduler_hints["same_host"] = []
336     else:
337         if "different_host" in scheduler_hints:
338             host_list = scheduler_hints["different_host"]
339         else:
340             host_list = scheduler_hints["different_host"] = []
341
342     for name in added_servers:
343         if name in placement_group.members:
344             host_list.append({'get_resource': name})