【Nova】nova-network网络模型之flat网络-代码学习

在上一篇“【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形同虚设。




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值