gnocchiclient使用查错及工作原理浅析

一、gnocchiclient not working

OpenStack 环境中安装了 gnocchi 后,调用 gnocchi 命令,出现如下提示:

[root@controller1 ~]# gnocchi status
gnocchi: 'status' is not a gnocchi command. See 'gnocchi --help'.
Did you mean one of these?
  complete
  help

照理说,是没安装 gnocchiclient 。于是检查一下环境:

[root@controller1 ~]# rpm -qa | grep gnocchiclient
python2-gnocchiclient-3.3.1-1.el7.noarch

有啊,到 /usr/lib/python2.7/site-packages/gnocchiclient 目录下看看代码,都有啊,怎么回事儿?

gnocchi: 'status' is not a gnocchi command. See 'gnocchi --help'. 谷歌一下,点开几个链接,貌似大家都没遇到过这种问题。

与此同时,我用一台centos7虚拟机装了 gnocchiclient 作为试验,运行 gnocchi status,可以使用该指令,只不过由于没安装别的服务,指令报错:

[root@centos-test gnocchiclient]# gnocchi status
Unable to establish connection to http://localhost:8041/v1/status?details=False: HTTPConnectionPool(host='localhost', port=8041): Max retries exceeded with url: /v1/status?details=False (Caused by NewConnectionError('<requests.packages.urllib3.connection.HTTPConnection object at 0x7fbe17fd7a50>: Failed to establish a new connection: [Errno 111] Connection refused',))

迫不得已看了源码,还是找不到头绪,百思不得其解之际,在某个地方看到了这个:

《gnocchiclient使用查错及工作原理浅析》 gnocchi.png

参考链接:https://github.com/gnocchixyz/python-gnocchiclient/issues/56

我的 gnocchiclient 版本正是 3.3.1 ,会不会是别的依赖版本不对?

于是我在测试的虚拟机上查看了 cliff 的版本:

[root@centos-test gnocchiclient]# pip list | grep cliff
cliff (2.8.0)

在 controller 机子上查看 cliff 版本:

[root@controller1 ~]# pip list | grep cliff
cliff                            2.14.1

有戏?

于是慎重地下载了 cliff 2.8.0 版本:

pip install cliff==2.8.0

再试一下指令:

gnocchi status
+-----------------------------------------------------+-------+
| Field                                               | Value |
+-----------------------------------------------------+-------+
| storage/number of metric having measures to process | 48    |
| storage/total number of measures to process         | 48    |
+-----------------------------------------------------+-------+

成功了。

有时候就是这么巧。

二、cliff是什么?

之前看 gnocchiclient 的时候就发现了 cliff,也去查了下它的作用,这里再次梳理一下。

cliff(CommandLine Interface Formulation Framework)即命令行接口制定框架。可定义多级命令、输出格式以及其他一些扩展来创建命令行相关项目。Cliff框架中定义的主程序处理参数解析,并调用子命令来完成工作。

以上来自这篇博客 https://blog.csdn.net/bc_vnetwork/article/details/53939946

OpenStack 各种 client 就是基于 cliff 框架写的,让你在命令行就可以操作 OpenStack 服务并获得格式化的输出。

gnocchiclient大概是怎么工作的呢?

三、gnocchiclient工作原理浅析

1 shell.py

首先看 /usr/lib/python2.7/site-packages/gnocchiclient/shell.py

shell文件的 main 方法接收命令行发来的指令,调用 GnocchiShell().run(args) 开始工作

def main(args=None):
    if args is None:
        args = sys.argv[1:]
    return GnocchiShell().run(args)

2 GnocchiShell

大致看一眼 GnocchiShell

from cliff import app

......

class GnocchiShell(app.App):
    def __init__(self):
        super(GnocchiShell, self).__init__(
            description='Gnocchi command line client',
            # FIXME(sileht): get version from pbr
            version=__version__,
            command_manager=GnocchiCommandManager(None),
            deferred_help=True,
            )

        self._client = None

    def build_option_parser(self, description, version):
        """Return an argparse option parser for this application.

        Subclasses may override this method to extend
        the parser with more global options.

        :param description: full description of the application
        :paramtype description: str
        :param version: version number for the application
        :paramtype version: str
        """
        parser = super(GnocchiShell, self).build_option_parser(
            description,
            version,
            argparse_kwargs={'allow_abbrev': False})
        parser.add_argument(
            '--gnocchi-api-version',
            default=os.environ.get('GNOCCHI_API_VERSION', '1'),
            help='Defaults to env[GNOCCHI_API_VERSION] or 1.')

gnocchishell 继承了 cliff 里的 app,在 /usr/lib/python2.7/site-packages/gnocchiclient/shell.py 里,我们没有找到 run 方法,需要到 cliff 源码里找

3. cliff app

locate 一下 cliff,找到 /usr/lib/python2.7/site-packages/cliff/app.py,看一下 run 方法:

 66     def __init__(self, description, version, command_manager,
 67                  stdin=None, stdout=None, stderr=None,
 68                  interactive_app_factory=None,
 69                  deferred_help=False):
 70         """Initialize the application.
 71         """
 72         self.command_manager = command_manager
 73         self.command_manager.add_command('help', help.HelpCommand)
 74         self.command_manager.add_command('complete', complete.CompleteCommand)
 75         self._set_streams(stdin, stdout, stderr)
 76         self.interactive_app_factory = interactive_app_factory
 77         self.deferred_help = deferred_help
 78         self.parser = self.build_option_parser(description, version)
 79         self.interactive_mode = False
 80         self.interpreter = None

240     def run(self, argv):
241         """Equivalent to the main program for the application.
242
243         :param argv: input arguments and options
244         :paramtype argv: list of str
245         """
246         try:
247             self.options, remainder = self.parser.parse_known_args(argv)
248             self.configure_logging()
249             self.interactive_mode = not remainder
250             if self.deferred_help and self.options.deferred_help and remainder:
251                 # When help is requested and `remainder` has any values disable
252                 # `deferred_help` and instead allow the help subcommand to
253                 # handle the request during run_subcommand(). This turns
254                 # "app foo bar --help" into "app help foo bar". However, when
255                 # `remainder` is empty use print_help_if_requested() to allow
256                 # for an early exit.
257                 # Disabling `deferred_help` here also ensures that
258                 # print_help_if_requested will not fire if called by a subclass
259                 # during its initialize_app().
260                 self.options.deferred_help = False
261                 remainder.insert(0, "help")
262             self.initialize_app(remainder)
263             self.print_help_if_requested()
264         except Exception as err:
265             if hasattr(self, 'options'):
266                 debug = self.options.debug
267             else:
268                 debug = True
269             if debug:
270                 self.LOG.exception(err)
271                 raise
272             else:
273                 self.LOG.error(err)
274             return 1
275         result = 1
276         if self.interactive_mode:
277             result = self.interact()
278         else:
279             result = self.run_subcommand(remainder)
280         return result

这里的关键是 247 行的 reminder,它是cliff 中一个参数解析器解析参数后的返回值。

我们进行断点调试,看一下 reminder 的返回值。

[root@controller1 cliff]# gnocchi status
> /usr/lib/python2.7/site-packages/cliff/app.py(248)run()
-> self.options, remainder = self.parser.parse_known_args(argv)
(Pdb) n
> /usr/lib/python2.7/site-packages/cliff/app.py(249)run()
-> self.configure_logging()
(Pdb) p remainder
['status']
(Pdb)

可以看到,当执行 gnocchi status 命令时,reminder 的值就是 gnocchi 后面跟的参数。

结合 /usr/lib/python2.7/site-packages/cliff/app.py 第249和276行往下可以明白,只输入 gnocchi 时,就进入了 gnocchiclient 的交互模式,而后面跟上参数,就直接执行 gnocchi 后面参数的子命令。

至于后面如何执行子命令,这里就不继续了。

4. /usr/bin/gnocchi

最后一个疑问,为什么我们在命令行里输入 gnocchi xxx,就会调用到 /usr/lib/python2.7/site-packages/gnocchiclient/shell.py 里的方法呢?

看到这个文件你就明白了 /usr/bin/gnocchi

import sys

from gnocchiclient.shell import main


if __name__ == "__main__":
    sys.exit(main())

5. 总结

那么 OpenStackgnocchiclient 的工作模式我们就理清了,大概是

  • /usr/bin/gnocchi
  • /gnocchiclient 目录下的 shell.py
  • shell.py 中 GnocchiShell 继承 cliff 框架 app 类,对命令行参数进行解析,最后决定进入交互模式还是直接执行子命令

除了 gnocchiclientOpenStack 中其他诸如 novaclientneutronclient 的工作原理也大致相似。

    原文作者:Murray66
    原文地址: https://www.jianshu.com/p/977bbb5e7302
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞