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

2019-04-14 21:08发布

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