1. 程式人生 > >openstack之neutron程式碼分析---(1)neutron初始化流程

openstack之neutron程式碼分析---(1)neutron初始化流程

Neutron是openstack中用於管理網路的專案。neutron程式碼的入口配置檔案neutron/setup.cfg,我們可以通過這個檔案瞭解整個專案的程式碼結構。文章中程式碼為neutron kilo版本。部分setup.cfg內容如下:

…
[entry_points]
console_scripts = 
	…
	neutron-l3-agent = neutron.agent.l3.agent:main
	neutron-lbaas-agent = neutron.services.loadbalancer.agent.agent:main
	neutron-linuxbridge-agent = neutron.plugins.linuxbridge.agent.linuxbridge_neutron_agent:main
	neutron-metadata-agent = neutron.agent.metadata.agent:main
…
	neutron-nvsd-agent = neutron.plugins.oneconvergence.agent.nvsd_neutron_agent:main
	neutron-openvswitch-agent = neutron.plugins.openvswitch.agent.ovs_neutron_agent:main
…
	neutron-server = neutron.server:main
…
neutron.core_plugins = 
	…
	hyperv = neutron.plugins.hyperv.hyperv_neutron_plugin:HyperVNeutronPlugin
	ibm = neutron.plugins.ibm.sdnve_neutron_plugin:SdnvePluginV2
	midonet = neutron.plugins.midonet.plugin:MidonetPluginV2
	ml2 = neutron.plugins.ml2.plugin:Ml2Plugin
…

neutron-l3-agent:l3 agent部署在計算節點或者網路節點上,負責3層虛擬網路的管理。根據setup.cfg檔案可以看出neutron-l3-agent的程式碼路徑是neutron\agent\l3\agent

neutron-openvswitch-agent:Open vSwitch Agent部署在計算節點或者網路節點上,進行管理OVS虛擬交換機。根據setup.cfg檔案可以看出neutron-openvswitch-agent的程式碼路徑是neutron\plugins\openvswitch\agent.ovs_neutron_agent

neutron-server:是Neutron中唯一的一個服務程序,負責接收使用者的RESTful API請求並分發處理給各種agen來完成這些的任務。根據setup.cfg檔案可以看出neutron程式碼路徑是neutron\server

ML2Plugin:用於提供二層虛擬網路,實現了network/subnet/port資源的操作,這些操作最終由Plugin通過RPC呼叫OpenvSwitch Agent來完成。根據setup.cfg檔案可以看出程式碼路徑是 neutron\plugins\ml2\plugin\Ml2Plugin

neutron初始化流程:

1.server初始化

server初始化檔案:neutron\neutron\server\__init__.py

def main():
    # the configuration will be read into the cfg.CONF global data structure
    config.init(sys.argv[1:])
    ....
    try:
        pool = eventlet.GreenPool()
        neutron_api = service.serve_wsgi(service.NeutronApiService)
        api_thread = pool.spawn(neutron_api.wait)
        try:
            neutron_rpc = service.serve_rpc()
            ....
        else:
            rpc_thread = pool.spawn(neutron_rpc.wait)
            # api and rpc should die together.  When one dies, kill the other.
            rpc_thread.link(lambda gt: api_thread.kill())
            api_thread.link(lambda gt: rpc_thread.kill())
        pool.waitall()
    ....

main方法的核心就是serve_wsgi、serve_rpc兩個方法呼叫,分別建立api服務和rpc服務。

1.1api服務初始化

api服務的實現是service.NeutronApiService,這是一個符合WSGI規範的app,通過paste進行配置,paste檔案位置可以在配置檔案中指定,預設是/etc/neutron/api-paste.ini,在程式碼中是etc/api-paste.ini。檢視api-paste.ini可以確定v2版api的實現是在neutron.api.v2.router:APIRouter

 [app:neutronapiapp_v2_0]
paste.app_factory = neutron.api.v2.router:APIRouter.factory

APIRouter在構造時,會根據配置檔案core_plugin的配置載入plugin、extension管理器。所在檔案:

neutron\neutron\api\v2\router.py

class APIRouter(wsgi.Router):
    @classmethod
    def factory(cls, global_config, **local_config):
        return cls(**local_config)
 
    def __init__(self, **local_config):
        mapper = routes_mapper.Mapper()
        plugin = manager.NeutronManager.get_plugin()
        ext_mgr = extensions.PluginAwareExtensionManager.get_instance()
        ext_mgr.extend_resources("2.0", attributes.RESOURCE_ATTRIBUTE_MAP)

1.2.rpc服務初始化

serve_rpc函式定義檔案:neutron\neutron\service.py

def serve_rpc():
    plugin = manager.NeutronManager.get_plugin()
    ....
    try:
        rpc = RpcWorker(plugin)
        if cfg.CONF.rpc_workers < 1:
            rpc.start()
            return rpc
        ....


首先會根據配置檔案core_plugin的配置載入plugin,然後建立RpcWorker,開始監聽rpc;通過呼叫_plugin.start_rpc_listeners進行監聽。

以ml2 plugin為例,在它的start_rpc_listener方法中,建立neutron.plugin.ml2.rpc.RpcCallbacks類的例項,並建立了dispatcher處理'q-plugin' topic

ml2 plugin所在檔案:neutron/plugins/ml2/plugin.py

    def start_rpc_listeners(self):
        self.endpoints = [rpc.RpcCallbacks(self.notifier, self.type_manager),
                          securitygroups_rpc.SecurityGroupServerRpcCallback(),
                          dvr_rpc.DVRServerRpcCallback(),
                          dhcp_rpc.DhcpRpcCallback(),
                          agents_db.AgentExtRpcCallback(),
                          metadata_rpc.MetadataRpcCallback()]
        self.topic = topics.PLUGIN
        self.conn = n_rpc.create_connection(new=True)
        self.conn.create_consumer(self.topic, self.endpoints,
                                  fanout=False)
        return self.conn.consume_in_threads()
 

 ML2Plugin初始化時,針對agent建立自己的訊息佇列(notify,生產者)(topics.AGENT)以便向agent傳送rpc請求,同時訂閱ml2 Agent訊息佇列的訊息(consumer,消費者)(topics.PLUGIN)以便接收來自agent的rpc請求。

同樣,ml2 Agent初始化時,也會建立自己的訊息佇列(notify,生產者)(topics.PLUGIN)來向plugin傳送rpc請求,同時訂閱ML2Plugin訊息佇列的訊息(consumer,消費者)(topics.AGENT)來接收來自plugin的rpc請求。

訊息佇列的生成者類(xxxxNotifyAPI)和對應的消費者類(xxxxRpcCallback)定義有相同的介面函式,生產者類中的函式主要作用是rpc呼叫消費者類中的同名函式,消費者類中的函式執行實際的動作。

如:xxxNotifyAPI類中定義有network_delete()函式,則xxxRpcCallback類中也會定義有network_delete()函式。xxxNotifyAPI::network_delete()通過rpc呼叫xxxRpcCallback::network_delete()函式,xxxRpcCallback::network_delete()執行實際的network  delete刪除動作。

RpcCallbacks類中的方法與neutron.agent.rpc.PluginApi的方法是對應的

2.agent初始化

以常用的openvswitch agent為例,可以執行以下命令啟動agent服務

$service neutron-openvswitch-agent start

setup.cfg配置檔案的以下內容可以知道,實際執行的方法是neutron.plugins.openvswitch.agent.ovs_neutron_agent:main

[entry_points]
console-scripts = 
    ...
    neutron-openvswitch-agent = neutron.plugins.openvswitch.agent.ovs_neutron_agent:main
    ...

2.1.neutron/plugins/openvswitch/agent/ovs_neutron_agent.py:main

def main():
    ...
    try:
        # 從配置檔案中讀取agent的配置,主要是network_mappings,各個bridges名稱
        agent_config = create_agent_config_map(cfg.CONF)
    except ValueError as e:
        LOG.error(_('%s Agent terminated!'), e)
        sys.exit(1)
    ...
    # 建立agent例項
    agent = OVSNeutronAgent(**agent_config)
    signal.signal(signal.SIGTERM, handle_sigterm)
    # Start everything.
    LOG.info(_("Agent initialized successfully, now running... "))
    agent.daemon_loop()
    sys.exit(0)

2.2.neutron/plugins/openvswitch/agent/ovs_neutron_agent.py:__init__

OVSNeutronAgentdocstring中,概要說明了agent實現虛擬的方式,有以下幾點:

· 建立br-int, br-tun以及每個物理網路介面一個bridge

· 虛擬機器的虛擬網絡卡都會接入到br-int。使用同一個虛擬網路的虛擬網絡卡共享一個localVLAN(與外部網路的VLAN無關,vlan id可以重疊)。這個localVLAN id會對映到外部網路的某個VLAN id

· 對於network_typeVLAN或者FLAT的網路,在br-int和各個物理網路bridge之間建立一個虛擬網絡卡,用於限定流規則、對映或者刪除VLAN id等處理。

· 對於network_typeGRE的,每個租戶在不同hypervisor之間的網路通訊通過一個邏輯交換機識別符號(Logical Switch identifier)進行區分,並建立一個連通各個hypervisorbr-tun的通道(tunnel)網路。Port patching用於連通br-int和各個hypervisorbr-tun上的VLAN

class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
                      l2population_rpc.L2populationRpcCallBackMixin):
 
    def __init__(self, integ_br, tun_br, local_ip,
                 bridge_mappings, root_helper,
                 polling_interval, tunnel_types=None,
                 veth_mtu=None, l2_population=False,
                 minimize_polling=False,
                 ovsdb_monitor_respawn_interval=(
                     constants.DEFAULT_OVSDBMON_RESPAWN)):
        ...
        # local VLAN id範圍是[1, 2094]
        self.available_local_vlans = set(xrange(q_const.MIN_VLAN_TAG,
                                                q_const.MAX_VLAN_TAG))
        ...
        # 建立br-int,重置流表規則等,通過呼叫brctl, ovs-vsctl, ip等命令實現
        self.int_br = ovs_lib.OVSBridge(integ_br, self.root_helper)
        self.setup_integration_br()
        self.int_br.set_secure_mode()
        # Stores port update notifications for processing in main rpc loop
        self.updated_ports = set()
        # 配置plugin的rpcapi連線(topic='q-plugin',介面neutron.agent.rpc.py:PluginApi)並監聽其它服務對agent的rpc的呼叫(topic='q-agent-notifier')
        self.setup_rpc()
        # 配置檔案中傳入的引數
        self.bridge_mappings = bridge_mappings
        # 給每個mapping建立一個bridge,並連線到br-int
        self.setup_physical_bridges(self.bridge_mappings)
        self.local_vlan_map = {}
        # 建立tunnel的程式碼省略
        # Security group agent supprot
        self.sg_agent = OVSSecurityGroupAgent(self.context,
                                              self.plugin_rpc,                    
                                              root_helper)                        
 

2.3.neutron/plugins/openvswitch/agent/ovs_neutron_agent.py:daemon_loop

該方法是服務的訊息迴圈方法,主要邏輯在它呼叫的rpc_loop方法中。

def rpc_loop(self, polling_manager=None):
    while True:
        ...
        # 從br-int確定配置更新或者刪除的埠資訊
        port_info = self.scan_ports(reg_ports, updated_ports_copy)
        ports = port_info['current']
        ...
        # Secure and wire/unwire VIFs and update their status
        # on Neutron server
        if (self._port_info_has_changes(port_info) or
            self.sg_agent.firewall_refresh_needed() or
            ovs_restarted):
            # If treat devices fails - must resync with plugin
            # 這個方法會從plugin查詢port的詳情,根據port的admin_state_up狀態,分別執行self.port_bound()或者self.port_dead()
            # 並呼叫plugin rpc的update_device_up或update_device_down方法更新埠狀態
            sync = self.process_network_ports(port_info,
                                              ovs_restarted)