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 ##############################################################################
15 from .common import utils
16 from . import ip_utils
17 from .common.constants import (
30 class NetworkSettings(dict):
32 This class parses APEX network settings yaml file into an object. It
33 generates or detects all missing fields for deployment.
35 The resulting object will be used later to generate network environment
36 file as well as configuring post deployment networks.
38 Currently the parsed object is dumped into a bash global definition file
39 for deploy.sh consumption. This object will later be used directly as
40 deployment script move to python.
42 def __init__(self, filename):
44 if isinstance(filename, str):
45 with open(filename, 'r') as network_settings_file:
46 init_dict = yaml.safe_load(network_settings_file)
48 # 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():
57 if isinstance(val, dict):
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.enabled_network_list = []
67 self.nics = {COMPUTE: {}, CONTROLLER: {}}
68 self.nics_specified = {COMPUTE: False, CONTROLLER: False}
69 self._validate_input()
71 def get_network(self, network):
72 if network == EXTERNAL_NETWORK and self['networks'][network]:
73 for net in self['networks'][network]:
77 raise NetworkSettingsException("The external network, "
78 "'public', should be defined "
79 "when external networks are "
82 return self['networks'][network]
84 def _validate_input(self):
86 Validates the network settings file and populates all fields.
88 NetworkSettingsException will be raised if validation fails.
90 if not self['networks'].get(ADMIN_NETWORK, {}).get('enabled', False):
91 raise NetworkSettingsException("You must enable admin network "
92 "and configure it explicitly or "
95 for network in OPNFV_NETWORK_TYPES:
96 if network in self['networks']:
97 _network = self.get_network(network)
98 if _network.get('enabled', True):
99 logging.info("{} enabled".format(network))
100 self._config_required_settings(network)
101 nicmap = _network['nic_mapping']
102 iface = nicmap[CONTROLLER]['members'][0]
103 self._config_ip_range(network=network,
105 ip_range='overcloud_ip_range',
106 start_offset=21, end_offset=21)
107 self.enabled_network_list.append(network)
108 self._validate_overcloud_nic_order(network)
109 # TODO self._config_optional_settings(network)
111 logging.info("{} disabled, will collapse with "
112 "admin network".format(network))
114 logging.info("{} is not in specified, will collapse with "
115 "admin network".format(network))
117 if 'dns-domain' not in self:
118 self['domain_name'] = DOMAIN_NAME
119 self['dns_servers'] = self.get('dns_nameservers', DNS_SERVERS)
120 self['ntp_servers'] = self.get('ntp', NTP_SERVER)
122 def _validate_overcloud_nic_order(self, network):
124 Detects if nic order is specified per profile (compute/controller)
127 If nic order is specified in a network for a profile, it should be
128 specified for every network with that profile other than admin network
130 Duplicate nic names are also not allowed across different networks
132 :param network: network to detect if nic order present
136 _network = self.get_network(network)
137 _nicmap = _network.get('nic_mapping', {})
138 _role = _nicmap.get(role, {})
139 interfaces = _role.get('members', [])
142 interface = interfaces[0]
143 if not isinstance(_role.get('vlan', 'native'), int) and \
144 any(y == interface for x, y in self.nics[role].items()):
145 raise NetworkSettingsException(
146 "Duplicate {} already specified for "
147 "another network".format(interface))
148 self.nics[role][network] = interface
149 self.nics_specified[role] = True
150 logging.info("{} nic order specified for network {"
151 "}".format(role, network))
153 raise NetworkSettingsException(
154 "Interface members are not supplied for {} network "
155 "for the {} role. Please add nic assignments"
156 "".format(network, role))
158 def _config_required_settings(self, network):
160 Configures either CIDR or bridged_interface setting
162 cidr takes precedence if both cidr and bridged_interface are specified
165 When using bridged_interface, we will detect network setting on the
166 given NIC in the system. The resulting config in settings object will
167 be an ipaddress.network object, replacing the NIC name.
169 _network = self.get_network(network)
170 # if vlan not defined then default it to native
171 if network is not ADMIN_NETWORK:
173 if 'vlan' not in _network['nic_mapping'][role]:
174 _network['nic_mapping'][role]['vlan'] = 'native'
176 cidr = _network.get('cidr')
179 cidr = ipaddress.ip_network(_network['cidr'])
180 _network['cidr'] = cidr
181 logging.info("{}_cidr: {}".format(network, cidr))
182 elif 'installer_vm' in _network:
183 ucloud_if_list = _network['installer_vm']['members']
184 # If cidr is not specified, we need to know if we should find
185 # IPv6 or IPv4 address on the interface
186 ip = ipaddress.ip_address(_network['installer_vm']['ip'])
187 nic_if = ip_utils.get_interface(ucloud_if_list[0], ip.version)
189 logging.info("{}_bridged_interface: {}".
190 format(network, nic_if))
192 raise NetworkSettingsException(
193 "Auto detection failed for {}: Unable to find valid "
194 "ip for interface {}".format(network, ucloud_if_list[0]))
197 raise NetworkSettingsException(
198 "Auto detection failed for {}: either installer_vm "
199 "members or cidr must be specified".format(network))
201 # undercloud settings
202 if network == ADMIN_NETWORK:
203 provisioner_ip = _network['installer_vm']['ip']
204 iface = _network['installer_vm']['members'][0]
205 if not provisioner_ip:
206 _network['installer_vm']['ip'] = self._gen_ip(network, 1)
207 self._config_ip_range(network=network, interface=iface,
208 ip_range='dhcp_range',
209 start_offset=2, count=9)
210 self._config_ip_range(network=network, interface=iface,
211 ip_range='introspection_range',
212 start_offset=11, count=9)
213 elif network == EXTERNAL_NETWORK:
214 provisioner_ip = _network['installer_vm']['ip']
215 iface = _network['installer_vm']['members'][0]
216 if not provisioner_ip:
217 _network['installer_vm']['ip'] = self._gen_ip(network, 1)
218 self._config_ip_range(network=network, interface=iface,
219 ip_range='floating_ip_range',
220 end_offset=2, count=20)
222 gateway = _network['gateway']
223 interface = _network['installer_vm']['ip']
224 self._config_gateway(network, gateway, interface)
226 def _config_ip_range(self, network, ip_range, interface=None,
227 start_offset=None, end_offset=None, count=None):
229 Configures IP range for a given setting.
230 If the setting is already specified, no change will be made.
231 The spec for start_offset, end_offset and count are identical to
232 ip_utils.get_ip_range.
234 _network = self.get_network(network)
235 if ip_range not in _network:
236 cidr = _network.get('cidr')
237 _ip_range = ip_utils.get_ip_range(start_offset=start_offset,
238 end_offset=end_offset,
242 _network[ip_range] = _ip_range.split(',')
244 logging.info("Config IP Range: {} {}".format(network, ip_range))
246 def _gen_ip(self, network, offset):
248 Generate and ip offset within the given network
250 _network = self.get_network(network)
251 cidr = _network.get('cidr')
252 ip = ip_utils.get_ip(offset, cidr)
253 logging.info("Config IP: {} {}".format(network, ip))
256 def _config_optional_settings(self, network):
258 Configures optional settings:
262 - introspection_range
268 if network == ADMIN_NETWORK:
269 self._config_ip(network, None, 'provisioner_ip', 1)
270 self._config_ip_range(network=network,
271 ip_range='dhcp_range',
272 start_offset=2, count=9)
273 self._config_ip_range(network=network,
274 ip_range='introspection_range',
275 start_offset=11, count=9)
276 elif network == EXTERNAL_NETWORK:
277 self._config_ip(network, None, 'provisioner_ip', 1)
278 self._config_ip_range(network=network,
279 ip_range='floating_ip_range',
280 end_offset=2, count=20)
281 self._config_gateway(network)
283 def _config_gateway(self, network, gateway, interface):
285 Configures gateway setting for a given network.
287 If cidr is specified, we always use the first address in the address
288 space for gateway. Otherwise, we detect the system gateway.
290 _network = self.get_network(network)
292 cidr = _network.get('cidr')
294 _gateway = ip_utils.get_ip(1, cidr)
296 _gateway = ip_utils.find_gateway(interface)
299 _network['gateway'] = _gateway
301 raise NetworkSettingsException("Failed to set gateway")
303 logging.info("Config Gateway: {} {}".format(network, gateway))
305 def dump_bash(self, path=None):
307 Prints settings for bash consumption.
309 If optional path is provided, bash string will be written to the file
312 def flatten(name, obj, delim=','):
314 flatten lists to delim separated strings
315 flatten dics to underscored key names and string values
317 if isinstance(obj, list):
318 return "{}=\'{}\'\n".format(name,
319 delim.join(map(lambda x: str(x),
321 elif isinstance(obj, dict):
324 flat_str += flatten("{}_{}".format(name, k), obj[k])
326 elif isinstance(obj, str):
327 return "{}='{}'\n".format(name, obj)
329 return "{}={}\n".format(name, str(obj))
332 for network in self.enabled_network_list:
333 _network = self.get_network(network)
334 bash_str += flatten(network, _network)
335 bash_str += flatten('enabled_network_list',
336 self.enabled_network_list, ' ')
337 bash_str += flatten('ip_addr_family', self.get_ip_addr_family())
338 bash_str += flatten('dns_servers', self['dns_servers'], ' ')
339 bash_str += flatten('domain_name', self['dns-domain'], ' ')
340 bash_str += flatten('ntp_server', self['ntp_servers'][0], ' ')
341 utils.write_str(bash_str, path)
343 def get_ip_addr_family(self,):
345 Returns IP address family for current deployment.
347 If any enabled network has IPv6 CIDR, the deployment is classified as
351 ipaddress.ip_network(self.get_network(n)['cidr']).version
352 for n in self.enabled_network_list])
355 class NetworkSettingsException(Exception):
356 def __init__(self, value):