当向含有多个子网的Network请求Port资源时,Neutron如何选择子网

当向含有多个子网的Network请求Port资源时,Neutron如何选择子网

概括

在Openstack Neutron中,路由添加网关,分配浮动IP或者创建虚拟机时,都需要创建Port资源;本文主要介绍创建Port选择网络时,如果网络中有多个子网时,如何选择子网来分配IP。

代码分析

本文选用OpenStack Rocky版本

子网参数强调

FieldValue
allocation_pools{“start”: “10.0.20.100”, “end”: “10.0.20.200”}
cidr10.0.20.0/24
enable_dhcpTrue
id51513b8e-261a-4e74-90ac-c34afdbf826a
ip_version4
nameWAN1
network_idbbdd5bc3-a135-493b-a20f-e7af7faec992
project_id707e0c610f954586a6c9a8b7d48c0151
service_types
tenant_id707e0c610f954586a6c9a8b7d48c0151

参数中service_types是本文关键
通过命令行可以增加service_types值
# openstack subnet set --service_type netowrk:floatingip/neutron:router_gateway subnet_id

创建Port前

简单介绍一下创建Port的调用

  1. create_floating
# 创建浮动IP的入口
neutron.service.l3_router.l3_router_plugin
class L3RouterPlugin():
	def create_floating():
        return super(L3RouterPlugin, self).create_floatingip(
            context, floatingip,
            initial_status=n_const.FLOATINGIP_STATUS_DOWN)

......
# 创建floatingip db数据
neutron.db.l3_db
class L3_NAT_dbonly_mixin():
	def _create_floatingip():
		......
	    fip = floatingip['floatingip']	# POST请求的数据
        f_net_id = fip['floating_network_id']	# 创建时选择的network_id
        port = {'tenant_id': '',
                'network_id': f_net_id,
                'admin_state_up': True,
                'device_id': 'PENDING',
                'device_owner': DEVICE_OWNER_FLOATINGIP,	# "network:floatingip"
                'status': constants.PORT_STATUS_NOTAPPLICABLE,
                'name': ''}

        fixed_ip = {}
        if validators.is_attr_set(fip.get('subnet_id')):
            fixed_ip['subnet_id'] = fip['subnet_id']
        if validators.is_attr_set(fip.get('floating_ip_address')):
            fixed_ip['ip_address'] = fip['floating_ip_address']
        if fixed_ip:
            port['fixed_ips'] = [fixed_ip]
	# 当创建时传入的body内已经选择了subnet或者IP,调用core_plugin创建port时则将subnet_id和ip地址传入。

        external_port = plugin_utils.create_port(
            self._core_plugin, context.elevated(),
            {'port': port}, check_allow_post=False)
		# 调用core_plugin创建port
  1. 路由增加网关
# 更新路由
neutron.db.l3_db
class L3_NAT_dbonly_mixin():
	def update_router():
	    r = router['router']	# UPDATE传入数据
        gw_info = r.pop(EXTERNAL_GW_INFO, constants.ATTR_NOT_SPECIFIED)
        if gw_info != constants.ATTR_NOT_SPECIFIED:
        	self._update_router_gw_info(context, id, gw_info)

	def _update_router_gw_info():
		ext_ips = info.get('external_fixed_ips') if info else []	
		# 如果传入body有指定网关ip时则创建port时传入
		self._create_gw_port(context, router_id, router, network_id, ext_ips)	# 只分析新增部分

	def _create_gw_port():
        ......

	def _create_router_gw_port():
        port_data = {'tenant_id': '', 
                     'network_id': network_id,	# 创建时选择的网络
                     'fixed_ips': ext_ips or constants.ATTR_NOT_SPECIFIED,	# 如果创建时选择IP则传入
                     'device_id': router['id'],
                     'device_owner': DEVICE_OWNER_ROUTER_GW,	# ‘network:router_gateway’
                     'admin_state_up': True,
                     'name': ''}
        gw_port = plugin_utils.create_port(
            self._core_plugin, context.elevated(), {'port': port_data})

创建Port

使用ml2_plugin创建port;
此时传入值有两种情况,是否传入了指定的IP;后续将分情况进行分析;

neutron.plugins.ml2.plugin
class Ml2Plugin():
	def create_port():
		result, mech_context = self._create_port_db(context, port)

	def _create_port_db():
		 port_db = self.create_port_db(context, port)

neutron.db.db_base_plugin_v2
class NeutronDbPluginV2():
	def create_port_db(self, context, port):
		......
		# 重点分析分配IP的部分
		self.ipam.allocate_ips_for_port_and_store(
                    context, port, port_id)
neutron.db.ipam_pluggable_backend
class IpamPluggableBackend():
	def allocate_ips_for_port_and_store(self, context, port, port_id):
	    port_copy = {'port': port['port'].copy()}
        port_copy['port']['id'] = port_id
        network_id = port_copy['port']['network_id']
        ips = []
        try:
            ips = self._allocate_ips_for_port(context, port_copy)	# 获取ip的主要方法
            for ip in ips:
                ip_address = ip['ip_address']
                subnet_id = ip['subnet_id']
                IpamPluggableBackend._store_ip_allocation(
                    context, ip_address, network_id,
                    subnet_id, port_id)
            return ips

	def _allocate_ips_for_port(self, context, port):
		p = port['port']
        fixed_configured = p['fixed_ips'] is not constants.ATTR_NOT_SPECIFIED	
        # 传入的值是否有指定的ip
        subnets = self._ipam_get_subnets(context,
                                         network_id=p['network_id'],
                                         host=p.get(portbindings.HOST_ID),
                                         service_type=p.get('device_owner'),
                                         fixed_configured=fixed_configured)	
                                         # 获取子网列表 稍后具体分析

		v4, v6_stateful, v6_stateless = self._classify_subnets(
            context, subnets)	# 对子网列表分类,v4,有状态v6,无状态v6

        if fixed_configured:
            ips = self._test_fixed_ips_for_port(context,
                                                p["network_id"],
                                                p['fixed_ips'],
                                                p['device_owner'],
                                                subnets)	
        # 如果创建时传入指定的IP,则测试是否能正确关联到已有子网,得到指定IP和子网的对应字典
                                               
        else:
            ips = []
            version_subnets = [v4, v6_stateful]
            for subnets in version_subnets:
                if subnets:
                    ips.append([{'subnet_id': s['id']}
                                for s in subnets])	
        # 创建时未传入指定的IP,则获取可用子网列表ips,ips分为三部分,ips的顺序为v4列表-->有状态v6列表-->无状态v6列表

        ips.extend(self._get_auto_address_ips(v6_stateless, p))

        ipam_driver = driver.Pool.get_instance(None, context)
        return self._ipam_allocate_ips(context, ipam_driver, p, ips)

	# 按照获取子网列表顺序来选择子网创建
	def _ipam_allocate_ips(self, context, ipam_driver, port, ips,
                           revert_on_fail=True):
         for ip in ips:
			 subnets = [ip_dict['subnet_id'] for ip_dict in ip_list]
	                try:
	                    factory = ipam_driver.get_address_request_factory()
	                    # 对每一部分取第一个子网
	                    ip_request = factory.get_request(context, port, ip_list[0])
	                    ipam_allocator = ipam_driver.get_allocator(subnets)
	                    ip_address, subnet_id = ipam_allocator.allocate(ip_request)

那子网列表顺序就是在网络中选择子网的关键了!
接下来看看之前子网列表的顺序是如何获得的

neutron.db.ipam_backend_mixin
class IpamBackendMixin():
	def _ipam_get_subnets(self, context, network_id, host, service_type=None,
                          fixed_configured=False):	
        # 参数分析: network_id为选择的网络,service_type为创建选择的port类型,fixed_configured为是否指定了IP
    	subnets = self._find_candidate_subnets(context, network_id, host,
                                               service_type, fixed_configured)	
        # 此方法查询和过滤符合的子网,获得子网中service_type为None和与port类型相等的子网
        if subnets:
            subnet_dicts = [self._make_subnet_dict(subnet, context=context)
                            for subnet in subnets]	
                            # 获取子网详细信息
            return sorted(
                subnet_dicts,
                key=lambda subnet: not subnet.get('service_types'))	
                # 排序, 将符合service_type的子网放在列表前方
	

总结

创建port选择子网时,需要先获取可用子网列表,按顺序选择;那么子网列表中的排序则为选择子网的关键;
第一:知道port的类型,当所选择网络里有子网的service_type属性与port类型相等的放在最前面,无service_type排在第二部分,不相等的则丢弃;
第二:将第一步排序后的子网分类,分为v4,有状态v6,无状态v6,此时的子网列表顺序就按照子网ID来排序(0-9, a-f);
第三:如果创建port时,指定了IP地址,则选用该IP地址对应的子网。
第四:如果未指定IP地址,则生成v4,有状态v6,无状态v6的子网列表,则每一部分(v4, stateful, stateless)都获取第一个子网上的ip;
注意:如果v4,v6stateful,v6stateless都存在一个网络上,则每一个类型都取一个IP关联到port。

Created with Raphaël 2.2.0 符合service_type, 无service_type 指定IP? 分配成功? 结束 分配失败,报错 v4,有状态v6,无状态v6 子网ID,0-9-a-z yes no yes no
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值