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

1.首先讲解flatdhcp网络下nova-network服务启动之前的准备工作:

OpenStack使用网桥设备来组织虚拟网络,使用iptables和ebtables这两个l3和l2的防火墙来进行流量过滤和隔离

使用了开源软件DNSmasq来作为DHCP服务和DNS中继,使用了开源软件radvd来作为IPv6路由器

# -----------------------------------------------
# l3 nova/network/linux_net.py backend
# -----------------------------------------------

# 在iptables中添加SNAT规则
def add_snat_rule(ip_range):
    # routing_source_ip就是my_ip, 也就是主机的可路由IP
    if CONF.routing_source_ip:
        # force_snat_range这个配置项表示前往该网段的流量会被强制进行SNAT转换
        for dest_range in CONF.force_snat_range or ['0.0.0.0/0']:
            # 对源地址为ip_range, 目的地址为dest_range的IP数据包进行SNAT修改其源地址为routing_source_ip
            rule = ('-s %s -d %s -j SNAT --to-source %s'
                    % (ip_range, dest_range, CONF.routing_source_ip))
            # 如果配置公网网卡, 那么还要为规则指定"出口网卡";
            # 通常public_interface的IP即为routing_source_ip
            if CONF.public_interface:
                rule += ' -o %s' % CONF.public_interface
            # 在iptables的nat表中添加此规则
            iptables_manager.ipv4['nat'].add_rule('snat', rule)
        # 使规则生效, 使用了iptables-save和iptables-restore这两个命令行工具
        iptables_manager.apply()


# 为了同步进行加锁, 这里使用的文件锁
@utils.synchronized('ebtables', external=True)
def ensure_ebtables_rules(rules, table='filter'):
    for rule in rules:
        # 首先删除此规则
        cmd = ['ebtables', '-t', table, '-D'] + rule.split()
        _execute(*cmd, check_exit_code=False, run_as_root=True)
        # 然后添加此规则
        cmd[3] = '-I'
        _execute(*cmd, run_as_root=True)


def init_host(ip_range):
    # 这里其实就是为每个虚拟网络添加默认的SNAT规则, 使虚拟机实例即使在没有浮动IP的情况下也能上网
    add_snat_rule(ip_range)

    # 以下用于添加ebtables规则
    # 使所有进入网桥的源地址为ip_range、目的地址为snat_range的IP数据包被路由而不是桥接
    rules = []
    for snat_range in CONF.force_snat_range:
        rules.append('PREROUTING -p ipv4 --ip-src %s --ip-dst %s '
                     '-j redirect --redirect-target ACCEPT' %
                     (ip_range, snat_range))
    if rules:
        ensure_ebtables_rules(rules, 'nat')

    # 在iptables的nat表中添加规则:
    # 使虚拟网络可以访问metadata_host, 所以在虚拟机实例中我们不仅可以通过169.254.169.254:80还可以通过
    # metadata_host:metadata_port访问元数据服务
    iptables_manager.ipv4['nat'].add_rule('POSTROUTING',
                                          '-s %s -d %s/32 -j ACCEPT' %
                                          (ip_range, CONF.metadata_host))

    # 为隔离区DMZ添加规则:使虚拟网络可以访问DMZ
    for dmz in CONF.dmz_cidr:
        iptables_manager.ipv4['nat'].add_rule('POSTROUTING',
                                              '-s %s -d %s -j ACCEPT' %
                                              (ip_range, dmz))

    # 使虚拟网络中的实例可以互相访问
    iptables_manager.ipv4['nat'].add_rule('POSTROUTING',
                                          '-s %(range)s -d %(range)s '
                                          '-m conntrack ! --ctstate DNAT '
                                          '-j ACCEPT' %
                                          {'range': ip_range})
    # 使添加的iptables规则生效
    iptables_manager.apply()

# 检测以太网设备是否存在
def device_exists(device):
    return os.path.exists('/sys/class/net/%s' % device)

# 创建命令:用于添加/删除网桥/设备的IPs
def _ip_bridge_cmd(action, params, device):
    cmd = ['ip', 'addr', action]
    cmd.extend(params)
    cmd.extend(['dev', device])
    return cmd
    
def get_gateway_rules(bridge):
    # 获取流量可以被网桥转发的端口列表, 默认['all'],
    interfaces = CONF.forward_bridge_interface
    if 'all' in interfaces:
        # 当all在forward_bridge_interface中时, 所有流量都将被转发
        return [('FORWARD', '-i %s -j ACCEPT' % bridge),
                ('FORWARD', '-o %s -j ACCEPT' % bridge)]
    rules = []
    for iface in CONF.forward_bridge_interface:
        if iface:
            # 只有在forward_bridge_interface中的端口, 进出的流量才会被转发
            rules.append(('FORWARD', '-i %s -o %s -j ACCEPT' % (bridge,
                                                                iface)))
            rules.append(('FORWARD', '-i %s -o %s -j ACCEPT' % (iface,
                                                                bridge)))
    # 进出网桥的流程会被转发
    rules.append(('FORWARD', '-i %s -o %s -j ACCEPT' % (bridge, bridge)))
    # 不匹配以上3条规则的流量都将被丢弃
    rules.append(('FORWARD', '-i %s -j %s' % (bridge,
                                              CONF.iptables_drop_action)))
    rules.append(('FORWARD', '-o %s -j %s' % (bridge,
                                              CONF.iptables_drop_action)))
    return rules

def isolate_dhcp_address(interface, address):
    rules = []
    # interface是内部网络网卡, 因此从其他节点发往本节点的虚拟网络流量会经由interface进入;
    # 进出interface的目的地址为address的ARP数据包全部丢弃,也就是DHCP服务只对本主机的实例服务
    rules.append('INPUT -p ARP -i %s --arp-ip-dst %s -j DROP'
                 % (interface, address))
    rules.append('OUTPUT -p ARP -o %s --arp-ip-src %s -j DROP'
                 % (interface, address))
    # 路过interface的DHCP包全部丢弃
    rules.append('FORWARD -p IPv4 -i %s --ip-protocol udp '
                 '--ip-destination-port 67:68 -j DROP'
                 % interface)
    rules.append('FORWARD -p IPv4 -o %s --ip-protocol udp '
                 '--ip-destination-port 67:68 -j DROP'
                 % interface)
    # 使用ebtables添加这些规则
    ensure_ebtables_rules(rules)

class LinuxBridgeInterfaceDriver(LinuxNetInterfaceDriver):

    @staticmethod
    @utils.synchronized('lock_bridge', external=True)
    # 创建网桥
    def ensure_bridge(bridge, interface, net_attrs=None, gateway=True,
                      filtering=True):
        # 首先判断网桥是否存在
        if not device_exists(bridge):
            # 如果不存在, 就自动进行创建
            LOG.debug(_('Starting Bridge %s'), bridge)
            # brctl是Linux以太网网桥工具bridge-utils中的命令行工具
            # addbr参数用于创建网桥
            _execute('brctl', 'addbr', bridge, run_as_root=True)
            # setfd参数用于设置网桥的转发延迟时间
            _execute('brctl', 'setfd', bridge, 0, run_as_root=True)
            # stp参数用于启用/关闭STP(生成树协议), 这里就是关闭STP
            _execute('brctl', 'stp', bridge, 'off', run_as_root=True)
            # 启用网桥
            _execute('ip', 'link', 'set', bridge, 'up', run_as_root=True)

        if interface:
            msg = _('Adding interface %(interface)s to bridge %(bridge)s')
            LOG.debug(msg, {'interface': interface, 'bridge': bridge})
            # 让interface物理网卡成为网桥的一个端口
            out, err = _execute('brctl', 'addif', bridge, interface,
                                check_exit_code=False, run_as_root=True)
            if (err and err != "device %s is already a member of a bridge; "
                     "can't enslave it to bridge %s.\n" % (interface, bridge)):
                msg = _('Failed to add interface: %s') % err
                raise exception.NovaException(msg)

            # 启用interface物理网卡
            out, err = _execute('ip', 'link', 'set', interface, 'up',
                                check_exit_code=False, run_as_root=True)

            old_routes = []
            # 获取interface物理网卡上的路由信息
            out, err = _execute('ip', 'route', 'show', 'dev', interface)
            for line in out.split('\n'):
                fields = line.split()
                if fields and 'via' in fields:
                    old_routes.append(fields)
                    # 删除已有的路由信息
                    _execute('ip', 'route', 'del', *fields,
                             run_as_root=True)
            # 获取interface物理网卡的作用域为global的配置信息
            out, err = _execute('ip', 'addr', 'show', 'dev', interface,
                                'scope', 'global')
            for line in out.split('\n'):
                fields = line.split()
                # 以下只针对IPv4
                if fields and fields[0] == 'inet':
                    if fields[-2] in ('secondary', 'dynamic', ):
                        params = fields[1:-2]
                    else:
                        params = fields[1:-1]
                    # 删除interface物理网卡上的该IP
                    # 所以, 如果我们部署的时候选定的内部网络网卡即使配置了IP, 那么部署成功后,
                    # 这些IP也会被nova-network擦除掉
                    _execute(*_ip_bridge_cmd('del', params, fields[-1]),
                             run_as_root=True, check_exit_code=[0, 2, 254])
                    # 将该IP添加至网桥
                    _execute(*_ip_bridge_cmd('add', params, bridge),
                             run_as_root=True, check_exit_code=[0, 2, 254])
            # 恢复之前删除的路由信息
            # 为什么要先删除再恢复呢? 看注释是说不要破坏接口上的已有连接
            for fields in old_routes:
                _execute('ip', 'route', 'add', *fields,
                         run_as_root=True)

        # 如果需要在网桥上创建过滤规则
        if filtering:
            ipv4_filter = iptables_manager.ipv4['filter']
            if gateway:
                # 网桥只有在作为网关的情况下, 才会转发流量;
                # 这里会获取网桥的规则并逐条添加, 默认情况下所有进出网桥的流量都会被转发
                for rule in get_gateway_rules(bridge):
                    ipv4_filter.add_rule(*rule)
            else:
                # 从网桥进入或出去的数据包全部丢弃
                ipv4_filter.add_rule('FORWARD',
                                     ('--in-interface %s -j %s'
                                      % (bridge, CONF.iptables_drop_action)))
                ipv4_filter.add_rule('FORWARD',
                                     ('--out-interface %s -j %s'
                                      % (bridge, CONF.iptables_drop_action)))

    def plug(self, network, mac_address, gateway=True):
        # flatdhcp网络下虚拟网络的vlan为空
        vlan = network.get('vlan')
        if vlan is not None:
            iface = CONF.vlan_interface or network['bridge_interface']
            LinuxBridgeInterfaceDriver.ensure_vlan_bridge(
                           vlan,
                           network['bridge'],
                           iface,
                           network,
                           mac_address)
            iface = 'vlan%s' % vlan
        else:
            # 从配置文件或者数据库存储的信息中获取flatdhcp桥接到的物理网卡, 也就是我们所说的内部网络网卡
            iface = CONF.flat_interface or network['bridge_interface']
            # 创建我们之前所说的br100网桥
            LinuxBridgeInterfaceDriver.ensure_bridge(
                          network['bridge'],
                          iface,
                          network, gateway)

        if CONF.share_dhcp_address:
            # 如果共用一个DHCP服务地址, 因为虚拟网络是互通的, 所以要进行相应的隔离工作
            isolate_dhcp_address(iface, network['dhcp_server'])
        # 使规则生效并返回网桥名
        iptables_manager.apply()
        return network['bridge']

def plug(network, mac_address, gateway=True):
    # 使用以太网接口驱动去创建一个接口, 这里的驱动就是LinuxBridgeInterfaceDriver
    return _get_interface_driver().plug(network, mac_address, gateway)

# 使能IPv4转发
def _enable_ipv4_forwarding():
    sysctl_key = 'net.ipv4.ip_forward'
    # 首先通过sysctl读取net.ipv4.ip_forward内核参数的当前值
    stdout, stderr = _execute('sysctl', '-n', sysctl_key)
    if stdout.strip() is not '1':
        # 如果没有开启IPv4转发, 那么修改内核参数使能
        _execute('sysctl', '-w', '%s=1' % sysctl_key, run_as_root=True)

# 向指定IP发现ARP包
def send_arp_for_ip(ip, device, count):
    out, err = _execute('arping', '-U', ip,
                        '-A', '-I', device,
                        '-c', str(count),
                        run_as_root=True, check_exit_code=False)

    if err:
        LOG.debug(_('arping error for ip %s'), ip)

@utils.synchronized('lock_gateway', external=True)
def initialize_gateway_device(dev, network_ref):
    if not network_ref:
        return

    # 使能Linux内核的IPv4转发功能
    _enable_ipv4_forwarding()

    # 获取虚拟网络CIDR的遮罩值, "10.0.0.0/24"中的24
    try:
        prefix = network_ref.cidr.prefixlen
    except AttributeError:
        prefix = network_ref['cidr'].rpartition('/')[2]

    full_ip = '%s/%s' % (network_ref['dhcp_server'], prefix)
    new_ip_params = [[full_ip, 'brd', network_ref['broadcast']]]
    old_ip_params = []
    # 获取网桥作用域为global的IPv4信息, 将每一行信息记录在old_ip_params中;
    # 将新增的信息记录追加在new_ip_params中
    out, err = _execute('ip', 'addr', 'show', 'dev', dev,
                        'scope', 'global')
    for line in out.split('\n'):
        fields = line.split()
        if fields and fields[0] == 'inet':
            ip_params = fields[1:-1]
            old_ip_params.append(ip_params)
            if ip_params[0] != full_ip:
                new_ip_params.append(ip_params)
    # 如果old_ip_params为空, 或者old_ip_params的第一条记录的IP不为full_ip,
    # 说明网桥的IP信息被修改过, 我们需要进行重新初始化
    if not old_ip_params or old_ip_params[0][0] != full_ip:
        old_routes = []
        # 先记录网桥的带via路由信息并删除
        result = _execute('ip', 'route', 'show', 'dev', dev)
        if result:
            out, err = result
            for line in out.split('\n'):
                fields = line.split()
                if fields and 'via' in fields:
                    old_routes.append(fields)
                    _execute('ip', 'route', 'del', fields[0],
                             'dev', dev, run_as_root=True)
        # 再删除网桥的现有IP信息
        for ip_params in old_ip_params:
            _execute(*_ip_bridge_cmd('del', ip_params, dev),
                     run_as_root=True, check_exit_code=[0, 2, 254])
        # 添加网桥的IP信息, [full_ip, 'brd', network_ref['broadcast']]应该作为第一条
        for ip_params in new_ip_params:
            _execute(*_ip_bridge_cmd('add', ip_params, dev),
                     run_as_root=True, check_exit_code=[0, 2, 254])
        # 添加之前被删除的路由信息
        for fields in old_routes:
            _execute('ip', 'route', 'add', *fields,
                     run_as_root=True)
        # 为了HA(高可用性), 我们可以发生ARP进行适量的检测工作
        # 默认下send_arp_for_ha为False
        if CONF.send_arp_for_ha and CONF.send_arp_for_ha_count > 0:
            # 这里就是通过网桥向DHCP服务地址发送ARP包, 正常情况下应该是收不到响应的
            send_arp_for_ip(network_ref['dhcp_server'], dev,
                            CONF.send_arp_for_ha_count)
    # 如果使用IPv6, 那么为网桥添加IPv6地址
    if CONF.use_ipv6:
        _execute('ip', '-f', 'inet6', 'addr',
                 'change', network_ref['cidr_v6'],
                 'dev', dev, run_as_root=True)

# 获取指定网桥/设备的DHCP相关文件
def _dhcp_file(dev, kind):
    # 这些文件在$state_path/networks下面
    fileutils.ensure_tree(CONF.networks_path)
    return os.path.abspath('%s/nova-%s.%s' % (CONF.networks_path,
                                              dev,
                                              kind))
                                              
def update_dns(context, dev, network_ref):
    # 获取DNSmasq服务的本地解析文件路径
    hostsfile = _dhcp_file(dev, 'hosts')
    # 将当前网络的所有实例的固定IP和主机名按照/etc/hosts文件的格式写入本地解析文件
    write_to_file(hostsfile, get_dns_hosts(context, network_ref))
    # 重启DNSmasq服务
    restart_dhcp(context, dev, network_ref)

# 按照DHCP服务的数据库文件格式返回固定IP的信息
def _host_dhcp(fixedip):
    # use_single_default_gateway默认为False
    if CONF.use_single_default_gateway:
        return '%s,%s.%s,%s,%s' % (fixedip.virtual_interface.address,
                               fixedip.instance.hostname,
                               CONF.dhcp_domain,
                               fixedip.address,
                               'net:' + _host_dhcp_network(fixedip))
    else:
        # 数据库文件格式为: [MAC地址]:[实例的主机名].[DHCP域名].[固定IP]
        return '%s,%s.%s,%s' % (fixedip.virtual_interface.address,
                               fixedip.instance.hostname,
                               CONF.dhcp_domain,
                               fixedip.address)

# 获取网络对应的DHCP服务数据库文件内容
def get_dhcp_hosts(context, network_ref):
    hosts = []
    host = None
    if network_ref['multi_host']:
        host = CONF.host
    macs = set()
    # 从nova-conductor或者数据库中获取该主机有关的固定IP
    for fixedip in fixed_ip_obj.FixedIPList.get_by_network(context,
                                                           network_ref,
                                                           host=host):
        if fixedip.virtual_interface.address not in macs:
            # 将当前固定IP的相关信息追加到hosts中
            hosts.append(_host_dhcp(fixedip))
            macs.add(fixedip.virtual_interface.address)
    return '\n'.join(hosts)

# 在iptables的mangle表中为DHCP添加规则
def _add_dhcp_mangle_rule(dev):
    # 判断vhost-net字符设备是否存在
    if not os.path.exists('/dev/vhost-net'):
        return
    # 向iptables的mangle表中添加以下规则:
    # 从网桥出来的DHCP响应数据包向其填充checksum;
    # 这样做的意义是: 有些老的dhcpclient必须检验checksum, 向其保持兼容
    table = iptables_manager.ipv4['mangle']
    table.add_rule('POSTROUTING',
                   '-o %s -p udp -m udp --dport 68 -j CHECKSUM '
                   '--checksum-fill' % dev)
    iptables_manager.apply()

# 在iptables的filter表中为DNSmasq添加ACCEPT规则
def _add_dnsmasq_accept_rules(dev):
    table = iptables_manager.ipv4['filter']
    # 进入网桥的DHCP和DNS数据包一律接收
    for port in [67, 53]:
        for proto in ['udp', 'tcp']:
            args = {'dev': dev, 'port': port, 'proto': proto}
            table.add_rule('INPUT',
                           '-i %(dev)s -p %(proto)s -m %(proto)s '
                           '--dport %(port)s -j ACCEPT' % args)
    iptables_manager.apply()

# 重启指定网络的DHCP服务, OpenStack使用的DNSmasq作为DHCP服务
@utils.synchronized('dnsmasq_start')
def restart_dhcp(context, dev, network_ref):
    # 获取网桥对应网络的DHCP数据库文件
    conffile = _dhcp_file(dev, 'conf')

    # use_single_default_gateway默认为False
    if CONF.use_single_default_gateway:
        optsfile = _dhcp_file(dev, 'opts')
        write_to_file(optsfile, get_dhcp_opts(context, network_ref))
        os.chmod(optsfile, 0o644)

    # 在iptables的mangle表中为DHCP添加规则
    _add_dhcp_mangle_rule(dev)

    # 修改数据库文件权限为644, 保证DNSmasq能进行读取
    os.chmod(conffile, 0o644)
    # DNSmasq的pid文件存放在和数据库文件一样的地方, 这里试图去读取该pid文件, 并返回pid;
    pid = _dnsmasq_pid_for(dev)

    # 如果pid不为空, 那么我们就认为DNSmasq正在运行
    if pid:
        # 通过pid获取启动对应DNSmasq进程的命令行
        out, _err = _execute('cat', '/proc/%d/cmdline' % pid,
                             check_exit_code=False)
        # 如果数据库文件名在命令行中
        if conffile.split('/')[-1] in out:
            try:
                # 向pid对应的进行发送挂起信号;
                # DNSmasq进程接收到该信号后, 会重新加载配置文件
                _execute('kill', '-HUP', pid, run_as_root=True)
                # 为网桥添加规则, 让DNSmasq能接收到DHCP和DNS数据包
                _add_dnsmasq_accept_rules(dev)
                return
            except Exception as exc:  # pylint: disable=W0703
                LOG.error(_('Hupping dnsmasq threw %s'), exc)
        else:
            # 如果数据库文件名不在命令行中, 我们认为pid是无效的
            LOG.debug(_('Pid %d is stale, relaunching dnsmasq'), pid)
    
    # 如果pid为空或者pid无效, 那我们需要启动DNSmasq进程
    cmd = ['env',
           'CONFIG_FILE=%s' % jsonutils.dumps(CONF.dhcpbridge_flagfile),
           'NETWORK_ID=%s' % str(network_ref['id']),
           'dnsmasq',
           '--strict-order',                                   # 严格遵照/etc/resolv.conf中的DNS顺序
           '--bind-interfaces',                                # 
           '--conf-file=%s' % CONF.dnsmasq_config_file,        # 默认为空
           '--pid-file=%s' % _dhcp_file(dev, 'pid'),           # pid文件路径
           '--listen-address=%s' % network_ref['dhcp_server'], # 监听的DHCP服务IP, 即网桥IP
           '--except-interface=lo',
           '--dhcp-range=set:%s,%s,static,%s,%ss' %
                         (network_ref['label'],                # 虚拟网络名
                          network_ref['dhcp_start'],           # 虚拟网络的起始固定IP
                          network_ref['netmask'],              # 虚拟网络的子网掩码
                          CONF.dhcp_lease_time),               # DHCP的租约时间, 默认是120s
           '--dhcp-lease-max=%s' % len(netaddr.IPNetwork(network_ref['cidr'])), # DHCP的最大租约数, 等于虚拟网络的固定IP数量
                                                                                # 其实我觉得这里应该减去一个网关IP的, 哈哈
           '--dhcp-hostsfile=%s' % _dhcp_file(dev, 'conf'),    # DHCP的数据库文件
           '--dhcp-script=%s' % CONF.dhcpbridge,               # 在DHCP创建和销毁租约时调用的脚本, 默认是nova-dhcpbridge;
                                                               # 当DHCP分配一个固定IP后, 调用该脚本, 脚本会通知相应的nova-network服务
                                                               # 修改该固定IP的数据库字段leased为True; 反之回收时, 修改为False
           '--leasefile-ro']                                   # 不使用租约文件

    if CONF.dhcp_domain:
        cmd.append('--domain=%s' % CONF.dhcp_domain)           # 指定DHCP域, 默认是novalocal

    dns_servers = set(CONF.dns_server)
    # 我们在创建虚拟网络的时候可以指定2个DNS, use_network_dns_servers用于设置是否使用虚拟网络中的2个DNS, 默认是False
    if CONF.use_network_dns_servers:
        if network_ref.get('dns1'):
            dns_servers.add(network_ref.get('dns1'))
        if network_ref.get('dns2'):
            dns_servers.add(network_ref.get('dns2'))
    if network_ref['multi_host'] or dns_servers:
        cmd.append('--no-hosts')                               # 不加载/etc/hosts
    if network_ref['multi_host']:
        cmd.append('--addn-hosts=%s' % _dhcp_file(dev, 'hosts'))# /etc/hosts之外的本地解析文件
    if dns_servers:
        cmd.append('--no-resolv')                              # 如果指定了外部DNS, 那么不读取/etc/resolv.conf
    for dns_server in dns_servers:
        cmd.append('--server=%s' % dns_server)                 # 指定外部DNS
    if CONF.use_single_default_gateway:
        cmd += ['--dhcp-optsfile=%s' % _dhcp_file(dev, 'opts')]# 从opts文件读取DHCP配置

    _execute(*cmd, run_as_root=True)

    # 为网桥添加规则, 让DNSmasq能接收到DHCP和DNS数据包
    _add_dnsmasq_accept_rules(dev)

def update_dhcp(context, dev, network_ref):
    # 获取网桥的conf文件, 其实就是网桥对应网络的DHCP服务数据库文件,
    # 里面按照[MAC地址]:[实例名.后缀]:[IP]的格式,记录着对应虚拟网络的已分配固定IP情况
    conffile = _dhcp_file(dev, 'conf')
    # 更新该数据库文件内容
    write_to_file(conffile, get_dhcp_hosts(context, network_ref))
    # 重启DHCP服务
    restart_dhcp(context, dev, network_ref)

# OpenStack使用radvd来作为IPv6路由器服务
# 更新radvd配置文件, 并重启radvd服务
@utils.synchronized('radvd_start')
def update_ra(context, dev, network_ref):
    # 获取网桥对应网络的radvd配置文件路径
    conffile = _ra_file(dev, 'conf')
    # radvd配置选项
    conf_str = """
interface %s
{
   AdvSendAdvert on;
   MinRtrAdvInterval 3;
   MaxRtrAdvInterval 10;
   prefix %s
   {
        AdvOnLink on;
        AdvAutonomous on;
   };
};
""" % (dev, network_ref['cidr_v6'])
    # 将radvd配置选项保存至配置文件
    write_to_file(conffile, conf_str)

    # 修改radvd配置文件权限为644, 以确保radvd服务可以进行读取
    os.chmod(conffile, 0o644)
    # 获取radvd服务的pid
    pid = _ra_pid_for(dev)

    # 如果pid不为空, 我们认为radvd服务正在运行
    if pid:
        # 通过pid获取启动对应radvd进程的命令行
        out, _err = _execute('cat', '/proc/%d/cmdline'
                             % pid, check_exit_code=False)
        if conffile in out:
            # 如果配置文件路径在命令行中, 我们认为pid是有效的
            try:
                # kill掉当前的radvd进程
                _execute('kill', pid, run_as_root=True)
            except Exception as exc:  # pylint: disable=W0703
                LOG.error(_('killing radvd threw %s'), exc)
        else:
            # 如果配置文件路径在命令行中, 我们认为pid是无效的
            LOG.debug(_('Pid %d is stale, relaunching radvd'), pid)

    # 启动radvd服务
    cmd = ['radvd',
           '-C', '%s' % _ra_file(dev, 'conf'),
           '-p', '%s' % _ra_file(dev, 'pid')]
    _execute(*cmd, run_as_root=True)
# -----------------------------------------------
# l3 nova/network/linux_net.py backend end
# -----------------------------------------------
    
class LinuxNetL3(L3Driver):

    def __init__(self):
        self.initialized = False

    def initialize(self, **kwargs):
        # 每个实例只能初始化一次
        if self.initialized:
            return
        LOG.debug("Initializing linux_net L3 driver")
        fixed_range = kwargs.get('fixed_range', False)
        networks = kwargs.get('networks', None)
        if not fixed_range and networks is not None:
            for network in networks:
                # 对每个网络调用自己的initialize_network进行初始化
                self.initialize_network(network['cidr'])
        else:
            # fixed_range为True或者没有指定网络时,调用linux_net backend里的init_host方法;
            # 这里其实是有bug的, 因为linux_net backend的init_host是需要传递一个参数的
            linux_net.init_host()
        # 前面说过nova-api-metadata需要借助nova-network才能正常工作;
        # 这里就是首先是为lo回环设备添加辅助ip:169.254.169.254;
        # 然后将访问169.254.169.254:80的tcp数据包转发至nova-api-metadata服务
        linux_net.ensure_metadata_ip()
        linux_net.metadata_forward()
        self.initialized = True
    
    def initialize_network(self, cidr):
        # 调用linux_net backend里的init_host方法
        linux_net.init_host(cidr)
        
    def initialize_gateway(self, network_ref):
        # 生成一个随机的MAC地址
        mac_address = utils.generate_mac_address()
        # 创建网桥, 貌似这里的mac_address没派上用场
        dev = linux_net.plug(network_ref, mac_address,
                    gateway=(network_ref['gateway'] is not None))
        # 对网桥进行初始化工作
        linux_net.initialize_gateway_device(dev, network_ref)


class NetworkManager(manager.Manager):

    def __init__(self, network_driver=None, *args, **kwargs):
        # 加载linux_net backend
        self.driver = driver.load_network_driver(network_driver)
        ...

    def init_host(self):
        ctxt = context.get_admin_context()
        for network in network_obj.NetworkList.get_by_host(ctxt, self.host):
            # 对与本主机有关的网络进行设置, 该方法需要子类进行实现
            self._setup_network_on_host(ctxt, network)
            # 如果要更新DNS记录, 默认是False;
            # 开启的情况下, 我们可以通过"实例的主机名"访问实例
            if CONF.update_dns_entries:
                # 获取网络的网桥名
                dev = self.driver.get_dev(network)
                # 将实例的固定IP和主机名按照/etc/hosts的格式写入文件, 并重启DHCP服务
                self.driver.update_dns(ctxt, dev, network)


class FlatDHCPManager(RPCAllocateFixedIP, floating_ips.FloatingIP,
                      NetworkManager):

    SHOULD_CREATE_BRIDGE = True
    DHCP = True
    required_create_args = ['bridge']

    # 主机初始化工作
    def init_host(self):
        # 获取一个管理员权限上下文
        ctxt = context.get_admin_context()
        # 通过nova-conductor或者直接从数据库获取与本主机有关的网络
        networks = network_obj.NetworkList.get_by_host(ctxt, self.host)
        # l3驱动初始化工作, 默认的l3驱动是nova.network.l3.LinuxNetL3
        self.l3driver.initialize(fixed_range=False, networks=networks)
        # 调用父类NetworkManager中的init_host
        super(FlatDHCPManager, self).init_host()
        self.init_host_floating_ips()
    
    # 为了进行同步, 此处进行加锁操作
    @utils.synchronized('get_dhcp')
    def _get_dhcp_ip(self, context, network_ref, host=None):
        # 如果网络没有使用multi_host模式, 也即只有一个nova-network节点;
        # 或者使用multi_host模式, 但是大家使用同一个DHCP服务地址;
        # 在这两种情况下直接返回网络的gateway
        if not network_ref.get('multi_host') or CONF.share_dhcp_address:
            return network_ref['gateway']

        if not host:
            host = self.host
        network_id = network_ref['id']
        try:
            # 找出该网络中与本主机有关的一个固定IP作为DHCP服务地址
            fip = fixed_ip_obj.FixedIP.get_by_network_and_host(context,
                                                               network_id,
                                                               host)
            return fip.address
        except exception.FixedIpNotFoundForNetworkHost:
            elevated = context.elevated()
            # 如果在该网络中找不出与本主机有关的固定IP, 那么从该网络的固定IP池分配给一个
            # 该主机
            fip = fixed_ip_obj.FixedIP.associate_pool(elevated,
                                                      network_id,
                                                      host=host)
            return fip.address
        
    def _setup_network_on_host(self, context, network):
        
        # 获取本主机在该网络下的DHCP服务地址
        network['dhcp_server'] = self._get_dhcp_ip(context, network)

        # 调用l3驱动的initialize_network方法也即linux_net backend的init_host方法对该网络进行初始化;
        # 感觉这里的工作已经在l3driver.initialize中进行了, 有点重复?
        self.l3driver.initialize_network(network.get('cidr'))
        # 对该网络初始化其网关
        self.l3driver.initialize_gateway(network)
        
        # fake_network默认为False
        if not CONF.fake_network:
            # 获取该网络的网桥名, 即network['bridge']
            dev = self.driver.get_dev(network)
            elevated = context.elevated()
            # 更新DHCP数据库文件, 并重启DHCP服务
            self.driver.update_dhcp(elevated, dev, network)
            # 如果启用IPv6
            if CONF.use_ipv6:
                # 更新radvd服务的配置文件并重启服务
                self.driver.update_ra(context, dev, network)
                # 获取网桥的IPv6地址
                gateway = utils.get_my_linklocal(dev)
                # 将网桥的IPv6地址保存至数据库
                network.gateway_v6 = gateway
                network.save()


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值