openstack Rocky系列之Cinder:Cinder服务启动

Posted sumoning

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了openstack Rocky系列之Cinder:Cinder服务启动相关的知识,希望对你有一定的参考价值。

比较忙,很长世间没空看openstack源码,抽时间看了一下cinder的源码,贴下学习心得。本文简单写一下cinder的三个服务的启动,cinder-api, cinder-scheduler, 以及cinder-volume,三者启动都差不多

1、cinder-api

  入口文件为/usr/bin/cinder-api,由此可知,入口为cinder.cmd.api文件中的main函数 

 1 #!/usr/bin/python2                                                                                                                                                                                         
 2 # PBR Generated from u‘console_scripts‘
 3 
 4 import sys 
 5 
 6 from cinder.cmd.api import main
 7 
 8 
 9 if __name__ == "__main__":
10     sys.exit(main())

  main函数如下,主要关注14-16行即可

 1 def main():
 2     objects.register_all()
 3     gmr_opts.set_defaults(CONF)
 4     CONF(sys.argv[1:], project=cinder,
 5          version=version.version_string())
 6     config.set_middleware_defaults()
 7     logging.setup(CONF, "cinder")
 8     python_logging.captureWarnings(True)
 9     utils.monkey_patch()
10 
11     gmr.TextGuruMeditation.setup_autorun(version, conf=CONF)
12 
13     rpc.init(CONF)
14     launcher = service.process_launcher()
15     server = service.WSGIService(osapi_volume)
16     launcher.launch_service(server, workers=server.workers)
17     launcher.wait()

  14行创建一个 ProcessLauncher 对象,以便后续对app进行launch

  15行创建 WSGIService 对象,名称为 osapi_volume

  16行,launch对象调用launch_service方法对server进行处理,通过查看源码,是调用了ProcessLauncher对象的_start_child对服务进行处理

 1     def launch_service(self, service, workers=1):
 2         """Launch a service with a given number of workers.
 3 
 4        :param service: a service to launch, must be an instance of
 5               :class:`oslo_service.service.ServiceBase`
 6        :param workers: a number of processes in which a service
 7               will be running
 8         """
 9         _check_service_base(service)  #对象类型校验
10         wrap = ServiceWrapper(service, workers)  #对象包装
11 
12         # Hide existing objects from the garbage collector, so that most
13         # existing pages will remain in shared memory rather than being
14         # duplicated between subprocesses in the GC mark-and-sweep. (Requires
15         # Python 3.7 or later.)
16         if hasattr(gc, freeze):
17             gc.freeze()
18 
19         LOG.info(Starting %d workers, wrap.workers)
20         while self.running and len(wrap.children) < wrap.workers:
21             self._start_child(wrap)

      _start_child方法很简单,调用了os.fork()创建了一个子进程,如果创建子进程成功,再次调用_child_process方法

 1     def _start_child(self, wrap):
 2         if len(wrap.forktimes) > wrap.workers:
 3             # Limit ourselves to one process a second (over the period of
 4             # number of workers * 1 second). This will allow workers to
 5             # start up quickly but ensure we don‘t fork off children that
 6             # die instantly too quickly.
 7             if time.time() - wrap.forktimes[0] < wrap.workers:
 8                 LOG.info(Forking too fast, sleeping)
 9                 time.sleep(1)
10 
11             wrap.forktimes.pop(0)
12 
13         wrap.forktimes.append(time.time())
14 
15         pid = os.fork()
16         if pid == 0:
17             self.launcher = self._child_process(wrap.service)
18             while True:
19                 self._child_process_handle_signal()
20                 status, signo = self._child_wait_for_exit_or_signal(
21                     self.launcher)
22                 if not _is_sighup_and_daemon(signo):
23                     self.launcher.wait()
24                     break
25                 self.launcher.restart()
26 
27             os._exit(status)
28 
29         LOG.debug(Started child %d, pid)
30 
31         wrap.children.add(pid)
32         self.children[pid] = wrap
33 
34         return pid
 1     def _child_process(self, service):
 2         self._child_process_handle_signal()
 3 
 4         # Reopen the eventlet hub to make sure we don‘t share an epoll
 5         # fd with parent and/or siblings, which would be bad
 6         eventlet.hubs.use_hub()
 7 
 8         # Close write to ensure only parent has it open
 9         os.close(self.writepipe)
10         # Create greenthread to watch for parent to close pipe
11         eventlet.spawn_n(self._pipe_watcher)
12 
13         # Reseed random number generator
14         random.seed()
15 
16         launcher = Launcher(self.conf, restart_method=self.restart_method)
17         launcher.launch_service(service)
18         return launcher

      _child_process方法中调用了eventlet,获取hub,并且创建一个线程,对进程进行观察,同时创建一个Launcher对象,对服务进行lanch,launch_service

 1     def launch_service(self, service, workers=1):
 2         """Load and start the given service.
 3 
 4         :param service: The service you would like to start, must be an
 5                         instance of :class:`oslo_service.service.ServiceBase`
 6         :param workers: This param makes this method compatible with
 7                         ProcessLauncher.launch_service. It must be None, 1 or
 8                         omitted.
 9         :returns: None
10 
11         """
12         if workers is not None and workers != 1:
13             raise ValueError(_("Launcher asked to start multiple workers"))
14         _check_service_base(service)
15         service.backdoor_port = self.backdoor_port
16         self.services.add(service)   #关键

Launcher 对象的关键在于这个add方法,它将所有调用其进行launch的服务添加到Service()的service列表中,最终调用了添加的service的start()方法

 1     def add(self, service):
 2         """Add a service to a list and create a thread to run it.
 3 
 4         :param service: service to run
 5         """
 6         self.services.append(service)
 7         self.tg.add_thread(self.run_service, service, self.done)
 8 
 9 @staticmethod
10     def run_service(service, done):
11         """Service start wrapper.
12 
13         :param service: service to run
14         :param done: event to wait on until a shutdown is triggered
15         :returns: None
16 
17         """
18         try:
19             service.start()
20         except Exception:
21             LOG.exception(Error starting thread.)
22             raise SystemExit(1)
23         else:
24             done.wait()

  这个service即最初提到的WSGIService 对象,查看一下其start方法

 1     def start(self):
 2         """Start serving this service using loaded configuration.
 3 
 4         Also, retrieve updated port number in case ‘0‘ was passed in, which
 5         indicates a random port should be used.
 6 
 7         :returns: None
 8 
 9         """
10         if self.manager:
11             self.manager.init_host()
12         self.server.start()
13         self.port = self.server.port

  此时self.manager为None,关键执行步骤为self.server.start(),这个server为WSGIService 进行init的时候构造的对象

 1 class WSGIService(service.ServiceBase):
 2     """Provides ability to launch API from a ‘paste‘ configuration."""
 3 
 4     def __init__(self, name, loader=None):
 5         """Initialize, but do not start the WSGI server.
 6 
 7         :param name: The name of the WSGI server given to the loader.
 8         :param loader: Loads the WSGI application using the given name.
 9         :returns: None
10 
11         """
12         self.name = name
13         self.manager = self._get_manager()
14         self.loader = loader or wsgi.Loader(CONF)
15         self.app = self.loader.load_app(name)
16         self.host = getattr(CONF, %s_listen % name, "0.0.0.0")
17         self.port = getattr(CONF, %s_listen_port % name, 0)
18         self.use_ssl = getattr(CONF, %s_use_ssl % name, False)
19         self.workers = (getattr(CONF, %s_workers % name, None) or
20                         processutils.get_worker_count())
21         if self.workers and self.workers < 1:
22             worker_name = %s_workers % name
23             msg = (_("%(worker_name)s value of %(workers)d is invalid, "
24                      "must be greater than 0.") %
25                    {worker_name: worker_name,
26                     workers: self.workers})
27             raise exception.InvalidConfigurationValue(msg)
28         setup_profiler(name, self.host)
29 
30         self.server = wsgi.Server(CONF,
31                                   name,
32                                   self.app,
33                                   host=self.host,
34                                   port=self.port,
35                                   use_ssl=self.use_ssl)   # 这里
 1     def start(self):
 2         """Start serving a WSGI application.
 3 
 4         :returns: None
 5         """
 6         # The server socket object will be closed after server exits,
 7         # but the underlying file descriptor will remain open, and will
 8         # give bad file descriptor error. So duplicating the socket object,
 9         # to keep file descriptor usable.
10 
11         self.dup_socket = self.socket.dup()
12 
13         if self._use_ssl:
14             self.dup_socket = sslutils.wrap(self.conf, self.dup_socket)
15 
16         wsgi_kwargs = {
17             func: eventlet.wsgi.server,
18             sock: self.dup_socket,
19             site: self.app,
20             protocol: self._protocol,
21             custom_pool: self._pool,
22             log: self._logger,
23             log_format: self.conf.wsgi_log_format,
24             debug: False,
25             keepalive: self.conf.wsgi_keep_alive,
26             socket_timeout: self.client_socket_timeout
27             }
28 
29         if self._max_url_len:
30             wsgi_kwargs[url_length_limit] = self._max_url_len
31 
32         self._server = eventlet.spawn(**wsgi_kwargs)

  至此,cinder-api启动顺利启动

2、cinder-scheduler

  入口文件为/usr/bin/cinder-scheduler,则实际调用文件为cinder/cmd/scheduler.py下的main

 1 #!/usr/bin/python2                                                                                                                                                                                         
 2 # PBR Generated from u‘console_scripts‘
 3 
 4 import sys 
 5 
 6 from cinder.cmd.scheduler import main
 7 
 8 
 9 if __name__ == "__main__":
10     sys.exit(main())
 1 def main():
 2     objects.register_all()
 3     gmr_opts.set_defaults(CONF)
 4     CONF(sys.argv[1:], project=cinder,
 5          version=version.version_string())
 6     logging.setup(CONF, "cinder")
 7     python_logging.captureWarnings(True)
 8     utils.monkey_patch()
 9     gmr.TextGuruMeditation.setup_autorun(version, conf=CONF)
10     server = service.Service.create(binary=cinder-scheduler)
11     service.serve(server)
12     service.wait()

  实际启动服务的只有10,11,12行,通过Service对象的类方法create创建一个名server的service,然后用serve方法(实际调用launch对service进行处理),launch方法通过判断serve传进的worker参数来判断,传入的对象是process还是service,但是不管是service还是process,都是调用了launch_service这个接口,此处,同上述api所述,Launcher 对象的关键在于这个add方法,它将所有调用其进行launch的服务添加到Service()的service列表中,最终调用了添加的Service的start()方法。

 1     @classmethod
 2     def create(cls, host=None, binary=None, topic=None, manager=None,
 3                report_interval=None, periodic_interval=None,
 4                periodic_fuzzy_delay=None, service_name=None,
 5                coordination=False, cluster=None, **kwargs):
 6         if not host:
 7             host = CONF.host
 8         if not binary:
 9             binary = os.path.basename(inspect.stack()[-1][1])
10         if not topic:
11             topic = binary
12         if not manager:
13             subtopic = topic.rpartition(cinder-)[2]
14             manager = CONF.get(%s_manager % subtopic, None)
15         if report_interval is None:
16             report_interval = CONF.report_interval
17         if periodic_interval is None:
18             periodic_interval = CONF.periodic_interval
19         if periodic_fuzzy_delay is None:
20             periodic_fuzzy_delay = CONF.periodic_fuzzy_delay
21         service_obj = cls(host, binary, topic, manager,
22                           report_interval=report_interval,
23                           periodic_interval=periodic_interval,
24                           periodic_fuzzy_delay=periodic_fuzzy_delay,
25                           service_name=service_name,
26                           coordination=coordination,
27                           cluster=cluster, **kwargs)
28 
29         return service_obj
1 def serve(server, workers=None):
2     global _launcher
3     if _launcher:
4         raise RuntimeError(_(serve() can only be called once))
5 
6     _launcher = service.launch(CONF, server, workers=workers)
 1 def launch(conf, service, workers=1, restart_method=reload):
 2     """Launch a service with a given number of workers.
 3 
 4     :param conf: an instance of ConfigOpts
 5     :param service: a service to launch, must be an instance of
 6            :class:`oslo_service.service.ServiceBase`
 7     :param workers: a number of processes in which a service will be running
 8     :param restart_method: Passed to the constructed launcher. If ‘reload‘, the
 9         launcher will call reload_config_files on SIGHUP. If ‘mutate‘, it will
10         call mutate_config_files on SIGHUP. Other values produce a ValueError.
11     :returns: instance of a launcher that was used to launch the service
12     """
13 
14     if workers is not None and workers <= 0:
15         raise ValueError(_("Number of workers should be positive!"))
16 
17     if workers is None or workers == 1:
18         launcher = ServiceLauncher(conf, restart_method=restart_method)
19     else:
20         launcher = ProcessLauncher(conf, restart_method=restart_method)
21     launcher.launch_service(service, workers=workers)
22 
23     return launcher

  至此,cinder-scheduler启动完成

3、cinder-volume

  cinder-volume的入口文件为/usr/bin/cinder-volume,由此可知真正的入口函数为cinder/cmd/volume.py中的main函数

 1 #!/usr/bin/python2                                                                                                                                                                                         
 2 # PBR Generated from u‘console_scripts‘
 3 
 4 import sys 
 5 
 6 from cinder.cmd.volume import main
 7 
 8 
 9 if __name__ == "__main__":
10     sys.exit(main())
 1 def _launch_services_win32():
 2     if CONF.backend_name and CONF.backend_name not in CONF.enabled_backends:
 3         msg = _(The explicitly passed backend name "%(backend_name)s" is not 
 4                 among the enabled backends: %(enabled_backends)s.)
 5         raise exception.InvalidInput(
 6             reason=msg % dict(backend_name=CONF.backend_name,
 7                               enabled_backends=CONF.enabled_backends))
 8 
 9     # We‘ll avoid spawning a subprocess if a single backend is requested.
10     single_backend_name = (CONF.enabled_backends[0]
11                            if len(CONF.enabled_backends) == 1
12                            else CONF.backend_name)
13     if single_backend_name:
14         launcher = service.get_launcher()
15         _launch_service(launcher, single_backend_name)
16     elif CONF.enabled_backends:
17         # We‘re using the ‘backend_name‘ argument, requesting a certain backend
18         # and constructing the service object within the child process.
19         launcher = service.WindowsProcessLauncher()
20         py_script_re = re.compile(r.*.pyw?$)
21         for backend in filter(None, CONF.enabled_backends):
22             cmd = sys.argv + [--backend_name=%s % backend]
23             # Recent setuptools versions will trim ‘-script.py‘ and ‘.exe‘
24             # extensions from sys.argv[0].
25             if py_script_re.match(sys.argv[0]):
26                 cmd = [sys.executable] + cmd
27             launcher.add_process(cmd)
28             _notify_service_started()
29 
30     _ensure_service_started()
31 
32     launcher.wait()
33 
34 
35 def _launch_services_posix():
36     launcher = service.get_launcher()
37 
38     for backend in filter(None, CONF.enabled_backends):
39         _launch_service(launcher, backend)
40 
41     _ensure_service_started()
42 
43     launcher.wait()
44 
45 
46 def main():
47     objects.register_all()
48     gmr_opts.set_defaults(CONF)
49     CONF(sys.argv[1:], project=cinder,
50          version=version.version_string())
51     logging.setup(CONF, "cinder")
52     python_logging.captureWarnings(True)
53     priv_context.init(root_helper=shlex.split(utils.get_root_helper()))
54     utils.monkey_patch()
55     gmr.TextGuruMeditation.setup_autorun(version, conf=CONF)
56     global LOG
57     LOG = logging.getLogger(__name__)
58 
59     if not CONF.enabled_backends:
60         LOG.error(Configuration for cinder-volume does not specify 
61                   "enabled_backends". Using DEFAULT section to configure 
62                   drivers is not supported since Ocata.)
63         sys.exit(1)
64 
65     if os.name == nt:
66         # We cannot use oslo.service to spawn multiple services on Windows.
67         # It relies on forking, which is not available on Windows.
68         # Furthermore, service objects are unmarshallable objects that are
69         # passed to subprocesses.
70         _launch_services_win32()
71     else:
72         _launch_services_posix()

  根据平台是windows还是linux,进行不同的调用,因为是在linux上部署,所有调用的函数为_launch_services_posix(),其中调用了_launch_service(launcher, backend)创建Service对象,同上述cinder-schedule的启动流程,后面不再累述了~

 

以上是关于openstack Rocky系列之Cinder:Cinder服务启动的主要内容,如果未能解决你的问题,请参考以下文章

Openstack 安装部署指南翻译系列 之 Cinder服务安装(Block Storage)

OpenStack实践系列⑨云硬盘服务Cinder

OpenStack入门之核心组件梳理——Cinder篇

OpenStack的部署之Cinder项目(7-8)

OpenStack的部署之Cinder项目(7-8)

openstack组件之cinder