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, network_isolation):
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.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 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_servers', DNS_SERVERS)
118 def _validate_overcloud_nic_order(self, network):
120 Detects if nic order is specified per profile (compute/controller)
123 If nic order is specified in a network for a profile, it should be
124 specified for every network with that profile other than admin network
126 Duplicate nic names are also not allowed across different networks
128 :param network: network to detect if nic order present
132 _network = self.get_network(network)
133 _nicmap = _network.get('nic_mapping', {})
134 _role = _nicmap.get(role, {})
135 interfaces = _role.get('members', [])
138 interface = interfaces[0]
139 if type(_role.get('vlan', 'native')) is not int and \
140 any(y == interface for x, y in self.nics[role].items()):
141 raise NetworkSettingsException(
142 "Duplicate {} already specified for "
143 "another network".format(interface))
144 self.nics[role][network] = interface
145 self.nics_specified[role] = True
146 logging.info("{} nic order specified for network {"
147 "}".format(role, network))
149 raise NetworkSettingsException(
150 "Interface members are not supplied for {} network "
151 "for the {} role. Please add nic assignments"
152 "".format(network, role))
154 def _config_required_settings(self, network):
156 Configures either CIDR or bridged_interface setting
158 cidr takes precedence if both cidr and bridged_interface are specified
161 When using bridged_interface, we will detect network setting on the
162 given NIC in the system. The resulting config in settings object will
163 be an ipaddress.network object, replacing the NIC name.
165 _network = self.get_network(network)
166 # if vlan not defined then default it to native
167 if network is not ADMIN_NETWORK:
169 if 'vlan' not in _network['nic_mapping'][role]:
170 _network['nic_mapping'][role]['vlan'] = 'native'
172 cidr = _network.get('cidr')
175 cidr = ipaddress.ip_network(_network['cidr'])
176 _network['cidr'] = cidr
177 logging.info("{}_cidr: {}".format(network, cidr))
178 elif 'installer_vm' in _network:
179 ucloud_if_list = _network['installer_vm']['members']
180 # If cidr is not specified, we need to know if we should find
181 # IPv6 or IPv4 address on the interface
182 ip = ipaddress.ip_address(_network['installer_vm']['ip'])
183 nic_if = ip_utils.get_interface(ucloud_if_list[0], ip.version)
185 ucloud_if_list = [nic_if]
186 logging.info("{}_bridged_interface: {}".
187 format(network, nic_if))
189 raise NetworkSettingsException(
190 "Auto detection failed for {}: Unable to find valid "
191 "ip for interface {}".format(network, ucloud_if_list[0]))
194 raise NetworkSettingsException(
195 "Auto detection failed for {}: either installer_vm "
196 "members or cidr must be specified".format(network))
198 # undercloud settings
199 if network == ADMIN_NETWORK:
200 provisioner_ip = _network['installer_vm']['ip']
201 iface = _network['installer_vm']['members'][0]
202 if not provisioner_ip:
203 _network['installer_vm']['ip'] = self._gen_ip(network, 1)
204 self._config_ip_range(network=network, interface=iface,
205 ip_range='dhcp_range',
206 start_offset=2, count=9)
207 self._config_ip_range(network=network, interface=iface,
208 ip_range='introspection_range',
209 start_offset=11, count=9)
210 elif network == EXTERNAL_NETWORK:
211 provisioner_ip = _network['installer_vm']['ip']
212 iface = _network['installer_vm']['members'][0]
213 if not provisioner_ip:
214 _network['installer_vm']['ip'] = self._gen_ip(network, 1)
215 self._config_ip_range(network=network, interface=iface,
216 ip_range='floating_ip_range',
217 end_offset=2, count=20)
219 gateway = _network['gateway']
220 interface = _network['installer_vm']['ip']
221 self._config_gateway(network, gateway, interface)
223 def _config_ip_range(self, network, ip_range, interface=None,
224 start_offset=None, end_offset=None, count=None):
226 Configures IP range for a given setting.
227 If the setting is already specified, no change will be made.
228 The spec for start_offset, end_offset and count are identical to
229 ip_utils.get_ip_range.
231 _network = self.get_network(network)
232 if ip_range not in _network:
233 cidr = _network.get('cidr')
234 _ip_range = ip_utils.get_ip_range(start_offset=start_offset,
235 end_offset=end_offset,
239 _network[ip_range] = _ip_range.split(',')
241 logging.info("Config IP Range: {} {}".format(network, ip_range))
243 def _gen_ip(self, network, offset):
245 Generate and ip offset within the given network
247 _network = self.get_network(network)
248 cidr = _network.get('cidr')
249 ip = ip_utils.get_ip(offset, cidr)
250 logging.info("Config IP: {} {}".format(network, ip))
253 def _config_optional_settings(self, network):
255 Configures optional settings:
259 - introspection_range
265 if network == ADMIN_NETWORK:
266 self._config_ip(network, None, 'provisioner_ip', 1)
267 self._config_ip_range(network=network,
268 ip_range='dhcp_range',
269 start_offset=2, count=9)
270 self._config_ip_range(network=network,
271 ip_range='introspection_range',
272 start_offset=11, count=9)
273 elif network == EXTERNAL_NETWORK:
274 self._config_ip(network, None, 'provisioner_ip', 1)
275 self._config_ip_range(network=network,
276 ip_range='floating_ip_range',
277 end_offset=2, count=20)
278 self._config_gateway(network)
280 def _config_gateway(self, network, gateway, interface):
282 Configures gateway setting for a given network.
284 If cidr is specified, we always use the first address in the address
285 space for gateway. Otherwise, we detect the system gateway.
287 _network = self.get_network(network)
289 cidr = _network.get('cidr')
291 _gateway = ip_utils.get_ip(1, cidr)
293 _gateway = ip_utils.find_gateway(interface)
296 _network['gateway'] = _gateway
298 raise NetworkSettingsException("Failed to set gateway")
300 logging.info("Config Gateway: {} {}".format(network, gateway))
302 def dump_bash(self, path=None):
304 Prints settings for bash consumption.
306 If optional path is provided, bash string will be written to the file
309 def flatten(name, obj, delim=','):
311 flatten lists to delim separated strings
312 flatten dics to underscored key names and string values
314 if type(obj) is list:
315 return "{}=\'{}\'\n".format(name,
316 delim.join(map(lambda x: str(x),
318 elif type(obj) is dict:
321 flat_str += flatten("{}_{}".format(name, k), obj[k])
323 elif type(obj) is str:
324 return "{}='{}'\n".format(name, obj)
326 return "{}={}\n".format(name, str(obj))
329 for network in self.enabled_network_list:
330 _network = self.get_network(network)
331 bash_str += flatten(network, _network)
332 bash_str += flatten('enabled_network_list',
333 self.enabled_network_list, ' ')
334 bash_str += flatten('ip_addr_family', self.get_ip_addr_family())
335 bash_str += flatten('dns_servers', self['dns_servers'], ' ')
336 bash_str += flatten('domain_name', self['dns-domain'], ' ')
338 with open(path, 'w') as file:
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):