Initial commit
[ta/config-manager.git] / cmdatahandlers / src / cmdatahandlers / networking / config.py
diff --git a/cmdatahandlers/src/cmdatahandlers/networking/config.py b/cmdatahandlers/src/cmdatahandlers/networking/config.py
new file mode 100644 (file)
index 0000000..4f724ec
--- /dev/null
@@ -0,0 +1,879 @@
+# 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']