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()