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')
108 self.physical_network = attrs.get('physical_network', 'physnet1')
109 self.provider = attrs.get('provider', None)
111 if "external_network" in attrs:
112 self.router = Router("router", self.name,
113 context, attrs["external_network"])
114 self.vld_id = attrs.get("vld_id", "")
116 Network.list.append(self)
118 def has_route_to(self, network_name):
119 """determines if this network has a route to the named network"""
120 if self.router and self.router.external_gateway_info == network_name:
125 def find_by_route_to(external_network):
126 """finds a network that has a route to the specified network"""
127 for network in Network.list:
128 if network.has_route_to(external_network):
132 def find_external_network():
133 """return the name of an external network some network in this
134 context has a route to
136 for network in Network.list:
138 return network.router.external_gateway_info
142 class Server(Object): # pragma: no cover
143 """Class that represents a server in the logical model"""
146 def __init__(self, name, context, attrs):
147 super(Server, self).__init__(name, context)
148 self.stack_name = self.name + "." + context.name
149 self.keypair_name = context.keypair_name
150 self.secgroup_name = context.secgroup_name
151 self.user = context.user
152 self.context = context
153 self.public_ip = None
154 self.private_ip = None
161 self.placement_groups = []
162 placement = attrs.get("placement", [])
163 placement = placement if isinstance(placement, list) else [placement]
165 pg = PlacementGroup.get(p)
167 raise ValueError("server '%s', placement '%s' is invalid" %
169 self.placement_groups.append(pg)
170 pg.add_member(self.stack_name)
172 # support servergroup attr
173 self.server_group = None
174 sg = attrs.get("server_group")
176 server_group = ServerGroup.get(sg)
178 raise ValueError("server '%s', server_group '%s' is invalid" %
180 self.server_group = server_group
181 server_group.add_member(self.stack_name)
184 if "instances" in attrs:
185 self.instances = attrs["instances"]
187 # dict with key network name, each item is a dict with port name and ip
190 self.floating_ip = None
191 self.floating_ip_assoc = None
192 if "floating_ip" in attrs:
193 self.floating_ip = {}
194 self.floating_ip_assoc = {}
196 if self.floating_ip is not None:
197 ext_net = Network.find_external_network()
198 assert ext_net is not None
199 self.floating_ip["external_network"] = ext_net
203 self._image = attrs["image"]
206 if "flavor" in attrs:
207 self._flavor = attrs["flavor"]
209 self.user_data = attrs.get('user_data', '')
211 Server.list.append(self)
215 """returns a server's image name"""
219 return self._context.image
223 """returns a server's flavor name"""
227 return self._context.flavor
229 def _add_instance(self, template, server_name, networks, scheduler_hints):
230 """adds to the template one server and corresponding resources"""
232 for network in networks:
233 port_name = server_name + "-" + network.name + "-port"
234 self.ports[network.name] = {"stack_name": port_name}
235 template.add_port(port_name, network.stack_name,
236 network.subnet_stack_name,
237 sec_group_id=self.secgroup_name,
238 provider=network.provider)
239 port_name_list.append(port_name)
242 external_network = self.floating_ip["external_network"]
243 if network.has_route_to(external_network):
244 self.floating_ip["stack_name"] = server_name + "-fip"
245 template.add_floating_ip(self.floating_ip["stack_name"],
248 network.router.stack_if_name,
250 self.floating_ip_assoc["stack_name"] = \
251 server_name + "-fip-assoc"
252 template.add_floating_ip_association(
253 self.floating_ip_assoc["stack_name"],
254 self.floating_ip["stack_name"],
257 if isinstance(self.flavor, dict):
258 self.flavor["name"] = \
259 self.flavor.setdefault("name", self.stack_name + "-flavor")
260 template.add_flavor(**self.flavor)
261 self.flavor_name = self.flavor["name"]
263 self.flavor_name = self.flavor
265 template.add_server(server_name, self.image, flavor=self.flavor_name,
266 flavors=self.context.flavors,
267 ports=port_name_list,
269 key_name=self.keypair_name,
270 user_data=self.user_data,
271 scheduler_hints=scheduler_hints)
273 def add_to_template(self, template, networks, scheduler_hints=None):
274 """adds to the template one or more servers (instances)"""
275 if self.instances == 1:
276 server_name = self.stack_name
277 self._add_instance(template, server_name, networks,
278 scheduler_hints=scheduler_hints)
280 # TODO(hafe) fix or remove, no test/sample for this
281 for i in range(self.instances):
282 server_name = "%s-%d" % (self.stack_name, i)
283 self._add_instance(template, server_name, networks,
284 scheduler_hints=scheduler_hints)
287 def update_scheduler_hints(scheduler_hints, added_servers, placement_group):
288 """update scheduler hints from server's placement configuration
289 TODO: this code is openstack specific and should move somewhere else
291 if placement_group.policy == "affinity":
292 if "same_host" in scheduler_hints:
293 host_list = scheduler_hints["same_host"]
295 host_list = scheduler_hints["same_host"] = []
297 if "different_host" in scheduler_hints:
298 host_list = scheduler_hints["different_host"]
300 host_list = scheduler_hints["different_host"] = []
302 for name in added_servers:
303 if name in placement_group.members:
304 host_list.append({'get_resource': name})