Heat context code refactor
[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
14
15 class Object(object):
16     '''Base class for classes in the logical model
17     Contains common attributes and methods
18     '''
19     def __init__(self, name, context):
20         # model identities and reference
21         self.name = name
22         self._context = context
23
24         # stack identities
25         self.stack_name = None
26         self.stack_id = None
27
28     @property
29     def dn(self):
30         '''returns distinguished name for object'''
31         return self.name + "." + self._context.name
32
33
34 class PlacementGroup(Object):
35     '''Class that represents a placement group in the logical model
36     Concept comes from the OVF specification. Policy should be one of
37     "availability" or "affinity (there are more but they are not supported)"
38     '''
39     map = {}
40
41     def __init__(self, name, context, policy):
42         if policy not in ["affinity", "availability"]:
43             raise ValueError("placement group '%s', policy '%s' is not valid" %
44                              (name, policy))
45         self.name = name
46         self.members = set()
47         self.stack_name = context.name + "-" + name
48         self.policy = policy
49         PlacementGroup.map[name] = self
50
51     def add_member(self, name):
52         self.members.add(name)
53
54     @staticmethod
55     def get(name):
56         if name in PlacementGroup.map:
57             return PlacementGroup.map[name]
58         else:
59             return None
60
61
62 class Router(Object):
63     '''Class that represents a router in the logical model'''
64     def __init__(self, name, network_name, context, external_gateway_info):
65         super(self.__class__, self).__init__(name, context)
66
67         self.stack_name = context.name + "-" + network_name + "-" + self.name
68         self.stack_if_name = self.stack_name + "-if0"
69         self.external_gateway_info = external_gateway_info
70
71
72 class Network(Object):
73     '''Class that represents a network in the logical model'''
74     list = []
75
76     def __init__(self, name, context, attrs):
77         super(self.__class__, self).__init__(name, context)
78         self.stack_name = context.name + "-" + self.name
79         self.subnet_stack_name = self.stack_name + "-subnet"
80         self.subnet_cidr = attrs.get('cidr', '10.0.1.0/24')
81         self.router = None
82
83         if "external_network" in attrs:
84             self.router = Router("router", self.name,
85                                  context, attrs["external_network"])
86
87         Network.list.append(self)
88
89     def has_route_to(self, network_name):
90         '''determines if this network has a route to the named network'''
91         if self.router and self.router.external_gateway_info == network_name:
92             return True
93         return False
94
95     @staticmethod
96     def find_by_route_to(external_network):
97         '''finds a network that has a route to the specified network'''
98         for network in Network.list:
99             if network.has_route_to(external_network):
100                 return network
101
102     @staticmethod
103     def find_external_network():
104         '''return the name of an external network some network in this
105         context has a route to'''
106         for network in Network.list:
107             if network.router:
108                 return network.router.external_gateway_info
109         return None
110
111
112 class Server(Object):
113     '''Class that represents a server in the logical model'''
114     list = []
115
116     def __init__(self, name, context, attrs):
117         super(self.__class__, self).__init__(name, context)
118         self.stack_name = self.name + "." + context.name
119         self.keypair_name = context.keypair_name
120         self.secgroup_name = context.secgroup_name
121         self.context = context
122         self.public_ip = None
123         self.private_ip = None
124
125         if attrs is None:
126             attrs = {}
127
128         self.placement_groups = []
129         placement = attrs.get("placement", [])
130         placement = placement if type(placement) is list else [placement]
131         for p in placement:
132             pg = PlacementGroup.get(p)
133             if not pg:
134                 raise ValueError("server '%s', placement '%s' is invalid" %
135                                  (name, p))
136             self.placement_groups.append(pg)
137             pg.add_member(self.stack_name)
138
139         self.instances = 1
140         if "instances" in attrs:
141             self.instances = attrs["instances"]
142
143         # dict with key network name, each item is a dict with port name and ip
144         self.ports = {}
145
146         self.floating_ip = None
147         if "floating_ip" in attrs:
148             self.floating_ip = {}
149
150         if self.floating_ip is not None:
151             ext_net = Network.find_external_network()
152             assert ext_net is not None
153             self.floating_ip["external_network"] = ext_net
154
155         self._image = None
156         if "image" in attrs:
157             self._image = attrs["image"]
158
159         self._flavor = None
160         if "flavor" in attrs:
161             self._flavor = attrs["flavor"]
162
163         Server.list.append(self)
164
165     @property
166     def image(self):
167         '''returns a server's image name'''
168         if self._image:
169             return self._image
170         else:
171             return self._context.image
172
173     @property
174     def flavor(self):
175         '''returns a server's flavor name'''
176         if self._flavor:
177             return self._flavor
178         else:
179             return self._context.flavor
180
181     def _add_instance(self, template, server_name, networks, scheduler_hints):
182         '''adds to the template one server and corresponding resources'''
183         port_name_list = []
184         for network in networks:
185             port_name = server_name + "-" + network.name + "-port"
186             self.ports[network.name] = {"stack_name": port_name}
187             template.add_port(port_name, network.stack_name,
188                               network.subnet_stack_name,
189                               sec_group_id=self.secgroup_name)
190             port_name_list.append(port_name)
191
192             if self.floating_ip:
193                 external_network = self.floating_ip["external_network"]
194                 if network.has_route_to(external_network):
195                     self.floating_ip["stack_name"] = server_name + "-fip"
196                     template.add_floating_ip(self.floating_ip["stack_name"],
197                                              external_network,
198                                              port_name,
199                                              network.router.stack_if_name,
200                                              self.secgroup_name)
201
202         template.add_server(server_name, self.image, self.flavor,
203                             ports=port_name_list,
204                             key_name=self.keypair_name,
205                             scheduler_hints=scheduler_hints)
206
207     def add_to_template(self, template, networks, scheduler_hints=None):
208         '''adds to the template one or more servers (instances)'''
209         if self.instances == 1:
210             server_name = self.stack_name
211             self._add_instance(template, server_name, networks,
212                                scheduler_hints=scheduler_hints)
213         else:
214             # TODO(hafe) fix or remove, no test/sample for this
215             for i in range(self.instances):
216                 server_name = "%s-%d" % (self.stack_name, i)
217                 self._add_instance(template, server_name, networks,
218                                    scheduler_hints=scheduler_hints)
219
220
221 def update_scheduler_hints(scheduler_hints, added_servers, placement_group):
222     ''' update scheduler hints from server's placement configuration
223     TODO: this code is openstack specific and should move somewhere else
224     '''
225     if placement_group.policy == "affinity":
226         if "same_host" in scheduler_hints:
227             host_list = scheduler_hints["same_host"]
228         else:
229             host_list = scheduler_hints["same_host"] = []
230     else:
231         if "different_host" in scheduler_hints:
232             host_list = scheduler_hints["different_host"]
233         else:
234             host_list = scheduler_hints["different_host"] = []
235
236     for name in added_servers:
237         if name in placement_group.members:
238             host_list.append({'get_resource': name})