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 (
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 type(filename) is 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():
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 return self['networks'][network][0]
75 return self['networks'][network]
77 def _validate_input(self):
79 Validates the network settings file and populates all fields.
81 NetworkSettingsException will be raised if validation fails.
83 if not self['networks'].get(ADMIN_NETWORK, {}).get('enabled', False):
84 raise NetworkSettingsException("You must enable admin network "
85 "and configure it explicitly or "
88 for network in OPNFV_NETWORK_TYPES:
89 if network in self['networks']:
90 _network = self.get_network(network)
91 if _network.get('enabled', True):
92 logging.info("{} enabled".format(network))
93 self._config_required_settings(network)
94 if network == EXTERNAL_NETWORK:
95 nicmap = _network['nic_mapping']
97 nicmap = _network['nic_mapping']
98 iface = nicmap[CONTROLLER]['members'][0]
99 self._config_ip_range(network=network,
101 ip_range='usable_ip_range',
102 start_offset=21, end_offset=21)
103 self.enabled_network_list.append(network)
104 self._validate_overcloud_nic_order(network)
105 # TODO self._config_optional_settings(network)
107 logging.info("{} disabled, will collapse with "
108 "admin network".format(network))
110 logging.info("{} is not in specified, will collapse with "
111 "admin network".format(network))
113 if 'dns-domain' not in self:
114 self['domain_name'] = DOMAIN_NAME
115 self['dns_servers'] = self.get('dns_nameservers', DNS_SERVERS)
117 def _validate_overcloud_nic_order(self, network):
119 Detects if nic order is specified per profile (compute/controller)
122 If nic order is specified in a network for a profile, it should be
123 specified for every network with that profile other than admin network
125 Duplicate nic names are also not allowed across different networks
127 :param network: network to detect if nic order present
131 _network = self.get_network(network)
132 _nicmap = _network.get('nic_mapping', {})
133 _role = _nicmap.get(role, {})
134 interfaces = _role.get('members', [])
137 interface = interfaces[0]
138 if type(_role.get('vlan', 'native')) is not int and \
139 any(y == interface for x, y in self.nics[role].items()):
140 raise NetworkSettingsException(
141 "Duplicate {} already specified for "
142 "another network".format(interface))
143 self.nics[role][network] = interface
144 self.nics_specified[role] = True
145 logging.info("{} nic order specified for network {"
146 "}".format(role, network))
148 raise NetworkSettingsException(
149 "Interface members are not supplied for {} network "
150 "for the {} role. Please add nic assignments"
151 "".format(network, role))
153 def _config_required_settings(self, network):
155 Configures either CIDR or bridged_interface setting
157 cidr takes precedence if both cidr and bridged_interface are specified
160 When using bridged_interface, we will detect network setting on the
161 given NIC in the system. The resulting config in settings object will
162 be an ipaddress.network object, replacing the NIC name.
164 _network = self.get_network(network)
165 # if vlan not defined then default it to native
166 if network is not ADMIN_NETWORK:
168 if 'vlan' not in _network['nic_mapping'][role]:
169 _network['nic_mapping'][role]['vlan'] = 'native'
171 cidr = _network.get('cidr')
174 cidr = ipaddress.ip_network(_network['cidr'])
175 _network['cidr'] = cidr
176 logging.info("{}_cidr: {}".format(network, cidr))
177 elif 'installer_vm' in _network:
178 ucloud_if_list = _network['installer_vm']['members']
179 # If cidr is not specified, we need to know if we should find
180 # IPv6 or IPv4 address on the interface
181 ip = ipaddress.ip_address(_network['installer_vm']['ip'])
182 nic_if = ip_utils.get_interface(ucloud_if_list[0], ip.version)
184 ucloud_if_list = [nic_if]
185 logging.info("{}_bridged_interface: {}".
186 format(network, nic_if))
188 raise NetworkSettingsException(
189 "Auto detection failed for {}: Unable to find valid "
190 "ip for interface {}".format(network, ucloud_if_list[0]))
193 raise NetworkSettingsException(
194 "Auto detection failed for {}: either installer_vm "
195 "members or cidr must be specified".format(network))
197 # undercloud settings
198 if network == ADMIN_NETWORK:
199 provisioner_ip = _network['installer_vm']['ip']
200 iface = _network['installer_vm']['members'][0]
201 if not provisioner_ip:
202 _network['installer_vm']['ip'] = self._gen_ip(network, 1)
203 self._config_ip_range(network=network, interface=iface,
204 ip_range='dhcp_range',
205 start_offset=2, count=9)
206 self._config_ip_range(network=network, interface=iface,
207 ip_range='introspection_range',
208 start_offset=11, count=9)
209 elif network == EXTERNAL_NETWORK:
210 provisioner_ip = _network['installer_vm']['ip']
211 iface = _network['installer_vm']['members'][0]
212 if not provisioner_ip:
213 _network['installer_vm']['ip'] = self._gen_ip(network, 1)
214 self._config_ip_range(network=network, interface=iface,
215 ip_range='floating_ip_range',
216 end_offset=2, count=20)
218 gateway = _network['gateway']
219 interface = _network['installer_vm']['ip']
220 self._config_gateway(network, gateway, interface)
222 def _config_ip_range(self, network, ip_range, interface=None,
223 start_offset=None, end_offset=None, count=None):
225 Configures IP range for a given setting.
226 If the setting is already specified, no change will be made.
227 The spec for start_offset, end_offset and count are identical to
228 ip_utils.get_ip_range.
230 _network = self.get_network(network)
231 if ip_range not in _network:
232 cidr = _network.get('cidr')
233 _ip_range = ip_utils.get_ip_range(start_offset=start_offset,
234 end_offset=end_offset,
238 _network[ip_range] = _ip_range.split(',')
240 logging.info("Config IP Range: {} {}".format(network, ip_range))
242 def _gen_ip(self, network, offset):
244 Generate and ip offset within the given network
246 _network = self.get_network(network)
247 cidr = _network.get('cidr')
248 ip = ip_utils.get_ip(offset, cidr)
249 logging.info("Config IP: {} {}".format(network, ip))
252 def _config_optional_settings(self, network):
254 Configures optional settings:
258 - introspection_range
264 if network == ADMIN_NETWORK:
265 self._config_ip(network, None, 'provisioner_ip', 1)
266 self._config_ip_range(network=network,
267 ip_range='dhcp_range',
268 start_offset=2, count=9)
269 self._config_ip_range(network=network,
270 ip_range='introspection_range',
271 start_offset=11, count=9)
272 elif network == EXTERNAL_NETWORK:
273 self._config_ip(network, None, 'provisioner_ip', 1)
274 self._config_ip_range(network=network,
275 ip_range='floating_ip_range',
276 end_offset=2, count=20)
277 self._config_gateway(network)
279 def _config_gateway(self, network, gateway, interface):
281 Configures gateway setting for a given network.
283 If cidr is specified, we always use the first address in the address
284 space for gateway. Otherwise, we detect the system gateway.
286 _network = self.get_network(network)
288 cidr = _network.get('cidr')
290 _gateway = ip_utils.get_ip(1, cidr)
292 _gateway = ip_utils.find_gateway(interface)
295 _network['gateway'] = _gateway
297 raise NetworkSettingsException("Failed to set gateway")
299 logging.info("Config Gateway: {} {}".format(network, gateway))
301 def dump_bash(self, path=None):
303 Prints settings for bash consumption.
305 If optional path is provided, bash string will be written to the file
308 def flatten(name, obj, delim=','):
310 flatten lists to delim separated strings
311 flatten dics to underscored key names and string values
313 if type(obj) is list:
314 return "{}=\'{}\'\n".format(name,
315 delim.join(map(lambda x: str(x),
317 elif type(obj) is dict:
320 flat_str += flatten("{}_{}".format(name, k), obj[k])
322 elif type(obj) is str:
323 return "{}='{}'\n".format(name, obj)
325 return "{}={}\n".format(name, str(obj))
328 for network in self.enabled_network_list:
329 _network = self.get_network(network)
330 bash_str += flatten(network, _network)
331 bash_str += flatten('enabled_network_list',
332 self.enabled_network_list, ' ')
333 bash_str += flatten('ip_addr_family', self.get_ip_addr_family())
334 bash_str += flatten('dns_servers', self['dns_servers'], ' ')
335 bash_str += flatten('domain_name', self['dns-domain'], ' ')
337 with open(path, 'w') as file:
342 def get_ip_addr_family(self,):
344 Returns IP address family for current deployment.
346 If any enabled network has IPv6 CIDR, the deployment is classified as
350 ipaddress.ip_network(self.get_network(n)['cidr']).version
351 for n in self.enabled_network_list])
354 class NetworkSettingsException(Exception):
355 def __init__(self, value):