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.vnic_type = attrs.get('vnic_type', 'normal')
115 self.allowed_address_pairs = attrs.get('allowed_address_pairs', [])
117 # we require 'null' or '' to disable setting gateway_ip
118 self.gateway_ip = attrs['gateway_ip']
120 # default to explicit None
121 self.gateway_ip = None
123 # null is None in YAML, so we have to convert back to string
124 if self.gateway_ip is None:
125 self.gateway_ip = "null"
127 if "external_network" in attrs:
128 self.router = Router("router", self.name,
129 context, attrs["external_network"])
130 self.vld_id = attrs.get("vld_id")
132 Network.list.append(self)
134 def has_route_to(self, network_name):
135 """determines if this network has a route to the named network"""
136 if self.router and self.router.external_gateway_info == network_name:
141 def find_by_route_to(external_network):
142 """finds a network that has a route to the specified network"""
143 for network in Network.list:
144 if network.has_route_to(external_network):
148 def find_external_network():
149 """return the name of an external network some network in this
150 context has a route to
152 for network in Network.list:
154 return network.router.external_gateway_info
158 class Server(Object): # pragma: no cover
159 """Class that represents a server in the logical model"""
162 def __init__(self, name, context, attrs):
163 super(Server, self).__init__(name, context)
164 self.stack_name = self.name + "." + context.name
165 self.keypair_name = context.keypair_name
166 self.secgroup_name = context.secgroup_name
167 self.user = context.user
168 self.context = context
169 self.public_ip = None
170 self.private_ip = None
177 self.placement_groups = []
178 placement = attrs.get("placement", [])
179 placement = placement if isinstance(placement, list) else [placement]
181 pg = PlacementGroup.get(p)
183 raise ValueError("server '%s', placement '%s' is invalid" %
185 self.placement_groups.append(pg)
186 pg.add_member(self.stack_name)
189 if "volume" in attrs:
190 self.volume = attrs.get("volume")
192 self.volume_mountpoint = None
193 if "volume_mountpoint" in attrs:
194 self.volume_mountpoint = attrs.get("volume_mountpoint")
196 # support servergroup attr
197 self.server_group = None
198 sg = attrs.get("server_group")
200 server_group = ServerGroup.get(sg)
202 raise ValueError("server '%s', server_group '%s' is invalid" %
204 self.server_group = server_group
205 server_group.add_member(self.stack_name)
208 if "instances" in attrs:
209 self.instances = attrs["instances"]
211 # dict with key network name, each item is a dict with port name and ip
214 self.floating_ip = None
215 self.floating_ip_assoc = None
216 if "floating_ip" in attrs:
217 self.floating_ip = {}
218 self.floating_ip_assoc = {}
220 if self.floating_ip is not None:
221 ext_net = Network.find_external_network()
222 assert ext_net is not None
223 self.floating_ip["external_network"] = ext_net
227 self._image = attrs["image"]
230 if "flavor" in attrs:
231 self._flavor = attrs["flavor"]
233 self.user_data = attrs.get('user_data', '')
235 Server.list.append(self)
239 """returns a server's image name"""
243 return self._context.image
247 """returns a server's flavor name"""
251 return self._context.flavor
253 def _add_instance(self, template, server_name, networks, scheduler_hints):
254 """adds to the template one server and corresponding resources"""
256 for network in networks:
257 port_name = server_name + "-" + network.name + "-port"
258 self.ports[network.name] = {"stack_name": port_name}
259 # we can't use secgroups if port_security_enabled is False
260 if network.port_security_enabled is False:
263 # if port_security_enabled is None we still need to add to secgroup
264 sec_group_id = self.secgroup_name
265 # don't refactor to pass in network object, that causes JSON
266 # circular ref encode errors
267 template.add_port(port_name, network.stack_name, network.subnet_stack_name,
268 network.vnic_type, sec_group_id=sec_group_id,
269 provider=network.provider,
270 allowed_address_pairs=network.allowed_address_pairs)
271 port_name_list.append(port_name)
274 external_network = self.floating_ip["external_network"]
275 if network.has_route_to(external_network):
276 self.floating_ip["stack_name"] = server_name + "-fip"
277 template.add_floating_ip(self.floating_ip["stack_name"],
280 network.router.stack_if_name,
282 self.floating_ip_assoc["stack_name"] = \
283 server_name + "-fip-assoc"
284 template.add_floating_ip_association(
285 self.floating_ip_assoc["stack_name"],
286 self.floating_ip["stack_name"],
289 if isinstance(self.flavor, dict):
290 self.flavor["name"] = \
291 self.flavor.setdefault("name", self.stack_name + "-flavor")
292 template.add_flavor(**self.flavor)
293 self.flavor_name = self.flavor["name"]
295 self.flavor_name = self.flavor
298 if isinstance(self.volume, dict):
299 self.volume["name"] = \
300 self.volume.setdefault("name", server_name + "-volume")
301 template.add_volume(**self.volume)
302 template.add_volume_attachment(server_name, self.volume["name"],
303 mountpoint=self.volume_mountpoint)
305 template.add_volume_attachment(server_name, self.volume,
306 mountpoint=self.volume_mountpoint)
308 template.add_server(server_name, self.image, flavor=self.flavor_name,
309 flavors=self.context.flavors,
310 ports=port_name_list,
312 key_name=self.keypair_name,
313 user_data=self.user_data,
314 scheduler_hints=scheduler_hints)
316 def add_to_template(self, template, networks, scheduler_hints=None):
317 """adds to the template one or more servers (instances)"""
318 if self.instances == 1:
319 server_name = self.stack_name
320 self._add_instance(template, server_name, networks,
321 scheduler_hints=scheduler_hints)
323 # TODO(hafe) fix or remove, no test/sample for this
324 for i in range(self.instances):
325 server_name = "%s-%d" % (self.stack_name, i)
326 self._add_instance(template, server_name, networks,
327 scheduler_hints=scheduler_hints)
330 def update_scheduler_hints(scheduler_hints, added_servers, placement_group):
331 """update scheduler hints from server's placement configuration
332 TODO: this code is openstack specific and should move somewhere else
334 if placement_group.policy == "affinity":
335 if "same_host" in scheduler_hints:
336 host_list = scheduler_hints["same_host"]
338 host_list = scheduler_hints["same_host"] = []
340 if "different_host" in scheduler_hints:
341 host_list = scheduler_hints["different_host"]
343 host_list = scheduler_hints["different_host"] = []
345 for name in added_servers:
346 if name in placement_group.members:
347 host_list.append({'get_resource': name})