--- /dev/null
+# Copyright 2019 Nokia
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import re
+
+from cmdatahandlers.api import configerror
+from cmdatahandlers.api import config
+from serviceprofiles import profiles
+from netaddr import IPNetwork, IPSet, IPRange
+
+
+VALID_NETWORKS = \
+ ['infra_external', 'infra_storage_cluster', 'infra_hw_management', 'infra_internal', 'cloud_tenant', 'infra_access']
+
+NETWORK_DOMAINS = 'network_domains'
+
+class Config(config.Config):
+ def __init__(self, confman):
+ super(Config, self).__init__(confman)
+ self.ROOT = 'cloud.networking'
+ self.DOMAIN = 'networking'
+ self.freepool = {}
+ self.external_vip = None
+
+ def init(self):
+ if self.ROOT not in self.config:
+ return
+ try:
+ # a mapping between network and free IPSet
+ self.freepool = {}
+ for network in self.config[self.ROOT].keys():
+ if network in VALID_NETWORKS:
+ if NETWORK_DOMAINS not in self.config[self.ROOT][network]:
+ raise configerror.ConfigError('No network domains for network %s' % network)
+
+ self.freepool[network] = {}
+ for domain in self.config[self.ROOT][network][NETWORK_DOMAINS].keys():
+ self.freepool[network][domain] = self._get_free_set(network, domain)
+
+ except configerror.ConfigError:
+ raise
+
+ except Exception as exp:
+ raise configerror.ConfigError(str(exp))
+
+ def _validate_network(self, network, domain=None):
+ networks = self.get_networks()
+ if network not in networks:
+ raise configerror.ConfigError('Invalid network name %s' % network)
+
+ if NETWORK_DOMAINS not in self.config[self.ROOT][network]:
+ raise configerror.ConfigError('No network domains for network %s' % network)
+
+ if domain and domain not in self.config[self.ROOT][network][NETWORK_DOMAINS]:
+ raise configerror.ConfigError('Invalid network domain name %s' % domain)
+
+ def _get_free_set(self, network, domain):
+ ip_range_start = self.get_network_ip_range_start(network, domain)
+ ip_range_end = self.get_network_ip_range_end(network, domain)
+ select_range = IPRange(ip_range_start, ip_range_end)
+ netset = IPSet(select_range)
+ if (network == self.get_infra_external_network_name() and
+ domain == self._get_vip_domain()):
+ iterator = netset.__iter__()
+ self.external_vip = str(iterator.next())
+ netset.remove(self.external_vip)
+
+ # check for the IP(s) taken by the nodes
+ try:
+ hostsconfig = self.confman.get_hosts_config_handler()
+ hosts = hostsconfig.get_hosts()
+ for host in hosts:
+ try:
+ hostip = self.get_host_ip(host, network)
+ netset.remove(hostip)
+ except configerror.ConfigError:
+ pass
+ except configerror.ConfigError:
+ pass
+
+ service_profiles_lib = profiles.Profiles()
+
+ # check for the IP(s) taken as VIPs
+ if network == self.get_infra_internal_network_name() and domain == self._get_vip_domain():
+ vips = self.get_net_vips(network)
+ for _, vip in vips.iteritems():
+ try:
+ netset.remove(vip)
+ except configerror.ConfigError:
+ pass
+
+ return netset
+
+ def get_dns(self):
+ """ get the list of dns servers
+
+ Return:
+
+ A list of dns servers
+
+ Raise:
+
+ ConfigError is raised in-case of an error
+ """
+ self.validate_root()
+ if 'dns' not in self.config[self.ROOT]:
+ raise configerror.ConfigError('dns not found!')
+
+ return self.config[self.ROOT]['dns']
+
+ def get_mtu(self):
+ """ get the mtu value
+
+ Return:
+
+ A number representing the mtu size
+
+ Raise:
+
+ ConfigError is raised in-case of an error
+ """
+ self.validate_root()
+ if 'mtu' not in self.config['cloud.networking']:
+ raise configerror.ConfigError('mtu not found!')
+ return self.config[self.ROOT]['mtu']
+
+ def get_networks(self):
+ """ get the list of network names
+
+ Return:
+
+ A list of network names
+
+ Raise:
+
+ ConfigError is raised in-case of an error
+ """
+ self.validate_root()
+ networks = []
+ for entry in self.config[self.ROOT]:
+ if entry in VALID_NETWORKS:
+ networks.append(entry)
+ return networks
+
+ def allocate_ip(self, network, domain):
+ """ get a new free ip in some network
+
+ Arguments:
+
+ Network name
+
+ Network domain
+
+ Return:
+
+ The free ip address
+
+ Raise:
+
+ ConfigError in-case of an error
+ """
+ self._validate_network(network, domain)
+
+ try:
+ iterator = self.freepool[network][domain].__iter__()
+ ip = str(iterator.next())
+ self.freepool[network][domain].remove(ip)
+ return ip
+ except Exception:
+ raise configerror.ConfigError('Failed to allocate ip for network %s in %s' % (network, domain))
+
+ def allocate_static_ip(self, ip, network, domain=None):
+ """ allocate the given ip in some network
+
+ Arguments:
+
+ Ip address
+
+ Network name
+
+ Network domain
+
+ Return:
+
+ The allocated ip address
+
+ Raise:
+
+ ConfigError in-case of an error
+ """
+ self._validate_network(network, domain)
+
+ try:
+ self.freepool[network][domain].remove(ip)
+ return ip
+ except Exception:
+ raise configerror.ConfigError('Failed to allocate %s for network %s in %s' % (ip, network, domain))
+
+ def allocate_vip(self, network):
+ return self.allocate_ip(network, self._get_vip_domain())
+
+ def get_network_domains(self, network):
+ self._validate_network(network)
+ return self.config[self.ROOT][network][NETWORK_DOMAINS].keys()
+
+ def get_network_cidr(self, network, domain):
+ """ get the network cidr
+
+ Arguments:
+
+ Network name
+
+ Network domain
+
+ Return:
+
+ The cidr address
+
+ Raise:
+
+ ConfigError in-case of an error
+ """
+ self._validate_network(network, domain)
+
+ if 'cidr' not in self.config[self.ROOT][network][NETWORK_DOMAINS][domain]:
+ raise configerror.ConfigError('No CIDR for network %s in %s' % (network, domain))
+
+ return self.config[self.ROOT][network][NETWORK_DOMAINS][domain]['cidr']
+
+ def get_vip_network_cidr(self, network):
+ return self.get_network_cidr(network, self._get_vip_domain())
+
+ def get_network_mask(self, network, domain):
+ """ get the network mask
+
+ Arguments:
+
+ Network name
+
+ Network domain
+
+ Return:
+
+ A number representing the mask
+
+ Raise:
+
+ ConfigError in-case of an error
+ """
+ cidr = self.get_network_cidr(network, domain)
+ try:
+ mask = cidr.split('/')[1]
+ return int(mask)
+ except Exception as exp:
+ raise configerror.ConfigError('Invalid network mask in %s: %s' % (cidr, str(exp)))
+
+ def get_vip_network_mask(self, network):
+ return self.get_network_mask(network, self._get_vip_domain())
+
+ def get_network_gateway(self, network, domain):
+ """ get the network gateway
+
+ Arguments:
+
+ Network name
+
+ Network domain
+
+ Return:
+
+ The gateway address
+
+ Raise:
+
+ ConfigError in-case of an error
+ """
+ self._validate_network(network, domain)
+
+ if 'gateway' not in self.config[self.ROOT][network][NETWORK_DOMAINS][domain]:
+ raise configerror.ConfigError('No gateway configured for network %s in %s' % (network, domain))
+
+ return self.config[self.ROOT][network][NETWORK_DOMAINS][domain]['gateway']
+
+ def get_network_routes(self, network, domain):
+ self._validate_network(network, domain)
+
+ if 'routes' not in self.config[self.ROOT][network][NETWORK_DOMAINS][domain]:
+ raise configerror.ConfigError('No routes configured for network %s in %s' % (network, domain))
+
+ return self.config[self.ROOT][network][NETWORK_DOMAINS][domain]['routes']
+
+ def get_network_ip_range_start(self, network, domain):
+ """ get the network allocation range start
+
+ Arguments:
+
+ Network name
+
+ Network domain
+
+ Return:
+
+ The starting allocation range
+
+ Raise:
+
+ ConfigError in-case of an error
+ """
+ net = IPNetwork(self.get_network_cidr(network, domain))
+
+ if 'ip_range_start' in self.config[self.ROOT][network][NETWORK_DOMAINS][domain]:
+ return self.config[self.ROOT][network][NETWORK_DOMAINS][domain]['ip_range_start']
+ else:
+ return str(net[1])
+
+ def get_network_ip_range_end(self, network, domain):
+ """ get the network allocation range end
+
+ Arguments:
+
+ Network name
+
+ Network domain
+
+ Return:
+
+ The end of the allocation range
+
+ Raise:
+
+ ConfigError in-case of an error
+ """
+ net = IPNetwork(self.get_network_cidr(network, domain))
+
+ if 'ip_range_end' in self.config[self.ROOT][network][NETWORK_DOMAINS][domain]:
+ return self.config[self.ROOT][network][NETWORK_DOMAINS][domain]['ip_range_end']
+ else:
+ return str(net[-2])
+
+ def get_network_vlan_id(self, network, domain):
+ """ get the network vlan id
+
+ Arguments:
+
+ Network name
+
+ Network domain
+
+ Return:
+
+ The vlan id
+
+ Raise:
+
+ ConfigError in-case of an error
+ """
+ self._validate_network(network, domain)
+
+ if 'vlan' not in self.config[self.ROOT][network][NETWORK_DOMAINS][domain]:
+ raise configerror.ConfigError('No vlan specified for %s in %s' % (network, domain))
+
+ return self.config[self.ROOT][network][NETWORK_DOMAINS][domain]['vlan']
+
+ def get_vip_network_vlan_id(self, network):
+ return self.get_network_vlan_id(network, self._get_vip_domain())
+
+ def get_network_mtu(self, network):
+ """ get the network mtu
+
+ Argument:
+
+ Network name
+
+ Return:
+
+ The mtu of the network
+
+ Raise:
+
+ ConfigError in-case of an error
+ """
+ self._validate_network(network)
+
+ if 'mtu' not in self.config[self.ROOT][network]:
+ raise configerror.ConfigError('No mtu specified for %s' % network)
+
+ return self.config[self.ROOT][network]['mtu']
+
+ def get_host_ip(self, host, network):
+ """ get the host ip allocated from a specific network
+
+ Argument:
+
+ hostname: The name of the host
+ networkname: The name of the network
+
+ Return:
+
+ The ip address assigned for the host
+
+ Raise:
+
+ ConfigError in-case of an error
+ """
+ self._validate_network(network)
+
+ hostnetconfigkey = host + '.' + self.DOMAIN
+ if hostnetconfigkey not in self.config:
+ raise configerror.ConfigError('No network configuration available for %s' % host)
+
+ if network not in self.config[hostnetconfigkey]:
+ raise configerror.ConfigError('No network configuration available for %s' % host)
+
+ if 'ip' not in self.config[hostnetconfigkey][network]:
+ raise configerror.ConfigError('No IP assigned for %s in network %s' % (host, network))
+
+ return self.config[hostnetconfigkey][network]['ip']
+
+ def _get_vip_domain(self):
+ return self.confman.get_hosts_config_handler().get_managements_network_domain()
+
+ @staticmethod
+ def get_infra_external_network_name():
+ """ get the network name for the external network
+
+ Return:
+
+ The external network name
+
+ Raise:
+
+ ConfigError in-case the network is not configured
+ """
+ return 'infra_external'
+
+ @staticmethod
+ def get_infra_storage_cluster_network_name():
+ """ get the infra storage cluster network name
+
+ Return:
+
+ The infra stroage cluster network name
+
+ Raise:
+
+ ConfigError in-case the network is not configured
+ """
+ return 'infra_storage_cluster'
+
+ @staticmethod
+ def get_hwmgmt_network_name():
+ """ get the hwmgmt network name
+
+ Return:
+
+ The hwmgmt network name
+
+ Raise:
+
+ ConfigError in-case the network is not defined
+ """
+ return 'infra_hw_management'
+
+ @staticmethod
+ def get_infra_internal_network_name():
+ """ get the infra management network name
+
+ Return:
+
+ The infra management network name
+
+ Raise:
+
+ ConfigError in-case the network is not defined
+ """
+ return 'infra_internal'
+
+ def get_cloud_tenant_network_name(self):
+ """ get the network name for the cloud tenant network
+
+ Return:
+
+ The cloud tenant network name
+
+ Raise:
+
+ ConfigError in-case the network is not configured
+ """
+ return 'cloud_tenant'
+
+ def get_infra_access_network_name(self):
+ """ get the network name for the infra access network
+
+ Return:
+
+ The infra access network name
+
+ Raise:
+
+ ConfigError in-case the network is not configured
+ """
+ return 'infra_access'
+
+ def add_host_networks(self, host):
+ """ add host network data
+
+ Argument:
+
+ Host name
+
+ Raise:
+
+ ConfigError in-case of an error
+ """
+ hostsconf = self.confman.get_hosts_config_handler()
+ networks = hostsconf.get_host_networks(host)
+ domain = hostsconf.get_host_network_domain(host)
+ for network in networks:
+ try:
+ ip = self.get_host_ip(host, network)
+ continue
+ except configerror.ConfigError:
+ pass
+
+ static_ip = hostsconf.get_pre_allocated_ips(host, network)
+ if static_ip:
+ ip = self.allocate_static_ip(static_ip, network, domain)
+ else:
+ ip = self.allocate_ip(network, domain)
+ interface = hostsconf.get_host_network_ip_holding_interface(host, network)
+ netmask = self.get_network_mask(network, domain)
+ networkdata = {'ip': ip, 'interface': interface, 'mask': netmask}
+
+ try:
+ vlan = self.get_network_vlan_id(network, domain)
+ networkdata['vlan'] = vlan
+ except configerror.ConfigError:
+ pass
+
+ try:
+ gw = self.get_network_gateway(network, domain)
+ networkdata['gateway'] = gw
+ except configerror.ConfigError:
+ pass
+
+ try:
+ routes = self.get_network_routes(network, domain)
+ networkdata['routes'] = routes
+ except configerror.ConfigError:
+ pass
+
+ key = host + '.' + self.DOMAIN
+ if key not in self.config:
+ self.config[key] = {}
+ if network not in self.config[key]:
+ self.config[key][network] = {}
+
+ self.config[key][network] = networkdata
+
+ def delete_host_networks(self, host):
+ """ delete host network data
+
+ Argument:
+
+ Host name
+ """
+ key = '{}.{}'.format(host, self.DOMAIN)
+ if key in self.config:
+ del self.config[key]
+
+ def get_networking_hosts(self):
+ """ get hosts with networking data
+
+ Return:
+
+ List of host names with existing networking data
+ """
+ hosts = []
+ match = r'^[^.]*\.networking$'
+ for key in self.config.keys():
+ if key != self.ROOT and re.match(match, key):
+ hosts.append(key.split('.')[0])
+ return hosts
+
+ def get_host_interface(self, host, network):
+ """ get the host interface allocated from a specific network
+
+ Argument:
+
+ hostname: The name of the host
+ networkname: The name of the network
+
+ Return:
+
+ The interface for the host
+
+ Raise:
+
+ ConfigError in-case of an error
+ """
+ self._validate_network(network)
+
+ hostnetconfigkey = host + '.' + self.DOMAIN
+ if hostnetconfigkey not in self.config:
+ raise configerror.ConfigError('No network configuration available for %s' % host)
+
+ if network not in self.config[hostnetconfigkey]:
+ raise configerror.ConfigError('No network configuration available for %s' % host)
+
+ if 'interface' not in self.config[hostnetconfigkey][network]:
+ raise configerror.ConfigError(
+ 'No interface assigned for %s in network %s' % (host, network))
+
+ return self.config[hostnetconfigkey][network]['interface']
+
+ def get_host_mask(self, host, network):
+ """ get the network mask for the host
+
+ Argument:
+
+ hostname: The name of the host
+ networkname: The name of the network
+
+ Return:
+
+ The network mask
+
+ Raise:
+
+ ConfigError in-case of an error
+ """
+ self._validate_network(network)
+
+ hostnetconfigkey = host + '.' + self.DOMAIN
+ if hostnetconfigkey not in self.config:
+ raise configerror.ConfigError('No network configuration available for %s' % host)
+
+ if network not in self.config[hostnetconfigkey]:
+ raise configerror.ConfigError('No network configuration available for %s' % host)
+
+ if 'mask' not in self.config[hostnetconfigkey][network]:
+ raise configerror.ConfigError('No mask assigned for %s in network %s' % (host, network))
+
+ return self.config[hostnetconfigkey][network]['mask']
+
+
+ def get_external_vip(self):
+ """ get the external vip ip, this is always the first ip in the range
+ """
+ return self.external_vip
+
+
+ def get_provider_networks(self):
+ """
+ Get provider network names
+
+ Returns:
+ A list of provider network names
+
+ Raises:
+ ConfigError in-case of an error
+ """
+ if 'provider_networks' not in self.config[self.ROOT]:
+ raise configerror.ConfigError('No provider networks configured')
+
+ return self.config[self.ROOT]['provider_networks'].keys()
+
+ def is_shared_provider_network(self, network):
+ """
+ Is shared provider network
+
+ Arguments:
+ Provider network name
+
+ Returns:
+ True if given provider network is shared, False otherwise
+
+ Raises:
+ ConfigError in-case of an error
+ """
+ networks = self.get_provider_networks()
+ if network not in networks:
+ raise configerror.ConfigError('Missing configuration for provider network %s' % network)
+
+ return (self.config[self.ROOT]['provider_networks'][network].get('shared') is True)
+
+ def get_provider_network_vlan_ranges(self, network):
+ """
+ Get vlan ranges for the given provider network
+
+ Arguments:
+ Provider network name
+
+ Returns:
+ Vlan ranges for the provider network
+
+ Raises:
+ ConfigError in-case of an error
+ """
+ networks = self.get_provider_networks()
+ if network not in networks:
+ raise configerror.ConfigError('Missing configuration for provider network %s' % network)
+
+ if 'vlan_ranges' not in self.config[self.ROOT]['provider_networks'][network]:
+ raise configerror.ConfigError(
+ 'Missing vlan ranges configuration for provider network %s' % network)
+
+ return self.config[self.ROOT]['provider_networks'][network]['vlan_ranges']
+
+
+ def get_provider_network_mtu(self, network):
+ """
+ Get mtu for the given provider network
+
+ Arguments:
+ Provider network name
+
+ Returns:
+ mtu for the provider network
+
+ Raises:
+ ConfigError in-case of an error
+ """
+ networks = self.get_provider_networks()
+ if network not in networks:
+ raise configerror.ConfigError('Missing configuration for provider network %s' % network)
+
+ if 'mtu' not in self.config[self.ROOT]['provider_networks'][network]:
+ raise configerror.ConfigError(
+ 'Missing mtu configuration for provider network %s' % network)
+
+ return self.config[self.ROOT]['provider_networks'][network]['mtu']
+
+ def is_l3_ha_enabled(self):
+ """ is L3 HA enabled
+
+ Return:
+
+ True if L3 HA is enabled, False otherwise
+ """
+ return True if 'l3_ha' in self.config[self.ROOT] else False
+
+ def _get_l3_ha_config(self):
+ if 'l3_ha' not in self.config[self.ROOT]:
+ raise configerror.ConfigError('Missing L3 HA configuration')
+
+ return self.config[self.ROOT]['l3_ha']
+
+ def get_l3_ha_provider_network(self):
+ """ get L3 HA provider network
+
+ Return:
+
+ L3 HA provider network name
+
+ Raise:
+
+ ConfigError in-case of an error
+ """
+ conf = self._get_l3_ha_config()
+ if 'provider_network' not in conf:
+ raise configerror.ConfigError('Missing L3 HA provider network configuration')
+
+ return conf['provider_network']
+
+ def get_l3_ha_cidr(self):
+ """ get L3 HA CIDR
+
+ Return:
+
+ L3 HA CIDR
+
+ Raise:
+
+ ConfigError in-case of an error
+ """
+ conf = self._get_l3_ha_config()
+ if 'cidr' not in conf:
+ raise configerror.ConfigError('Missing L3 HA CIDR configuration')
+
+ return conf['cidr']
+
+ def add_ovs_config_defaults(self, host):
+ """ Add Openvswitch default config """
+
+ ovs_defaults = { 'tx-flush-interval': 0, 'rxq-rebalance': 0 }
+
+ key = self.ROOT
+ if key not in self.config:
+ self.config[key] = {}
+ if 'ovs_config' not in self.config[key]:
+ self.config[key]['ovs_config'] = {}
+ if host not in self.config[key]['ovs_config']:
+ self.config[key]['ovs_config'][host] = {}
+
+ self.config[key]['ovs_config'][host] = ovs_defaults
+
+ def del_ovs_config(self, host):
+ """ Delete Openvswitch config """
+ if host in self.config[self.ROOT]['ovs_config']:
+ self.config[self.ROOT]['ovs_config'].pop(host, None)
+
+ def get_ovs_config(self, host):
+ return self.config[self.ROOT]['ovs_config'].get(host, None)
+
+ def _validate_ovs_config_args(self, host, args):
+ ovs_conf = self.config[self.ROOT]['ovs_config']
+
+ if args.get('tx_flush_interval') is not None:
+ if int(args['tx_flush_interval']) >= 0 and int(args['tx_flush_interval']) <= 1000000:
+ ovs_conf[host]['tx-flush-interval'] = int(args['tx_flush_interval'])
+ else:
+ raise ValueError("tx-flush-interval value must be 0..1000000")
+
+ if args.get('rxq_rebalance_interval') is not None:
+ if int(args['rxq_rebalance_interval']) >= 0 and int(args['rxq_rebalance_interval']) <= 1000000:
+ ovs_conf[host]['rxq-rebalance'] = int(args['rxq_rebalance_interval'])
+ else:
+ raise ValueError("rxq_rebalance_interval value must be 0..1000000")
+
+ def update_ovs_config(self, host, args):
+ if self.config[self.ROOT]['ovs_config'].get(host, None) is None:
+ return None
+ self._validate_ovs_config_args(host, args)
+ return self.config[self.ROOT]
+
+ def get_ovs_config_hosts(self):
+ return [host for host in self.config[self.ROOT]['ovs_config']]
+
+ def add_vip(self, network, name, ip):
+ if 'vips' not in self.config[self.ROOT]:
+ self.config[self.ROOT]['vips'] = {}
+
+ if network not in self.config[self.ROOT]['vips']:
+ self.config[self.ROOT]['vips'][network] = {}
+
+ self.config[self.ROOT]['vips'][network][name] = ip
+
+ def add_external_vip(self):
+ external_vip = self.get_external_vip()
+ self.add_vip('infra_external', 'external_vip', external_vip)
+
+ def add_internal_vip(self):
+ internal_vip = self.allocate_vip('infra_internal')
+ self.add_vip('infra_internal', 'internal_vip', internal_vip)
+
+ def get_internal_vip(self):
+ try:
+ return self.config[self.ROOT]['vips']['infra_internal']['internal_vip']
+ except KeyError as exp:
+ raise configerror.ConfigError('Internal vip not found')
+
+ def get_vips(self):
+ if 'vips' not in self.config[self.ROOT]:
+ return {}
+
+ return self.config[self.ROOT]['vips']
+
+ def get_net_vips(self, net):
+ if 'vips' not in self.config[self.ROOT]:
+ return {}
+
+ if net not in self.config[self.ROOT]['vips']:
+ return {}
+
+ return self.config[self.ROOT]['vips'][net]
+
+ return self.config[self.ROOT]['vips']