Merge "Bux: task_id parameter from API can not pass to yardstick core"
[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         if name in PlacementGroup.map:
60             return PlacementGroup.map[name]
61         else:
62             return None
63
64
65 class Router(Object):
66     """Class that represents a router in the logical model"""
67
68     def __init__(self, name, network_name, context, external_gateway_info):
69         super(Router, self).__init__(name, context)
70
71         self.stack_name = context.name + "-" + network_name + "-" + self.name
72         self.stack_if_name = self.stack_name + "-if0"
73         self.external_gateway_info = external_gateway_info
74
75
76 class Network(Object):
77     """Class that represents a network in the logical model"""
78     list = []
79
80     def __init__(self, name, context, attrs):
81         super(Network, self).__init__(name, context)
82         self.stack_name = context.name + "-" + self.name
83         self.subnet_stack_name = self.stack_name + "-subnet"
84         self.subnet_cidr = attrs.get('cidr', '10.0.1.0/24')
85         self.router = None
86
87         if "external_network" in attrs:
88             self.router = Router("router", self.name,
89                                  context, attrs["external_network"])
90
91         Network.list.append(self)
92
93     def has_route_to(self, network_name):
94         """determines if this network has a route to the named network"""
95         if self.router and self.router.external_gateway_info == network_name:
96             return True
97         return False
98
99     @staticmethod
100     def find_by_route_to(external_network):
101         """finds a network that has a route to the specified network"""
102         for network in Network.list:
103             if network.has_route_to(external_network):
104                 return network
105
106     @staticmethod
107     def find_external_network():
108         """return the name of an external network some network in this
109         context has a route to"""
110         for network in Network.list:
111             if network.router:
112                 return network.router.external_gateway_info
113         return None
114
115
116 class Server(Object):
117     """Class that represents a server in the logical model"""
118     list = []
119
120     def __init__(self, name, context, attrs):
121         super(Server, self).__init__(name, context)
122         self.stack_name = self.name + "." + context.name
123         self.keypair_name = context.keypair_name
124         self.secgroup_name = context.secgroup_name
125         self.user = context.user
126         self.context = context
127         self.public_ip = None
128         self.private_ip = None
129
130         if attrs is None:
131             attrs = {}
132
133         self.placement_groups = []
134         placement = attrs.get("placement", [])
135         placement = placement if type(placement) is list else [placement]
136         for p in placement:
137             pg = PlacementGroup.get(p)
138             if not pg:
139                 raise ValueError("server '%s', placement '%s' is invalid" %
140                                  (name, p))
141             self.placement_groups.append(pg)
142             pg.add_member(self.stack_name)
143
144         self.instances = 1
145         if "instances" in attrs:
146             self.instances = attrs["instances"]
147
148         # dict with key network name, each item is a dict with port name and ip
149         self.ports = {}
150
151         self.floating_ip = None
152         self.floating_ip_assoc = None
153         if "floating_ip" in attrs:
154             self.floating_ip = {}
155             self.floating_ip_assoc = {}
156
157         if self.floating_ip is not None:
158             ext_net = Network.find_external_network()
159             assert ext_net is not None
160             self.floating_ip["external_network"] = ext_net
161
162         self._image = None
163         if "image" in attrs:
164             self._image = attrs["image"]
165
166         self._flavor = None
167         if "flavor" in attrs:
168             self._flavor = attrs["flavor"]
169
170         Server.list.append(self)
171
172     @property
173     def image(self):
174         """returns a server's image name"""
175         if self._image:
176             return self._image
177         else:
178             return self._context.image
179
180     @property
181     def flavor(self):
182         """returns a server's flavor name"""
183         if self._flavor:
184             return self._flavor
185         else:
186             return self._context.flavor
187
188     def _add_instance(self, template, server_name, networks, scheduler_hints):
189         """adds to the template one server and corresponding resources"""
190         port_name_list = []
191         for network in networks:
192             port_name = server_name + "-" + network.name + "-port"
193             self.ports[network.name] = {"stack_name": port_name}
194             template.add_port(port_name, network.stack_name,
195                               network.subnet_stack_name,
196                               sec_group_id=self.secgroup_name)
197             port_name_list.append(port_name)
198
199             if self.floating_ip:
200                 external_network = self.floating_ip["external_network"]
201                 if network.has_route_to(external_network):
202                     self.floating_ip["stack_name"] = server_name + "-fip"
203                     template.add_floating_ip(self.floating_ip["stack_name"],
204                                              external_network,
205                                              port_name,
206                                              network.router.stack_if_name,
207                                              self.secgroup_name)
208                     self.floating_ip_assoc["stack_name"] = \
209                         server_name + "-fip-assoc"
210                     template.add_floating_ip_association(
211                         self.floating_ip_assoc["stack_name"],
212                         self.floating_ip["stack_name"],
213                         port_name)
214
215         template.add_server(server_name, self.image, self.flavor,
216                             ports=port_name_list,
217                             user=self.user,
218                             key_name=self.keypair_name,
219                             scheduler_hints=scheduler_hints)
220
221     def add_to_template(self, template, networks, scheduler_hints=None):
222         """adds to the template one or more servers (instances)"""
223         if self.instances == 1:
224             server_name = self.stack_name
225             self._add_instance(template, server_name, networks,
226                                scheduler_hints=scheduler_hints)
227         else:
228             # TODO(hafe) fix or remove, no test/sample for this
229             for i in range(self.instances):
230                 server_name = "%s-%d" % (self.stack_name, i)
231                 self._add_instance(template, server_name, networks,
232                                    scheduler_hints=scheduler_hints)
233
234
235 def update_scheduler_hints(scheduler_hints, added_servers, placement_group):
236     """ update scheduler hints from server's placement configuration
237     TODO: this code is openstack specific and should move somewhere else
238     """
239     if placement_group.policy == "affinity":
240         if "same_host" in scheduler_hints:
241             host_list = scheduler_hints["same_host"]
242         else:
243             host_list = scheduler_hints["same_host"] = []
244     else:
245         if "different_host" in scheduler_hints:
246             host_list = scheduler_hints["different_host"]
247         else:
248             host_list = scheduler_hints["different_host"] = []
249
250     for name in added_servers:
251         if name in placement_group.members:
252             host_list.append({'get_resource': name})