Migrates network environment parsing to python
[apex.git] / lib / python / apex / network_settings.py
1 ##############################################################################
2 # Copyright (c) 2016 Feng Pan (fpan@redhat.com) 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 import yaml
11 import logging
12 import ipaddress
13 from . import ip_utils
14 from .common import constants, utils
15
16
17 class NetworkSettings:
18     """
19     This class parses APEX network settings yaml file into an object. It
20     generates or detects all missing fields for deployment.
21
22     The resulting object will be used later to generate network environment file
23     as well as configuring post deployment networks.
24
25     Currently the parsed object is dumped into a bash global definition file
26     for deploy.sh consumption. This object will later be used directly as
27     deployment script move to python.
28     """
29     def __init__(self, filename, network_isolation):
30         with open(filename, 'r') as network_settings_file:
31             self.settings_obj = yaml.load(network_settings_file)
32             self.network_isolation = network_isolation
33             self.enabled_network_list = []
34             self._validate_input()
35
36     def _validate_input(self):
37         """
38         Validates the network settings file and populates all fields.
39
40         NetworkSettingsException will be raised if validation fails.
41         """
42         if constants.ADMIN_NETWORK not in self.settings_obj or \
43             not utils.str2bool(self.settings_obj[constants.ADMIN_NETWORK].get(
44                     'enabled')):
45             raise NetworkSettingsException("You must enable admin_network "
46                                            "and configure it explicitly or "
47                                            "use auto-detection")
48         if self.network_isolation and \
49             (constants.PUBLIC_NETWORK not in self.settings_obj or not
50                 utils.str2bool(self.settings_obj[constants.PUBLIC_NETWORK].get(
51                     'enabled'))):
52             raise NetworkSettingsException("You must enable public_network "
53                                            "and configure it explicitly or "
54                                            "use auto-detection")
55
56         for network in constants.OPNFV_NETWORK_TYPES:
57             if network in self.settings_obj:
58                 if utils.str2bool(self.settings_obj[network].get('enabled')):
59                     logging.info("{} enabled".format(network))
60                     self._config_required_settings(network)
61                     self._config_ip_range(network=network,
62                                           setting='usable_ip_range',
63                                           start_offset=21, end_offset=21)
64                     self._config_optional_settings(network)
65                     self.enabled_network_list.append(network)
66                 else:
67                     logging.info("{} disabled, will collapse with "
68                                  "admin_network".format(network))
69             else:
70                 logging.info("{} is not in specified, will collapse with "
71                              "admin_network".format(network))
72
73     def _config_required_settings(self, network):
74         """
75         Configures either CIDR or bridged_interface setting
76
77         cidr takes precedence if both cidr and bridged_interface are specified
78         for a given network.
79
80         When using bridged_interface, we will detect network setting on the
81         given NIC in the system. The resulting config in settings object will
82         be an ipaddress.network object, replacing the NIC name.
83         """
84         cidr = self.settings_obj[network].get('cidr')
85         nic_name = self.settings_obj[network].get('bridged_interface')
86
87         if cidr:
88             cidr = ipaddress.ip_network(self.settings_obj[network]['cidr'])
89             self.settings_obj[network]['cidr'] = cidr
90             logging.info("{}_cidr: {}".format(network, cidr))
91             return 0
92         elif nic_name:
93             # If cidr is not specified, we need to know if we should find
94             # IPv6 or IPv4 address on the interface
95             if utils.str2bool(self.settings_obj[network].get('ipv6')):
96                 address_family = 6
97             else:
98                 address_family = 4
99             nic_interface = ip_utils.get_interface(nic_name, address_family)
100             if nic_interface:
101                 self.settings_obj[network]['bridged_interface'] = nic_interface
102                 logging.info("{}_bridged_interface: {}".
103                              format(network, nic_interface))
104                 return 0
105             else:
106                 raise NetworkSettingsException("Auto detection failed for {}: "
107                                                "Unable to find valid ip for "
108                                                "interface {}"
109                                                .format(network, nic_name))
110
111         else:
112             raise NetworkSettingsException("Auto detection failed for {}: "
113                                            "either bridge_interface or cidr "
114                                            "must be specified"
115                                            .format(network))
116
117     def _config_ip_range(self, network, setting, start_offset=None,
118                          end_offset=None, count=None):
119         """
120         Configures IP range for a given setting.
121
122         If the setting is already specified, no change will be made.
123
124         The spec for start_offset, end_offset and count are identical to
125         ip_utils.get_ip_range.
126         """
127         ip_range = self.settings_obj[network].get(setting)
128         interface = self.settings_obj[network].get('bridged_interface')
129
130         if not ip_range:
131             cidr = self.settings_obj[network].get('cidr')
132             ip_range = ip_utils.get_ip_range(start_offset=start_offset,
133                                              end_offset=end_offset,
134                                              count=count,
135                                              cidr=cidr,
136                                              interface=interface)
137             self.settings_obj[network][setting] = ip_range
138
139         logging.info("{}_{}: {}".format(network, setting, ip_range))
140
141     def _config_ip(self, network, setting, offset):
142         """
143         Configures IP for a given setting.
144
145         If the setting is already specified, no change will be made.
146
147         The spec for offset is identical to ip_utils.get_ip
148         """
149         ip = self.settings_obj[network].get(setting)
150         interface = self.settings_obj[network].get('bridged_interface')
151
152         if not ip:
153             cidr = self.settings_obj[network].get('cidr')
154             ip = ip_utils.get_ip(offset, cidr, interface)
155             self.settings_obj[network][setting] = ip
156
157         logging.info("{}_{}: {}".format(network, setting, ip))
158
159     def _config_optional_settings(self, network):
160         """
161         Configures optional settings:
162         - admin_network:
163             - provisioner_ip
164             - dhcp_range
165             - introspection_range
166         - public_network:
167             - provisioner_ip
168             - floating_ip
169             - gateway
170         """
171         if network == constants.ADMIN_NETWORK:
172             self._config_ip(network, 'provisioner_ip', 1)
173             self._config_ip_range(network=network, setting='dhcp_range',
174                                   start_offset=2, count=9)
175             self._config_ip_range(network=network,
176                                   setting='introspection_range',
177                                   start_offset=11, count=9)
178         elif network == constants.PUBLIC_NETWORK:
179             self._config_ip(network, 'provisioner_ip', 1)
180             self._config_ip_range(network=network,
181                                   setting='floating_ip',
182                                   end_offset=2, count=20)
183             self._config_gateway(network)
184
185     def _config_gateway(self, network):
186         """
187         Configures gateway setting for a given network.
188
189         If cidr is specified, we always use the first address in the address
190         space for gateway. Otherwise, we detect the system gateway.
191         """
192         gateway = self.settings_obj[network].get('gateway')
193         interface = self.settings_obj[network].get('bridged_interface')
194
195         if not gateway:
196             cidr = self.settings_obj[network].get('cidr')
197             if cidr:
198                 gateway = ip_utils.get_ip(1, cidr)
199             else:
200                 gateway = ip_utils.find_gateway(interface)
201
202             if gateway:
203                 self.settings_obj[network]['gateway'] = gateway
204             else:
205                 raise NetworkSettingsException("Failed to set gateway")
206
207         logging.info("{}_gateway: {}".format(network, gateway))
208
209     def dump_bash(self, path=None):
210         """
211         Prints settings for bash consumption.
212
213         If optional path is provided, bash string will be written to the file
214         instead of stdout.
215         """
216         bash_str = ''
217         for network in self.enabled_network_list:
218             for key, value in self.settings_obj[network].items():
219                 bash_str += "{}_{}={}\n".format(network, key, value)
220         bash_str += "enabled_network_list='{}'\n" \
221             .format(' '.join(self.enabled_network_list))
222         bash_str += "ip_addr_family={}\n".format(self.get_ip_addr_family())
223         if path:
224             with open(path, 'w') as file:
225                 file.write(bash_str)
226         else:
227             print(bash_str)
228
229     def get_ip_addr_family(self):
230         """
231         Returns IP address family for current deployment.
232
233         If any enabled network has IPv6 CIDR, the deployment is classified as
234         IPv6.
235         """
236         for network in self.enabled_network_list:
237             cidr = ipaddress.ip_network(self.settings_obj[network]['cidr'])
238             if cidr.version == 6:
239                 return 6
240
241         return 4
242
243     def get_network_settings(self):
244         """
245         Getter for network settings
246         :return: network settings dictionary
247         """
248         return self.settings_obj
249
250     def get_enabled_networks(self):
251         """
252         Getter for enabled network list
253         :return: list of enabled networks
254         """
255         return self.enabled_network_list
256
257
258 class NetworkSettingsException(Exception):
259     def __init__(self, value):
260         self.value = value
261
262     def __str__(self):
263             return self.value
264
265
266