Blame cloudinit/net/network_state.py

Packit Service a04d08
# Copyright (C) 2017 Canonical Ltd.
Packit Service a04d08
#
Packit Service a04d08
# Author: Ryan Harper <ryan.harper@canonical.com>
Packit Service a04d08
#
Packit Service a04d08
# This file is part of cloud-init. See LICENSE file for license information.
Packit Service a04d08
Packit Service a04d08
import copy
Packit Service a04d08
import functools
Packit Service a04d08
import logging
Packit Service a04d08
import socket
Packit Service a04d08
import struct
Packit Service a04d08
Packit Service a04d08
from cloudinit import safeyaml
Packit Service a04d08
from cloudinit import util
Packit Service a04d08
Packit Service a04d08
LOG = logging.getLogger(__name__)
Packit Service a04d08
Packit Service a04d08
NETWORK_STATE_VERSION = 1
Packit Service a04d08
IPV6_DYNAMIC_TYPES = ['dhcp6',
Packit Service a04d08
                      'ipv6_slaac',
Packit Service a04d08
                      'ipv6_dhcpv6-stateless',
Packit Service a04d08
                      'ipv6_dhcpv6-stateful']
Packit Service a04d08
NETWORK_STATE_REQUIRED_KEYS = {
Packit Service a04d08
    1: ['version', 'config', 'network_state'],
Packit Service a04d08
}
Packit Service a04d08
NETWORK_V2_KEY_FILTER = [
Packit Service a04d08
    'addresses', 'dhcp4', 'dhcp4-overrides', 'dhcp6', 'dhcp6-overrides',
Packit Service a04d08
    'gateway4', 'gateway6', 'interfaces', 'match', 'mtu', 'nameservers',
Packit Service a04d08
    'renderer', 'set-name', 'wakeonlan', 'accept-ra'
Packit Service a04d08
]
Packit Service a04d08
Packit Service a04d08
NET_CONFIG_TO_V2 = {
Packit Service a04d08
    'bond': {'bond-ad-select': 'ad-select',
Packit Service a04d08
             'bond-arp-interval': 'arp-interval',
Packit Service a04d08
             'bond-arp-ip-target': 'arp-ip-target',
Packit Service a04d08
             'bond-arp-validate': 'arp-validate',
Packit Service a04d08
             'bond-downdelay': 'down-delay',
Packit Service a04d08
             'bond-fail-over-mac': 'fail-over-mac-policy',
Packit Service a04d08
             'bond-lacp-rate': 'lacp-rate',
Packit Service a04d08
             'bond-miimon': 'mii-monitor-interval',
Packit Service a04d08
             'bond-min-links': 'min-links',
Packit Service a04d08
             'bond-mode': 'mode',
Packit Service a04d08
             'bond-num-grat-arp': 'gratuitious-arp',
Packit Service a04d08
             'bond-primary': 'primary',
Packit Service a04d08
             'bond-primary-reselect': 'primary-reselect-policy',
Packit Service a04d08
             'bond-updelay': 'up-delay',
Packit Service a04d08
             'bond-xmit-hash-policy': 'transmit-hash-policy'},
Packit Service a04d08
    'bridge': {'bridge_ageing': 'ageing-time',
Packit Service a04d08
               'bridge_bridgeprio': 'priority',
Packit Service a04d08
               'bridge_fd': 'forward-delay',
Packit Service a04d08
               'bridge_gcint': None,
Packit Service a04d08
               'bridge_hello': 'hello-time',
Packit Service a04d08
               'bridge_maxage': 'max-age',
Packit Service a04d08
               'bridge_maxwait': None,
Packit Service a04d08
               'bridge_pathcost': 'path-cost',
Packit Service a04d08
               'bridge_portprio': 'port-priority',
Packit Service a04d08
               'bridge_stp': 'stp',
Packit Service a04d08
               'bridge_waitport': None}}
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def parse_net_config_data(net_config, skip_broken=True):
Packit Service a04d08
    """Parses the config, returns NetworkState object
Packit Service a04d08
Packit Service a04d08
    :param net_config: curtin network config dict
Packit Service a04d08
    """
Packit Service a04d08
    state = None
Packit Service a04d08
    version = net_config.get('version')
Packit Service a04d08
    config = net_config.get('config')
Packit Service a04d08
    if version == 2:
Packit Service a04d08
        # v2 does not have explicit 'config' key so we
Packit Service a04d08
        # pass the whole net-config as-is
Packit Service a04d08
        config = net_config
Packit Service a04d08
Packit Service a04d08
    if version and config is not None:
Packit Service a04d08
        nsi = NetworkStateInterpreter(version=version, config=config)
Packit Service a04d08
        nsi.parse_config(skip_broken=skip_broken)
Packit Service a04d08
        state = nsi.get_network_state()
Packit Service a04d08
Packit Service a04d08
    return state
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def parse_net_config(path, skip_broken=True):
Packit Service a04d08
    """Parses a curtin network configuration file and
Packit Service a04d08
       return network state"""
Packit Service a04d08
    ns = None
Packit Service a04d08
    net_config = util.read_conf(path)
Packit Service a04d08
    if 'network' in net_config:
Packit Service a04d08
        ns = parse_net_config_data(net_config.get('network'),
Packit Service a04d08
                                   skip_broken=skip_broken)
Packit Service a04d08
    return ns
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def from_state_file(state_file):
Packit Service a04d08
    state = util.read_conf(state_file)
Packit Service a04d08
    nsi = NetworkStateInterpreter()
Packit Service a04d08
    nsi.load(state)
Packit Service a04d08
    return nsi
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def diff_keys(expected, actual):
Packit Service a04d08
    missing = set(expected)
Packit Service a04d08
    for key in actual:
Packit Service a04d08
        missing.discard(key)
Packit Service a04d08
    return missing
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
class InvalidCommand(Exception):
Packit Service a04d08
    pass
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def ensure_command_keys(required_keys):
Packit Service a04d08
Packit Service a04d08
    def wrapper(func):
Packit Service a04d08
Packit Service a04d08
        @functools.wraps(func)
Packit Service a04d08
        def decorator(self, command, *args, **kwargs):
Packit Service a04d08
            if required_keys:
Packit Service a04d08
                missing_keys = diff_keys(required_keys, command)
Packit Service a04d08
                if missing_keys:
Packit Service a04d08
                    raise InvalidCommand("Command missing %s of required"
Packit Service a04d08
                                         " keys %s" % (missing_keys,
Packit Service a04d08
                                                       required_keys))
Packit Service a04d08
            return func(self, command, *args, **kwargs)
Packit Service a04d08
Packit Service a04d08
        return decorator
Packit Service a04d08
Packit Service a04d08
    return wrapper
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
class CommandHandlerMeta(type):
Packit Service a04d08
    """Metaclass that dynamically creates a 'command_handlers' attribute.
Packit Service a04d08
Packit Service a04d08
    This will scan the to-be-created class for methods that start with
Packit Service a04d08
    'handle_' and on finding those will populate a class attribute mapping
Packit Service a04d08
    so that those methods can be quickly located and called.
Packit Service a04d08
    """
Packit Service a04d08
    def __new__(cls, name, parents, dct):
Packit Service a04d08
        command_handlers = {}
Packit Service a04d08
        for attr_name, attr in dct.items():
Packit Service a04d08
            if callable(attr) and attr_name.startswith('handle_'):
Packit Service a04d08
                handles_what = attr_name[len('handle_'):]
Packit Service a04d08
                if handles_what:
Packit Service a04d08
                    command_handlers[handles_what] = attr
Packit Service a04d08
        dct['command_handlers'] = command_handlers
Packit Service a04d08
        return super(CommandHandlerMeta, cls).__new__(cls, name,
Packit Service a04d08
                                                      parents, dct)
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
class NetworkState(object):
Packit Service a04d08
Packit Service a04d08
    def __init__(self, network_state, version=NETWORK_STATE_VERSION):
Packit Service a04d08
        self._network_state = copy.deepcopy(network_state)
Packit Service a04d08
        self._version = version
Packit Service a04d08
        self.use_ipv6 = network_state.get('use_ipv6', False)
Packit Service a04d08
        self._has_default_route = None
Packit Service a04d08
Packit Service a04d08
    @property
Packit Service a04d08
    def config(self):
Packit Service a04d08
        return self._network_state['config']
Packit Service a04d08
Packit Service a04d08
    @property
Packit Service a04d08
    def version(self):
Packit Service a04d08
        return self._version
Packit Service a04d08
Packit Service a04d08
    @property
Packit Service a04d08
    def dns_nameservers(self):
Packit Service a04d08
        try:
Packit Service a04d08
            return self._network_state['dns']['nameservers']
Packit Service a04d08
        except KeyError:
Packit Service a04d08
            return []
Packit Service a04d08
Packit Service a04d08
    @property
Packit Service a04d08
    def dns_searchdomains(self):
Packit Service a04d08
        try:
Packit Service a04d08
            return self._network_state['dns']['search']
Packit Service a04d08
        except KeyError:
Packit Service a04d08
            return []
Packit Service a04d08
Packit Service a04d08
    @property
Packit Service a04d08
    def has_default_route(self):
Packit Service a04d08
        if self._has_default_route is None:
Packit Service a04d08
            self._has_default_route = self._maybe_has_default_route()
Packit Service a04d08
        return self._has_default_route
Packit Service a04d08
Packit Service a04d08
    def iter_interfaces(self, filter_func=None):
Packit Service a04d08
        ifaces = self._network_state.get('interfaces', {})
Packit Service 9bfd13
        for iface in ifaces.values():
Packit Service a04d08
            if filter_func is None:
Packit Service a04d08
                yield iface
Packit Service a04d08
            else:
Packit Service a04d08
                if filter_func(iface):
Packit Service a04d08
                    yield iface
Packit Service a04d08
Packit Service a04d08
    def iter_routes(self, filter_func=None):
Packit Service a04d08
        for route in self._network_state.get('routes', []):
Packit Service a04d08
            if filter_func is not None:
Packit Service a04d08
                if filter_func(route):
Packit Service a04d08
                    yield route
Packit Service a04d08
            else:
Packit Service a04d08
                yield route
Packit Service a04d08
Packit Service a04d08
    def _maybe_has_default_route(self):
Packit Service a04d08
        for route in self.iter_routes():
Packit Service a04d08
            if self._is_default_route(route):
Packit Service a04d08
                return True
Packit Service a04d08
        for iface in self.iter_interfaces():
Packit Service a04d08
            for subnet in iface.get('subnets', []):
Packit Service a04d08
                for route in subnet.get('routes', []):
Packit Service a04d08
                    if self._is_default_route(route):
Packit Service a04d08
                        return True
Packit Service a04d08
        return False
Packit Service a04d08
Packit Service a04d08
    def _is_default_route(self, route):
Packit Service a04d08
        default_nets = ('::', '0.0.0.0')
Packit Service a04d08
        return (
Packit Service a04d08
            route.get('prefix') == 0
Packit Service a04d08
            and route.get('network') in default_nets
Packit Service 9bfd13
        )
Packit Service a04d08
Packit Service a04d08
Packit Service 9bfd13
class NetworkStateInterpreter(metaclass=CommandHandlerMeta):
Packit Service a04d08
Packit Service a04d08
    initial_network_state = {
Packit Service a04d08
        'interfaces': {},
Packit Service a04d08
        'routes': [],
Packit Service a04d08
        'dns': {
Packit Service a04d08
            'nameservers': [],
Packit Service a04d08
            'search': [],
Packit Service a04d08
        },
Packit Service a04d08
        'use_ipv6': False,
Packit Service a04d08
        'config': None,
Packit Service a04d08
    }
Packit Service a04d08
Packit Service a04d08
    def __init__(self, version=NETWORK_STATE_VERSION, config=None):
Packit Service a04d08
        self._version = version
Packit Service a04d08
        self._config = config
Packit Service a04d08
        self._network_state = copy.deepcopy(self.initial_network_state)
Packit Service a04d08
        self._network_state['config'] = config
Packit Service a04d08
        self._parsed = False
Packit Service a04d08
Packit Service a04d08
    @property
Packit Service a04d08
    def network_state(self):
Packit Service a04d08
        return NetworkState(self._network_state, version=self._version)
Packit Service a04d08
Packit Service a04d08
    @property
Packit Service a04d08
    def use_ipv6(self):
Packit Service a04d08
        return self._network_state.get('use_ipv6')
Packit Service a04d08
Packit Service a04d08
    @use_ipv6.setter
Packit Service a04d08
    def use_ipv6(self, val):
Packit Service a04d08
        self._network_state.update({'use_ipv6': val})
Packit Service a04d08
Packit Service a04d08
    def dump(self):
Packit Service a04d08
        state = {
Packit Service a04d08
            'version': self._version,
Packit Service a04d08
            'config': self._config,
Packit Service a04d08
            'network_state': self._network_state,
Packit Service a04d08
        }
Packit Service a04d08
        return safeyaml.dumps(state)
Packit Service a04d08
Packit Service a04d08
    def load(self, state):
Packit Service a04d08
        if 'version' not in state:
Packit Service a04d08
            LOG.error('Invalid state, missing version field')
Packit Service a04d08
            raise ValueError('Invalid state, missing version field')
Packit Service a04d08
Packit Service a04d08
        required_keys = NETWORK_STATE_REQUIRED_KEYS[state['version']]
Packit Service a04d08
        missing_keys = diff_keys(required_keys, state)
Packit Service a04d08
        if missing_keys:
Packit Service a04d08
            msg = 'Invalid state, missing keys: %s' % (missing_keys)
Packit Service a04d08
            LOG.error(msg)
Packit Service a04d08
            raise ValueError(msg)
Packit Service a04d08
Packit Service a04d08
        # v1 - direct attr mapping, except version
Packit Service a04d08
        for key in [k for k in required_keys if k not in ['version']]:
Packit Service a04d08
            setattr(self, key, state[key])
Packit Service a04d08
Packit Service a04d08
    def dump_network_state(self):
Packit Service a04d08
        return safeyaml.dumps(self._network_state)
Packit Service a04d08
Packit Service a04d08
    def as_dict(self):
Packit Service a04d08
        return {'version': self._version, 'config': self._config}
Packit Service a04d08
Packit Service a04d08
    def get_network_state(self):
Packit Service a04d08
        ns = self.network_state
Packit Service a04d08
        return ns
Packit Service a04d08
Packit Service a04d08
    def parse_config(self, skip_broken=True):
Packit Service a04d08
        if self._version == 1:
Packit Service a04d08
            self.parse_config_v1(skip_broken=skip_broken)
Packit Service a04d08
            self._parsed = True
Packit Service a04d08
        elif self._version == 2:
Packit Service a04d08
            self.parse_config_v2(skip_broken=skip_broken)
Packit Service a04d08
            self._parsed = True
Packit Service a04d08
Packit Service a04d08
    def parse_config_v1(self, skip_broken=True):
Packit Service a04d08
        for command in self._config:
Packit Service a04d08
            command_type = command['type']
Packit Service a04d08
            try:
Packit Service a04d08
                handler = self.command_handlers[command_type]
Packit Service 9bfd13
            except KeyError as e:
Packit Service 9bfd13
                raise RuntimeError(
Packit Service 9bfd13
                    "No handler found for  command '%s'" % command_type
Packit Service 9bfd13
                ) from e
Packit Service a04d08
            try:
Packit Service a04d08
                handler(self, command)
Packit Service a04d08
            except InvalidCommand:
Packit Service a04d08
                if not skip_broken:
Packit Service a04d08
                    raise
Packit Service a04d08
                else:
Packit Service a04d08
                    LOG.warning("Skipping invalid command: %s", command,
Packit Service a04d08
                                exc_info=True)
Packit Service a04d08
                    LOG.debug(self.dump_network_state())
Packit Service a04d08
Packit Service a04d08
    def parse_config_v2(self, skip_broken=True):
Packit Service a04d08
        for command_type, command in self._config.items():
Packit Service 9bfd13
            if command_type in ['version', 'renderer']:
Packit Service a04d08
                continue
Packit Service a04d08
            try:
Packit Service a04d08
                handler = self.command_handlers[command_type]
Packit Service 9bfd13
            except KeyError as e:
Packit Service 9bfd13
                raise RuntimeError(
Packit Service 9bfd13
                    "No handler found for command '%s'" % command_type
Packit Service 9bfd13
                ) from e
Packit Service a04d08
            try:
Packit Service a04d08
                handler(self, command)
Packit Service a04d08
                self._v2_common(command)
Packit Service a04d08
            except InvalidCommand:
Packit Service a04d08
                if not skip_broken:
Packit Service a04d08
                    raise
Packit Service a04d08
                else:
Packit Service a04d08
                    LOG.warning("Skipping invalid command: %s", command,
Packit Service a04d08
                                exc_info=True)
Packit Service a04d08
                    LOG.debug(self.dump_network_state())
Packit Service a04d08
Packit Service a04d08
    @ensure_command_keys(['name'])
Packit Service a04d08
    def handle_loopback(self, command):
Packit Service a04d08
        return self.handle_physical(command)
Packit Service a04d08
Packit Service a04d08
    @ensure_command_keys(['name'])
Packit Service a04d08
    def handle_physical(self, command):
Packit Service a04d08
        '''
Packit Service a04d08
        command = {
Packit Service a04d08
            'type': 'physical',
Packit Service a04d08
            'mac_address': 'c0:d6:9f:2c:e8:80',
Packit Service a04d08
            'name': 'eth0',
Packit Service a04d08
            'subnets': [
Packit Service a04d08
                {'type': 'dhcp4'}
Packit Service a04d08
             ],
Packit Service a04d08
            'accept-ra': 'true'
Packit Service a04d08
        }
Packit Service a04d08
        '''
Packit Service a04d08
Packit Service a04d08
        interfaces = self._network_state.get('interfaces', {})
Packit Service a04d08
        iface = interfaces.get(command['name'], {})
Packit Service a04d08
        for param, val in command.get('params', {}).items():
Packit Service a04d08
            iface.update({param: val})
Packit Service a04d08
Packit Service a04d08
        # convert subnet ipv6 netmask to cidr as needed
Packit Service a04d08
        subnets = _normalize_subnets(command.get('subnets'))
Packit Service a04d08
Packit Service a04d08
        # automatically set 'use_ipv6' if any addresses are ipv6
Packit Service a04d08
        if not self.use_ipv6:
Packit Service a04d08
            for subnet in subnets:
Packit Service a04d08
                if (subnet.get('type').endswith('6') or
Packit Service a04d08
                        is_ipv6_addr(subnet.get('address'))):
Packit Service a04d08
                    self.use_ipv6 = True
Packit Service a04d08
                    break
Packit Service a04d08
Packit Service a04d08
        accept_ra = command.get('accept-ra', None)
Packit Service a04d08
        if accept_ra is not None:
Packit Service a04d08
            accept_ra = util.is_true(accept_ra)
Packit Service a04d08
        iface.update({
Packit Service a04d08
            'name': command.get('name'),
Packit Service a04d08
            'type': command.get('type'),
Packit Service a04d08
            'mac_address': command.get('mac_address'),
Packit Service a04d08
            'inet': 'inet',
Packit Service a04d08
            'mode': 'manual',
Packit Service a04d08
            'mtu': command.get('mtu'),
Packit Service a04d08
            'address': None,
Packit Service a04d08
            'gateway': None,
Packit Service a04d08
            'subnets': subnets,
Packit Service a04d08
            'accept-ra': accept_ra
Packit Service a04d08
        })
Packit Service a04d08
        self._network_state['interfaces'].update({command.get('name'): iface})
Packit Service a04d08
        self.dump_network_state()
Packit Service a04d08
Packit Service a04d08
    @ensure_command_keys(['name', 'vlan_id', 'vlan_link'])
Packit Service a04d08
    def handle_vlan(self, command):
Packit Service a04d08
        '''
Packit Service a04d08
            auto eth0.222
Packit Service a04d08
            iface eth0.222 inet static
Packit Service a04d08
                    address 10.10.10.1
Packit Service a04d08
                    netmask 255.255.255.0
Packit Service a04d08
                    hwaddress ether BC:76:4E:06:96:B3
Packit Service a04d08
                    vlan-raw-device eth0
Packit Service a04d08
        '''
Packit Service a04d08
        interfaces = self._network_state.get('interfaces', {})
Packit Service a04d08
        self.handle_physical(command)
Packit Service a04d08
        iface = interfaces.get(command.get('name'), {})
Packit Service a04d08
        iface['vlan-raw-device'] = command.get('vlan_link')
Packit Service a04d08
        iface['vlan_id'] = command.get('vlan_id')
Packit Service a04d08
        interfaces.update({iface['name']: iface})
Packit Service a04d08
Packit Service a04d08
    @ensure_command_keys(['name', 'bond_interfaces', 'params'])
Packit Service a04d08
    def handle_bond(self, command):
Packit Service a04d08
        '''
Packit Service a04d08
    #/etc/network/interfaces
Packit Service a04d08
    auto eth0
Packit Service a04d08
    iface eth0 inet manual
Packit Service a04d08
        bond-master bond0
Packit Service a04d08
        bond-mode 802.3ad
Packit Service a04d08
Packit Service a04d08
    auto eth1
Packit Service a04d08
    iface eth1 inet manual
Packit Service a04d08
        bond-master bond0
Packit Service a04d08
        bond-mode 802.3ad
Packit Service a04d08
Packit Service a04d08
    auto bond0
Packit Service a04d08
    iface bond0 inet static
Packit Service a04d08
         address 192.168.0.10
Packit Service a04d08
         gateway 192.168.0.1
Packit Service a04d08
         netmask 255.255.255.0
Packit Service a04d08
         bond-slaves none
Packit Service a04d08
         bond-mode 802.3ad
Packit Service a04d08
         bond-miimon 100
Packit Service a04d08
         bond-downdelay 200
Packit Service a04d08
         bond-updelay 200
Packit Service a04d08
         bond-lacp-rate 4
Packit Service a04d08
        '''
Packit Service a04d08
Packit Service a04d08
        self.handle_physical(command)
Packit Service a04d08
        interfaces = self._network_state.get('interfaces')
Packit Service a04d08
        iface = interfaces.get(command.get('name'), {})
Packit Service a04d08
        for param, val in command.get('params').items():
Packit Service a04d08
            iface.update({param: val})
Packit Service a04d08
        iface.update({'bond-slaves': 'none'})
Packit Service a04d08
        self._network_state['interfaces'].update({iface['name']: iface})
Packit Service a04d08
Packit Service a04d08
        # handle bond slaves
Packit Service a04d08
        for ifname in command.get('bond_interfaces'):
Packit Service a04d08
            if ifname not in interfaces:
Packit Service a04d08
                cmd = {
Packit Service a04d08
                    'name': ifname,
Packit Service a04d08
                    'type': 'bond',
Packit Service a04d08
                }
Packit Service a04d08
                # inject placeholder
Packit Service a04d08
                self.handle_physical(cmd)
Packit Service a04d08
Packit Service a04d08
            interfaces = self._network_state.get('interfaces', {})
Packit Service a04d08
            bond_if = interfaces.get(ifname)
Packit Service a04d08
            bond_if['bond-master'] = command.get('name')
Packit Service a04d08
            # copy in bond config into slave
Packit Service a04d08
            for param, val in command.get('params').items():
Packit Service a04d08
                bond_if.update({param: val})
Packit Service a04d08
            self._network_state['interfaces'].update({ifname: bond_if})
Packit Service a04d08
Packit Service a04d08
    @ensure_command_keys(['name', 'bridge_interfaces'])
Packit Service a04d08
    def handle_bridge(self, command):
Packit Service a04d08
        '''
Packit Service a04d08
            auto br0
Packit Service a04d08
            iface br0 inet static
Packit Service a04d08
                    address 10.10.10.1
Packit Service a04d08
                    netmask 255.255.255.0
Packit Service a04d08
                    bridge_ports eth0 eth1
Packit Service a04d08
                    bridge_stp off
Packit Service a04d08
                    bridge_fd 0
Packit Service a04d08
                    bridge_maxwait 0
Packit Service a04d08
Packit Service a04d08
        bridge_params = [
Packit Service a04d08
            "bridge_ports",
Packit Service a04d08
            "bridge_ageing",
Packit Service a04d08
            "bridge_bridgeprio",
Packit Service a04d08
            "bridge_fd",
Packit Service a04d08
            "bridge_gcint",
Packit Service a04d08
            "bridge_hello",
Packit Service a04d08
            "bridge_hw",
Packit Service a04d08
            "bridge_maxage",
Packit Service a04d08
            "bridge_maxwait",
Packit Service a04d08
            "bridge_pathcost",
Packit Service a04d08
            "bridge_portprio",
Packit Service a04d08
            "bridge_stp",
Packit Service a04d08
            "bridge_waitport",
Packit Service a04d08
        ]
Packit Service a04d08
        '''
Packit Service a04d08
Packit Service a04d08
        # find one of the bridge port ifaces to get mac_addr
Packit Service a04d08
        # handle bridge_slaves
Packit Service a04d08
        interfaces = self._network_state.get('interfaces', {})
Packit Service a04d08
        for ifname in command.get('bridge_interfaces'):
Packit Service a04d08
            if ifname in interfaces:
Packit Service a04d08
                continue
Packit Service a04d08
Packit Service a04d08
            cmd = {
Packit Service a04d08
                'name': ifname,
Packit Service a04d08
            }
Packit Service a04d08
            # inject placeholder
Packit Service a04d08
            self.handle_physical(cmd)
Packit Service a04d08
Packit Service a04d08
        interfaces = self._network_state.get('interfaces', {})
Packit Service a04d08
        self.handle_physical(command)
Packit Service a04d08
        iface = interfaces.get(command.get('name'), {})
Packit Service a04d08
        iface['bridge_ports'] = command['bridge_interfaces']
Packit Service a04d08
        for param, val in command.get('params', {}).items():
Packit Service a04d08
            iface.update({param: val})
Packit Service a04d08
Packit Service a04d08
        # convert value to boolean
Packit Service a04d08
        bridge_stp = iface.get('bridge_stp')
Packit Service a04d08
        if bridge_stp is not None and type(bridge_stp) != bool:
Packit Service a04d08
            if bridge_stp in ['on', '1', 1]:
Packit Service a04d08
                bridge_stp = True
Packit Service a04d08
            elif bridge_stp in ['off', '0', 0]:
Packit Service a04d08
                bridge_stp = False
Packit Service a04d08
            else:
Packit Service a04d08
                raise ValueError(
Packit Service a04d08
                    'Cannot convert bridge_stp value ({stp}) to'
Packit Service a04d08
                    ' boolean'.format(stp=bridge_stp))
Packit Service a04d08
            iface.update({'bridge_stp': bridge_stp})
Packit Service a04d08
Packit Service a04d08
        interfaces.update({iface['name']: iface})
Packit Service a04d08
Packit Service a04d08
    @ensure_command_keys(['name'])
Packit Service a04d08
    def handle_infiniband(self, command):
Packit Service a04d08
        self.handle_physical(command)
Packit Service a04d08
Packit Service a04d08
    @ensure_command_keys(['address'])
Packit Service a04d08
    def handle_nameserver(self, command):
Packit Service a04d08
        dns = self._network_state.get('dns')
Packit Service a04d08
        if 'address' in command:
Packit Service a04d08
            addrs = command['address']
Packit Service a04d08
            if not type(addrs) == list:
Packit Service a04d08
                addrs = [addrs]
Packit Service a04d08
            for addr in addrs:
Packit Service a04d08
                dns['nameservers'].append(addr)
Packit Service a04d08
        if 'search' in command:
Packit Service a04d08
            paths = command['search']
Packit Service a04d08
            if not isinstance(paths, list):
Packit Service a04d08
                paths = [paths]
Packit Service a04d08
            for path in paths:
Packit Service a04d08
                dns['search'].append(path)
Packit Service a04d08
Packit Service a04d08
    @ensure_command_keys(['destination'])
Packit Service a04d08
    def handle_route(self, command):
Packit Service a04d08
        self._network_state['routes'].append(_normalize_route(command))
Packit Service a04d08
Packit Service a04d08
    # V2 handlers
Packit Service a04d08
    def handle_bonds(self, command):
Packit Service a04d08
        '''
Packit Service a04d08
        v2_command = {
Packit Service a04d08
          bond0: {
Packit Service a04d08
            'interfaces': ['interface0', 'interface1'],
Packit Service a04d08
            'parameters': {
Packit Service a04d08
               'mii-monitor-interval': 100,
Packit Service a04d08
               'mode': '802.3ad',
Packit Service a04d08
               'xmit_hash_policy': 'layer3+4'}},
Packit Service a04d08
          bond1: {
Packit Service a04d08
            'bond-slaves': ['interface2', 'interface7'],
Packit Service a04d08
            'parameters': {
Packit Service a04d08
                'mode': 1,
Packit Service a04d08
            }
Packit Service a04d08
          }
Packit Service a04d08
        }
Packit Service a04d08
Packit Service a04d08
        v1_command = {
Packit Service a04d08
            'type': 'bond'
Packit Service a04d08
            'name': 'bond0',
Packit Service a04d08
            'bond_interfaces': [interface0, interface1],
Packit Service a04d08
            'params': {
Packit Service a04d08
                'bond-mode': '802.3ad',
Packit Service a04d08
                'bond_miimon: 100,
Packit Service a04d08
                'bond_xmit_hash_policy': 'layer3+4',
Packit Service a04d08
            }
Packit Service a04d08
        }
Packit Service a04d08
Packit Service a04d08
        '''
Packit Service a04d08
        self._handle_bond_bridge(command, cmd_type='bond')
Packit Service a04d08
Packit Service a04d08
    def handle_bridges(self, command):
Packit Service a04d08
Packit Service a04d08
        '''
Packit Service a04d08
        v2_command = {
Packit Service a04d08
          br0: {
Packit Service a04d08
            'interfaces': ['interface0', 'interface1'],
Packit Service a04d08
            'forward-delay': 0,
Packit Service a04d08
            'stp': False,
Packit Service a04d08
            'maxwait': 0,
Packit Service a04d08
          }
Packit Service a04d08
        }
Packit Service a04d08
Packit Service a04d08
        v1_command = {
Packit Service a04d08
            'type': 'bridge'
Packit Service a04d08
            'name': 'br0',
Packit Service a04d08
            'bridge_interfaces': [interface0, interface1],
Packit Service a04d08
            'params': {
Packit Service a04d08
                'bridge_stp': 'off',
Packit Service a04d08
                'bridge_fd: 0,
Packit Service a04d08
                'bridge_maxwait': 0
Packit Service a04d08
            }
Packit Service a04d08
        }
Packit Service a04d08
Packit Service a04d08
        '''
Packit Service a04d08
        self._handle_bond_bridge(command, cmd_type='bridge')
Packit Service a04d08
Packit Service a04d08
    def handle_ethernets(self, command):
Packit Service a04d08
        '''
Packit Service a04d08
        ethernets:
Packit Service a04d08
          eno1:
Packit Service a04d08
            match:
Packit Service a04d08
              macaddress: 00:11:22:33:44:55
Packit Service a04d08
              driver: hv_netsvc
Packit Service a04d08
            wakeonlan: true
Packit Service a04d08
            dhcp4: true
Packit Service a04d08
            dhcp6: false
Packit Service a04d08
            addresses:
Packit Service a04d08
              - 192.168.14.2/24
Packit Service a04d08
              - 2001:1::1/64
Packit Service a04d08
            gateway4: 192.168.14.1
Packit Service a04d08
            gateway6: 2001:1::2
Packit Service a04d08
            nameservers:
Packit Service a04d08
              search: [foo.local, bar.local]
Packit Service a04d08
              addresses: [8.8.8.8, 8.8.4.4]
Packit Service a04d08
          lom:
Packit Service a04d08
            match:
Packit Service a04d08
              driver: ixgbe
Packit Service a04d08
            set-name: lom1
Packit Service a04d08
            dhcp6: true
Packit Service a04d08
            accept-ra: true
Packit Service a04d08
          switchports:
Packit Service a04d08
            match:
Packit Service a04d08
              name: enp2*
Packit Service a04d08
            mtu: 1280
Packit Service a04d08
Packit Service a04d08
        command = {
Packit Service a04d08
            'type': 'physical',
Packit Service a04d08
            'mac_address': 'c0:d6:9f:2c:e8:80',
Packit Service a04d08
            'name': 'eth0',
Packit Service a04d08
            'subnets': [
Packit Service a04d08
                {'type': 'dhcp4'}
Packit Service a04d08
             ]
Packit Service a04d08
        }
Packit Service a04d08
        '''
Packit Service a04d08
        for eth, cfg in command.items():
Packit Service a04d08
            phy_cmd = {
Packit Service a04d08
                'type': 'physical',
Packit Service a04d08
                'name': cfg.get('set-name', eth),
Packit Service a04d08
            }
Packit Service a04d08
            match = cfg.get('match', {})
Packit Service a04d08
            mac_address = match.get('macaddress', None)
Packit Service a04d08
            if not mac_address:
Packit Service a04d08
                LOG.debug('NetworkState Version2: missing "macaddress" info '
Packit Service a04d08
                          'in config entry: %s: %s', eth, str(cfg))
Packit Service a04d08
            phy_cmd['mac_address'] = mac_address
Packit Service a04d08
            driver = match.get('driver', None)
Packit Service a04d08
            if driver:
Packit Service a04d08
                phy_cmd['params'] = {'driver': driver}
Packit Service a04d08
            for key in ['mtu', 'match', 'wakeonlan', 'accept-ra']:
Packit Service a04d08
                if key in cfg:
Packit Service a04d08
                    phy_cmd[key] = cfg[key]
Packit Service a04d08
Packit Service a04d08
            subnets = self._v2_to_v1_ipcfg(cfg)
Packit Service a04d08
            if len(subnets) > 0:
Packit Service a04d08
                phy_cmd.update({'subnets': subnets})
Packit Service a04d08
Packit Service a04d08
            LOG.debug('v2(ethernets) -> v1(physical):\n%s', phy_cmd)
Packit Service a04d08
            self.handle_physical(phy_cmd)
Packit Service a04d08
Packit Service a04d08
    def handle_vlans(self, command):
Packit Service a04d08
        '''
Packit Service a04d08
        v2_vlans = {
Packit Service a04d08
            'eth0.123': {
Packit Service a04d08
                'id': 123,
Packit Service a04d08
                'link': 'eth0',
Packit Service a04d08
                'dhcp4': True,
Packit Service a04d08
            }
Packit Service a04d08
        }
Packit Service a04d08
Packit Service a04d08
        v1_command = {
Packit Service a04d08
            'type': 'vlan',
Packit Service a04d08
            'name': 'eth0.123',
Packit Service a04d08
            'vlan_link': 'eth0',
Packit Service a04d08
            'vlan_id': 123,
Packit Service a04d08
            'subnets': [{'type': 'dhcp4'}],
Packit Service a04d08
        }
Packit Service a04d08
        '''
Packit Service a04d08
        for vlan, cfg in command.items():
Packit Service a04d08
            vlan_cmd = {
Packit Service a04d08
                'type': 'vlan',
Packit Service a04d08
                'name': vlan,
Packit Service a04d08
                'vlan_id': cfg.get('id'),
Packit Service a04d08
                'vlan_link': cfg.get('link'),
Packit Service a04d08
            }
Packit Service a04d08
            if 'mtu' in cfg:
Packit Service a04d08
                vlan_cmd['mtu'] = cfg['mtu']
Packit Service a04d08
            subnets = self._v2_to_v1_ipcfg(cfg)
Packit Service a04d08
            if len(subnets) > 0:
Packit Service a04d08
                vlan_cmd.update({'subnets': subnets})
Packit Service a04d08
            LOG.debug('v2(vlans) -> v1(vlan):\n%s', vlan_cmd)
Packit Service a04d08
            self.handle_vlan(vlan_cmd)
Packit Service a04d08
Packit Service a04d08
    def handle_wifis(self, command):
Packit Service a04d08
        LOG.warning('Wifi configuration is only available to distros with'
Packit Service 9bfd13
                    ' netplan rendering support.')
Packit Service a04d08
Packit Service a04d08
    def _v2_common(self, cfg):
Packit Service a04d08
        LOG.debug('v2_common: handling config:\n%s', cfg)
Packit Service a04d08
        if 'nameservers' in cfg:
Packit Service a04d08
            search = cfg.get('nameservers').get('search', [])
Packit Service a04d08
            dns = cfg.get('nameservers').get('addresses', [])
Packit Service a04d08
            name_cmd = {'type': 'nameserver'}
Packit Service a04d08
            if len(search) > 0:
Packit Service a04d08
                name_cmd.update({'search': search})
Packit Service a04d08
            if len(dns) > 0:
Packit Service a04d08
                name_cmd.update({'addresses': dns})
Packit Service a04d08
            LOG.debug('v2(nameserver) -> v1(nameserver):\n%s', name_cmd)
Packit Service a04d08
            self.handle_nameserver(name_cmd)
Packit Service a04d08
Packit Service a04d08
    def _handle_bond_bridge(self, command, cmd_type=None):
Packit Service a04d08
        """Common handler for bond and bridge types"""
Packit Service a04d08
Packit Service a04d08
        # inverse mapping for v2 keynames to v1 keynames
Packit Service a04d08
        v2key_to_v1 = dict((v, k) for k, v in
Packit Service a04d08
                           NET_CONFIG_TO_V2.get(cmd_type).items())
Packit Service a04d08
Packit Service a04d08
        for item_name, item_cfg in command.items():
Packit Service a04d08
            item_params = dict((key, value) for (key, value) in
Packit Service a04d08
                               item_cfg.items() if key not in
Packit Service a04d08
                               NETWORK_V2_KEY_FILTER)
Packit Service 9bfd13
            # we accept the fixed spelling, but write the old for compatibility
Packit Service a04d08
            # Xenial does not have an updated netplan which supports the
Packit Service a04d08
            # correct spelling.  LP: #1756701
Packit Service 9bfd13
            params = item_params.get('parameters', {})
Packit Service a04d08
            grat_value = params.pop('gratuitous-arp', None)
Packit Service a04d08
            if grat_value:
Packit Service a04d08
                params['gratuitious-arp'] = grat_value
Packit Service a04d08
Packit Service a04d08
            v1_cmd = {
Packit Service a04d08
                'type': cmd_type,
Packit Service a04d08
                'name': item_name,
Packit Service a04d08
                cmd_type + '_interfaces': item_cfg.get('interfaces'),
Packit Service 9bfd13
                'params': dict((v2key_to_v1[k], v) for k, v in params.items())
Packit Service a04d08
            }
Packit Service a04d08
            if 'mtu' in item_cfg:
Packit Service a04d08
                v1_cmd['mtu'] = item_cfg['mtu']
Packit Service a04d08
            subnets = self._v2_to_v1_ipcfg(item_cfg)
Packit Service a04d08
            if len(subnets) > 0:
Packit Service a04d08
                v1_cmd.update({'subnets': subnets})
Packit Service a04d08
Packit Service a04d08
            LOG.debug('v2(%s) -> v1(%s):\n%s', cmd_type, cmd_type, v1_cmd)
Packit Service a04d08
            if cmd_type == "bridge":
Packit Service a04d08
                self.handle_bridge(v1_cmd)
Packit Service a04d08
            elif cmd_type == "bond":
Packit Service a04d08
                self.handle_bond(v1_cmd)
Packit Service a04d08
            else:
Packit Service a04d08
                raise ValueError('Unknown command type: {cmd_type}'.format(
Packit Service a04d08
                    cmd_type=cmd_type))
Packit Service a04d08
Packit Service a04d08
    def _v2_to_v1_ipcfg(self, cfg):
Packit Service a04d08
        """Common ipconfig extraction from v2 to v1 subnets array."""
Packit Service a04d08
Packit Service a04d08
        def _add_dhcp_overrides(overrides, subnet):
Packit Service a04d08
            if 'route-metric' in overrides:
Packit Service a04d08
                subnet['metric'] = overrides['route-metric']
Packit Service a04d08
Packit Service a04d08
        subnets = []
Packit Service a04d08
        if cfg.get('dhcp4'):
Packit Service a04d08
            subnet = {'type': 'dhcp4'}
Packit Service a04d08
            _add_dhcp_overrides(cfg.get('dhcp4-overrides', {}), subnet)
Packit Service a04d08
            subnets.append(subnet)
Packit Service a04d08
        if cfg.get('dhcp6'):
Packit Service a04d08
            subnet = {'type': 'dhcp6'}
Packit Service a04d08
            self.use_ipv6 = True
Packit Service a04d08
            _add_dhcp_overrides(cfg.get('dhcp6-overrides', {}), subnet)
Packit Service a04d08
            subnets.append(subnet)
Packit Service a04d08
Packit Service a04d08
        gateway4 = None
Packit Service a04d08
        gateway6 = None
Packit Service a04d08
        nameservers = {}
Packit Service a04d08
        for address in cfg.get('addresses', []):
Packit Service a04d08
            subnet = {
Packit Service a04d08
                'type': 'static',
Packit Service a04d08
                'address': address,
Packit Service a04d08
            }
Packit Service a04d08
Packit Service a04d08
            if ":" in address:
Packit Service a04d08
                if 'gateway6' in cfg and gateway6 is None:
Packit Service a04d08
                    gateway6 = cfg.get('gateway6')
Packit Service a04d08
                    subnet.update({'gateway': gateway6})
Packit Service a04d08
            else:
Packit Service a04d08
                if 'gateway4' in cfg and gateway4 is None:
Packit Service a04d08
                    gateway4 = cfg.get('gateway4')
Packit Service a04d08
                    subnet.update({'gateway': gateway4})
Packit Service a04d08
Packit Service a04d08
            if 'nameservers' in cfg and not nameservers:
Packit Service a04d08
                addresses = cfg.get('nameservers').get('addresses')
Packit Service a04d08
                if addresses:
Packit Service a04d08
                    nameservers['dns_nameservers'] = addresses
Packit Service a04d08
                search = cfg.get('nameservers').get('search')
Packit Service a04d08
                if search:
Packit Service a04d08
                    nameservers['dns_search'] = search
Packit Service a04d08
                subnet.update(nameservers)
Packit Service a04d08
Packit Service a04d08
            subnets.append(subnet)
Packit Service a04d08
Packit Service a04d08
        routes = []
Packit Service a04d08
        for route in cfg.get('routes', []):
Packit Service a04d08
            routes.append(_normalize_route(
Packit Service a04d08
                {'destination': route.get('to'), 'gateway': route.get('via')}))
Packit Service a04d08
Packit Service a04d08
        # v2 routes are bound to the interface, in v1 we add them under
Packit Service a04d08
        # the first subnet since there isn't an equivalent interface level.
Packit Service a04d08
        if len(subnets) and len(routes):
Packit Service a04d08
            subnets[0]['routes'] = routes
Packit Service a04d08
Packit Service a04d08
        return subnets
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def _normalize_subnet(subnet):
Packit Service a04d08
    # Prune all keys with None values.
Packit Service a04d08
    subnet = copy.deepcopy(subnet)
Packit Service a04d08
    normal_subnet = dict((k, v) for k, v in subnet.items() if v)
Packit Service a04d08
Packit Service a04d08
    if subnet.get('type') in ('static', 'static6'):
Packit Service a04d08
        normal_subnet.update(
Packit Service 4a237f
            _normalize_net_keys(normal_subnet, address_keys=(
Packit Service 4a237f
                'address', 'ip_address',)))
Packit Service a04d08
    normal_subnet['routes'] = [_normalize_route(r)
Packit Service a04d08
                               for r in subnet.get('routes', [])]
Packit Service a04d08
Packit Service a04d08
    def listify(snet, name):
Packit Service a04d08
        if name in snet and not isinstance(snet[name], list):
Packit Service a04d08
            snet[name] = snet[name].split()
Packit Service a04d08
Packit Service a04d08
    for k in ('dns_search', 'dns_nameservers'):
Packit Service a04d08
        listify(normal_subnet, k)
Packit Service a04d08
Packit Service a04d08
    return normal_subnet
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def _normalize_net_keys(network, address_keys=()):
Packit Service a04d08
    """Normalize dictionary network keys returning prefix and address keys.
Packit Service a04d08
Packit Service a04d08
    @param network: A dict of network-related definition containing prefix,
Packit Service a04d08
        netmask and address_keys.
Packit Service a04d08
    @param address_keys: A tuple of keys to search for representing the address
Packit Service a04d08
        or cidr. The first address_key discovered will be used for
Packit Service a04d08
        normalization.
Packit Service a04d08
Packit Service a04d08
    @returns: A dict containing normalized prefix and matching addr_key.
Packit Service a04d08
    """
Packit Service a04d08
    net = dict((k, v) for k, v in network.items() if v)
Packit Service a04d08
    addr_key = None
Packit Service a04d08
    for key in address_keys:
Packit Service a04d08
        if net.get(key):
Packit Service a04d08
            addr_key = key
Packit Service a04d08
            break
Packit Service a04d08
    if not addr_key:
Packit Service a04d08
        message = (
Packit Service a04d08
            'No config network address keys [%s] found in %s' %
Packit Service a04d08
            (','.join(address_keys), network))
Packit Service a04d08
        LOG.error(message)
Packit Service a04d08
        raise ValueError(message)
Packit Service a04d08
Packit Service a04d08
    addr = net.get(addr_key)
Packit Service a04d08
    ipv6 = is_ipv6_addr(addr)
Packit Service a04d08
    netmask = net.get('netmask')
Packit Service a04d08
    if "/" in addr:
Packit Service a04d08
        addr_part, _, maybe_prefix = addr.partition("/")
Packit Service a04d08
        net[addr_key] = addr_part
Packit Service a04d08
        try:
Packit Service a04d08
            prefix = int(maybe_prefix)
Packit Service a04d08
        except ValueError:
Packit Service a04d08
            # this supports input of <address>/255.255.255.0
Packit Service a04d08
            prefix = mask_to_net_prefix(maybe_prefix)
Packit Service a04d08
    elif netmask:
Packit Service a04d08
        prefix = mask_to_net_prefix(netmask)
Packit Service a04d08
    elif 'prefix' in net:
Packit Service a04d08
        prefix = int(net['prefix'])
Packit Service a04d08
    else:
Packit Service a04d08
        prefix = 64 if ipv6 else 24
Packit Service a04d08
Packit Service a04d08
    if 'prefix' in net and str(net['prefix']) != str(prefix):
Packit Service a04d08
        LOG.warning("Overwriting existing 'prefix' with '%s' in "
Packit Service a04d08
                    "network info: %s", prefix, net)
Packit Service a04d08
    net['prefix'] = prefix
Packit Service a04d08
Packit Service a04d08
    if ipv6:
Packit Service a04d08
        # TODO: we could/maybe should add this back with the very uncommon
Packit Service a04d08
        # 'netmask' for ipv6.  We need a 'net_prefix_to_ipv6_mask' for that.
Packit Service a04d08
        if 'netmask' in net:
Packit Service a04d08
            del net['netmask']
Packit Service a04d08
    else:
Packit Service a04d08
        net['netmask'] = net_prefix_to_ipv4_mask(net['prefix'])
Packit Service a04d08
Packit Service a04d08
    return net
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def _normalize_route(route):
Packit Service a04d08
    """normalize a route.
Packit Service a04d08
    return a dictionary with only:
Packit Service a04d08
       'type': 'route' (only present if it was present in input)
Packit Service a04d08
       'network': the network portion of the route as a string.
Packit Service a04d08
       'prefix': the network prefix for address as an integer.
Packit Service a04d08
       'metric': integer metric (only if present in input).
Packit Service a04d08
       'netmask': netmask (string) equivalent to prefix iff network is ipv4.
Packit Service a04d08
       """
Packit Service a04d08
    # Prune None-value keys.  Specifically allow 0 (a valid metric).
Packit Service a04d08
    normal_route = dict((k, v) for k, v in route.items()
Packit Service a04d08
                        if v not in ("", None))
Packit Service a04d08
    if 'destination' in normal_route:
Packit Service a04d08
        normal_route['network'] = normal_route['destination']
Packit Service a04d08
        del normal_route['destination']
Packit Service a04d08
Packit Service a04d08
    normal_route.update(
Packit Service a04d08
        _normalize_net_keys(
Packit Service a04d08
            normal_route, address_keys=('network', 'destination')))
Packit Service a04d08
Packit Service a04d08
    metric = normal_route.get('metric')
Packit Service a04d08
    if metric:
Packit Service a04d08
        try:
Packit Service a04d08
            normal_route['metric'] = int(metric)
Packit Service 9bfd13
        except ValueError as e:
Packit Service a04d08
            raise TypeError(
Packit Service 9bfd13
                'Route config metric {} is not an integer'.format(metric)
Packit Service 9bfd13
            ) from e
Packit Service a04d08
    return normal_route
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def _normalize_subnets(subnets):
Packit Service a04d08
    if not subnets:
Packit Service a04d08
        subnets = []
Packit Service a04d08
    return [_normalize_subnet(s) for s in subnets]
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def is_ipv6_addr(address):
Packit Service a04d08
    if not address:
Packit Service a04d08
        return False
Packit Service a04d08
    return ":" in str(address)
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def subnet_is_ipv6(subnet):
Packit Service a04d08
    """Common helper for checking network_state subnets for ipv6."""
Packit Service a04d08
    # 'static6', 'dhcp6', 'ipv6_dhcpv6-stateful', 'ipv6_dhcpv6-stateless' or
Packit Service a04d08
    # 'ipv6_slaac'
Packit Service a04d08
    if subnet['type'].endswith('6') or subnet['type'] in IPV6_DYNAMIC_TYPES:
Packit Service 9bfd13
        # This is a request either static6 type or DHCPv6.
Packit Service a04d08
        return True
Packit Service a04d08
    elif subnet['type'] == 'static' and is_ipv6_addr(subnet.get('address')):
Packit Service a04d08
        return True
Packit Service a04d08
    return False
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def net_prefix_to_ipv4_mask(prefix):
Packit Service a04d08
    """Convert a network prefix to an ipv4 netmask.
Packit Service a04d08
Packit Service a04d08
    This is the inverse of ipv4_mask_to_net_prefix.
Packit Service a04d08
        24 -> "255.255.255.0"
Packit Service a04d08
    Also supports input as a string."""
Packit Service a04d08
    mask = socket.inet_ntoa(
Packit Service a04d08
        struct.pack(">I", (0xffffffff << (32 - int(prefix)) & 0xffffffff)))
Packit Service a04d08
    return mask
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def ipv4_mask_to_net_prefix(mask):
Packit Service a04d08
    """Convert an ipv4 netmask into a network prefix length.
Packit Service a04d08
Packit Service a04d08
    If the input is already an integer or a string representation of
Packit Service a04d08
    an integer, then int(mask) will be returned.
Packit Service a04d08
       "255.255.255.0" => 24
Packit Service a04d08
       str(24)         => 24
Packit Service a04d08
       "24"            => 24
Packit Service a04d08
    """
Packit Service a04d08
    if isinstance(mask, int):
Packit Service a04d08
        return mask
Packit Service 9bfd13
    if isinstance(mask, str):
Packit Service a04d08
        try:
Packit Service a04d08
            return int(mask)
Packit Service a04d08
        except ValueError:
Packit Service a04d08
            pass
Packit Service a04d08
    else:
Packit Service a04d08
        raise TypeError("mask '%s' is not a string or int")
Packit Service a04d08
Packit Service a04d08
    if '.' not in mask:
Packit Service a04d08
        raise ValueError("netmask '%s' does not contain a '.'" % mask)
Packit Service a04d08
Packit Service a04d08
    toks = mask.split(".")
Packit Service a04d08
    if len(toks) != 4:
Packit Service a04d08
        raise ValueError("netmask '%s' had only %d parts" % (mask, len(toks)))
Packit Service a04d08
Packit Service a04d08
    return sum([bin(int(x)).count('1') for x in toks])
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def ipv6_mask_to_net_prefix(mask):
Packit Service a04d08
    """Convert an ipv6 netmask (very uncommon) or prefix (64) to prefix.
Packit Service a04d08
Packit Service a04d08
    If 'mask' is an integer or string representation of one then
Packit Service a04d08
    int(mask) will be returned.
Packit Service a04d08
    """
Packit Service a04d08
Packit Service a04d08
    if isinstance(mask, int):
Packit Service a04d08
        return mask
Packit Service 9bfd13
    if isinstance(mask, str):
Packit Service a04d08
        try:
Packit Service a04d08
            return int(mask)
Packit Service a04d08
        except ValueError:
Packit Service a04d08
            pass
Packit Service a04d08
    else:
Packit Service a04d08
        raise TypeError("mask '%s' is not a string or int")
Packit Service a04d08
Packit Service a04d08
    if ':' not in mask:
Packit Service a04d08
        raise ValueError("mask '%s' does not have a ':'")
Packit Service a04d08
Packit Service a04d08
    bitCount = [0, 0x8000, 0xc000, 0xe000, 0xf000, 0xf800, 0xfc00, 0xfe00,
Packit Service a04d08
                0xff00, 0xff80, 0xffc0, 0xffe0, 0xfff0, 0xfff8, 0xfffc,
Packit Service a04d08
                0xfffe, 0xffff]
Packit Service a04d08
    prefix = 0
Packit Service a04d08
    for word in mask.split(':'):
Packit Service a04d08
        if not word or int(word, 16) == 0:
Packit Service a04d08
            break
Packit Service a04d08
        prefix += bitCount.index(int(word, 16))
Packit Service a04d08
Packit Service a04d08
    return prefix
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def mask_to_net_prefix(mask):
Packit Service a04d08
    """Return the network prefix for the netmask provided.
Packit Service a04d08
Packit Service a04d08
    Supports ipv4 or ipv6 netmasks."""
Packit Service a04d08
    try:
Packit Service a04d08
        # if 'mask' is a prefix that is an integer.
Packit Service a04d08
        # then just return it.
Packit Service a04d08
        return int(mask)
Packit Service a04d08
    except ValueError:
Packit Service a04d08
        pass
Packit Service a04d08
    if is_ipv6_addr(mask):
Packit Service a04d08
        return ipv6_mask_to_net_prefix(mask)
Packit Service a04d08
    else:
Packit Service a04d08
        return ipv4_mask_to_net_prefix(mask)
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def mask_and_ipv4_to_bcast_addr(mask, ip):
Packit Service a04d08
    """Calculate the broadcast address from the subnet mask and ip addr.
Packit Service a04d08
Packit Service a04d08
    Supports ipv4 only."""
Packit Service a04d08
    ip_bin = int(''.join([bin(int(x) + 256)[3:] for x in ip.split('.')]), 2)
Packit Service a04d08
    mask_dec = ipv4_mask_to_net_prefix(mask)
Packit Service a04d08
    bcast_bin = ip_bin | (2**(32 - mask_dec) - 1)
Packit Service a04d08
    bcast_str = '.'.join([str(bcast_bin >> (i << 3) & 0xFF)
Packit Service a04d08
                          for i in range(4)[::-1]])
Packit Service a04d08
    return bcast_str
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
# vi: ts=4 expandtab