<commit_before>import functools
import netaddr
import logging
import random

from neutron.api.v2 import attributes
from neutron.common.config import cfg
from neutron.common import exceptions as q_exc
from neutron.db import db_base_plugin_v2
from neutron.db import models_v2 as qmodels
from neutron.db import l3_db
from neutron import manager
from sqlalchemy.orm import exc

from akanda.neutron.db import models_v2 as akmodels

IPV6_ASSIGNMENT_ATTEMPTS = 1000
LOG = logging.getLogger(__name__)

akanda_opts = [
    cfg.StrOpt('akanda_ipv6_tenant_range',
               default='fdd6:a1fa:cfa8::/48',
               help='IPv6 address prefix'),
    cfg.IntOpt('akanda_ipv6_prefix_length',
               default=64,
               help='Default length of prefix to pre-assign'),
    cfg.ListOpt(
        'akanda_allowed_cidr_ranges',
        default=['10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16', 'fc00::/7'],
        help='List of allowed subnet cidrs for non-admin users')
]

cfg.CONF.register_opts(akanda_opts)

SUPPORTED_EXTENSIONS = [
    'dhportforward', 'dhaddressgroup', 'dhaddressentry', 'dhfilterrule',
    'dhportalias'
]

# Provide a list of the default port aliases to be
# created for a tenant.
# FIXME(dhellmann): This list should come from
# a configuration file somewhere.
DEFAULT_PORT_ALIASES = [
    ('tcp', 0, 'Any TCP'),
    ('udp', 0, 'Any UDP'),
    ('tcp', 22, 'ssh'),
    ('udp', 53, 'DNS'),
    ('tcp', 80, 'HTTP'),
    ('tcp', 443, 'HTTPS'),
]

# Provide a list of the default address entries
# to be created for a tenant.
# FIXME(dhellmann): This list should come from
# a configuration file somewhere.
DEFAULT_ADDRESS_GROUPS = [
    ('Any', [('Any', '0.0.0.0/0')]),
]


def auto_add_ipv6_subnet(f):
    @functools.wraps(f)
    def wrapper(self, context, network):
        LOG.debug('auto_add_ipv6_subnet')
        net = f(self, context, network)
        _add_ipv6_subnet(context, net)
        return net
    return wrapper


def auto_add_subnet_to_router(f):
    @functools.wraps(f)
    def wrapper(self, context, subnet):
        LOG.debug('auto_add_subnet_to_router')
        check_subnet_cidr_meets_policy(context, subnet)
        subnet = f(self, context, subnet)
        _add_subnet_to_router(context, subnet)
        return subnet
    return wrapper

# NOTE(mark): in Havana gateway_ip cannot be updated leaving here if this
# returns in Icehouse.
def sync_subnet_gateway_port(f):
    @functools.wraps(f)
    def wrapper(self, context, id, subnet):
        LOG.debug('sync_subnet_gateway_port')
        retval = f(self, context, id, subnet)
        _update_internal_gateway_port_ip(context, retval)
        return retval
    return wrapper


def auto_add_other_resources(f):
    @functools.wraps(f)
    def wrapper(self, context, *args, **kwargs):
        LOG.debug('auto_add_other_resources')
        retval = f(self, context, *args, **kwargs)
        if not context.is_admin:
            _auto_add_port_aliases(context)
            _auto_add_address_groups(context)
        return retval
    return wrapper


def monkey_patch_ipv6_generator():
    cls = db_base_plugin_v2.NeutronDbPluginV2
    cls._generate_mac = _wrap_generate_mac(cls._generate_mac)
    cls._generate_ip = _wrap_generate_ip(cls, cls._generate_ip)


def check_subnet_cidr_meets_policy(context, subnet):
    if context.is_admin:
        return
    elif getattr(context, '_akanda_auto_add', None):
        return

    net = netaddr.IPNetwork(subnet['subnet']['cidr'])

    for allowed_cidr in cfg.CONF.akanda_allowed_cidr_ranges:
        if net in netaddr.IPNetwork(allowed_cidr):
            return

    else:
        reason = _('Cannot create a subnet that is not within the '
                   'allowed address ranges [%s].' %
                   cfg.CONF.akanda_allowed_cidr_ranges)
        raise q_exc.AdminRequired(reason=reason)


def get_special_ipv6_addrs(ips, mac_address):
    current_ips = set(ips)
    special_ips = set([_generate_ipv6_address('fe80::/64', mac_address)])

    akanda_ipv6_cidr = netaddr.IPNetwork(cfg.CONF.akanda_ipv6_tenant_range)

    for ip in current_ips:
        if '/' not in ip and netaddr.IPAddress(ip) in akanda_ipv6_cidr:
            # Calculate the cidr here because the caller does not have access
            # to request context, subnet or port_id.
            special_ips.add(
                '%s/%s' % (
                    netaddr.IPAddress(
                        netaddr.IPNetwork(
                            '%s/%d' % (ip, cfg.CONF.akanda_ipv6_prefix_length)
                        ).first
                    ),
                    cfg.CONF.akanda_ipv6_prefix_length
                )
            )
    return special_ips - current_ips


def _add_subnet_to_router(context, subnet):
    LOG.debug('_add_subnet_to_router')
    if context.is_admin:
        # admins can manually add their own interfaces
        return

    if not subnet.get('gateway_ip'):
        return

    plugin = manager.NeutronManager.get_plugin()

    router_q = context.session.query(l3_db.Router)
    router_q = router_q.filter_by(tenant_id=context.tenant_id)

    router = router_q.first()

    if not router:
        router_args = {'tenant_id': context.tenant_id,
                       'name': 'ak-%s' % context.tenant_id,
                       'admin_state_up': True}
        router = plugin.create_router(context, {'router': router_args})
    if not _update_internal_gateway_port_ip(context, router['id'], subnet):
        plugin.add_router_interface(context.elevated(),
                                    router['id'],
                                    {'subnet_id': subnet['id']})


def _update_internal_gateway_port_ip(context, router_id, subnet):
    """Attempt to update internal gateway port if one already exists."""
    LOG.debug(
        'setting gateway port IP for router %s on network %s for subnet %s',
        router_id,
        subnet['network_id'],
        subnet['id'],
    )
    if not subnet.get('gateway_ip'):
        LOG.debug('no gateway set for subnet %s, skipping', subnet['id'])
        return

    q = context.session.query(l3_db.RouterPort)
    q = q.join(qmodels.Port)
    q = q.filter(
        l3_db.RouterPort.router_id == router_id,
        l3_db.RouterPort.port_type == l3_db.DEVICE_OWNER_ROUTER_INTF,
        qmodels.Port.network_id == subnet['network_id']

    )
    routerport = q.first()

    if not routerport:
        LOG.info(
            'Unable to find a %s port for router %s on network %s.'
            % ('DEVICE_OWNER_ROUTER_INTF', router_id, subnet['network_id'])
        )
        return

    fixed_ips = [
        {'subnet_id': ip["subnet_id"], 'ip_address': ip["ip_address"]}
        for ip in routerport.port["fixed_ips"]
    ]

    plugin = manager.NeutronManager.get_plugin()

    for index, ip in enumerate(fixed_ips):
        if ip['subnet_id'] == subnet['id']:
            if not subnet['gateway_ip']:
                del fixed_ips[index]
            elif ip['ip_address'] != subnet['gateway_ip']:
                ip['ip_address'] = subnet['gateway_ip']
            else:
                return True  # nothing to update
            break
    else:
        try:
            plugin._check_for_dup_router_subnet(
                context,
                routerport.router,
                subnet['network_id'],
                subnet
            )
        except:
            LOG.info(
                ('Subnet %(id)s will not be auto added to router because '
                 '%(gateway_ip)s is already in use by another attached '
                 'network attached to this router.'),
                subnet
            )
            return True  # nothing to add
        fixed_ips.append(
            {'subnet_id': subnet['id'], 'ip_address': subnet['gateway_ip']}
        )

    # we call into the plugin vs updating the db directly because of l3 hooks
    # baked into the plugins.
    port_dict = {'fixed_ips': fixed_ips}
    plugin.update_port(
        context.elevated(),
        routerport.port['id'],
        {'port': port_dict}
    )
    return True


def _add_ipv6_subnet(context, network):

    plugin = manager.NeutronManager.get_plugin()

    try:
        subnet_generator = _ipv6_subnet_generator(
            cfg.CONF.akanda_ipv6_tenant_range,
            cfg.CONF.akanda_ipv6_prefix_length)
    except:
        LOG.exception('Unable able to add tenant IPv6 subnet.')
        return

    remaining = IPV6_ASSIGNMENT_ATTEMPTS

    while remaining:
        remaining -= 1

        candidate_cidr = subnet_generator.next()

        sub_q = context.session.query(qmodels.Subnet)
        sub_q = sub_q.filter_by(cidr=str(candidate_cidr))
        existing = sub_q.all()

        if not existing:
            create_args = {
                'network_id': network['id'],
                'name': '',
                'cidr': str(candidate_cidr),
                'ip_version': candidate_cidr.version,
                'enable_dhcp': False,
                'gateway_ip': attributes.ATTR_NOT_SPECIFIED,
                'dns_nameservers': attributes.ATTR_NOT_SPECIFIED,
                'host_routes': attributes.ATTR_NOT_SPECIFIED,
                'allocation_pools': attributes.ATTR_NOT_SPECIFIED<commit_msg>Fix autocreate subnet ownership problem<commit_after>import functools
import netaddr
import logging
import random

from neutron.api.v2 import attributes
from neutron.common.config import cfg
from neutron.common import exceptions as q_exc
from neutron.db import db_base_plugin_v2
from neutron.db import models_v2 as qmodels
from neutron.db import l3_db
from neutron import manager
from sqlalchemy.orm import exc

from akanda.neutron.db import models_v2 as akmodels

IPV6_ASSIGNMENT_ATTEMPTS = 1000
LOG = logging.getLogger(__name__)

akanda_opts = [
    cfg.StrOpt('akanda_ipv6_tenant_range',
               default='fdd6:a1fa:cfa8::/48',
               help='IPv6 address prefix'),
    cfg.IntOpt('akanda_ipv6_prefix_length',
               default=64,
               help='Default length of prefix to pre-assign'),
    cfg.ListOpt(
        'akanda_allowed_cidr_ranges',
        default=['10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16', 'fc00::/7'],
        help='List of allowed subnet cidrs for non-admin users')
]

cfg.CONF.register_opts(akanda_opts)

SUPPORTED_EXTENSIONS = [
    'dhportforward', 'dhaddressgroup', 'dhaddressentry', 'dhfilterrule',
    'dhportalias'
]

# Provide a list of the default port aliases to be
# created for a tenant.
# FIXME(dhellmann): This list should come from
# a configuration file somewhere.
DEFAULT_PORT_ALIASES = [
    ('tcp', 0, 'Any TCP'),
    ('udp', 0, 'Any UDP'),
    ('tcp', 22, 'ssh'),
    ('udp', 53, 'DNS'),
    ('tcp', 80, 'HTTP'),
    ('tcp', 443, 'HTTPS'),
]

# Provide a list of the default address entries
# to be created for a tenant.
# FIXME(dhellmann): This list should come from
# a configuration file somewhere.
DEFAULT_ADDRESS_GROUPS = [
    ('Any', [('Any', '0.0.0.0/0')]),
]


def auto_add_ipv6_subnet(f):
    @functools.wraps(f)
    def wrapper(self, context, network):
        LOG.debug('auto_add_ipv6_subnet')
        net = f(self, context, network)
        _add_ipv6_subnet(context, net)
        return net
    return wrapper


def auto_add_subnet_to_router(f):
    @functools.wraps(f)
    def wrapper(self, context, subnet):
        LOG.debug('auto_add_subnet_to_router')
        check_subnet_cidr_meets_policy(context, subnet)
        subnet = f(self, context, subnet)
        _add_subnet_to_router(context, subnet)
        return subnet
    return wrapper


# NOTE(mark): in Havana gateway_ip cannot be updated leaving here if this
# returns in Icehouse.
def sync_subnet_gateway_port(f):
    @functools.wraps(f)
    def wrapper(self, context, id, subnet):
        LOG.debug('sync_subnet_gateway_port')
        retval = f(self, context, id, subnet)
        _update_internal_gateway_port_ip(context, retval)
        return retval
    return wrapper


def auto_add_other_resources(f):
    @functools.wraps(f)
    def wrapper(self, context, *args, **kwargs):
        LOG.debug('auto_add_other_resources')
        retval = f(self, context, *args, **kwargs)
        if not context.is_admin:
            _auto_add_port_aliases(context)
            _auto_add_address_groups(context)
        return retval
    return wrapper


def monkey_patch_ipv6_generator():
    cls = db_base_plugin_v2.NeutronDbPluginV2
    cls._generate_mac = _wrap_generate_mac(cls._generate_mac)
    cls._generate_ip = _wrap_generate_ip(cls, cls._generate_ip)


def check_subnet_cidr_meets_policy(context, subnet):
    if context.is_admin:
        return
    elif getattr(context, '_akanda_auto_add', None):
        return

    net = netaddr.IPNetwork(subnet['subnet']['cidr'])

    for allowed_cidr in cfg.CONF.akanda_allowed_cidr_ranges:
        if net in netaddr.IPNetwork(allowed_cidr):
            return

    else:
        reason = _('Cannot create a subnet that is not within the '
                   'allowed address ranges [%s].' %
                   cfg.CONF.akanda_allowed_cidr_ranges)
        raise q_exc.AdminRequired(reason=reason)


def get_special_ipv6_addrs(ips, mac_address):
    current_ips = set(ips)
    special_ips = set([_generate_ipv6_address('fe80::/64', mac_address)])

    akanda_ipv6_cidr = netaddr.IPNetwork(cfg.CONF.akanda_ipv6_tenant_range)

    for ip in current_ips:
        if '/' not in ip and netaddr.IPAddress(ip) in akanda_ipv6_cidr:
            # Calculate the cidr here because the caller does not have access
            # to request context, subnet or port_id.
            special_ips.add(
                '%s/%s' % (
                    netaddr.IPAddress(
                        netaddr.IPNetwork(
                            '%s/%d' % (ip, cfg.CONF.akanda_ipv6_prefix_length)
                        ).first
                    ),
                    cfg.CONF.akanda_ipv6_prefix_length
                )
            )
    return special_ips - current_ips


def _add_subnet_to_router(context, subnet):
    LOG.debug('_add_subnet_to_router')
    if context.is_admin:
        # admins can manually add their own interfaces
        return

    if not subnet.get('gateway_ip'):
        return

    plugin = manager.NeutronManager.get_plugin()

    router_q = context.session.query(l3_db.Router)
    router_q = router_q.filter_by(tenant_id=context.tenant_id)

    router = router_q.first()

    if not router:
        router_args = {
            'tenant_id': subnet['tenant_id'],
            'name': 'ak-%s' % subnet['tenant_id'],
            'admin_state_up': True
        }
        router = plugin.create_router(context, {'router': router_args})
    if not _update_internal_gateway_port_ip(context, router['id'], subnet):
        plugin.add_router_interface(context.elevated(),
                                    router['id'],
                                    {'subnet_id': subnet['id']})


def _update_internal_gateway_port_ip(context, router_id, subnet):
    """Attempt to update internal gateway port if one already exists."""
    LOG.debug(
        'setting gateway port IP for router %s on network %s for subnet %s',
        router_id,
        subnet['network_id'],
        subnet['id'],
    )
    if not subnet.get('gateway_ip'):
        LOG.debug('no gateway set for subnet %s, skipping', subnet['id'])
        return

    q = context.session.query(l3_db.RouterPort)
    q = q.join(qmodels.Port)
    q = q.filter(
        l3_db.RouterPort.router_id == router_id,
        l3_db.RouterPort.port_type == l3_db.DEVICE_OWNER_ROUTER_INTF,
        qmodels.Port.network_id == subnet['network_id']

    )
    routerport = q.first()

    if not routerport:
        LOG.info(
            'Unable to find a %s port for router %s on network %s.'
            % ('DEVICE_OWNER_ROUTER_INTF', router_id, subnet['network_id'])
        )
        return

    fixed_ips = [
        {'subnet_id': ip["subnet_id"], 'ip_address': ip["ip_address"]}
        for ip in routerport.port["fixed_ips"]
    ]

    plugin = manager.NeutronManager.get_plugin()

    for index, ip in enumerate(fixed_ips):
        if ip['subnet_id'] == subnet['id']:
            if not subnet['gateway_ip']:
                del fixed_ips[index]
            elif ip['ip_address'] != subnet['gateway_ip']:
                ip['ip_address'] = subnet['gateway_ip']
            else:
                return True  # nothing to update
            break
    else:
        try:
            plugin._check_for_dup_router_subnet(
                context,
                routerport.router,
                subnet['network_id'],
                subnet
            )
        except:
            LOG.info(
                ('Subnet %(id)s will not be auto added to router because '
                 '%(gateway_ip)s is already in use by another attached '
                 'network attached to this router.'),
                subnet
            )
            return True  # nothing to add
        fixed_ips.append(
            {'subnet_id': subnet['id'], 'ip_address': subnet['gateway_ip']}
        )

    # we call into the plugin vs updating the db directly because of l3 hooks
    # baked into the plugins.
    port_dict = {'fixed_ips': fixed_ips}
    plugin.update_port(
        context.elevated(),
        routerport.port['id'],
        {'port': port_dict}
    )
    return True


def _add_ipv6_subnet(context, network):

    plugin = manager.NeutronManager.get_plugin()

    try:
        subnet_generator = _ipv6_subnet_generator(
            cfg.CONF.akanda_ipv6_tenant_range,
            cfg.CONF.akanda_ipv6_prefix_length)
    except:
        LOG.exception('Unable able to add tenant IPv6 subnet.')
        return

    remaining = IPV6_ASSIGNMENT_ATTEMPTS

    while remaining:
        remaining -= 1

        candidate_cidr = subnet_generator.next()

        sub_q = context.session.query(qmodels.Subnet)
        sub_q = sub_q.filter_by(cidr=str(candidate_cidr))
        existing = sub_q.all()

        if not existing:
            create_args = {
                'tenant_id': network['tenant_id'],
                'network_id': network['id'],
                'name': '',
                'cidr': str(candidate_cidr),
                'ip_version': candidate_cidr.version,
                'enable_dhcp': False,
                'gateway_ip': attributes.ATTR_NOT_SPECIFIED,
                'dns_nameservers': attributes.ATTR_NOT_SPECIFIED,
                'host_routes': attributes.ATTR_NOT_SPECIFIED,
                'allocation_pools': attributes.ATTR_NOT_SPECIFIED
{'fraction_non_alphanumeric': 0.10219530658591976, 'fraction_numerical': 0.010165459067805775, 'mean_word_length': 2.402317880794702, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 8, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7132499', 'n_tokens_mistral': 6000, 'n_tokens_neox': 5862, 'n_words': 1299}