在上一篇“【Nova】nova-network网络模型之flat网络”中对flat网络的架构有了一定的认识,那么现在学习代码就事半功倍了。
1.nova-network在接受rpc请求之前,会调用网络管理器的init_host方法进行一定的初始化工作,flat模式下这个初始化工作十分的简单:
只需要配置网络的injected为True即可,也就是我们需要注入网络配置到虚拟机内。前面我们有讲过nova-api-metadata服务,那这里为什么不使用元数据服务来告诉虚拟机它的固定ip呢?因为虚拟机在没有配置网络的情况下,它是无法访问元数据服务的。
class NetworkManager(manager.Manager):
SHOULD_CREATE_BRIDGE = False
DHCP = False
def init_host(self):
ctxt = context.get_admin_context()
# 对和主机有关联的网络进行以下操作, 通常flat模式仅有一个虚拟网络
for network in network_obj.NetworkList.get_by_host(ctxt, self.host):
# 对网络进行设置工作
self._setup_network_on_host(ctxt, network)
# update_dns_entries默认为False
if CONF.update_dns_entries:
dev = self.driver.get_dev(network)
self.driver.update_dns(ctxt, dev, network)
class FlatManager(NetworkManager):
def _setup_network_on_host(self, context, network)
# 设置网络的injected字段为True;
# flat_injected代表是否需要注入网络配置到虚拟机内;
# 由于flat模式并没有DHCP服务, 因此是需要进行注入的
network.injected = CONF.flat_injected
network.save()
2.然后我们看一下flat模式下为实例分配网络资源的API allocate_for_instance:
class NetworkManager(manager.Manager):
# 为实例获取网络列表
def _get_networks_for_instance(self, context, instance_id, project_id,
requested_networks=None):
if requested_networks is not None and len(requested_networks) != 0:
# 如果requested_networks不为空, 那么就在requested_networks获取网络列表
network_uuids = [uuid for (uuid, fixed_ip) in requested_networks]
networks = self._get_networks_by_uuids(context, network_uuids)
else:
# 反之requested_networks为空, 那就获取当前project下的所有网络
try:
networks = network_obj.NetworkList.get_all(context)
except exception.NoNetworksFound:
return []
# 过滤出不是vlan的网络列表并返回
return [network for network in networks if not network.vlan]
# 从network中提取出需要使用的部分信息, 并返回字典
def _get_network_dict(self, network):
network_dict = {'id': network['uuid'],
'bridge': network['bridge'],
'label': network['label'],
'tenant_id': network['project_id']}
if network.get('injected'):
network_dict['injected'] = network['injected']
return network_dict
# 为实例创建数据库的VirtualInterface记录, VirtualInterface可理解为虚拟网卡
def _add_virtual_interface(self, context, instance_uuid, network_id,
mac=None):
# 确定重试次数
attempts = 1 if mac else CONF.create_unique_mac_address_attempts
for i in range(attempts):
try:
vif = vif_obj.VirtualInterface()
# 如果没有传递mac地址, 那么我就自动生成一个mac地址;
# OpenStack的虚拟机mac地址有一个特征: 开头3个字节是0xfa, 0x16, 0x3e
vif.address = mac or utils.generate_mac_address()
vif.instance_uuid = instance_uuid
vif.network_id = network_id
vif.uuid = str(uuid.uuid4())
# 将VirtualInterface实例存入数据库并返回
vif.create(context)
return vif
except exception.VirtualInterfaceCreateException:
# Try again up to max number of attempts
pass
raise exception.VirtualInterfaceMacAddressException()
# 为虚拟机实例分配mac地址
def _allocate_mac_addresses(self, context, instance_uuid, networks, macs):
if macs is not None:
available_macs = set(macs)
for network in networks:
if macs is None:
# 为实例创建mac地址并创建数据库的VirtualInterface记录
self._add_virtual_interface(context, instance_uuid,
network['id'])
else:
# 当传递的macs不为空的情况, 我们从中选取一个mac
try:
mac = available_macs.pop()
except KeyError:
raise exception.VirtualInterfaceCreateException()
# 为实例创建数据库的VirtualInterface记录
self._add_virtual_interface(context, instance_uuid,
network['id'], mac)
# 为实例分配固定IP
def allocate_fixed_ip(self, context, instance_id, network, **kwargs):
address = None
instance = instance_obj.Instance.get_by_uuid(context, instance_id)
# OpenStack的配额管理可以限制租户对资源的使用情况;
# 下面这段代码就是判断当前租户的固定ip使用情况是否超出配额
quotas = self.quotas_cls()
quota_project, quota_user = quotas_obj.ids_from_instance(context,
instance)
try:
quotas.reserve(context, fixed_ips=1, project_id=quota_project,
user_id=quota_user)
except exception.OverQuota:
LOG.warn(_("Quota exceeded for %s, tried to allocate "
"fixed IP"), context.project_id)
raise exception.FixedIpLimitExceeded()
try:
if network['cidr']:
address = kwargs.get('address', None)
if address:
# 如果我们指明了要分配的固定ip,那么直接进行绑定即可;
# 在use_local为False的情况下, 这里其实是调用nova-conductor的RpcAPI来实现的
fip = fixed_ip_obj.FixedIP.associate(context, address,
instance_id,
network['id'])
else:
# 如果我们没有指明要分配的固定IP,那么我们从固定IP池中选取一个可用的固定IP与实例进行绑定
fip = fixed_ip_obj.FixedIP.associate_pool(
context.elevated(), network['id'], instance_id)
vif = vif_obj.VirtualInterface.get_by_instance_and_network(
context, instance_id, network['id'])
# 标记固定IP为已分配的, 并与实例在该网络下的虚拟网卡进行关联
fip.allocated = True
fip.virtual_interface_id = vif.id
fip.save()
# 触发安全组成员实例刷新触发器
self._do_trigger_security_group_members_refresh_for_instance(
instance_id)
name = instance.display_name
# 验证实例所在的availability_zone是否与DNS的domain一致, 默认情况下验证通过
if self._validate_instance_zone_for_dns_domain(context, instance):
# 使用实例DNS管理器为该实例创建记录;
# 默认配置下的实例DNS管理器的方法都是noop, 也就是什么都不做
self.instance_dns_manager.create_entry(
name, str(fip.address), "A", self.instance_dns_domain)
self.instance_dns_manager.create_entry(
instance_id, str(fip.address), "A",
self.instance_dns_domain)
# 在该主机上设置该网络, flat模式下就是设置网络的injected字段为True
self._setup_network_on_host(context, network)
# 更新该租户的配额使用情况
quotas.commit(context)
return address
except Exception:
with excutils.save_and_reraise_exception():
quotas.rollback(context)
# RpcAPI:为实例分配网络资源
def allocate_for_instance(self, context, **kwargs):
instance_uuid = kwargs['instance_id']
if not uuidutils.is_uuid_like(instance_uuid):
instance_uuid = kwargs.get('instance_uuid')
host = kwargs['host']
project_id = kwargs['project_id']
rxtx_factor = kwargs['rxtx_factor']
requested_networks = kwargs.get('requested_networks')
vpn = kwargs['vpn']
macs = kwargs['macs']
admin_context = context.elevated()
LOG.debug(_("network allocations"), instance_uuid=instance_uuid,
context=context)
# 获取当前实例的可用网络列表
networks = self._get_networks_for_instance(admin_context,
instance_uuid, project_id,
requested_networks=requested_networks)
# 从networks中提取出需要的部分网络信息, 包括uuid、bridge、label、project_id和injected等
networks_list = [self._get_network_dict(network)
for network in networks]
LOG.debug(_('networks retrieved for instance: |%s|'),
networks_list, context=context, instance_uuid=instance_uuid)
try:
# 为实例分配mac地址并创建数据库的VirtualInterface记录
self._allocate_mac_addresses(context, instance_uuid, networks,
macs)
except Exception as e:
with excutils.save_and_reraise_exception():
# 发生异常时, 删除实例的已有VirtualInterface记录并向外抛出异常
vif_obj.VirtualInterface.delete_by_instance_uuid(context,
instance_uuid)
# 为实例分配固定ip
self._allocate_fixed_ips(admin_context, instance_uuid,
host, networks, vpn=vpn,
requested_networks=requested_networks)
# 默认不用更新DNS记录
if CONF.update_dns_entries:
network_ids = [network['id'] for network in networks]
self.network_rpcapi.update_dns(context, network_ids)
# 返回实例的所有网络信息
return self.get_instance_nw_info(context, instance_uuid, rxtx_factor,
host)
class FlatManager(NetworkManager):
def _allocate_fixed_ips(self, context, instance_id, host, networks,
**kwargs):
requested_networks = kwargs.get('requested_networks')
# 针对networks中的每个网络分配一次固定ip
for network in networks:
address = None
if requested_networks is not None:
# 如果我们希望对特定的网络指定要分配的固定ip, 那么就可以通过requested_networks来传递这个意愿;
# 我们假设对同一个网络指定了多个要分配的固定ip, 那么只选取第一个来进行分配
for address in (fixed_ip for (uuid, fixed_ip) in
requested_networks if network['uuid'] == uuid):
break
# 调用基类的allocate_fixed_ip来实现真正的分配工作
self.allocate_fixed_ip(context, instance_id,
network, address=address)
3.接下来我们看一下flat模式下为实例解绑固定ip的API deallocate_fixed_ip:
class NetworkManager(manager.Manager):
def deallocate_fixed_ip(self, context, address, host=None, teardown=True,
instance=None):
# 通过address从数据库中获取到FixedIP对象
fixed_ip_ref = fixed_ip_obj.FixedIP.get_by_address(
context, address, expected_attrs=['network'])
# 获取与固定ip绑定的实例uuid
instance_uuid = fixed_ip_ref.instance_uuid
# 获取与固定ip关联的虚拟网卡id
vif_id = fixed_ip_ref.virtual_interface_id
if not instance:
# 如果没有传递instance, 那么通过实例uuid从数据库中获取到Instance对象
instance = instance_obj.Instance.get_by_uuid(
context.elevated(read_deleted='yes'), instance_uuid)
quotas = self.quotas_cls()
quota_project, quota_user = quotas_obj.ids_from_instance(context,
instance)
# 更新该租户的配额使用情况
try:
quotas.reserve(context, fixed_ips=-1, project_id=quota_project,
user_id=quota_user)
except Exception:
LOG.exception(_("Failed to update usages deallocating "
"fixed IP"))
# 触发安全组成员实例刷新触发器
self._do_trigger_security_group_members_refresh_for_instance(
instance_uuid)
# 默认通过验证
if self._validate_instance_zone_for_dns_domain(context, instance):
# 默认什么也不做, noop
for n in self.instance_dns_manager.get_entries_by_address(address,
self.instance_dns_domain):
self.instance_dns_manager.delete_entry(n,
self.instance_dns_domain)
# 修改固定ip的已分配情况和关联的虚拟网卡id, 并保存进数据库
fixed_ip_ref.allocated = False
fixed_ip_ref.virtual_interface_id = None
fixed_ip_ref.save()
if teardown:
network = fixed_ip_ref.network
# 如果配置迫使DHCP释放IP
if CONF.force_dhcp_release:
# 获取网络的网桥, 即network['bridge']
dev = self.driver.get_dev(network)
msg = _("Unable to release %s because vif doesn't exist.")
if not vif_id:
LOG.error(msg % address)
return
# 通过vif_id从数据库中获取VirtualInterface对象
vif = vif_obj.VirtualInterface.get_by_id(context, vif_id)
if not vif:
LOG.error(msg % address)
return
# 什么也不做
self._teardown_network_on_host(context, network)
# 使用dhcp_release命令行工具迫使DHCP释放address;
# 如果说flat模式没有DHCP服务,那么这里也就是无意义的?
self.driver.release_dhcp(dev, address, vif.address)
# 解绑固定ip,这里就是清空FixedIP的instance_uuid字段
fixed_ip_ref = fixed_ip_obj.FixedIP.get_by_address(
context, address)
if (instance_uuid == fixed_ip_ref.instance_uuid and
not fixed_ip_ref.leased):
fixed_ip_ref.disassociate()
else:
# 什么也不做
self._teardown_network_on_host(context, network)
# 提交配额的更新事务
quotas.commit(context)
class FlatManager(NetworkManager):
# 解绑固定ip使之返回固定ip池
def deallocate_fixed_ip(self, context, address, host=None, teardown=True,
instance=None):
# 调用父类的解绑方法
super(FlatManager, self).deallocate_fixed_ip(context, address, host,
teardown,
instance=instance)
# disassociate_by_address这个接口, 看名字是解绑固定IP;
# 但是深入进去, 发现本质是获取固定IP, 不太明白;
# 不过上面deallocate_fixed_ip已经实现了解绑功能, 这里什么也不做也是可以的
fixed_ip_obj.FixedIP.disassociate_by_address(context, address)
def _teardown_network_on_host(self, context, network):
pass
4.在Icehouse版本中,flat模式下nova-network的RpcAPI很少,连浮动IP有关的API都没有,难以在生产环境中的使用
5.不过曾经遇到一些特殊需求, 譬如反nat网络的软件。那么可以考虑使用flat网络来解决,创建网桥br100让虚拟机实例与计算节点共享物理网络,如果物理网络有DHCP,那么可以直接使用,也省去了配置虚拟机实例网络的问题,但是也有新的问题: 分配给虚拟机的固定ip和我们在虚拟机的来宾系统中看到的ip是不一样的,也就是分配给虚拟机的固定ip基本上起不到作用了,这种情况下nova-network形同虚设。