From ab385feb8db847960dc83adbe8fb267b4471a0b9 Mon Sep 17 00:00:00 2001 From: zsdc Date: Thu, 30 Jul 2020 21:15:31 +0300 Subject: cc_vyos: T2403: Resolved IP addresses configuration conflicts This commit fixes a situation when it is necessary to configure more than one IP address on an interface. Previously only the latest one address survived. With this fix, it is possible to add all compatible addresses. --- cloudinit/config/cc_vyos.py | 120 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 96 insertions(+), 24 deletions(-) (limited to 'cloudinit/config/cc_vyos.py') diff --git a/cloudinit/config/cc_vyos.py b/cloudinit/config/cc_vyos.py index f1b8781f..37f45cf8 100644 --- a/cloudinit/config/cc_vyos.py +++ b/cloudinit/config/cc_vyos.py @@ -133,13 +133,13 @@ def set_config_ovf(config, metadata): cidr = str(ipaddress.IPv4Network('0.0.0.0/' + mask_0).prefixlen) ipcidr = ip_0 + '/' + cidr - config.set(['interfaces', 'ethernet', 'eth0', 'address'], value=ipcidr, replace=True) + set_ipaddress(config, 'eth0', ipcidr) config.set_tag(['interfaces', 'ethernet']) config.set(['protocols', 'static', 'route', '0.0.0.0/0', 'next-hop'], value=gateway, replace=True) config.set_tag(['protocols', 'static', 'route']) config.set_tag(['protocols', 'static', 'route', '0.0.0.0/0', 'next-hop']) else: - config.set(['interfaces', 'ethernet', 'eth0', 'address'], value='dhcp', replace=True) + set_ipaddress(config, 'eth0', 'dhcp') config.set_tag(['interfaces', 'ethernet']) DNS = [server for server in DNS if server and server != 'null'] @@ -165,8 +165,58 @@ def set_config_ovf(config, metadata): config.set_tag(['service', 'https', 'listen-address']) +# get an IP address type +def get_ip_type(address): + addr_type = None + if address in ['dhcp', 'dhcpv6']: + addr_type = address + else: + try: + ip_version = ipaddress.ip_interface(address).version + if ip_version == 4: + addr_type = 'ipv4' + if ip_version == 6: + addr_type = 'ipv6' + except Exception as err: + logger.error("Unable to detect IP address type: {}".format(err)) + logger.debug("IP address {} have type: {}".format(address, addr_type)) + return addr_type + + +# configure IP address for interface +def set_ipaddress(config, iface, address): + # detect an IP address type + addr_type = get_ip_type(address) + if not addr_type: + logger.error("Unable to configure the IP address: {}".format(address)) + return + + # check a current configuration of an interface + if config.exists(['interfaces', 'ethernet', iface, 'address']): + current_addresses = config.return_values(['interfaces', 'ethernet', iface, 'address']) + logger.debug("IP address for interface {} already configured: {}".format(iface, current_addresses)) + # check if currently configured addresses can be used with new one + incompatible_addresses = [] + for current_address in current_addresses: + # dhcp cannot be used with static IP address at the same time + if ((addr_type == 'dhcp' and get_ip_type(current_address) == 'ipv4') or + (addr_type == 'ipv4' and get_ip_type(current_address) == 'dhcp') or + (addr_type == 'dhcpv6' and get_ip_type(current_address) == 'ipv6') or + (addr_type == 'ipv6' and get_ip_type(current_address) == 'dhcpv6')): + incompatible_addresses.append(current_address) + # inform about error and skip configuration + if incompatible_addresses: + logger.error("IP address {} cannot be configured, because it conflicts with already exists: {}".format(address, incompatible_addresses)) + return + + # configure address + logger.debug("Configuring IP address {} on interface {}".format(address, iface)) + config.set(['interfaces', 'ethernet', iface, 'address'], value=address, replace=False) + + # configure interface from networking config version 1 def set_config_interfaces_v1(config, iface_config): + logger.debug("Configuring network using Cloud-init networking config version 1") # configure physical interfaces if iface_config['type'] == 'physical': iface_name = iface_config['name'] @@ -178,20 +228,13 @@ def set_config_interfaces_v1(config, iface_config): # configure subnets if 'subnets' in iface_config: - # if DHCP is already configured, we should ignore all other addresses, as in VyOS it is impossible to use both on the same interface - dhcp4_configured = False - dhcp6_configured = False for subnet in iface_config['subnets']: # configure DHCP client if subnet['type'] in ['dhcp', 'dhcp4', 'dhcp6']: if subnet['type'] == 'dhcp6': - logger.debug("Configuring DHCPv6 for {}".format(iface_name)) - config.set(['interfaces', 'ethernet', iface_name, 'address'], value='dhcp6', replace=True) - dhcp6_configured = True + set_ipaddress(config, iface_name, 'dhcpv6') else: - logger.debug("Configuring DHCPv4 for {}".format(iface_name)) - config.set(['interfaces', 'ethernet', iface_name, 'address'], value='dhcp', replace=True) - dhcp4_configured = True + set_ipaddress(config, iface_name, 'dhcp') config.set_tag(['interfaces', 'ethernet']) continue @@ -205,18 +248,17 @@ def set_config_interfaces_v1(config, iface_config): ip_address = ip_interface.ip.compressed ip_static_addr = '' # format IPv4 - if ip_version == 4 and ip_address != '0.0.0.0' and dhcp4_configured is not True: + if ip_version == 4 and ip_address != '0.0.0.0': if '/' in subnet['address']: ip_static_addr = ip_interface.compressed else: ip_static_addr = ipaddress.IPv4Interface('{}/{}'.format(ip_address, subnet['netmask'])).compressed # format IPv6 - if ip_version == 6 and dhcp6_configured is not True: + if ip_version == 6: ip_static_addr = ip_interface.compressed # apply to the configuration if ip_static_addr: - logger.debug("Configuring static IP address for {}: {}".format(iface_name, ip_static_addr)) - config.set(['interfaces', 'ethernet', iface_name, 'address'], value=ip_static_addr, replace=True) + set_ipaddress(config, iface_name, ip_static_addr) config.set_tag(['interfaces', 'ethernet']) except Exception as err: logger.error("Impossible to configure static IP address: {}".format(err)) @@ -237,12 +279,12 @@ def set_config_interfaces_v1(config, iface_config): logger.debug("Configuring IPv4 route on {}: {} via {}".format(iface_name, ip_network.compressed, item['gateway'])) config.set(['protocols', 'static', 'route', ip_network.compressed, 'next-hop'], value=item['gateway'], replace=True) config.set_tag(['protocols', 'static', 'route']) - config.set_tag(['protocols', 'static', 'route', item['to'], 'next-hop']) + config.set_tag(['protocols', 'static', 'route', ip_network.compressed, 'next-hop']) if ip_network.version == 6: logger.debug("Configuring IPv6 route on {}: {} via {}".format(iface_name, ip_network.compressed, item['gateway'])) config.set(['protocols', 'static', 'route6', ip_network.compressed, 'next-hop'], value=item['gateway'], replace=True) config.set_tag(['protocols', 'static', 'route6']) - config.set_tag(['protocols', 'static', 'route6', item['to'], 'next-hop']) + config.set_tag(['protocols', 'static', 'route6', ip_network.compressed, 'next-hop']) except Exception as err: logger.error("Impossible to detect IP protocol version: {}".format(err)) @@ -257,26 +299,56 @@ def set_config_interfaces_v1(config, iface_config): logger.debug("Configuring DNS search domain for {}: {}".format(iface_name, item)) config.set(['system', 'domain-search'], value=item, replace=False) + # configure nameservers + if iface_config['type'] == 'nameserver': + for item in iface_config['address']: + logger.debug("Configuring DNS nameserver: {}".format(item)) + config.set(['system', 'name-server'], value=item, replace=False) + + if 'search' in iface_config: + for item in iface_config['search']: + logger.debug("Configuring DNS search domain: {}".format(item)) + config.set(['system', 'domain-search'], value=item, replace=False) + + # configure routes + if iface_config['type'] == 'route': + try: + ip_network = ipaddress.ip_network(iface_config['destination']) + if ip_network.version == 4: + logger.debug("Configuring IPv4 route: {} via {}".format(ip_network.compressed, iface_config['gateway'])) + config.set(['protocols', 'static', 'route', ip_network.compressed, 'next-hop'], value=iface_config['gateway'], replace=True) + config.set_tag(['protocols', 'static', 'route']) + config.set_tag(['protocols', 'static', 'route', ip_network.compressed, 'next-hop']) + if 'metric' in iface_config: + config.set(['protocols', 'static', 'route', ip_network.compressed, 'next-hop', iface_config['gateway'], distance], value=iface_config['metric'], replace=True) + if ip_network.version == 6: + logger.debug("Configuring IPv6 route: {} via {}".format(ip_network.compressed, iface_config['gateway'])) + config.set(['protocols', 'static', 'route6', ip_network.compressed, 'next-hop'], value=iface_config['gateway'], replace=True) + config.set_tag(['protocols', 'static', 'route6']) + config.set_tag(['protocols', 'static', 'route6', ip_network.compressed, 'next-hop']) + if 'metric' in iface_config: + config.set(['protocols', 'static', 'route6', ip_network.compressed, 'next-hop', iface_config['gateway'], distance], value=iface_config['metric'], replace=True) + except Exception as err: + logger.error("Impossible to detect IP protocol version: {}".format(err)) + # configure interface from networking config version 2 def set_config_interfaces_v2(config, iface_name, iface_config): + logger.debug("Configuring network using Cloud-init networking config version 2") # configure DHCP client if 'dhcp4' in iface_config: if iface_config['dhcp4'] is True: - logger.debug("Configuring DHCPv4 for {}".format(iface_name)) - config.set(['interfaces', 'ethernet', iface_name, 'address'], value='dhcp', replace=True) + set_ipaddress(config, iface_name, 'dhcp') config.set_tag(['interfaces', 'ethernet']) if 'dhcp6' in iface_config: if iface_config['dhcp6'] is True: - logger.debug("Configuring DHCPv6 for {}".format(iface_name)) - config.set(['interfaces', 'ethernet', iface_name, 'address'], value='dhcp6', replace=True) + set_ipaddress(config, iface_name, 'dhcpv6') config.set_tag(['interfaces', 'ethernet']) # configure static addresses if 'addresses' in iface_config: for item in iface_config['addresses']: - logger.debug("Configuring static IP address for {}: {}".format(iface_name, item)) - config.set(['interfaces', 'ethernet', iface_name, 'address'], value=item, replace=True) + set_ipaddress(config, iface_name, item) config.set_tag(['interfaces', 'ethernet']) # configure gateways @@ -329,7 +401,7 @@ def set_config_interfaces_v2(config, iface_name, iface_config): # configure DHCP client for eth0 interface (fallback) def set_config_dhcp(config): logger.debug("Configuring DHCPv4 on eth0 interface (fallback)") - config.set(['interfaces', 'ethernet', 'eth0', 'address'], value='dhcp', replace=True) + set_ipaddress(config, 'eth0', 'dhcp') config.set_tag(['interfaces', 'ethernet']) -- cgit v1.2.3