Adding VLAN support
[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         self.settings_obj['dns_servers'] = self.settings_obj.get(
74             'dns_servers', constants.DNS_SERVERS)
75
76     def _config_required_settings(self, network):
77         """
78         Configures either CIDR or bridged_interface setting
79
80         cidr takes precedence if both cidr and bridged_interface are specified
81         for a given network.
82
83         When using bridged_interface, we will detect network setting on the
84         given NIC in the system. The resulting config in settings object will
85         be an ipaddress.network object, replacing the NIC name.
86         """
87         # if vlan not defined then default it to native
88         if network is not constants.ADMIN_NETWORK:
89             if 'vlan' not in self.settings_obj[network]:
90                 self.settings_obj[network]['vlan'] = 'native'
91
92         cidr = self.settings_obj[network].get('cidr')
93         nic_name = self.settings_obj[network].get('bridged_interface')
94
95         if cidr:
96             cidr = ipaddress.ip_network(self.settings_obj[network]['cidr'])
97             self.settings_obj[network]['cidr'] = cidr
98             logging.info("{}_cidr: {}".format(network, cidr))
99             return 0
100         elif nic_name:
101             # If cidr is not specified, we need to know if we should find
102             # IPv6 or IPv4 address on the interface
103             if utils.str2bool(self.settings_obj[network].get('ipv6')):
104                 address_family = 6
105             else:
106                 address_family = 4
107             nic_interface = ip_utils.get_interface(nic_name, address_family)
108             if nic_interface:
109                 self.settings_obj[network]['bridged_interface'] = nic_interface
110                 logging.info("{}_bridged_interface: {}".
111                              format(network, nic_interface))
112                 return 0
113             else:
114                 raise NetworkSettingsException("Auto detection failed for {}: "
115                                                "Unable to find valid ip for "
116                                                "interface {}"
117                                                .format(network, nic_name))
118
119         else:
120             raise NetworkSettingsException("Auto detection failed for {}: "
121                                            "either bridge_interface or cidr "
122                                            "must be specified"
123                                            .format(network))
124
125     def _config_ip_range(self, network, setting, start_offset=None,
126                          end_offset=None, count=None):
127         """
128         Configures IP range for a given setting.
129
130         If the setting is already specified, no change will be made.
131
132         The spec for start_offset, end_offset and count are identical to
133         ip_utils.get_ip_range.
134         """
135         ip_range = self.settings_obj[network].get(setting)
136         interface = self.settings_obj[network].get('bridged_interface')
137
138         if not ip_range:
139             cidr = self.settings_obj[network].get('cidr')
140             ip_range = ip_utils.get_ip_range(start_offset=start_offset,
141                                              end_offset=end_offset,
142                                              count=count,
143                                              cidr=cidr,
144                                              interface=interface)
145             self.settings_obj[network][setting] = ip_range
146
147         logging.info("{}_{}: {}".format(network, setting, ip_range))
148
149     def _config_ip(self, network, setting, offset):
150         """
151         Configures IP for a given setting.
152
153         If the setting is already specified, no change will be made.
154
155         The spec for offset is identical to ip_utils.get_ip
156         """
157         ip = self.settings_obj[network].get(setting)
158         interface = self.settings_obj[network].get('bridged_interface')
159
160         if not ip:
161             cidr = self.settings_obj[network].get('cidr')
162             ip = ip_utils.get_ip(offset, cidr, interface)
163             self.settings_obj[network][setting] = ip
164
165         logging.info("{}_{}: {}".format(network, setting, ip))
166
167     def _config_optional_settings(self, network):
168         """
169         Configures optional settings:
170         - admin_network:
171             - provisioner_ip
172             - dhcp_range
173             - introspection_range
174         - public_network:
175             - provisioner_ip
176             - floating_ip
177             - gateway
178         """
179         if network == constants.ADMIN_NETWORK:
180             self._config_ip(network, 'provisioner_ip', 1)
181             self._config_ip_range(network=network, setting='dhcp_range',
182                                   start_offset=2, count=9)
183             self._config_ip_range(network=network,
184                                   setting='introspection_range',
185                                   start_offset=11, count=9)
186         elif network == constants.PUBLIC_NETWORK:
187             self._config_ip(network, 'provisioner_ip', 1)
188             self._config_ip_range(network=network,
189                                   setting='floating_ip',
190                                   end_offset=2, count=20)
191             self._config_gateway(network)
192
193     def _config_gateway(self, network):
194         """
195         Configures gateway setting for a given network.
196
197         If cidr is specified, we always use the first address in the address
198         space for gateway. Otherwise, we detect the system gateway.
199         """
200         gateway = self.settings_obj[network].get('gateway')
201         interface = self.settings_obj[network].get('bridged_interface')
202
203         if not gateway:
204             cidr = self.settings_obj[network].get('cidr')
205             if cidr:
206                 gateway = ip_utils.get_ip(1, cidr)
207             else:
208                 gateway = ip_utils.find_gateway(interface)
209
210             if gateway:
211                 self.settings_obj[network]['gateway'] = gateway
212             else:
213                 raise NetworkSettingsException("Failed to set gateway")
214
215         logging.info("{}_gateway: {}".format(network, gateway))
216
217     def dump_bash(self, path=None):
218         """
219         Prints settings for bash consumption.
220
221         If optional path is provided, bash string will be written to the file
222         instead of stdout.
223         """
224         bash_str = ''
225         for network in self.enabled_network_list:
226             for key, value in self.settings_obj[network].items():
227                 bash_str += "{}_{}={}\n".format(network, key, value)
228         bash_str += "enabled_network_list='{}'\n" \
229             .format(' '.join(self.enabled_network_list))
230         bash_str += "ip_addr_family={}\n".format(self.get_ip_addr_family())
231         dns_list = ""
232         for dns_server in self.settings_obj['dns_servers']:
233             dns_list = dns_list + "{} ".format(dns_server)
234         dns_list = dns_list.strip()
235         bash_str += "dns_servers=\'{}\'\n".format(dns_list)
236         if path:
237             with open(path, 'w') as file:
238                 file.write(bash_str)
239         else:
240             print(bash_str)
241
242     def get_ip_addr_family(self):
243         """
244         Returns IP address family for current deployment.
245
246         If any enabled network has IPv6 CIDR, the deployment is classified as
247         IPv6.
248         """
249         for network in self.enabled_network_list:
250             cidr = ipaddress.ip_network(self.settings_obj[network]['cidr'])
251             if cidr.version == 6:
252                 return 6
253
254         return 4
255
256     def get_network_settings(self):
257         """
258         Getter for network settings
259         :return: network settings dictionary
260         """
261         return self.settings_obj
262
263     def get_enabled_networks(self):
264         """
265         Getter for enabled network list
266         :return: list of enabled networks
267         """
268         return self.enabled_network_list
269
270
271 class NetworkSettingsException(Exception):
272     def __init__(self, value):
273         self.value = value
274
275     def __str__(self):
276             return self.value
277
278
279