1 ##############################################################################
2 # Copyright (c) 2016 Feng Pan (fpan@redhat.com) and others.
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 ##############################################################################
13 from . import ip_utils
14 from .common.utils import str2bool
15 from .common.constants import (
29 class NetworkSettings(dict):
31 This class parses APEX network settings yaml file into an object. It
32 generates or detects all missing fields for deployment.
34 The resulting object will be used later to generate network environment
35 file as well as configuring post deployment networks.
37 Currently the parsed object is dumped into a bash global definition file
38 for deploy.sh consumption. This object will later be used directly as
39 deployment script move to python.
41 def __init__(self, filename, network_isolation):
43 if type(filename) is str:
44 with open(filename, 'r') as network_settings_file:
45 init_dict = yaml.safe_load(network_settings_file)
47 # assume input is a dict to build from
50 super().__init__(init_dict)
53 # merge two dics Nondestructively
55 for key, val in sec.items():
60 # do not overwrite what's already there
63 # merge the apex specific config into the first class settings
64 merge(self, copy(self['apex']))
66 self.network_isolation = network_isolation
67 self.enabled_network_list = []
68 self.nics = {COMPUTE: {}, CONTROLLER: {}}
69 self.nics_specified = {COMPUTE: False, CONTROLLER: False}
70 self._validate_input()
72 def _validate_input(self):
74 Validates the network settings file and populates all fields.
76 NetworkSettingsException will be raised if validation fails.
78 if ADMIN_NETWORK not in self or \
79 not str2bool(self[ADMIN_NETWORK].get(
81 raise NetworkSettingsException("You must enable admin_network "
82 "and configure it explicitly or "
84 if self.network_isolation and \
85 (PUBLIC_NETWORK not in self or not
86 str2bool(self[PUBLIC_NETWORK].get(
88 raise NetworkSettingsException("You must enable public_network "
89 "and configure it explicitly or "
92 for network in OPNFV_NETWORK_TYPES:
94 if str2bool(self[network].get('enabled')):
95 logging.info("{} enabled".format(network))
96 self._config_required_settings(network)
97 self._config_ip_range(network=network,
98 setting='usable_ip_range',
99 start_offset=21, end_offset=21)
100 self._config_optional_settings(network)
101 self.enabled_network_list.append(network)
102 self._validate_overcloud_nic_order(network)
104 logging.info("{} disabled, will collapse with "
105 "admin_network".format(network))
107 logging.info("{} is not in specified, will collapse with "
108 "admin_network".format(network))
110 self['dns_servers'] = self.get('dns_servers', DNS_SERVERS)
111 self['domain_name'] = self.get('domain_name', DOMAIN_NAME)
113 def _validate_overcloud_nic_order(self, network):
115 Detects if nic order is specified per profile (compute/controller)
118 If nic order is specified in a network for a profile, it should be
119 specified for every network with that profile other than admin_network
121 Duplicate nic names are also not allowed across different networks
123 :param network: network to detect if nic order present
128 interface = role+'_interface'
129 nic_index = self.get_enabled_networks().index(network) + 1
130 if interface in self[network]:
131 if any(y == self[network][interface] for x, y in
132 self.nics[role].items()):
133 raise NetworkSettingsException("Duplicate {} already "
136 .format(self[network]
138 self.nics[role][network] = self[network][interface]
139 self.nics_specified[role] = True
140 logging.info("{} nic order specified for network {"
141 "}".format(role, network))
142 elif self.nics_specified[role]:
143 logging.error("{} nic order not specified for network {"
144 "}".format(role, network))
145 raise NetworkSettingsException("Must specify {} for all "
146 "enabled networks (other than "
147 " admin) or not specify it for "
148 "any".format(interface))
150 logging.info("{} nic order not specified for network {"
151 "}. Will use logical default "
152 "nic{}".format(interface, network, nic_index))
153 self.nics[role][network] = 'nic' + str(nic_index)
156 def _config_required_settings(self, network):
158 Configures either CIDR or bridged_interface setting
160 cidr takes precedence if both cidr and bridged_interface are specified
163 When using bridged_interface, we will detect network setting on the
164 given NIC in the system. The resulting config in settings object will
165 be an ipaddress.network object, replacing the NIC name.
167 # if vlan not defined then default it to native
168 if network is not ADMIN_NETWORK:
169 if 'vlan' not in self[network]:
170 self[network]['vlan'] = 'native'
172 cidr = self[network].get('cidr')
173 nic_name = self[network].get('bridged_interface')
176 cidr = ipaddress.ip_network(self[network]['cidr'])
177 self[network]['cidr'] = cidr
178 logging.info("{}_cidr: {}".format(network, cidr))
181 # If cidr is not specified, we need to know if we should find
182 # IPv6 or IPv4 address on the interface
183 if str2bool(self[network].get('ipv6')):
187 nic_interface = ip_utils.get_interface(nic_name, address_family)
189 self[network]['bridged_interface'] = nic_interface
190 logging.info("{}_bridged_interface: {}".
191 format(network, nic_interface))
194 raise NetworkSettingsException("Auto detection failed for {}: "
195 "Unable to find valid ip for "
197 .format(network, nic_name))
200 raise NetworkSettingsException("Auto detection failed for {}: "
201 "either bridge_interface or cidr "
205 def _config_ip_range(self, network, setting, start_offset=None,
206 end_offset=None, count=None):
208 Configures IP range for a given setting.
210 If the setting is already specified, no change will be made.
212 The spec for start_offset, end_offset and count are identical to
213 ip_utils.get_ip_range.
215 ip_range = self[network].get(setting)
216 interface = self[network].get('bridged_interface')
219 cidr = self[network].get('cidr')
220 ip_range = ip_utils.get_ip_range(start_offset=start_offset,
221 end_offset=end_offset,
225 self[network][setting] = ip_range
227 logging.info("{}_{}: {}".format(network, setting, ip_range))
229 def _config_ip(self, network, setting, offset):
231 Configures IP for a given setting.
233 If the setting is already specified, no change will be made.
235 The spec for offset is identical to ip_utils.get_ip
237 ip = self[network].get(setting)
238 interface = self[network].get('bridged_interface')
241 cidr = self[network].get('cidr')
242 ip = ip_utils.get_ip(offset, cidr, interface)
243 self[network][setting] = ip
245 logging.info("{}_{}: {}".format(network, setting, ip))
247 def _config_optional_settings(self, network):
249 Configures optional settings:
253 - introspection_range
259 if network == ADMIN_NETWORK:
260 self._config_ip(network, 'provisioner_ip', 1)
261 self._config_ip_range(network=network, setting='dhcp_range',
262 start_offset=2, count=9)
263 self._config_ip_range(network=network,
264 setting='introspection_range',
265 start_offset=11, count=9)
266 elif network == PUBLIC_NETWORK:
267 self._config_ip(network, 'provisioner_ip', 1)
268 self._config_ip_range(network=network,
269 setting='floating_ip_range',
270 end_offset=2, count=20)
271 self._config_gateway(network)
273 def _config_gateway(self, network):
275 Configures gateway setting for a given network.
277 If cidr is specified, we always use the first address in the address
278 space for gateway. Otherwise, we detect the system gateway.
280 gateway = self[network].get('gateway')
281 interface = self[network].get('bridged_interface')
284 cidr = self[network].get('cidr')
286 gateway = ip_utils.get_ip(1, cidr)
288 gateway = ip_utils.find_gateway(interface)
291 self[network]['gateway'] = gateway
293 raise NetworkSettingsException("Failed to set gateway")
295 logging.info("{}_gateway: {}".format(network, gateway))
297 def dump_bash(self, path=None):
299 Prints settings for bash consumption.
301 If optional path is provided, bash string will be written to the file
305 for network in self.enabled_network_list:
306 for key, value in self[network].items():
307 bash_str += "{}_{}={}\n".format(network, key, value)
308 bash_str += "enabled_network_list='{}'\n" \
309 .format(' '.join(self.enabled_network_list))
310 bash_str += "ip_addr_family={}\n".format(self.get_ip_addr_family())
312 for dns_server in self['dns_servers']:
313 dns_list = dns_list + "{} ".format(dns_server)
314 dns_list = dns_list.strip()
315 bash_str += "dns_servers=\'{}\'\n".format(dns_list)
316 bash_str += "domain_name=\'{}\'\n".format(self['domain_name'])
318 with open(path, 'w') as file:
323 def get_ip_addr_family(self):
325 Returns IP address family for current deployment.
327 If any enabled network has IPv6 CIDR, the deployment is classified as
330 for network in self.enabled_network_list:
331 cidr = ipaddress.ip_network(self[network]['cidr'])
332 if cidr.version == 6:
337 def get_enabled_networks(self):
339 Getter for enabled network list
340 :return: list of enabled networks
342 return self.enabled_network_list
345 class NetworkSettingsException(Exception):
346 def __init__(self, value):