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

来源:互联网 发布:淘宝开放平台登录授权 编辑:程序博客网 时间:2024/05/22 03:43

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