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

来源:互联网 发布:银行家算法实验分析 编辑:程序博客网 时间:2024/05/21 22:39

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)# 创建命令:用于添加/删除网桥/设备的IPsdef _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 rulesdef 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
原创粉丝点击