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
14 from six.moves import range
18 """Base class for classes in the logical model
19 Contains common attributes and methods
22 def __init__(self, name, context):
23 # model identities and reference
25 self._context = context
28 self.stack_name = None
33 """returns distinguished name for object"""
34 return self.name + "." + self._context.name
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)"
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" %
50 self.stack_name = context.name + "-" + name
52 PlacementGroup.map[name] = self
54 def add_member(self, name):
55 self.members.add(name)
59 return PlacementGroup.map.get(name)
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"
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" %
75 self.stack_name = context.name + "-" + name
77 ServerGroup.map[name] = self
79 def add_member(self, name):
80 self.members.add(name)
84 return ServerGroup.map.get(name)
88 """Class that represents a router in the logical model"""
90 def __init__(self, name, network_name, context, external_gateway_info):
91 super(Router, self).__init__(name, context)
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
98 class Network(Object):
99 """Class that represents a network in the logical model"""
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')
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', [])
116 # we require 'null' or '' to disable setting gateway_ip
117 self.gateway_ip = attrs['gateway_ip']
119 # default to explicit None
120 self.gateway_ip = None
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"
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")
131 Network.list.append(self)
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:
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):
147 def find_external_network():
148 """return the name of an external network some network in this
149 context has a route to
151 for network in Network.list:
153 return network.router.external_gateway_info
157 class Server(Object): # pragma: no cover
158 """Class that represents a server in the logical model"""
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
176 self.placement_groups = []
177 placement = attrs.get("placement", [])
178 placement = placement if isinstance(placement, list) else [placement]
180 pg = PlacementGroup.get(p)
182 raise ValueError("server '%s', placement '%s' is invalid" %
184 self.placement_groups.append(pg)
185 pg.add_member(self.stack_name)
188 if "volume" in attrs:
189 self.volume = attrs.get("volume")
191 self.volume_mountpoint = None
192 if "volume_mountpoint" in attrs:
193 self.volume_mountpoint = attrs.get("volume_mountpoint")
195 # support servergroup attr
196 self.server_group = None
197 sg = attrs.get("server_group")
199 server_group = ServerGroup.get(sg)
201 raise ValueError("server '%s', server_group '%s' is invalid" %
203 self.server_group = server_group
204 server_group.add_member(self.stack_name)
207 if "instances" in attrs:
208 self.instances = attrs["instances"]
210 # dict with key network name, each item is a dict with port name and ip
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 = {}
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
226 self._image = attrs["image"]
229 if "flavor" in attrs:
230 self._flavor = attrs["flavor"]
232 self.user_data = attrs.get('user_data', '')
234 Server.list.append(self)
238 """returns a server's image name"""
242 return self._context.image
246 """returns a server's flavor name"""
250 return self._context.flavor
252 def _add_instance(self, template, server_name, networks, scheduler_hints):
253 """adds to the template one server and corresponding resources"""
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
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)
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"],
277 network.router.stack_if_name,
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"],
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"]
292 self.flavor_name = self.flavor
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)
302 template.add_volume_attachment(server_name, self.volume,
303 mountpoint=self.volume_mountpoint)
305 template.add_server(server_name, self.image, flavor=self.flavor_name,
306 flavors=self.context.flavors,
307 ports=port_name_list,
309 key_name=self.keypair_name,
310 user_data=self.user_data,
311 scheduler_hints=scheduler_hints)
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)
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)
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
331 if placement_group.policy == "affinity":
332 if "same_host" in scheduler_hints:
333 host_list = scheduler_hints["same_host"]
335 host_list = scheduler_hints["same_host"] = []
337 if "different_host" in scheduler_hints:
338 host_list = scheduler_hints["different_host"]
340 host_list = scheduler_hints["different_host"] = []
342 for name in added_servers:
343 if name in placement_group.members:
344 host_list.append({'get_resource': name})