分享

openstack Neutron分析(5)-- neutron openvswitch agent

pig2 发表于 2014-10-15 19:13:05 [显示全部楼层] 只看大图 回帖奖励 阅读模式 关闭右栏 0 76114
问题导读

1.openvswitch agent启动做了哪些工作?
2.你认为neutron-openvswitch-agent的作用是什么?






neutron-openvswitch-agent只是Neutron的一个网络插件,那么什么是openvswitch,这里我们需要了解下:


OpenvSwitch,简称OVS是一个虚拟交换软件,主要用于虚拟机VM环境,作为一个虚拟交换机,支持Xen/XenServer, KVM, and VirtualBox多种虚拟化技术。

在这种某一台机器的虚拟化的环境中,一个虚拟交换机(vswitch)主要有两个作用:传递虚拟机VM之间的流量,以及实现VM和外界网络的通信。

简单了解,我们接着继续






在openstack中目前用的比较多的L2层agent应该就是openvswitch agent了。本文大致分析了一下openvswithc agent做了哪些事。

看一下openvswitch agent的启动:

  1. neutron/plugins/openvswitch/agent/ovs_neutron_agent.py:main()
  2.         plugin = OVSNeutronAgent(**agent_config)
  3.                 self.setup_rpc()
  4.                         self.plugin_rpc = OVSPluginApi(topics.PLUGIN)
  5.                         self.state_rpc = agent_rpc.PluginReportStateAPI(topics.PLUGIN)
  6.                         self.connection = agent_rpc.create_consumers(...)
  7.                         heartbeat = loopingcall.FixedIntervalLoopingCall(self._report_state)
  8.                 self.setup_integration_br()
  9.                 self.setup_physical_bridges(bridge_mappings)
  10.                 self.sg_agent = OVSSecurityGroupAgent(...)
  11.         plugin.daemon_loop()
  12.                 self.rpc_loop()
  13.                         port_info = self.update_ports(ports)
  14.                         sync = self.process_network_ports(port_info)
复制代码

启动时做了以下工作:
1. 设置plugin_rpc,这是用来与neutron-server通信的。
2. 设置state_rpc,用于agent状态信息上报。
3. 设置connection,用于接收neutron-server的消息。
4. 启动状态周期上报。
5. 设置br-int。
6. 设置bridge_mapping对应的网桥。
7. 初始化sg_agent,用于处理security group。
8. 周期检测br-int上的端口变化,调用process_network_ports处理添加/删除端口。

neutron-server和neutron-openvswitch-agent的消息队列如下:

1.png



neutron-server可能会发生上述四种消息广播给neutron-openvswitch-agent。openvswitch agent会先看一下端口是否在本地,如果在本地则进行对应动作。

最后看下nova与neutron-openvswitch-agent的交互,这张图片来源于GongYongSheng在香港峰会的PPT:

2.png


首先boot虚机时,nova-compute发消息给neutron-server请求创建port。之后,在driver里面在br-int上建立port后,neutron-openvswitch-port循环检测br-int会发现新增端口,对其设定合适的openflow规则以及localvlan,最后将port状态设置为ACTIVE。






neutron-openvswitch-agent代码分析
neutron.plugins.openvswitch.agent.ovs_neutron_agent:main

  1. # init ovs first by agent_config:
  2. # setup plugin_rpc, state_rpc, msgq consumer, periodically state report
  3. # setup br-int, br-tun, bridge_mapping
  4. # start sg_agent
  5. agent = OVSNeutronAgent(**agent_config)
  6. # start to process rpc events
  7. # process port up/down and related flow update/local vlan bounding
  8. # process security group updates
  9. agent.daemon_loop()
复制代码

OVSNeutronAgent的初始化
  1. class OVSNeutronAgent(n_rpc.RpcCallback,
  2.                       sg_rpc.SecurityGroupAgentRpcCallbackMixin,
  3.                       l2population_rpc.L2populationRpcCallBackMixin):
  4.     '''Implements OVS-based tunneling, VLANs and flat networks.
  5.     Two local bridges are created: an integration bridge (defaults to
  6.     'br-int') and a tunneling bridge (defaults to 'br-tun'). An
  7.     additional bridge is created for each physical network interface
  8.     used for VLANs and/or flat networks.
  9.     All VM VIFs are plugged into the integration bridge. VM VIFs on a
  10.     given virtual network share a common "local" VLAN (i.e. not
  11.     propagated externally). The VLAN id of this local VLAN is mapped
  12.     to the physical networking details realizing that virtual network.
  13.     For virtual networks realized as GRE tunnels, a Logical Switch
  14.     (LS) identifier is used to differentiate tenant traffic on
  15.     inter-HV tunnels. A mesh of tunnels is created to other
  16.     Hypervisors in the cloud. These tunnels originate and terminate on
  17.     the tunneling bridge of each hypervisor. Port patching is done to
  18.     connect local VLANs on the integration bridge to inter-hypervisor
  19.     tunnels on the tunnel bridge.
  20.     For each virtual network realized as a VLAN or flat network, a
  21.     veth or a pair of patch ports is used to connect the local VLAN on
  22.     the integration bridge with the physical network bridge, with flow
  23.     rules adding, modifying, or stripping VLAN tags as necessary.
  24.     '''
  25.     # history
  26.     #   1.0 Initial version
  27.     #   1.1 Support Security Group RPC
  28.     RPC_API_VERSION = '1.1'
  29.     def __init__(self, integ_br, tun_br, local_ip,
  30.                  bridge_mappings, root_helper,
  31.                  polling_interval, tunnel_types=None,
  32.                  veth_mtu=None, l2_population=False,
  33.                  minimize_polling=False,
  34.                  ovsdb_monitor_respawn_interval=(
  35.                      constants.DEFAULT_OVSDBMON_RESPAWN),
  36.                  arp_responder=False,
  37.                  use_veth_interconnection=False):
  38.         '''Constructor.
  39.         :param integ_br: name of the integration bridge.
  40.         :param tun_br: name of the tunnel bridge.
  41.         :param local_ip: local IP address of this hypervisor.
  42.         :param bridge_mappings: mappings from physical network name to bridge.
  43.         :param root_helper: utility to use when running shell cmds.
  44.         :param polling_interval: interval (secs) to poll DB.
  45.         :param tunnel_types: A list of tunnel types to enable support for in
  46.                the agent. If set, will automatically set enable_tunneling to
  47.                True.
  48.         :param veth_mtu: MTU size for veth interfaces.
  49.         :param l2_population: Optional, whether L2 population is turned on
  50.         :param minimize_polling: Optional, whether to minimize polling by
  51.                monitoring ovsdb for interface changes.
  52.         :param ovsdb_monitor_respawn_interval: Optional, when using polling
  53.                minimization, the number of seconds to wait before respawning
  54.                the ovsdb monitor.
  55.         :param arp_responder: Optional, enable local ARP responder if it is
  56.                supported.
  57.         :param use_veth_interconnection: use veths instead of patch ports to
  58.                interconnect the integration bridge to physical bridges.
  59.         '''
  60.         super(OVSNeutronAgent, self).__init__()
  61.         self.use_veth_interconnection = use_veth_interconnection
  62.         self.veth_mtu = veth_mtu
  63.         self.root_helper = root_helper
  64.         self.available_local_vlans = set(moves.xrange(q_const.MIN_VLAN_TAG, # 1-1094
  65.                                                       q_const.MAX_VLAN_TAG))
  66.         self.tunnel_types = tunnel_types or []
  67.         self.l2_pop = l2_population
  68.         # TODO(ethuleau): Initially, local ARP responder is be dependent to the
  69.         #                 ML2 l2 population mechanism driver.
  70.         self.arp_responder_enabled = (arp_responder and
  71.                                       self._check_arp_responder_support() and
  72.                                       self.l2_pop)
  73.         self.agent_state = {
  74.             'binary': 'neutron-openvswitch-agent',
  75.             'host': cfg.CONF.host,
  76.             'topic': q_const.L2_AGENT_TOPIC,
  77.             'configurations': {'bridge_mappings': bridge_mappings,
  78.                                'tunnel_types': self.tunnel_types,
  79.                                'tunneling_ip': local_ip,
  80.                                'l2_population': self.l2_pop,
  81.                                'arp_responder_enabled':
  82.                                self.arp_responder_enabled},
  83.             'agent_type': q_const.AGENT_TYPE_OVS,
  84.             'start_flag': True}
  85.         # Keep track of int_br's device count for use by _report_state()
  86.         self.int_br_device_count = 0
  87.         self.int_br = ovs_lib.OVSBridge(integ_br, self.root_helper)
  88.         # Create integration bridge and patch ports,
  89.         # remove all existing flows, and then witch all traffic using L2 learning
  90.         # add a canary flow to int_br to track OVS restarts
  91.         self.setup_integration_br()
  92.         # Stores port update notifications for processing in main rpc loop
  93.         self.updated_ports = set()
  94.         # state_rpc: periodically report state by topic q-plugin
  95.         # plugin_rpc:communicate with plugin by topic q-agent-notifier
  96.         # consumer topic id includes:
  97.         #       q-agent-notifier-port_update
  98.         #       q-agent-notifier-network_delete
  99.         #       q-agent-notifier-tunnel_update
  100.         #       q-agent-notifier-security_group_update
  101.         #       q-agent-notifier-l2population_update (需要启动l2populcation)
  102.         self.setup_rpc()
  103.         # bridge_mappings = default:br-eth1
  104.         self.bridge_mappings = bridge_mappings
  105.         # Creates physical network bridges and links them to integration bridge using veths.
  106.         self.setup_physical_bridges(self.bridge_mappings)
  107.         self.local_vlan_map = {}
  108.         self.tun_br_ofports = {p_const.TYPE_GRE: {},
  109.                                p_const.TYPE_VXLAN: {}}
  110.         self.polling_interval = polling_interval
  111.         self.minimize_polling = minimize_polling
  112.         self.ovsdb_monitor_respawn_interval = ovsdb_monitor_respawn_interval
  113.         if tunnel_types:
  114.             self.enable_tunneling = True
  115.         else:
  116.             self.enable_tunneling = False
  117.         self.local_ip = local_ip
  118.         self.tunnel_count = 0
  119.         self.vxlan_udp_port = cfg.CONF.AGENT.vxlan_udp_port
  120.         self.dont_fragment = cfg.CONF.AGENT.dont_fragment
  121.         self.tun_br = None
  122.         # Creates tunnel bridge, and links it to the integration bridge
  123.         # using a patch port.
  124.         # create default flow tables
  125.         if self.enable_tunneling:
  126.             self.setup_tunnel_br(tun_br)
  127.         # Collect additional bridges to monitor
  128.         # Setup ancillary bridges - for example br-ex
  129.         self.ancillary_brs = self.setup_ancillary_bridges(integ_br, tun_br)
  130.         # Security group agent support
  131.         # firewall_driver = neutron.agent.linux.iptables_firewall.OVSHybridIptablesFirewallDriver
  132.         self.sg_agent = OVSSecurityGroupAgent(self.context,
  133.                                               self.plugin_rpc,
  134.                                               root_helper)
  135.         # Initialize iteration counter
  136.         self.iter_num = 0
  137.         self.run_daemon_loop = True
复制代码

OVSNeutronAgent初始化完成后启动agent.daemon_loop()

  1. def daemon_loop(self):
  2.         with polling.get_polling_manager(
  3.             self.minimize_polling,
  4.             self.root_helper,
  5.             self.ovsdb_monitor_respawn_interval) as pm:
  6.             self.rpc_loop(polling_manager=pm)
复制代码

rpc_loop
  1. def rpc_loop(self, polling_manager=None):
  2.         # sync tunnel and check ovs restart
  3.         if not polling_manager:
  4.             polling_manager = polling.AlwaysPoll()
  5.         sync = True
  6.         ports = set()
  7.         updated_ports_copy = set()
  8.         ancillary_ports = set()
  9.         tunnel_sync = True
  10.         ovs_restarted = False
  11.         while self.run_daemon_loop:
  12.             start = time.time()
  13.             port_stats = {'regular': {'added': 0,
  14.                                       'updated': 0,
  15.                                       'removed': 0},
  16.                           'ancillary': {'added': 0,
  17.                                         'removed': 0}}
  18.             LOG.debug(_("Agent rpc_loop - iteration:%d started"),
  19.                       self.iter_num)
  20.             if sync:
  21.                 LOG.info(_("Agent out of sync with plugin!"))
  22.                 ports.clear()
  23.                 ancillary_ports.clear()
  24.                 sync = False
  25.                 polling_manager.force_polling()
  26.             # check ovs restart by checking exist for the canary flow
  27.             ovs_restarted = self.check_ovs_restart()
  28.             if ovs_restarted:
  29.                 # reset br-int, br-tun, and other physical bridges
  30.                 self.setup_integration_br()
  31.                 self.setup_physical_bridges(self.bridge_mappings)
  32.                 if self.enable_tunneling:
  33.                     self.setup_tunnel_br()
  34.                     tunnel_sync = True
  35.             # Notify the plugin of tunnel IP
  36.             if self.enable_tunneling and tunnel_sync:
  37.                 LOG.info(_("Agent tunnel out of sync with plugin!"))
  38.                 try:
  39.                     tunnel_sync = self.tunnel_sync()
  40.                 except Exception:
  41.                     LOG.exception(_("Error while synchronizing tunnels"))
  42.                     tunnel_sync = True
  43.             # check if ports/security_groups updates and perform update
  44.             if self._agent_has_updates(polling_manager) or ovs_restarted:
  45.                 try:
  46.                     LOG.debug(_("Agent rpc_loop - iteration:%(iter_num)d - "
  47.                                 "starting polling. Elapsed:%(elapsed).3f"),
  48.                               {'iter_num': self.iter_num,
  49.                                'elapsed': time.time() - start})
  50.                     # Save updated ports dict to perform rollback in
  51.                     # case resync would be needed, and then clear
  52.                     # self.updated_ports. As the greenthread should not yield
  53.                     # between these two statements, this will be thread-safe
  54.                     updated_ports_copy = self.updated_ports
  55.                     self.updated_ports = set()
  56.                     reg_ports = (set() if ovs_restarted else ports)
  57.                     # {
  58.                     #   'current': ['ovs-vsctl', --format=json', '--', '--columns=name,external_ids,ofport','list', 'Interface'] and then filted in get_vif_port_set()
  59.                     #   'updated': check_changed_vlans() returns a set of port ids of the ports concerned by a vlan tag loss, and then updated_ports &= cur_ports
  60.                     #   'added'  : cur_ports - registered_ports
  61.                     #   'removed': registered_ports - cur_ports
  62.                     #  }
  63.                     port_info = self.scan_ports(reg_ports, updated_ports_copy)
  64.                     LOG.debug(_("Agent rpc_loop - iteration:%(iter_num)d - "
  65.                                 "port information retrieved. "
  66.                                 "Elapsed:%(elapsed).3f"),
  67.                               {'iter_num': self.iter_num,
  68.                                'elapsed': time.time() - start})
  69.                     # Secure and wire/unwire VIFs and update their status
  70.                     # on Neutron server
  71.                     if (self._port_info_has_changes(port_info) or
  72.                         self.sg_agent.firewall_refresh_needed() or
  73.                         ovs_restarted):
  74.                         LOG.debug(_("Starting to process devices in:%s"),
  75.                                   port_info)
  76.                         # If treat devices fails - must resync with plugin
  77.                         sync = self.process_network_ports(port_info,
  78.                                                           ovs_restarted)
  79.                         LOG.debug(_("Agent rpc_loop - iteration:%(iter_num)d -"
  80.                                     "ports processed. Elapsed:%(elapsed).3f"),
  81.                                   {'iter_num': self.iter_num,
  82.                                    'elapsed': time.time() - start})
  83.                         port_stats['regular']['added'] = (
  84.                             len(port_info.get('added', [])))
  85.                         port_stats['regular']['updated'] = (
  86.                             len(port_info.get('updated', [])))
  87.                         port_stats['regular']['removed'] = (
  88.                             len(port_info.get('removed', [])))
  89.                     ports = port_info['current']
  90.                     # Treat ancillary devices if they exist
  91.                     if self.ancillary_brs:
  92.                         port_info = self.update_ancillary_ports(
  93.                             ancillary_ports)
  94.                         LOG.debug(_("Agent rpc_loop - iteration:%(iter_num)d -"
  95.                                     "ancillary port info retrieved. "
  96.                                     "Elapsed:%(elapsed).3f"),
  97.                                   {'iter_num': self.iter_num,
  98.                                    'elapsed': time.time() - start})
  99.                         if port_info:
  100.                             rc = self.process_ancillary_network_ports(
  101.                                 port_info)
  102.                             LOG.debug(_("Agent rpc_loop - iteration:"
  103.                                         "%(iter_num)d - ancillary ports "
  104.                                         "processed. Elapsed:%(elapsed).3f"),
  105.                                       {'iter_num': self.iter_num,
  106.                                        'elapsed': time.time() - start})
  107.                             ancillary_ports = port_info['current']
  108.                             port_stats['ancillary']['added'] = (
  109.                                 len(port_info.get('added', [])))
  110.                             port_stats['ancillary']['removed'] = (
  111.                                 len(port_info.get('removed', [])))
  112.                             sync = sync | rc
  113.                     polling_manager.polling_completed()
  114.                 except Exception:
  115.                     LOG.exception(_("Error while processing VIF ports"))
  116.                     # Put the ports back in self.updated_port
  117.                     self.updated_ports |= updated_ports_copy
  118.                     sync = True
  119.             # sleep till end of polling interval
  120.             elapsed = (time.time() - start)
  121.             LOG.debug(_("Agent rpc_loop - iteration:%(iter_num)d "
  122.                         "completed. Processed ports statistics: "
  123.                         "%(port_stats)s. Elapsed:%(elapsed).3f"),
  124.                       {'iter_num': self.iter_num,
  125.                        'port_stats': port_stats,
  126.                        'elapsed': elapsed})
  127.             if (elapsed < self.polling_interval):
  128.                 time.sleep(self.polling_interval - elapsed)
  129.             else:
  130.                 LOG.debug(_("Loop iteration exceeded interval "
  131.                             "(%(polling_interval)s vs. %(elapsed)s)!"),
  132.                           {'polling_interval': self.polling_interval,
  133.                            'elapsed': elapsed})
  134.             self.iter_num = self.iter_num + 1
复制代码

rpc_loop()中最重要的两个函数为tunnel_sync(查询并建立隧道)和process_network_ports(处理port和安全组变更)
tunnel_sync 查询并建立隧道
  1. def tunnel_sync(self):
  2.         resync = False
  3.         try:
  4.             for tunnel_type in self.tunnel_types:  # tunnel_types = vxlan, gre
  5.                 # query tunnel details from plugin_rpc by local_ip and tunnel_type
  6.                 details = self.plugin_rpc.tunnel_sync(self.context,
  7.                                                       self.local_ip,
  8.                                                       tunnel_type)
  9.                 # establish tunnel with all other tunnel_ip if l2_pop disabled
  10.                 # if l2_pop enabled, tunnel sync is processed by agent in q-agent-notifier-l2population_update consumer id
  11.                 if not self.l2_pop:
  12.                     tunnels = details['tunnels']
  13.                     for tunnel in tunnels:
  14.                         if self.local_ip != tunnel['ip_address']:
  15.                             tunnel_id = tunnel.get('id')
  16.                             # Unlike the OVS plugin, ML2 doesn't return an id
  17.                             # key. So use ip_address to form port name instead.
  18.                             # Port name must be <=15 chars, so use shorter hex.
  19.                             remote_ip = tunnel['ip_address']
  20.                             remote_ip_hex = self.get_ip_in_hex(remote_ip)
  21.                             if not tunnel_id and not remote_ip_hex:
  22.                                 continue
  23.                             tun_name = '%s-%s' % (tunnel_type,
  24.                                                   tunnel_id or remote_ip_hex)
  25.                             # setup tunnel_port and related flows
  26.                             self.setup_tunnel_port(tun_name,
  27.                                                    tunnel['ip_address'],
  28.                                                    tunnel_type)
  29.         except Exception as e:
  30.             LOG.debug(_("Unable to sync tunnel IP %(local_ip)s: %(e)s"),
  31.                       {'local_ip': self.local_ip, 'e': e})
  32.             resync = True
  33.         return resync
复制代码

process_network_ports 处理port和安全组变更

  1. def process_network_ports(self, port_info, ovs_restarted):
  2.         resync_a = False
  3.         resync_b = False
  4.         # TODO(salv-orlando): consider a solution for ensuring notifications
  5.         # are processed exactly in the same order in which they were
  6.         # received. This is tricky because there are two notification
  7.         # sources: the neutron server, and the ovs db monitor process
  8.         # If there is an exception while processing security groups ports
  9.         # will not be wired anyway, and a resync will be triggered
  10.         # TODO(salv-orlando): Optimize avoiding applying filters unnecessarily
  11.         # (eg: when there are no IP address changes)
  12.         # 通过plugin_rpc到plugin查询安全组,然后通过sg_agent应用安全组
  13.         self.sg_agent.setup_port_filters(port_info.get('added', set()),
  14.                                          port_info.get('updated', set()))
  15.         # VIF wiring needs to be performed always for 'new' devices.
  16.         # For updated ports, re-wiring is not needed in most cases, but needs
  17.         # to be performed anyway when the admin state of a device is changed.
  18.         # A device might be both in the 'added' and 'updated'
  19.         # list at the same time; avoid processing it twice.
  20.         devices_added_updated = (port_info.get('added', set()) |
  21.                                  port_info.get('updated', set()))
  22.         if devices_added_updated:
  23.             start = time.time()
  24.             try:
  25.                 # 添加或更新的ports: 查询port详细信息后通知plugin port up/down,接着再port_bound/port_dead
  26.                 skipped_devices = self.treat_devices_added_or_updated(
  27.                     devices_added_updated, ovs_restarted)
  28.                 LOG.debug(_("process_network_ports - iteration:%(iter_num)d -"
  29.                             "treat_devices_added_or_updated completed. "
  30.                             "Skipped %(num_skipped)d devices of "
  31.                             "%(num_current)d devices currently available. "
  32.                             "Time elapsed: %(elapsed).3f"),
  33.                           {'iter_num': self.iter_num,
  34.                            'num_skipped': len(skipped_devices),
  35.                            'num_current': len(port_info['current']),
  36.                            'elapsed': time.time() - start})
  37.                 # Update the list of current ports storing only those which
  38.                 # have been actually processed.
  39.                 port_info['current'] = (port_info['current'] -
  40.                                         set(skipped_devices))
  41.             except DeviceListRetrievalError:
  42.                 # Need to resync as there was an error with server
  43.                 # communication.
  44.                 LOG.exception(_("process_network_ports - iteration:%d - "
  45.                                 "failure while retrieving port details "
  46.                                 "from server"), self.iter_num)
  47.                 resync_a = True
  48.         if 'removed' in port_info:
  49.             start = time.time()
  50.             # 删除的port:删除安全组,通知plugin port down, 并unbound local_vlan,
  51.             resync_b = self.treat_devices_removed(port_info['removed'])
  52.             LOG.debug(_("process_network_ports - iteration:%(iter_num)d -"
  53.                         "treat_devices_removed completed in %(elapsed).3f"),
  54.                       {'iter_num': self.iter_num,
  55.                        'elapsed': time.time() - start})
  56.         # If one of the above operations fails => resync with plugin
  57.         return (resync_a | resync_b)
复制代码

neutron-ovs-cleanup
This service starts on boot and ensures that Networking has full control over the creation and management of tap devices.

  1. def main():
  2.     """Main method for cleaning up OVS bridges.
  3.     The utility cleans up the integration bridges used by Neutron.
  4.     """
  5.     conf = setup_conf()
  6.     conf()
  7.     config.setup_logging(conf)
  8.     configuration_bridges = set([conf.ovs_integration_bridge,
  9.                                  conf.external_network_bridge])
  10.     ovs_bridges = set(ovs_lib.get_bridges(conf.AGENT.root_helper))
  11.     available_configuration_bridges = configuration_bridges & ovs_bridges
  12.     if conf.ovs_all_ports:
  13.         bridges = ovs_bridges
  14.     else:
  15.         bridges = available_configuration_bridges
  16.     # Collect existing ports created by Neutron on configuration bridges.
  17.     # After deleting ports from OVS bridges, we cannot determine which
  18.     # ports were created by Neutron, so port information is collected now.
  19.     ports = collect_neutron_ports(available_configuration_bridges,
  20.                                   conf.AGENT.root_helper)
  21.     for bridge in bridges:
  22.         LOG.info(_("Cleaning %s"), bridge)
  23.         ovs = ovs_lib.OVSBridge(bridge, conf.AGENT.root_helper)
  24.         ovs.delete_ports(all_ports=conf.ovs_all_ports)
  25.     # Remove remaining ports created by Neutron (usually veth pair)
  26.     delete_neutron_ports(ports, conf.AGENT.root_helper)
  27.     LOG.info(_("OVS cleanup completed successfully"))
复制代码


相关文章:

openstack Neutron分析(2)—— neutron-l3-agent


openstack Neutron分析(3)—— neutron-dhcp-agent源码分析

openstack Neutron分析(4)—— neutron-l3-agent中的iptables




Openstack之neutron入门


Openstack之neutron入门二


Openstack之neutron入门三






没找到任何评论,期待你打破沉寂

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关闭

推荐上一条 /2 下一条