neutron 关于 allowed_address_pairs (AAP)的实现

39 阅读3分钟

AAP 的实现

最近一直在考虑 AAP 在kube-ovn的实现方式,所以将 neutron 的 AAP 相关实现梳理一下

image.png

基于 neutron port-create 接口 和 neutron port-update 接口是可以更新 port(作为主网卡)能够使用的 vip 的,这个 allowed_address_pairs 可以记录多个 vip 的 ip 和 mac 。

OVS 场景

在 L2 L3 场景,在 switch arp neigh 表,dhcp,icmp iptables 规则,安全组,防火墙,Fip 等场景均有读取该字段的数据

最直观的表现是在 安全组场景新建了一条面向 vip ip 的 iptables 规则。

image.png

OVN 场景:

L2 L3 都不再基于 iptables,而是完全基于 OVN 实现,需要基于 OVN 的 nbctl 接口做操作


    def create_port(self, context, port):
        if utils.is_lsp_ignored(port):
            return

        port_info, external_ids = self.get_external_ids_from_port(port)
        lswitch_name = utils.ovn_name(port['network_id'])

        # It's possible to have a network created on one controller and then a
        # port created on a different controller quickly enough that the second
        # controller does not yet see that network in its local cache of the
        # OVN northbound database.  Check if the logical switch is present
        # or not in the idl's local copy of the database before creating
        # the lswitch port.
        self._nb_idl.check_for_row_by_value_and_retry(
            'Logical_Switch', 'name', lswitch_name)

        with self._nb_idl.transaction(check_error=True) as txn:
            dhcpv4_options, dhcpv6_options = self.update_port_dhcp_options(
                port_info, txn=txn)
            # The lport_name *must* be neutron port['id'].  It must match the
            # iface-id set in the Interfaces table of the Open_vSwitch
            # database which nova sets to be the port ID.

            kwargs = {
                'lport_name': port['id'],
                'lswitch_name': lswitch_name,
                'addresses': port_info.addresses,
                'external_ids': external_ids,
                'parent_name': port_info.parent_name,
                'tag': port_info.tag,
                'enabled': port.get('admin_state_up'),
                'options': port_info.options,
                'type': port_info.type,
                'port_security': port_info.port_security,
                'dhcpv4_options': dhcpv4_options,
                'dhcpv6_options': dhcpv6_options
            }

            if (self.is_external_ports_supported() and
                    port_info.type == ovn_const.LSP_TYPE_EXTERNAL):
                kwargs['ha_chassis_group'] = utils.sync_ha_chassis_group(
                    context, port['network_id'], self._nb_idl, self._sb_idl,
                    txn)

            # NOTE(mjozefcz): Do not set addresses if the port is not
            # bound, has no device_owner and it is OVN LB VIP port.
            # For more details check related bug #1789686.
            if (port.get('name').startswith(ovn_const.LB_VIP_PORT_PREFIX) and
                    not port.get('device_owner') and
                    port.get(portbindings.VIF_TYPE) ==
                    portbindings.VIF_TYPE_UNBOUND):
                kwargs['addresses'] = []

            # Check if the parent port was created with the
            # allowed_address_pairs already set
            allowed_address_pairs = port.get('allowed_address_pairs', [])
            if (allowed_address_pairs and
                    port_info.type != ovn_const.LSP_TYPE_VIRTUAL):
                addrs = [addr['ip_address'] for addr in allowed_address_pairs]
                self._set_unset_virtual_port_type(context, txn, port, addrs) # 关于 AAP的属性的值的直接使用只有这一个方法,而这个方法只是找了下 AAP中的 ip 对应的 lsp,并设置改 lsp 的 type 为 virtual,以及标记该 lsp 的 virtual parents 为其主 port 的名字

            port_cmd = txn.add(self._nb_idl.create_lswitch_port(
                **kwargs))

            sg_ids = utils.get_lsp_security_groups(port)
            # If this is not a trusted port and port security is enabled,
            # add it to the default drop Port Group so that all traffic
            # is dropped by default.
            if not utils.is_lsp_trusted(port) and port_info.port_security:
                self._add_port_to_drop_port_group(port_cmd, txn)
            # Just add the port to its Port Group.
            for sg in sg_ids:
                txn.add(self._nb_idl.pg_add_ports(
                    utils.ovn_port_group_name(sg), port_cmd))
                    # 这里搞了一个基于 PG 的安全组规则,不过这里根据注释应该是完全禁用
                    # 有的业务需求可能会在该默认安全组的基础上加几条默认放开的规则,优先级更高
                    # 但是基于的应该都是同一个 PG

            if self.is_dns_required_for_port(port):
                self.add_txns_to_sync_port_dns_records(txn, port)

            self._qos_driver.create_port(txn, port, port_cmd)

        db_rev.bump_revision(context, port, ovn_const.TYPE_PORTS)


    def _set_unset_virtual_port_type(self, context, txn, parent_port,
                                     addresses, unset=False):
        cmd = self._nb_idl.set_lswitch_port_to_virtual_type
        if unset:
            cmd = self._nb_idl.unset_lswitch_port_to_virtual_type

        for addr in addresses:
            virt_port = self._plugin.get_ports(context, filters={
                portbindings.VIF_TYPE: portbindings.VIF_TYPE_UNBOUND,
                'network_id': [parent_port['network_id']],
                'fixed_ips': {'ip_address': [addr]}})
            if not virt_port: # 如果随意设置了一个 ip,那么就不会配置 virtual type lsp 和 其 父 lsp 的关联关系, 可能达不到用户随意设置的目的
                continue
            virt_port = virt_port[0]
            args = {'lport_name': virt_port['id'],
                    'virtual_parent': parent_port['id'],
                    'if_exists': True}
            LOG.debug("Parent port %(virtual_parent)s found for "
                      "virtual port %(lport_name)s", args)
            if not unset:
                args['vip'] = addr
            txn.add(cmd(**args))



image.png

结论: neutron ovn 模式的, port-create 和 update 接口都要求 vip 的 lsp 存在,否则这个设置 ip 在代码层面看不出来任何意义

其余对于 Vip 这种资源,在 FIP 场景,无论是 iptables 的 nat 实现的 fip,或者是 ovn 实现的 nat,不开启安全组,就是 对应的 vip 可以不用映射到网络控制面的逻辑资源(比如 lsp )。 如果是开启了安全组,ovn的 nat 就必须要先创建有对应的 lsp

参考: www.xiexianbin.cn/openstack/n…