概括
在Openstack Neutron中,路由添加网关,分配浮动IP或者创建虚拟机时,都需要创建Port资源;本文主要介绍创建Port选择网络时,如果网络中有多个子网时,如何选择子网来分配IP。
代码分析
本文选用OpenStack Rocky版本
子网参数强调
Field | Value |
---|---|
allocation_pools | {“start”: “10.0.20.100”, “end”: “10.0.20.200”} |
cidr | 10.0.20.0/24 |
enable_dhcp | True |
id | 51513b8e-261a-4e74-90ac-c34afdbf826a |
ip_version | 4 |
name | WAN1 |
network_id | bbdd5bc3-a135-493b-a20f-e7af7faec992 |
project_id | 707e0c610f954586a6c9a8b7d48c0151 |
service_types | |
tenant_id | 707e0c610f954586a6c9a8b7d48c0151 |
参数中service_types是本文关键
通过命令行可以增加service_types值
# openstack subnet set --service_type netowrk:floatingip/neutron:router_gateway subnet_id
创建Port前
简单介绍一下创建Port的调用
- 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
- 路由增加网关
# 更新路由
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。