Refactoring of VolumeSettings to extend VolumeConfig
[snaps.git] / snaps / openstack / utils / settings_utils.py
1 # Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
2 #                    and others.  All rights reserved.
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at:
7 #
8 #     http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15 import uuid
16
17 from snaps import file_utils
18 from snaps.config.flavor import FlavorConfig
19 from snaps.config.keypair import KeypairConfig
20 from snaps.config.router import RouterConfig
21 from snaps.config.volume import VolumeConfig
22 from snaps.config.volume_type import (
23     ControlLocation,  VolumeTypeEncryptionConfig, VolumeTypeConfig)
24 from snaps.openstack.create_instance import (
25     VmInstanceSettings, FloatingIpSettings)
26 from snaps.openstack.create_network import (
27     PortSettings, SubnetSettings, NetworkSettings)
28 from snaps.openstack.create_security_group import (
29     SecurityGroupSettings, SecurityGroupRuleSettings)
30 from snaps.openstack.utils import (
31     neutron_utils, nova_utils, heat_utils, glance_utils)
32
33
34 def create_network_settings(neutron, network):
35     """
36     Returns a NetworkSettings object
37     :param neutron: the neutron client
38     :param network: a SNAPS-OO Network domain object
39     :return:
40     """
41     return NetworkSettings(
42         name=network.name, network_type=network.type,
43         subnet_settings=create_subnet_settings(neutron, network))
44
45
46 def create_security_group_settings(neutron, security_group):
47     """
48     Returns a NetworkSettings object
49     :param neutron: the neutron client
50     :param security_group: a SNAPS-OO SecurityGroup domain object
51     :return:
52     """
53     rules = neutron_utils.get_rules_by_security_group(neutron, security_group)
54
55     rule_settings = list()
56     for rule in rules:
57         rule_settings.append(SecurityGroupRuleSettings(
58             sec_grp_name=security_group.name, description=rule.description,
59             direction=rule.direction, ethertype=rule.ethertype,
60             port_range_min=rule.port_range_min,
61             port_range_max=rule.port_range_max, protocol=rule.protocol,
62             remote_group_id=rule.remote_group_id,
63             remote_ip_prefix=rule.remote_ip_prefix))
64
65     return SecurityGroupSettings(
66         name=security_group.name, description=security_group.description,
67         rule_settings=rule_settings)
68
69
70 def create_subnet_settings(neutron, network):
71     """
72     Returns a list of SubnetSettings objects for a given network
73     :param neutron: the OpenStack neutron client
74     :param network: the SNAPS-OO Network domain object
75     :return: a list
76     """
77     out = list()
78
79     subnets = neutron_utils.get_subnets_by_network(neutron, network)
80     for subnet in subnets:
81         kwargs = dict()
82         kwargs['cidr'] = subnet.cidr
83         kwargs['ip_version'] = subnet.ip_version
84         kwargs['name'] = subnet.name
85         kwargs['start'] = subnet.start
86         kwargs['end'] = subnet.end
87         kwargs['gateway_ip'] = subnet.gateway_ip
88         kwargs['enable_dhcp'] = subnet.enable_dhcp
89         kwargs['dns_nameservers'] = subnet.dns_nameservers
90         kwargs['host_routes'] = subnet.host_routes
91         kwargs['ipv6_ra_mode'] = subnet.ipv6_ra_mode
92         kwargs['ipv6_address_mode'] = subnet.ipv6_address_mode
93         out.append(SubnetSettings(**kwargs))
94     return out
95
96
97 def create_router_settings(neutron, router):
98     """
99     Returns a RouterConfig object
100     :param neutron: the neutron client
101     :param router: a SNAPS-OO Router domain object
102     :return:
103     """
104     ext_net_name = None
105
106     if router.external_network_id:
107         network = neutron_utils.get_network_by_id(
108             neutron, router.external_network_id)
109         if network:
110             ext_net_name = network.name
111
112     ports_tuple_list = list()
113     if router.port_subnets:
114         for port, subnets in router.port_subnets:
115             network = neutron_utils.get_network_by_id(
116                 neutron, port.network_id)
117
118             ip_addrs = list()
119             if network and router.external_fixed_ips:
120                 for ext_fixed_ips in router.external_fixed_ips:
121                     for subnet in subnets:
122                         if ext_fixed_ips['subnet_id'] == subnet.id:
123                             ip_addrs.append(ext_fixed_ips['ip_address'])
124             else:
125                 for ip in port.ips:
126                     ip_addrs.append(ip)
127
128             ip_list = list()
129             if len(ip_addrs) > 0:
130                 for ip_addr in ip_addrs:
131                     if isinstance(ip_addr, dict):
132                         ip_list.append(ip_addr['ip_address'])
133                     else:
134                         ip_list.append(ip_addr)
135
136             ports_tuple_list.append((network, ip_list))
137
138     port_settings = __create_port_settings(neutron, ports_tuple_list)
139
140     filtered_settings = list()
141     for port_setting in port_settings:
142         if port_setting.network_name != ext_net_name:
143             filtered_settings.append(port_setting)
144
145     return RouterConfig(
146         name=router.name, external_gateway=ext_net_name,
147         admin_state_up=router.admin_state_up,
148         port_settings=filtered_settings)
149
150
151 def create_volume_config(volume):
152     """
153     Returns a VolumeSettings object
154     :param volume: a SNAPS-OO Volume object
155     """
156
157     return VolumeConfig(
158         name=volume.name, description=volume.description,
159         size=volume.size, type_name=volume.type,
160         availability_zone=volume.availability_zone,
161         multi_attach=volume.multi_attach)
162
163
164 def create_volume_type_config(volume_type):
165     """
166     Returns a VolumeTypeSettings object
167     :param volume_type: a SNAPS-OO VolumeType object
168     """
169
170     control = None
171     if volume_type.encryption:
172         if (volume_type.encryption.control_location
173                 == ControlLocation.front_end.value):
174             control = ControlLocation.front_end
175         else:
176             control = ControlLocation.back_end
177
178     encrypt_settings = VolumeTypeEncryptionConfig(
179         name=volume_type.encryption.__class__,
180         provider_class=volume_type.encryption.provider,
181         control_location=control,
182         cipher=volume_type.encryption.cipher,
183         key_size=volume_type.encryption.key_size)
184
185     qos_spec_name = None
186     if volume_type.qos_spec:
187         qos_spec_name = volume_type.qos_spec.name
188
189     return VolumeTypeConfig(
190         name=volume_type.name, encryption=encrypt_settings,
191         qos_spec_name=qos_spec_name, public=volume_type.public)
192
193
194 def create_flavor_config(flavor):
195     """
196     Returns a VolumeSettings object
197     :param flavor: a SNAPS-OO Volume object
198     """
199     return FlavorConfig(
200         name=flavor.name, flavor_id=flavor.id, ram=flavor.ram,
201         disk=flavor.disk, vcpus=flavor.vcpus, ephemeral=flavor.ephemeral,
202         swap=flavor.swap, rxtx_factor=flavor.rxtx_factor,
203         is_public=flavor.is_public)
204
205
206 def create_keypair_settings(heat_cli, stack, keypair, pk_output_key):
207     """
208     Instantiates a KeypairConfig object from a Keypair domain objects
209     :param heat_cli: the heat client
210     :param stack: the Stack domain object
211     :param keypair: the Keypair SNAPS domain object
212     :param pk_output_key: the key to the heat template's outputs for retrieval
213                           of the private key file
214     :return: a KeypairConfig object
215     """
216     if pk_output_key:
217         outputs = heat_utils.get_outputs(heat_cli, stack)
218         for output in outputs:
219             if output.key == pk_output_key:
220                 # Save to file
221                 guid = uuid.uuid4()
222                 key_file = file_utils.save_string_to_file(
223                     output.value, str(guid), 0o400)
224
225                 # Use outputs, file and resources for the KeypairConfig
226                 return KeypairConfig(
227                     name=keypair.name, private_filepath=key_file.name)
228
229     return KeypairConfig(name=keypair.name)
230
231
232 def create_vm_inst_settings(nova, neutron, server):
233     """
234     Returns a NetworkSettings object
235     :param nova: the nova client
236     :param neutron: the neutron client
237     :param server: a SNAPS-OO VmInst domain object
238     :return:
239     """
240
241     flavor_name = nova_utils.get_flavor_by_id(nova, server.flavor_id)
242
243     kwargs = dict()
244     kwargs['name'] = server.name
245     kwargs['flavor'] = flavor_name
246
247     net_tuples = list()
248     for net_name, ips in server.networks.items():
249         network = neutron_utils.get_network(neutron, network_name=net_name)
250         if network:
251             net_tuples.append((network, ips))
252
253     kwargs['port_settings'] = __create_port_settings(
254         neutron, net_tuples)
255     kwargs['security_group_names'] = server.sec_grp_names
256     kwargs['floating_ip_settings'] = __create_floatingip_settings(
257         neutron, kwargs['port_settings'])
258
259     return VmInstanceSettings(**kwargs)
260
261
262 def __create_port_settings(neutron, networks):
263     """
264     Returns a list of port settings based on the networks parameter
265     :param neutron: the neutron client
266     :param networks: a list of tuples where #1 is the SNAPS Network domain
267                      object and #2 is a list of IP addresses
268     :return:
269     """
270     out = list()
271
272     for network, ips in networks:
273         ports = neutron_utils.get_ports(neutron, network, ips)
274         for port in ports:
275             if port.device_owner != 'network:dhcp':
276                 ip_addrs = list()
277                 for ip_dict in port.ips:
278                     subnet = neutron_utils.get_subnet_by_id(
279                         neutron, ip_dict['subnet_id'])
280                     ip_addrs.append({'subnet_name': subnet.name,
281                                      'ip': ip_dict['ip_address']})
282
283                 kwargs = dict()
284                 if port.name:
285                     kwargs['name'] = port.name
286                 kwargs['network_name'] = network.name
287                 kwargs['mac_address'] = port.mac_address
288                 kwargs['allowed_address_pairs'] = port.allowed_address_pairs
289                 kwargs['admin_state_up'] = port.admin_state_up
290                 kwargs['ip_addrs'] = ip_addrs
291                 out.append(PortSettings(**kwargs))
292
293     return out
294
295
296 def __create_floatingip_settings(neutron, port_settings):
297     """
298     Returns a list of FloatingIPSettings objects as they pertain to an
299     existing deployed server instance
300     :param neutron: the neutron client
301     :param port_settings: list of SNAPS-OO PortSettings objects
302     :return: a list of FloatingIPSettings objects or an empty list if no
303              floating IPs have been created
304     """
305     base_fip_name = 'fip-'
306     fip_ctr = 1
307     out = list()
308
309     fip_ports = list()
310     for port_setting in port_settings:
311         setting_port = neutron_utils.get_port(neutron, port_setting)
312         if setting_port:
313             network = neutron_utils.get_network(
314                 neutron, network_name=port_setting.network_name)
315             network_ports = neutron_utils.get_ports(neutron, network)
316             if network_ports:
317                 for setting_port in network_ports:
318                     if port_setting.mac_address == setting_port.mac_address:
319                         fip_ports.append((port_setting.name, setting_port))
320                         break
321
322     floating_ips = neutron_utils.get_floating_ips(neutron, fip_ports)
323
324     for port_id, floating_ip in floating_ips:
325         router = neutron_utils.get_router_by_id(neutron, floating_ip.router_id)
326         setting_port = neutron_utils.get_port_by_id(
327             neutron, floating_ip.port_id)
328         kwargs = dict()
329         kwargs['name'] = base_fip_name + str(fip_ctr)
330         kwargs['port_name'] = setting_port.name
331         kwargs['port_id'] = setting_port.id
332         kwargs['router_name'] = router.name
333
334         if setting_port:
335             for ip_dict in setting_port.ips:
336                 if ('ip_address' in ip_dict and
337                         'subnet_id' in ip_dict and
338                         ip_dict['ip_address'] == floating_ip.fixed_ip_address):
339                     subnet = neutron_utils.get_subnet_by_id(
340                         neutron, ip_dict['subnet_id'])
341                     if subnet:
342                         kwargs['subnet_name'] = subnet.name
343
344         out.append(FloatingIpSettings(**kwargs))
345
346         fip_ctr += 1
347
348     return out
349
350
351 def determine_image_config(glance, server, image_settings):
352     """
353     Returns a ImageConfig object from the list that matches the name in one
354     of the image_settings parameter
355     :param glance: the glance client
356     :param server: a SNAPS-OO VmInst domain object
357     :param image_settings: list of ImageConfig objects
358     :return: ImageConfig or None
359     """
360     if image_settings:
361         for image_setting in image_settings:
362             image = glance_utils.get_image_by_id(glance, server.image_id)
363             if image and image.name == image_setting.name:
364                 return image_setting
365
366
367 def determine_keypair_config(heat_cli, stack, server, keypair_settings=None,
368                              priv_key_key=None):
369     """
370     Returns a KeypairConfig object from the list that matches the
371     server.keypair_name value in the keypair_settings parameter if not None,
372     else if the output_key is not None, the output's value when contains the
373     string 'BEGIN RSA PRIVATE KEY', this value will be stored into a file and
374     encoded into the KeypairConfig object returned
375     :param heat_cli: the OpenStack heat client
376     :param stack: a SNAPS-OO Stack domain object
377     :param server: a SNAPS-OO VmInst domain object
378     :param keypair_settings: list of KeypairConfig objects
379     :param priv_key_key: the stack options that holds the private key value
380     :return: KeypairConfig or None
381     """
382     # Existing keypair being used by Heat Template
383     if keypair_settings:
384         for keypair_setting in keypair_settings:
385             if server.keypair_name == keypair_setting.name:
386                 return keypair_setting
387
388     # Keypair created by Heat template
389     if priv_key_key:
390         outputs = heat_utils.get_outputs(heat_cli, stack)
391         for output in outputs:
392             if output.key == priv_key_key:
393                 # Save to file
394                 guid = uuid.uuid4()
395                 key_file = file_utils.save_string_to_file(
396                     output.value, str(guid), 0o400)
397
398                 # Use outputs, file and resources for the KeypairConfig
399                 return KeypairConfig(
400                     name=server.keypair_name, private_filepath=key_file.name)