1 ##############################################################################
2 # Copyright (c) 2015 Ericsson AB and others.
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 ##############################################################################
13 from __future__ import absolute_import
18 from collections import Mapping
19 from six.moves import range
21 from yardstick.common import constants as consts
24 LOG = logging.getLogger(__name__)
28 """Base class for classes in the logical model
29 Contains common attributes and methods
32 def __init__(self, name, context):
33 # model identities and reference
35 self._context = context
38 self.stack_name = None
43 """returns distinguished name for object"""
44 return self.name + "." + self._context.name
47 class PlacementGroup(Object):
48 """Class that represents a placement group in the logical model
49 Concept comes from the OVF specification. Policy should be one of
50 "availability" or "affinity (there are more but they are not supported)"
54 def __init__(self, name, context, policy):
55 if policy not in ["affinity", "availability"]:
56 raise ValueError("placement group '%s', policy '%s' is not valid" %
60 self.stack_name = context.name + "-" + name
62 PlacementGroup.map[name] = self
64 def add_member(self, name):
65 self.members.add(name)
69 return PlacementGroup.map.get(name)
72 class ServerGroup(Object): # pragma: no cover
73 """Class that represents a server group in the logical model
74 Policy should be one of "anti-affinity" or "affinity"
78 def __init__(self, name, context, policy):
79 super(ServerGroup, self).__init__(name, context)
80 if policy not in {"affinity", "anti-affinity"}:
81 raise ValueError("server group '%s', policy '%s' is not valid" %
85 self.stack_name = context.name + "-" + name
87 ServerGroup.map[name] = self
89 def add_member(self, name):
90 self.members.add(name)
94 return ServerGroup.map.get(name)
98 """Class that represents a router in the logical model"""
100 def __init__(self, name, network_name, context, external_gateway_info):
101 super(Router, self).__init__(name, context)
103 self.stack_name = context.name + "-" + network_name + "-" + self.name
104 self.stack_if_name = self.stack_name + "-if0"
105 self.external_gateway_info = external_gateway_info
108 class Network(Object):
109 """Class that represents a network in the logical model"""
112 def __init__(self, name, context, attrs):
113 super(Network, self).__init__(name, context)
114 self.stack_name = context.name + "-" + self.name
115 self.subnet_stack_name = self.stack_name + "-subnet"
116 self.subnet_cidr = attrs.get('cidr', '10.0.1.0/24')
117 self.enable_dhcp = attrs.get('enable_dhcp', 'true')
119 self.physical_network = attrs.get('physical_network', 'physnet1')
120 self.provider = attrs.get('provider')
121 self.segmentation_id = attrs.get('segmentation_id')
122 self.network_type = attrs.get('network_type')
123 self.port_security_enabled = attrs.get('port_security_enabled')
124 self.vnic_type = attrs.get('vnic_type', 'normal')
125 self.allowed_address_pairs = attrs.get('allowed_address_pairs', [])
127 # we require 'null' or '' to disable setting gateway_ip
128 self.gateway_ip = attrs['gateway_ip']
130 # default to explicit None
131 self.gateway_ip = None
133 # null is None in YAML, so we have to convert back to string
134 if self.gateway_ip is None:
135 self.gateway_ip = "null"
137 self.net_flags = attrs.get('net_flags', {})
138 if self.is_existing():
139 self.subnet = attrs.get('subnet')
141 raise Warning('No subnet set in existing netwrok!')
143 if "external_network" in attrs:
144 self.router = Router("router", self.name,
145 context, attrs["external_network"])
146 Network.list.append(self)
148 def is_existing(self):
149 net_is_existing = self.net_flags.get(consts.IS_EXISTING)
150 if net_is_existing and not isinstance(net_is_existing, bool):
151 raise SyntaxError('Network flags should be bool type!')
152 return net_is_existing
155 net_is_public = self.net_flags.get(consts.IS_PUBLIC)
156 if net_is_public and not isinstance(net_is_public, bool):
157 raise SyntaxError('Network flags should be bool type!')
160 def has_route_to(self, network_name):
161 """determines if this network has a route to the named network"""
162 if self.router and self.router.external_gateway_info == network_name:
167 def find_by_route_to(external_network):
168 """finds a network that has a route to the specified network"""
169 for network in Network.list:
170 if network.has_route_to(external_network):
174 def find_external_network():
175 """return the name of an external network some network in this
176 context has a route to
178 for network in Network.list:
180 return network.router.external_gateway_info
184 class Server(Object): # pragma: no cover
185 """Class that represents a server in the logical model"""
188 def __init__(self, name, context, attrs):
189 super(Server, self).__init__(name, context)
190 self.stack_name = self.name + "." + context.name
191 self.keypair_name = context.keypair_name
192 self.secgroup_name = context.secgroup_name
193 self.user = context.user
194 self.context = context
195 self.public_ip = None
196 self.private_ip = None
203 self.placement_groups = []
204 placement = attrs.get("placement", [])
205 placement = placement if isinstance(placement, list) else [placement]
207 pg = PlacementGroup.get(p)
209 raise ValueError("server '%s', placement '%s' is invalid" %
211 self.placement_groups.append(pg)
212 pg.add_member(self.stack_name)
215 if "volume" in attrs:
216 self.volume = attrs.get("volume")
218 self.volume_mountpoint = None
219 if "volume_mountpoint" in attrs:
220 self.volume_mountpoint = attrs.get("volume_mountpoint")
222 # support servergroup attr
223 self.server_group = None
224 sg = attrs.get("server_group")
226 server_group = ServerGroup.get(sg)
228 raise ValueError("server '%s', server_group '%s' is invalid" %
230 self.server_group = server_group
231 server_group.add_member(self.stack_name)
234 if "instances" in attrs:
235 self.instances = attrs["instances"]
237 # dict with key network name, each item is a dict with port name and ip
238 self.network_ports = attrs.get("network_ports", {})
241 self.floating_ip = None
242 self.floating_ip_assoc = None
243 if "floating_ip" in attrs:
244 self.floating_ip = {}
245 self.floating_ip_assoc = {}
247 if self.floating_ip is not None:
248 ext_net = Network.find_external_network()
249 assert ext_net is not None
250 self.floating_ip["external_network"] = ext_net
254 self._image = attrs["image"]
257 if "flavor" in attrs:
258 self._flavor = attrs["flavor"]
260 self.user_data = attrs.get('user_data', '')
261 self.availability_zone = attrs.get('availability_zone')
263 Server.list.append(self)
265 def override_ip(self, network_name, port):
266 def find_port_overrides():
268 # p can be string or dict
269 # we can't just use p[port['port'] in case p is a string
270 # and port['port'] is an int?
271 if isinstance(p, Mapping):
272 g = p.get(port['port'])
273 # filter out empty dicts
277 ports = self.network_ports.get(network_name, [])
278 intf = self.interfaces[port['port']]
279 for override in find_port_overrides():
280 intf['local_ip'] = override.get('local_ip', intf['local_ip'])
281 intf['netmask'] = override.get('netmask', intf['netmask'])
282 # only use the first value
287 """returns a server's image name"""
291 return self._context.image
295 """returns a server's flavor name"""
299 return self._context.flavor
301 def _add_instance(self, template, server_name, networks, scheduler_hints):
302 """adds to the template one server and corresponding resources"""
304 for network in networks:
305 # if explicit mapping skip unused networks
306 if self.network_ports:
308 ports = self.network_ports[network.name]
310 # no port for this network
313 if isinstance(ports, six.string_types):
314 # because strings are iterable we have to check specifically
315 raise SyntaxError("network_port must be a list '{}'".format(ports))
316 # convert port subdicts into their just port name
317 # port subdicts are used to override Heat IP address,
318 # but we just need the port name
319 # we allow duplicates here and let Heat raise the error
320 ports = [next(iter(p)) if isinstance(p, dict) else p for p in ports]
321 # otherwise add a port for every network with port name as network name
323 ports = [network.name]
324 net_flags = network.net_flags
326 port_name = "{0}-{1}-port".format(server_name, port)
327 port_info = {"stack_name": port_name, "port": port}
329 port_info['net_flags'] = net_flags
330 self.ports.setdefault(network.name, []).append(port_info)
331 # we can't use secgroups if port_security_enabled is False
332 if network.port_security_enabled is False:
335 # if port_security_enabled is None we still need to add to secgroup
336 sec_group_id = self.secgroup_name
337 # don't refactor to pass in network object, that causes JSON
338 # circular ref encode errors
339 template.add_port(port_name, network,
340 sec_group_id=sec_group_id,
341 provider=network.provider,
342 allowed_address_pairs=network.allowed_address_pairs)
343 if network.is_public():
344 port_name_list.insert(0, port_name)
346 port_name_list.append(port_name)
349 external_network = self.floating_ip["external_network"]
350 if network.has_route_to(external_network):
351 self.floating_ip["stack_name"] = server_name + "-fip"
352 template.add_floating_ip(self.floating_ip["stack_name"],
355 network.router.stack_if_name,
357 self.floating_ip_assoc["stack_name"] = \
358 server_name + "-fip-assoc"
359 template.add_floating_ip_association(
360 self.floating_ip_assoc["stack_name"],
361 self.floating_ip["stack_name"],
364 if isinstance(self.flavor, dict):
365 self.flavor["name"] = \
366 self.flavor.setdefault("name", self.stack_name + "-flavor")
367 template.add_flavor(**self.flavor)
368 self.flavor_name = self.flavor["name"]
370 self.flavor_name = self.flavor
373 if isinstance(self.volume, dict):
374 self.volume["name"] = \
375 self.volume.setdefault("name", server_name + "-volume")
376 template.add_volume(**self.volume)
377 template.add_volume_attachment(server_name, self.volume["name"],
378 mountpoint=self.volume_mountpoint)
380 template.add_volume_attachment(server_name, self.volume,
381 mountpoint=self.volume_mountpoint)
383 template.add_server(server_name, self.image, flavor=self.flavor_name,
384 flavors=self.context.flavors, ports=port_name_list,
385 scheduler_hints=scheduler_hints, user=self.user,
386 key_name=self.keypair_name, user_data=self.user_data,
387 availability_zone=self.availability_zone)
389 def add_to_template(self, template, networks, scheduler_hints=None):
390 """adds to the template one or more servers (instances)"""
391 if self.instances == 1:
392 server_name = self.stack_name
393 self._add_instance(template, server_name, networks,
394 scheduler_hints=scheduler_hints)
396 # TODO(hafe) fix or remove, no test/sample for this
397 for i in range(self.instances):
398 server_name = "%s-%d" % (self.stack_name, i)
399 self._add_instance(template, server_name, networks,
400 scheduler_hints=scheduler_hints)
403 def update_scheduler_hints(scheduler_hints, added_servers, placement_group):
404 """update scheduler hints from server's placement configuration
405 TODO: this code is openstack specific and should move somewhere else
407 if placement_group.policy == "affinity":
408 if "same_host" in scheduler_hints:
409 host_list = scheduler_hints["same_host"]
411 host_list = scheduler_hints["same_host"] = []
413 if "different_host" in scheduler_hints:
414 host_list = scheduler_hints["different_host"]
416 host_list = scheduler_hints["different_host"] = []
418 for name in added_servers:
419 if name in placement_group.members:
420 host_list.append({'get_resource': name})