neutron api源码分析

南楼画角 提交于 2020-03-07 10:14:03

 neutron server的启动setup.cfg内定义neutron-server = neutron.cmd.eventlet.server:main。这里只专注介绍neutron server启动,setup.cfg不在这里详细介绍

# setup.cfg
console_scripts =
    ....
    neutron-ovs-cleanup = neutron.cmd.ovs_cleanup:main
    neutron-pd-notify = neutron.cmd.pd_notify:main
    neutron-server = neutron.cmd.eventlet.server:main

 main函数主要是运行wsgi_eventlet.eventlet_wsgi_server函数,并进行配置文件和日志的初始化配置

#neutron.cmd.eventlet.server:main
from neutron import server
from neutron.server import rpc_eventlet
from neutron.server import wsgi_eventlet


def main():
    server.boot_server(wsgi_eventlet.eventlet_wsgi_server)

#boot_server函数,为初始化配置,并执行传过来的server_func函数
def boot_server(server_func):
    _init_configuration()
    try:
        server_func()
    except KeyboardInterrupt:
        pass
    except RuntimeError as e:
        sys.exit(_("ERROR: %s") % e)

#_init_configuration() 
def _init_configuration():
    # the configuration will be read into the cfg.CONF global data structure
    conf_files = _get_config_files()   //获取配置文件

    config.init(sys.argv[1:], default_config_files=conf_files)  //通过oslo_confg初始化配置
    config.setup_logging()  //设置日志相关信息
    config.set_config_defaults()  //配置默认配置
    if not cfg.CONF.config_file:
        sys.exit(_("ERROR: Unable to find configuration file via the default"
                   " search paths (~/.neutron/, ~/, /etc/neutron/, /etc/) and"
                   " the '--config-file' option!"))

接下来主要分析wsgi_eventlet.eventlet_wsgi_server


def eventlet_wsgi_server():
    neutron_api = service.serve_wsgi(service.NeutronApiService) //neutron api的初始化
    start_api_and_rpc_workers(neutron_api)   //neutron api和rpc启动

接下来主要分析neutron api初始化过程

### service.serve_wsgi  主要是启动api------service.NeutronApiService
def serve_wsgi(cls):

    try:
        service = cls.create()
        service.start()
    except Exception:
        with excutils.save_and_reraise_exception():
            LOG.exception('Unrecoverable error: please check log '
                          'for details.')

    registry.publish(resources.PROCESS, events.BEFORE_SPAWN, service)
    return service

# service.NeutronApiService
class NeutronApiService(WsgiService):
    """Class for neutron-api service."""
    def __init__(self, app_name):
        profiler.setup('neutron-server', cfg.CONF.host)  
        super(NeutronApiService, self).__init__(app_name)  //通过父类创建neutron app

    @classmethod
    def create(cls, app_name='neutron'):
        # Setup logging early
        config.setup_logging()
        service = cls(app_name)
        return service

class WsgiService(object):
    """Base class for WSGI based services.

    For each api you define, you must also define these flags:
    :<api>_listen: The address on which to listen
    :<api>_listen_port: The port on which to listen

    """

    def __init__(self, app_name):
        self.app_name = app_name
        self.wsgi_app = None

    def start(self):
        self.wsgi_app = _run_wsgi(self.app_name)  //运行一个wsgi app

    def wait(self):
        self.wsgi_app.wait()

# _run_wsgi
def _run_wsgi(app_name):
    app = config.load_paste_app(app_name)  //通过api-paste.ini文件加载neutron api类,这个过程比较复杂,会专门介绍
    if not app:
        LOG.error('No known API applications configured.')
        return 
    return run_wsgi_app(app)  //启动api

#run_wsgi_app  该函数主要是根据配置初始化socket

def run_wsgi_app(app):
    server = wsgi.Server("Neutron") 
    server.start(app, cfg.CONF.bind_port, cfg.CONF.bind_host,
                 workers=_get_api_workers(), desc="api worker")
    LOG.info("Neutron service started, listening on %(host)s:%(port)s",
             {'host': cfg.CONF.bind_host, 'port': cfg.CONF.bind_port})
    return server


#####server = wsgi.Server("Neutron")  

class Server(object):
    """Server class to manage multiple WSGI sockets and applications."""

    def __init__(self, name, num_threads=None, disable_ssl=False):
        # Raise the default from 8192 to accommodate large tokens
        eventlet.wsgi.MAX_HEADER_LINE = CONF.max_header_line //socket 接收的head的最大只,当使用keystone v3时,改值设置的要大一些
        self.num_threads = num_threads or CONF.wsgi_default_pool_size //线程数量
        self.disable_ssl = disable_ssl   //是否启用ssl
        # Pool for a greenthread in which wsgi server will be running
        self.pool = eventlet.GreenPool(1) 启动一个协程
        self.name = name
        self._server = None
        # A value of 0 is converted to None because None is what causes the
        # wsgi server to wait forever.
        self.client_socket_timeout = CONF.client_socket_timeout or None  //等待client连接超时时间,None代表一直等待
        if CONF.use_ssl and not self.disable_ssl:
            sslutils.is_enabled(CONF)

# start _init主要是进行初始化配置,start是启动socket
    def start(self, application, port, host='0.0.0.0', workers=0, desc=None):
        """Run a WSGI server with the given application."""
        self._host = host
        self._port = port
        backlog = CONF.backlog   //配置最大连接数

        self._socket = self._get_socket(self._host,    //获取socket
                                        self._port,
                                        backlog=backlog)

        self._launch(application, workers)   //启动socket

  
#_get_socket  获取socket

 def _get_socket(self, host, port, backlog):
        bind_addr = (host, port)
        # TODO(dims): eventlet's green dns/socket module does not actually
        # support IPv6 in getaddrinfo(). We need to get around this in the
        # future or monitor upstream for a fix
        try:
            #socket.getaddrinfo(host, port[, family[, socktype[, proto[, flags]]]])返回值 (family, socktype, proto, canonname, sockaddr)
            #family: 表示socket使用的协议簇。常用的协议簇包括AF_UNIX(本机通信)/AF_INET(TCP/IP协议簇中的IPv4协议)/AF_INET6(TCP/IP协议簇中的IPv4协议)。在python的socket包中,用1表示AF_UNIX,2表示AF_INET,10表示AF_INET6。
            #sockettype:表示socket的类型。常见的socket类型包括SOCK_STREAM(TCP流)/SOCK_DGRAM(UDP数据报)/SOCK_RAW(原始套接字)。其中,SOCK_STREAM=1,SOCK_DGRAM=2,SOCK_RAW=3
            #proto:指定协议。套接字所用的协议。如调用者不想指定,可用0。常用的协议有,IPPROTO_TCP(=6)和IPPTOTO_UDP(=17),它们分别对应TCP传输协议、UDP传输协议
            info = socket.getaddrinfo(bind_addr[0],
                                      bind_addr[1],
                                      socket.AF_UNSPEC,  //ai_family参数指定调用者期待返回的套接口地址结构的类型,AF_INET,AF_INET6和AF_UNSPEC,AF_INET:函数就不能返回任何IPV6相关的地址信息,AF_INET6:不能返回任何IPV4地址信息,AF_UNSPEC:适用于指定主机名和服务名且适合任何协议族的地址
                                      socket.SOCK_STREAM)[0]
            family = info[0]  
            bind_addr = info[-1]  //
        except Exception:
            LOG.exception("Unable to listen on %(host)s:%(port)s",
                          {'host': host, 'port': port})
            sys.exit(1)

        sock = None
        retry_until = time.time() + CONF.retry_until_window
        while not sock and time.time() < retry_until:
            try:
                """ eventlet.listen(addr, family=2, backlog=50) 

  创建套接字,可以用于 serve() 或一个定制的 accept() 循环。设置套接字的 SO_REUSEADDR 可以减少打扰。

  参数:

addr:要监听的地址,比如对于 TCP 协议的套接字,这是一个(host, port) 元组。
family:套接字族。
backlog:排队连接的最大个数,至少是1,上限由系统决定。
  返回:

  监听中的“绿色”套接字对象。"""
                sock = eventlet.listen(bind_addr,
                                       backlog=backlog,
                                       family=family)
            except socket.error as err:
                with excutils.save_and_reraise_exception() as ctxt:
                    if err.errno == errno.EADDRINUSE:
                        ctxt.reraise = False
                        eventlet.sleep(0.1)
        if not sock:
            raise RuntimeError(_("Could not bind to %(host)s:%(port)s "
                               "after trying for %(time)d seconds") %
                               {'host': host,
                                'port': port,
                                'time': CONF.retry_until_window})
        """一般来说,一个端口释放后会等待两分钟之后才能再被使用,SO_REUSEADDR是让端口释放后立即就可以被再次使用。
SO_REUSEADDR用于对TCP套接字处于TIME_WAIT状态下的socket,才可以重复绑定使用。server程序总是应该在调用bind()之前设置SO_REUSEADDR套接字选项。TCP,先调用close()的一方会进入TIME_WAIT状态"""
        sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        # sockets can hang around forever without keepalive
        """可以永久保持会话,具体工作原理:SO_KEEPALIVE 保持连接检测对方主机是否崩溃,避免(服务器)永远阻塞于TCP连接的输入。设置该选项后,如果2小时内在此套接口的任一方向都没有数据交换,TCP就自 动给对方 发一个保持存活探测分节(keepalive probe)。这是一个对方必须响应的TCP分节.它会导致以下三种情况:对方接收一切正常:以期望的ACK响应。2小时后,TCP将发出另一个探测分 节。对方已崩溃且已重新启动:以RST响应。套接口的待处理错误被置为ECONNRESET,套接 口本身则被关闭。对方无任何响应:源自berkeley的TCP发送另外8个探测分节,相隔75秒一个,试图得到一个响应。在发出第一个探测分节11分钟 15秒后若仍无响应就放弃。套接口的待处理错误被置为ETIMEOUT,套接口本身则被关闭。如ICMP错误是“host unreachable(主机不可达)”,说明对方主机并没有崩溃,但是不可达,这种情况下待处理错误被置为 EHOSTUNREACH。

根据上面的介绍我们可以知道对端以一种非优雅的方式断开连接的时候,我们可以设置SO_KEEPALIVE属性使得我们在2小时以后发现对方的TCP连接是否依然存在。  """
        sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)

        # This option isn't available in the OS X version of eventlet
        if hasattr(socket, 'TCP_KEEPIDLE'):
            sock.setsockopt(socket.IPPROTO_TCP,
                            socket.TCP_KEEPIDLE,
                            CONF.tcp_keepidle)

        return sock


    def _launch(self, application, workers=0, desc=None):
        set_proctitle = "off" if desc is None else CONF.setproctitle
        service = WorkerService(self, application, set_proctitle,
                                self.disable_ssl, workers)
        if workers < 1:
            # The API service should run in the current process.
            self._server = service
            # Dump the initial option values
            cfg.CONF.log_opt_values(LOG, logging.DEBUG)
            service.start(desc=desc)
            systemd.notify_once()
        else:
            # dispose the whole pool before os.fork, otherwise there will
            # be shared DB connections in child processes which may cause
            # DB errors.
            db_api.get_context_manager().dispose_pool()
            # The API service runs in a number of child processes.
            # Minimize the cost of checking for child exit by extending the
            # wait interval past the default of 0.01s.
            self._server = common_service.ProcessLauncher(
                cfg.CONF, wait_interval=1.0, restart_method='mutate')
            self._server.launch_service(service,
                                        workers=service.worker_process_count)

目前先更新到这里,面会更新对部分细节进行进一步增加,对api router进一步分析

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!