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 ##############################################################################
16 from . import ip_utils
17 from .common import utils
18 from .common.constants import (
31 class NetworkSettings(dict):
33 This class parses APEX network settings yaml file into an object. It
34 generates or detects all missing fields for deployment.
36 The resulting object will be used later to generate network environment
37 file as well as configuring post deployment networks.
39 Currently the parsed object is dumped into a bash global definition file
40 for deploy.sh consumption. This object will later be used directly as
41 deployment script move to python.
43 def __init__(self, filename):
45 if type(filename) is str:
46 with open(filename, 'r') as network_settings_file:
47 init_dict = yaml.safe_load(network_settings_file)
49 # assume input is a dict to build from
51 super().__init__(init_dict)
54 # merge two dics Nondestructively
56 for key, val in sec.items():
61 # do not overwrite what's already there
64 # merge the apex specific config into the first class settings
65 merge(self, copy(self['apex']))
67 self.enabled_network_list = []
68 self.nics = {COMPUTE: {}, CONTROLLER: {}}
69 self.nics_specified = {COMPUTE: False, CONTROLLER: False}
70 self._validate_input()
72 def get_network(self, network):
73 if network == EXTERNAL_NETWORK and self['networks'][network]:
74 return self['networks'][network][0]
76 return self['networks'][network]
78 def _validate_input(self):
80 Validates the network settings file and populates all fields.
82 NetworkSettingsException will be raised if validation fails.
84 if not self['networks'].get(ADMIN_NETWORK, {}).get('enabled', False):
85 raise NetworkSettingsException("You must enable admin network "
86 "and configure it explicitly or "
89 for network in OPNFV_NETWORK_TYPES:
90 if network in self['networks']:
91 _network = self.get_network(network)
92 if _network.get('enabled', True):
93 logging.info("{} enabled".format(network))
94 self._config_required_settings(network)
95 if network == EXTERNAL_NETWORK:
96 nicmap = _network['nic_mapping']
98 nicmap = _network['nic_mapping']
99 iface = nicmap[CONTROLLER]['members'][0]
100 self._config_ip_range(network=network,
102 ip_range='usable_ip_range',
103 start_offset=21, end_offset=21)
104 self.enabled_network_list.append(network)
105 self._validate_overcloud_nic_order(network)
106 # TODO self._config_optional_settings(network)
108 logging.info("{} disabled, will collapse with "
109 "admin network".format(network))
111 logging.info("{} is not in specified, will collapse with "
112 "admin network".format(network))
114 if 'dns-domain' not in self:
115 self['domain_name'] = DOMAIN_NAME
116 self['dns_servers'] = self.get('dns_nameservers', DNS_SERVERS)
117 self['ntp_servers'] = self.get('ntp', NTP_SERVER)
119 def _validate_overcloud_nic_order(self, network):
121 Detects if nic order is specified per profile (compute/controller)
124 If nic order is specified in a network for a profile, it should be
125 specified for every network with that profile other than admin network
127 Duplicate nic names are also not allowed across different networks
129 :param network: network to detect if nic order present
133 _network = self.get_network(network)
134 _nicmap = _network.get('nic_mapping', {})
135 _role = _nicmap.get(role, {})
136 interfaces = _role.get('members', [])
139 interface = interfaces[0]
140 if type(_role.get('vlan', 'native')) is not int and \
141 any(y == interface for x, y in self.nics[role].items()):
142 raise NetworkSettingsException(
143 "Duplicate {} already specified for "
144 "another network".format(interface))
145 self.nics[role][network] = interface
146 self.nics_specified[role] = True
147 logging.info("{} nic order specified for network {"
148 "}".format(role, network))
150 raise NetworkSettingsException(
151 "Interface members are not supplied for {} network "
152 "for the {} role. Please add nic assignments"
153 "".format(network, role))
155 def _config_required_settings(self, network):
157 Configures either CIDR or bridged_interface setting
159 cidr takes precedence if both cidr and bridged_interface are specified
162 When using bridged_interface, we will detect network setting on the
163 given NIC in the system. The resulting config in settings object will
164 be an ipaddress.network object, replacing the NIC name.
166 _network = self.get_network(network)
167 # if vlan not defined then default it to native
168 if network is not ADMIN_NETWORK:
170 if 'vlan' not in _network['nic_mapping'][role]:
171 _network['nic_mapping'][role]['vlan'] = 'native'
173 cidr = _network.get('cidr')
176 cidr = ipaddress.ip_network(_network['cidr'])
177 _network['cidr'] = cidr
178 logging.info("{}_cidr: {}".format(network, cidr))
179 elif 'installer_vm' in _network:
180 ucloud_if_list = _network['installer_vm']['members']
181 # If cidr is not specified, we need to know if we should find
182 # IPv6 or IPv4 address on the interface
183 ip = ipaddress.ip_address(_network['installer_vm']['ip'])
184 nic_if = ip_utils.get_interface(ucloud_if_list[0], ip.version)
186 ucloud_if_list = [nic_if]
187 logging.info("{}_bridged_interface: {}".
188 format(network, nic_if))
190 raise NetworkSettingsException(
191 "Auto detection failed for {}: Unable to find valid "
192 "ip for interface {}".format(network, ucloud_if_list[0]))
195 raise NetworkSettingsException(
196 "Auto detection failed for {}: either installer_vm "
197 "members or cidr must be specified".format(network))
199 # undercloud settings
200 if network == ADMIN_NETWORK:
201 provisioner_ip = _network['installer_vm']['ip']
202 iface = _network['installer_vm']['members'][0]
203 if not provisioner_ip:
204 _network['installer_vm']['ip'] = self._gen_ip(network, 1)
205 self._config_ip_range(network=network, interface=iface,
206 ip_range='dhcp_range',
207 start_offset=2, count=9)
208 self._config_ip_range(network=network, interface=iface,
209 ip_range='introspection_range',
210 start_offset=11, count=9)
211 elif network == EXTERNAL_NETWORK:
212 provisioner_ip = _network['installer_vm']['ip']
213 iface = _network['installer_vm']['members'][0]
214 if not provisioner_ip:
215 _network['installer_vm']['ip'] = self._gen_ip(network, 1)
216 self._config_ip_range(network=network, interface=iface,
217 ip_range='floating_ip_range',
218 end_offset=2, count=20)
220 gateway = _network['gateway']
221 interface = _network['installer_vm']['ip']
222 self._config_gateway(network, gateway, interface)
224 def _config_ip_range(self, network, ip_range, interface=None,
225 start_offset=None, end_offset=None, count=None):
227 Configures IP range for a given setting.
228 If the setting is already specified, no change will be made.
229 The spec for start_offset, end_offset and count are identical to
230 ip_utils.get_ip_range.
232 _network = self.get_network(network)
233 if ip_range not in _network:
234 cidr = _network.get('cidr')
235 _ip_range = ip_utils.get_ip_range(start_offset=start_offset,
236 end_offset=end_offset,
240 _network[ip_range] = _ip_range.split(',')
242 logging.info("Config IP Range: {} {}".format(network, ip_range))
244 def _gen_ip(self, network, offset):
246 Generate and ip offset within the given network
248 _network = self.get_network(network)
249 cidr = _network.get('cidr')
250 ip = ip_utils.get_ip(offset, cidr)
251 logging.info("Config IP: {} {}".format(network, ip))
254 def _config_optional_settings(self, network):
256 Configures optional settings:
260 - introspection_range
266 if network == ADMIN_NETWORK:
267 self._config_ip(network, None, 'provisioner_ip', 1)
268 self._config_ip_range(network=network,
269 ip_range='dhcp_range',
270 start_offset=2, count=9)
271 self._config_ip_range(network=network,
272 ip_range='introspection_range',
273 start_offset=11, count=9)
274 elif network == EXTERNAL_NETWORK:
275 self._config_ip(network, None, 'provisioner_ip', 1)
276 self._config_ip_range(network=network,
277 ip_range='floating_ip_range',
278 end_offset=2, count=20)
279 self._config_gateway(network)
281 def _config_gateway(self, network, gateway, interface):
283 Configures gateway setting for a given network.
285 If cidr is specified, we always use the first address in the address
286 space for gateway. Otherwise, we detect the system gateway.
288 _network = self.get_network(network)
290 cidr = _network.get('cidr')
292 _gateway = ip_utils.get_ip(1, cidr)
294 _gateway = ip_utils.find_gateway(interface)
297 _network['gateway'] = _gateway
299 raise NetworkSettingsException("Failed to set gateway")
301 logging.info("Config Gateway: {} {}".format(network, gateway))
303 def dump_bash(self, path=None):
305 Prints settings for bash consumption.
307 If optional path is provided, bash string will be written to the file
310 def flatten(name, obj, delim=','):
312 flatten lists to delim separated strings
313 flatten dics to underscored key names and string values
315 if type(obj) is list:
316 return "{}=\'{}\'\n".format(name,
317 delim.join(map(lambda x: str(x),
319 elif type(obj) is dict:
322 flat_str += flatten("{}_{}".format(name, k), obj[k])
324 elif type(obj) is str:
325 return "{}='{}'\n".format(name, obj)
327 return "{}={}\n".format(name, str(obj))
330 for network in self.enabled_network_list:
331 _network = self.get_network(network)
332 bash_str += flatten(network, _network)
333 bash_str += flatten('enabled_network_list',
334 self.enabled_network_list, ' ')
335 bash_str += flatten('ip_addr_family', self.get_ip_addr_family())
336 bash_str += flatten('dns_servers', self['dns_servers'], ' ')
337 bash_str += flatten('domain_name', self['dns-domain'], ' ')
338 bash_str += flatten('ntp_server', self['ntp_servers'][0], ' ')
340 with open(path, 'w') as file:
345 def get_ip_addr_family(self,):
347 Returns IP address family for current deployment.
349 If any enabled network has IPv6 CIDR, the deployment is classified as
353 ipaddress.ip_network(self.get_network(n)['cidr']).version
354 for n in self.enabled_network_list])
357 class NetworkSettingsException(Exception):
358 def __init__(self, value):