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 ##############################################################################
14 from . import ip_utils
15 from .common.utils import str2bool
16 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.load(network_settings_file)
48 # 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.network_isolation = network_isolation
68 self.enabled_network_list = []
69 self.nics = {COMPUTE: {}, CONTROLLER: {}}
70 self.nics_specified = {COMPUTE: False, CONTROLLER: False}
71 self._validate_input()
73 def _validate_input(self):
75 Validates the network settings file and populates all fields.
77 NetworkSettingsException will be raised if validation fails.
79 if ADMIN_NETWORK not in self or \
80 not str2bool(self[ADMIN_NETWORK].get(
82 raise NetworkSettingsException("You must enable admin_network "
83 "and configure it explicitly or "
85 if self.network_isolation and \
86 (PUBLIC_NETWORK not in self or not
87 str2bool(self[PUBLIC_NETWORK].get(
89 raise NetworkSettingsException("You must enable public_network "
90 "and configure it explicitly or "
93 for network in OPNFV_NETWORK_TYPES:
95 if str2bool(self[network].get('enabled')):
96 logging.info("{} enabled".format(network))
97 self._config_required_settings(network)
98 self._config_ip_range(network=network,
99 setting='usable_ip_range',
100 start_offset=21, end_offset=21)
101 self._config_optional_settings(network)
102 self.enabled_network_list.append(network)
103 self._validate_overcloud_nic_order(network)
105 logging.info("{} disabled, will collapse with "
106 "admin_network".format(network))
108 logging.info("{} is not in specified, will collapse with "
109 "admin_network".format(network))
111 self['dns_servers'] = self.get('dns_servers', DNS_SERVERS)
112 self['domain_name'] = self.get('domain_name', DOMAIN_NAME)
114 def _validate_overcloud_nic_order(self, network):
116 Detects if nic order is specified per profile (compute/controller)
119 If nic order is specified in a network for a profile, it should be
120 specified for every network with that profile other than admin_network
122 Duplicate nic names are also not allowed across different networks
124 :param network: network to detect if nic order present
129 interface = role+'_interface'
130 nic_index = self.get_enabled_networks().index(network) + 1
131 if interface in self[network]:
132 if any(y == self[network][interface] for x, y in
133 self.nics[role].items()):
134 raise NetworkSettingsException("Duplicate {} already "
137 .format(self[network]
139 self.nics[role][network] = self[network][interface]
140 self.nics_specified[role] = True
141 logging.info("{} nic order specified for network {"
142 "}".format(role, network))
143 elif self.nics_specified[role]:
144 logging.error("{} nic order not specified for network {"
145 "}".format(role, network))
146 raise NetworkSettingsException("Must specify {} for all "
147 "enabled networks (other than "
148 " admin) or not specify it for "
149 "any".format(interface))
151 logging.info("{} nic order not specified for network {"
152 "}. Will use logical default "
153 "nic{}".format(interface, network, nic_index))
154 self.nics[role][network] = 'nic' + str(nic_index)
157 def _config_required_settings(self, network):
159 Configures either CIDR or bridged_interface setting
161 cidr takes precedence if both cidr and bridged_interface are specified
164 When using bridged_interface, we will detect network setting on the
165 given NIC in the system. The resulting config in settings object will
166 be an ipaddress.network object, replacing the NIC name.
168 # if vlan not defined then default it to native
169 if network is not ADMIN_NETWORK:
170 if 'vlan' not in self[network]:
171 self[network]['vlan'] = 'native'
173 cidr = self[network].get('cidr')
174 nic_name = self[network].get('bridged_interface')
177 cidr = ipaddress.ip_network(self[network]['cidr'])
178 self[network]['cidr'] = cidr
179 logging.info("{}_cidr: {}".format(network, cidr))
182 # If cidr is not specified, we need to know if we should find
183 # IPv6 or IPv4 address on the interface
184 if str2bool(self[network].get('ipv6')):
188 nic_interface = ip_utils.get_interface(nic_name, address_family)
190 self[network]['bridged_interface'] = nic_interface
191 logging.info("{}_bridged_interface: {}".
192 format(network, nic_interface))
195 raise NetworkSettingsException("Auto detection failed for {}: "
196 "Unable to find valid ip for "
198 .format(network, nic_name))
201 raise NetworkSettingsException("Auto detection failed for {}: "
202 "either bridge_interface or cidr "
206 def _config_ip_range(self, network, setting, start_offset=None,
207 end_offset=None, count=None):
209 Configures IP range for a given setting.
211 If the setting is already specified, no change will be made.
213 The spec for start_offset, end_offset and count are identical to
214 ip_utils.get_ip_range.
216 ip_range = self[network].get(setting)
217 interface = self[network].get('bridged_interface')
220 cidr = self[network].get('cidr')
221 ip_range = ip_utils.get_ip_range(start_offset=start_offset,
222 end_offset=end_offset,
226 self[network][setting] = ip_range
228 logging.info("{}_{}: {}".format(network, setting, ip_range))
230 def _config_ip(self, network, setting, offset):
232 Configures IP for a given setting.
234 If the setting is already specified, no change will be made.
236 The spec for offset is identical to ip_utils.get_ip
238 ip = self[network].get(setting)
239 interface = self[network].get('bridged_interface')
242 cidr = self[network].get('cidr')
243 ip = ip_utils.get_ip(offset, cidr, interface)
244 self[network][setting] = ip
246 logging.info("{}_{}: {}".format(network, setting, ip))
248 def _config_optional_settings(self, network):
250 Configures optional settings:
254 - introspection_range
260 if network == ADMIN_NETWORK:
261 self._config_ip(network, 'provisioner_ip', 1)
262 self._config_ip_range(network=network, setting='dhcp_range',
263 start_offset=2, count=9)
264 self._config_ip_range(network=network,
265 setting='introspection_range',
266 start_offset=11, count=9)
267 elif network == PUBLIC_NETWORK:
268 self._config_ip(network, 'provisioner_ip', 1)
269 self._config_ip_range(network=network,
270 setting='floating_ip_range',
271 end_offset=2, count=20)
272 self._config_gateway(network)
274 def _config_gateway(self, network):
276 Configures gateway setting for a given network.
278 If cidr is specified, we always use the first address in the address
279 space for gateway. Otherwise, we detect the system gateway.
281 gateway = self[network].get('gateway')
282 interface = self[network].get('bridged_interface')
285 cidr = self[network].get('cidr')
287 gateway = ip_utils.get_ip(1, cidr)
289 gateway = ip_utils.find_gateway(interface)
292 self[network]['gateway'] = gateway
294 raise NetworkSettingsException("Failed to set gateway")
296 logging.info("{}_gateway: {}".format(network, gateway))
298 def dump_bash(self, path=None):
300 Prints settings for bash consumption.
302 If optional path is provided, bash string will be written to the file
306 for network in self.enabled_network_list:
307 for key, value in self[network].items():
308 bash_str += "{}_{}={}\n".format(network, key, value)
309 bash_str += "enabled_network_list='{}'\n" \
310 .format(' '.join(self.enabled_network_list))
311 bash_str += "ip_addr_family={}\n".format(self.get_ip_addr_family())
313 for dns_server in self['dns_servers']:
314 dns_list = dns_list + "{} ".format(dns_server)
315 dns_list = dns_list.strip()
316 bash_str += "dns_servers=\'{}\'\n".format(dns_list)
317 bash_str += "domain_name=\'{}\'\n".format(self['domain_name'])
319 with open(path, 'w') as file:
324 def get_ip_addr_family(self):
326 Returns IP address family for current deployment.
328 If any enabled network has IPv6 CIDR, the deployment is classified as
331 for network in self.enabled_network_list:
332 cidr = ipaddress.ip_network(self[network]['cidr'])
333 if cidr.version == 6:
338 def get_enabled_networks(self):
340 Getter for enabled network list
341 :return: list of enabled networks
343 return self.enabled_network_list
346 class NetworkSettingsException(Exception):
347 def __init__(self, value):