注意

本文档适用于 Ceph 的开发版本。

ceph-mgr 模块开发者指南

警告

这是开发者文档,描述了仅与编写 ceph-mgr 模块的人相关的 Ceph 内部结构。

创建模块

pybind/mgr/ 中,创建一个 Python 模块。在您的模块中,创建一个继承自 MgrModule 的类。为了让 ceph-mgr 检测到您的模块,您的目录必须包含一个名为 module.py 的文件。

最重要的要重写的方法是

  • 用于服务器类型模块的 serve 成员函数。此函数应该永远阻塞。

  • 如果您的模块需要在有新的集群数据可用时采取行动,则需要一个 notify 成员函数。

  • 如果您的模块公开了 CLI 命令,则需要一个 handle_command 成员函数。但是这种公开命令的方法已被弃用。有关更多详细信息,请参阅公开命令

有些模块与外部协调器接口,以部署 Ceph 服务。这些模块也继承自 Orchestrator,它为基类 MgrModule 添加了额外的方法。有关创建这些模块的更多信息,请参阅协调器模块

安装模块

一旦您的模块位于由 mgr module path 配置设置指定的位置,您就可以通过 ceph mgr module enable 命令启用它

ceph mgr module enable mymodule

请注意,MgrModule 接口不稳定,因此在 Ceph 树之外维护的任何模块在针对任何更新或更旧的 Ceph 版本运行时都可能中断。

日志

Ceph 管理器模块中的日志记录与任何其他 Python 程序中的日志记录一样。只需导入 logging 包,并使用 logging.getLogger 函数获取一个日志记录器实例。

每个模块都有一个 log_level 选项,用于指定模块当前的 Python 日志级别。

要更改或查询模块的日志级别,请使用以下 Ceph 命令

ceph config get mgr mgr/<module_name>/log_level
ceph config set mgr mgr/<module_name>/log_level <info|debug|critical|error|warning|>

模块启动时使用的日志级别由 mgr 守护进程的当前日志级别决定,除非之前使用 config set ... 命令设置了 log_level 选项。mgr 守护进程的日志级别与模块 Python 日志级别映射如下

  • <= 0 是 CRITICAL

  • <= 1 是 WARNING

  • <= 4 是 INFO

  • <= +inf 是 DEBUG

我们可以通过运行以下命令来取消设置模块日志级别,并回退到 mgr 守护进程的日志级别

ceph config set mgr mgr/<module_name>/log_level ''

默认情况下,模块的日志消息由 Ceph 日志层处理,并记录在 mgr 守护进程的日志文件中。但也可以将模块的日志消息发送到其自己的文件。

模块的日志文件位于与管理器守护进程的日志文件相同的目录中,并采用以下命名模式

<mgr_daemon_log_file_name>.<module_name>.log

要在模块上启用文件日志记录,请使用以下命令

ceph config set mgr mgr/<module_name>/log_to_file true

当模块的文件日志记录启用时,模块的日志消息将停止写入管理器守护进程的日志文件,而只写入模块的日志文件。

还可以使用以下命令检查状态并禁用文件日志记录

ceph config get mgr mgr/<module_name>/log_to_file
ceph config set mgr mgr/<module_name>/log_to_file false

公开命令

有两种公开命令的方法。第一种方法是使用 @CLICommand 装饰器来装饰处理命令所需的方法。第二种方法是使用为模块类定义的 COMMANDS 属性。

CLICommand 方法

@CLICommand('antigravity send to blackhole',
            perm='rw')
def send_to_blackhole(self, oid: str, blackhole: Optional[str] = None, inbuf: Optional[str] = None):
    '''
    Send the specified object to black hole
    '''
    obj = self.find_object(oid)
    if obj is None:
        return HandleCommandResult(-errno.ENOENT, stderr=f"object '{oid}' not found")
    if blackhole is not None and inbuf is not None:
        try:
            location = self.decrypt(blackhole, passphrase=inbuf)
        except ValueError:
            return HandleCommandResult(-errno.EINVAL, stderr='unable to decrypt location')
    else:
        location = blackhole
    self.send_object_to(obj, location)
    return HandleCommandResult(stdout=f"the black hole swallowed '{oid}'")

传递给 CLICommand 的第一个参数是命令的“名称”。由于 Ceph 中有许多命令,我们倾向于使用一个共同的前缀来对相关命令进行分组。在这种情况下,“antigravity”就是为此目的使用的。因为作者可能正在设计一个也能够将火箭发射到深空的模块。

这里方法参数的 类型注解 是强制性的,这样命令的用法才能正确地报告给 ceph CLI,并且管理器守护程序可以在将客户端发送的序列化命令参数传递给处理程序方法之前将其转换为预期类型。通过正确实现的类型,还可以对参数执行一些健全性检查!

参数的名称是命令接口的一部分,因此在更改它们时请尽量考虑向后兼容性。但是你不能更改 inbuf 参数的名称,它用于传递由 ceph --in-file 选项指定的文件内容。

方法的 docstring 用于命令的描述。

管理器守护程序从这些要素中生成命令的用法,例如

antigravity send to blackhole <oid> [<blackhole>]  Send the specified object to black hole

作为 ceph --help 输出的一部分。

除了 @CLICommand,如果您的命令分别只需要读取权限或写入权限,您还可以使用 @CLIReadCommand@CLIWriteCommand

COMMANDS 方法

此方法使用模块的 COMMANDS 类属性来定义一个字典列表,如下所示

COMMANDS = [
    {
        "cmd": "foobar name=myarg,type=CephString",
        "desc": "Do something awesome",
        "perm": "rw",
        # optional:
        "poll": "true"
    }
]

每个条目的 cmd 部分以与内部 Ceph mon 和 admin socket 命令相同的方式进行解析(有关示例,请参阅 Ceph 源代码中的 mon/MonCommands.h)。请注意,“poll”字段是可选的,默认设置为 False;这表示 ceph CLI 应重复调用此命令并输出结果(请参阅 ceph -h 及其 --period 选项)。

每个命令预期返回一个元组 (retval, stdout, stderr)retval 是一个整数,表示 libc 错误代码(例如 EINVAL、EPERM,或者 0 表示没有错误),stdout 是一个包含任何非错误输出的字符串,而 stderr 是一个包含任何进度或错误解释输出的字符串。两个字符串中的任何一个或两个都可以为空。

实现 handle_command 函数以在命令发送时作出响应

MgrModule.handle_command(inbuf, cmd)

由 ceph-mgr 调用,请求插件处理其在 self.COMMANDS 中声明的其中一个命令

返回状态码、输出缓冲区和输出字符串。输出缓冲区用于数据结果,输出字符串用于信息文本。

参数:
  • inbuf (str) -- 提供给 ceph cli 的任何“-i <file>”的内容

  • cmd (dict) -- 来自 Ceph 的 cmdmap_t

返回类型:

Union[HandleCommandResult, Tuple[int, str, str]]

返回:

HandleCommandResult 或 (int, str, str) 的三元组

响应和格式化

处理管理器函数的函数预期返回一个三元素元组,其类型签名为 Tuple[int, str, str]。第一个元素是返回值/错误代码,其中零表示没有错误,负数 errno 通常用于错误情况。第二个元素对应于命令的“输出”。第三个元素对应于命令的“错误输出”(类似于 stderr),当返回代码非零时,通常用于报告文本错误详细信息。mgr_module.HandleCommandResult 类型也可以代替响应元组使用。

当命令的实现引发异常时,有两种可能的异常处理方法。首先,命令函数可以不进行任何操作,让异常冒泡到管理器。发生这种情况时,管理器将自动将返回代码设置为 -EINVAL,并在错误输出中记录回溯。在某些情况下,此回溯可能非常长。第二种方法是在 try-except 块中处理异常,并将异常转换为更适合异常的错误代码(例如,将 KeyError 转换为 -ENOENT)。在这种情况下,错误输出也可以设置为调用命令的人可以采取的更具体和可操作的内容。

在许多情况下,尤其是在较新版本的 Ceph 中,管理器命令旨在向调用者返回结构化输出。结构化输出包括机器可解析的数据,例如 JSON、YAML、XML 等。JSON 是管理器命令返回的最常见的结构化输出格式。从 Ceph Reef 开始,object_format 模块提供了许多新的装饰器,有助于自动管理输出格式和处理异常。目的是管理器命令的大部分实现可以按照惯用(也称为“Pythonic”)风格编写,装饰器将负责格式化输出和返回管理器响应元组所需的大部分工作。

在大多数情况下,全新的代码应使用 Responder 装饰器。例如

@CLICommand('antigravity list wormholes', perm='r')
@Responder()
def list_wormholes(self, oid: str, details: bool = False) -> List[Dict[str, Any]]:
    '''List wormholes associated with the supplied oid.
    '''
    with self.open_wormhole_db() as db:
        wormholes = db.query(oid=oid)
    if not details:
        return [{'name': wh.name} for wh in wormholes]
    return [{'name': wh.name, 'age': wh.get_age(), 'destination': wh.dest}
            for wh in wormholes]

格式化

Responder 装饰器会自动将 Python 对象转换为带格式输出的响应元组。默认情况下,此装饰器可以自动返回 JSON 和 YAML。从命令行调用时,可以使用 --format 标志选择响应格式。如果未指定,将返回 JSON。自动格式化可以应用于任何基本的 Python 类型:列表、字典、str、int 等。如果其他对象符合 SimpleDataProvider 协议——它们提供 to_simplified 方法,也可以自动格式化。 to_simplified 函数必须返回由基本类型组成的对象的简化表示。

class MyCleverObject:
    def to_simplified(self) -> Dict[str, int]:
        # returns a python object(s) made up from basic types
        return {"gravitons": 999, "tachyons": 404}

@CLICommand('antigravity list wormholes', perm='r')
@Responder()
def list_wormholes(self, oid: str, details: bool = False) -> MyCleverObject:
    '''List wormholes associated with the supplied oid.
    '''
    ...

自动输出格式化的行为可以自定义并扩展到其他类型的格式化(XML、纯文本等)。由于这是一个复杂的主题,请参阅 object_format 模块的模块文档。

错误处理

此外,Responder 装饰器可以自动处理将某些异常转换为响应元组。任何继承自 ErrorResponseBase 的已引发异常都将自动转换为响应元组。通常的方法是使用 ErrorResponse,这是一种可以直接使用的异常类型,它具有错误输出和返回值的参数,或者可以使用 wrap 类方法从现有异常构造。wrap 类方法将自动使用异常文本,如果可用,则使用其他异常的 errno 属性。

将我们之前的示例转换为使用此异常处理方法

@CLICommand('antigravity list wormholes', perm='r')
@Responder()
def list_wormholes(self, oid: str, details: bool = False) -> List[Dict[str, Any]]:
    '''List wormholes associated with the supplied oid.
    '''
    try:
        with self.open_wormhole_db() as db:
            wormholes = db.query(oid=oid)
    except UnknownOIDError:
         raise ErrorResponse(f"Unknown oid: {oid}", return_value=-errno.ENOENT)
    except WormholeDBError as err:
        raise ErrorResponse.wrap(err)
    if not details:
        return [{'name': wh.name} for wh in wormholes]
    return [{'name': wh.name, 'age': wh.get_age(), 'destination': wh.dest}
            for wh in wormholes]

注意

因为装饰器无法区分编程错误和预期错误条件,所以它不会尝试捕获所有异常。

附加装饰器

object_format 模块提供了额外的装饰器,以补充 Responder,但适用于 Responder 不足或过于“笨重”的情况。

对于您必须仍然返回管理器响应元组但希望将错误作为异常处理(如典型的 Python 代码中)的情况,存在 ErrorResponseHandler 装饰器。简而言之,它类似于 Responder,但仅限于异常。就像 Responder 一样,它处理继承自 ErrorResponseBase 的异常。这在您需要在输出中返回原始数据的情况下可能很有用。例如

@CLICommand('antigravity dump config', perm='r')
@ErrorResponseHandler()
def dump_config(self, oid: str) -> Tuple[int, str, str]:
    '''Dump configuration
    '''
    # we have no control over what data is inside the blob!
    try:
         blob = self.fetch_raw_config_blob(oid)
         return 0, blob, ''
    except KeyError:
         raise ErrorResponse("Blob does not exist", return_value=-errno.ENOENT)

当成功时根本不应生成任何输出时,存在 EmptyResponder 装饰器。如果您使用 Responder 和默认 JSON 格式化,那么如果命令无错误完成,您可能会总是看到 {}[] 这样的输出。相反,EmptyResponder 帮助您创建遵守 静默规则 的管理器命令,当命令在成功时没有有趣的输出要发出时。 EmptyResponder 装饰的函数应始终返回 None。像 ResponderErrorResponseHandler 一样,继承自 ErrorResponseBase 的异常将自动处理。例如

@CLICommand('antigravity create wormhole', perm='rw')
@EmptyResponder()
def create_wormhole(self, oid: str, name: str) -> None:
    '''Create a new wormhole.
    '''
    try:
        with self.open_wormhole_db() as db:
            wh = Wormhole(name)
            db.insert(oid=oid, wormhole=wh)
    except UnknownOIDError:
        raise ErrorResponse(f"Unknown oid: {oid}", return_value=-errno.ENOENT)
    except InvalidWormholeError as err:
        raise ErrorResponse.wrap(err)
    except WormholeDBError as err:
        raise ErrorResponse.wrap(err)

配置选项

模块可以使用 set_module_optionget_module_option 方法加载和存储配置选项。

注意

使用 set_module_optionget_module_option 管理用户可见的非二进制大对象(如证书)的配置选项。如果您想持久化模块内部数据或二进制配置数据,请考虑使用 KV 存储

配置选项必须在 MODULE_OPTIONS 类属性中声明

MODULE_OPTIONS = [
    Option(name="check_interval",
           level=OptionLevel.ADVANCED,
           default=60,
           type="secs",
           desc="The interval in seconds to check the inbox",
           long_desc="The interval in seconds to check the inbox. Set to 0 to disable the check",
           min=0,
           runtime=True)
]

上面的示例演示了大多数受支持的属性,尽管通常只需要一个子集。支持以下属性

name

选项的名称。这是唯一必需的属性。

type (可选,默认:str

选项的数据类型。支持的类型

  • uint - 无符号 64 位整数

  • int - 带符号 64 位整数

  • str - 字符串

  • float - 双精度浮点数

  • bool - 布尔值

  • addr - 用于对等通信的 Ceph 信使地址

  • addrvec - 逗号分隔的 Ceph 信使地址列表

  • uuid - 由 RFC 4122 定义的 UUID

  • size - 大小值(存储为 uint64_t)

  • secs - 格式为 "<number><unit>..." 的时间跨度,例如 "3h 2m 1s""3weeks"

    支持的单位:s, sec, second(s), m, min, minute(s), h, hr, hour(s), d, day(s), w, wk, week(s), mo, month(s), y, yr, year(s)

  • millisecs - 以毫秒为单位的时间跨度

desc

简短描述(可以是句子片段)。

long_desc

使用完整句子或段落的详细描述。

default

选项的默认值。

min / max

允许的最小值和最大值。

enum_allowed

对于 str 类型选项,允许的字符串值列表。此集合之外的值将被拒绝。

see_also

相关选项名称列表(仅用于文档)。

tags

用于对选项进行分类的标签列表(仅用于文档)。

runtime (默认:False

模块加载后是否可以更新选项。

如果您尝试对未在 MODULE_OPTIONS 中声明的选项使用 set_module_optionget_module_option,则会引发异常。

您可以选择在模块中提供 setter 命令来执行高级验证。用户还可以使用普通的 ceph config set 命令修改配置,其中管理器模块的配置选项的名称形式为 mgr/<module name>/<option>

如果配置选项根据管理器运行的节点而不同,则使用本地化配置(get_localized_module_option, set_localized_module_option)。这对于诸如要监听的地址之类的选项可能是必要的。本地化选项也可以通过 ceph config set 从外部设置,其中键名类似于 mgr/<module name>/<mgr id>/<option>

如果需要加载和存储数据(例如,较大的、二进制的或多行数据),请使用 KV 存储而不是配置选项(请参阅下一节)。

使用配置选项的提示

  • 读取速度快:ceph-mgr 维护一个本地内存副本,因此在许多情况下,您每次使用选项时都可以直接进行 get_module_option,而无需将其复制到变量中。

  • 写入会阻塞,直到值持久化(即往返于监视器),但来自另一个线程的读取会立即看到新值。

  • 如果用户通过命令行使用了 config set,那么新值将立即对 get_module_option 可见,尽管 mon->mgr 更新是异步的,因此 config set 会在新值对 mgr 可见之前几分之一秒返回。

  • 要删除配置值(即恢复为默认值),只需将 None 传递给 set_module_option 即可。

MgrModule.get_module_option(key, default=None)

检索持久配置设置的值

返回类型:

Union[bool, int, float, str, None]

MgrModule.set_module_option(key, val)

设置持久配置设置的值

参数:

key (str) --

引发:

ValueError -- 如果 val 无法解析或超出指定范围

返回类型:

None

MgrModule.get_localized_module_option(key, default=None)

检索此 ceph-mgr 实例的本地化配置

返回类型:

Union[bool, int, float, str, None]

MgrModule.set_localized_module_option(key, val)

为此 ceph-mgr 实例设置本地化配置。

参数:
  • key (str) --

  • val (str) --

返回类型:

None

返回:

str

KV 存储

模块可以访问私有(每个模块)键值存储,该存储使用监视器的“config-key”命令实现。使用 set_storeget_store 方法从模块访问 KV 存储。

KV 存储命令以类似于配置命令的方式工作。读取速度快,从本地缓存操作。写入阻塞持久化并往返于监视器。

可以使用 ceph config-key [get|set] 命令从 ceph-mgr 外部访问此数据。键名遵循与配置选项相同的约定。请注意,从 ceph-mgr 外部更新的任何值在下次重启之前不会被运行中的模块看到。应劝阻用户从外部访问模块 KV 数据——如果用户需要填充数据,模块应提供特殊命令通过模块设置数据。

使用 get_store_prefix 函数枚举特定前缀内的键(即以特定子字符串开头的所有键)。

MgrModule.get_store(key, default=None)

从此模块的持久键值存储中获取值

返回类型:

Optional[str]

MgrModule.set_store(key, val)

在此模块的持久键值存储中设置值。如果 val 为 None,则从存储中移除键

返回类型:

None

MgrModule.get_localized_store(key, default=None)
返回类型:

Optional[str]

MgrModule.set_localized_store(key, val)
返回类型:

None

MgrModule.get_store_prefix(key_prefix)

检索键值存储中键到值的字典,其中键具有给定前缀

参数:

key_prefix (str) --

返回类型:

Dict[str, str]

返回:

str

访问集群数据

模块可以访问 mgr 维护的 Ceph 集群状态的内存副本。访问函数作为 MgrModule 的成员公开。

访问集群或守护进程状态的调用通常是从 Python 进入原生 C++ 例程。这会带来一些开销,但比调用 REST API 或 SQL 数据库的开销要小得多。

关于访问集群结构或守护进程元数据没有一致性规则。例如,OSD 可能存在于 OSDMap 中但没有元数据,反之亦然。在一个健康的集群中,这些都是非常罕见的瞬态状态,但模块应该编写成能够应对这种可能性。

请注意,这些访问器不得在模块的 __init__ 函数中调用。这将导致循环锁定异常。

MgrModule.get(data_name)

由插件调用,从 ceph-mgr 获取命名集群范围的对象。

参数:

data_name (str) -- 可获取的有效内容有 osdmap_crush_map_text、osd_map、osd_map_tree、osd_map_crush、config、mon_map、fs_map、osd_metadata、pg_summary、io_rate、pg_dump、df、osd_stats、health、mon_status、devices、device <devid>、pg_stats、pool_stats、pg_ready、osd_ping_times、mgr_map、mgr_ips、modified_config_options、service_map、mds_metadata、have_local_config_map、osd_pool_stats、pg_status。

注意

所有这些结构都有自己的 JSON 表示:请进行实验或查看 C++ dump() 方法以了解它们。

返回类型:

任意

MgrModule.get_server(hostname)

由插件调用,从 ceph-mgr 获取有关特定主机名的元数据。

这是 ceph-mgr 从在特定服务器上运行的守护进程报告的守护进程元数据中获取的信息。

参数:

hostname (str) -- 主机名

返回类型:

Dict[str, Union[str, List[Dict[str, str]]]]

MgrModule.list_servers()

get_server 类似,但提供所有服务器的信息(即守护进程元数据中提到的所有唯一主机名)

返回:

所有服务器的信息列表

返回类型:

列出

MgrModule.get_metadata(svc_type, svc_id, default=None)

获取特定服务的守护进程元数据。

ceph-mgr 异步获取元数据,因此在添加/删除服务期间,元数据在一段时间内对模块不可用。如果没有元数据,则返回 None

参数:
  • svc_type (str) -- 服务类型(例如,“mds”、“osd”、“mon”)

  • svc_id (str) -- 服务 ID。调用此函数时将 OSD 整数 ID 转换为字符串

返回类型:

字典,如果未找到元数据则为 None

MgrModule.get_daemon_status(svc_type, svc_id)

获取特定服务守护程序的最新状态。

如果服务信息不可用,例如因为守护程序尚未完全启动,此方法可能会返回 None

参数:
  • svc_type (str) -- 字符串(例如,“rgw”)

  • svc_id (str) -- 字符串

返回类型:

Dict[str, str]

返回:

字典,如果未找到服务则为 None

MgrModule.get_unlabeled_perf_schema(svc_type, svc_name)

由插件调用以获取无标签性能计数器模式信息。svc_name 可以为 nullptr,svc_type 也可以为 nullptr,在这种情况下它们是通配符

参数:
  • svc_type (str) --

  • svc_name (str) --

返回类型:

Dict[str, Dict[str, Dict[str, Union[str, int]]]]

返回:

描述所请求计数器的字典列表

MgrModule.get_unlabeled_counter(svc_type, svc_name, path)

由插件调用,以获取特定服务上特定计数器的最新性能计数器数据。

参数:
  • svc_type (str) --

  • svc_name (str) --

  • path (str) -- 子系统和计数器名称的句点分隔连接,例如“mds.inodes”。

返回类型:

Dict[str, List[Tuple[float, int]]]

返回:

一个字典,将计数器名称映射到其值。每个值是一个由 (timestamp, value) 组成的二元组列表。如果没有数据,此列表可能为空。

MgrModule.get_latest_unlabeled_counter(svc_type, svc_name, path)

由插件调用以获取特定服务上特定计数器最新无标签性能计数器数据点。

参数:
  • svc_type (str) --

  • svc_name (str) --

  • path (str) -- 子系统和计数器名称的句点分隔连接,例如“mds.inodes”。

返回类型:

Dict[str, Union[Tuple[float, int], Tuple[float, int, int]]]

返回:

返回一个由 (timestamp, value) 组成的二元组列表或由 (timestamp, value, count) 组成的三元组。如果没有数据,此列表可能为空。

MgrModule.get_perf_schema(svc_type, svc_name)

由插件调用以获取性能计数器模式信息。svc_name 可以为 nullptr,svc_type 也可以为 nullptr,在这种情况下它们是通配符

参数:
  • svc_type (str) --

  • svc_name (str) --

返回类型:

Dict[str, Dict[str, Dict[str, Union[str, int]]]]

返回:

描述所请求计数器的字典列表

MgrModule.get_latest_counter(svc_type, svc_name, counter_name, sub_counter_name, labels)

由插件调用,以获取特定服务上特定计数器最新性能计数器数据点。

参数:
  • svc_type (str) --

  • svc_name (str) --

  • counter_name (str) -- 计数器的 key_name,例如“osd_scrub_sh_repl”

  • sub_counter_name (str) -- key_name 下存在的计数器,例如“successful_scrubs_elapsed”

  • labels (list[(str, str)]) -- 与计数器关联的标签,例如“[(“level”, “deep”), (“pooltype”, “ec”)]”

返回类型:

Dict[str, Union[Tuple[float, int], Tuple[float, int, int]]]

返回:

返回一个由 (timestamp, value) 组成的二元组列表或由 (timestamp, value, count) 组成的三元组。如果没有数据,此列表可能为空。

MgrModule.get_mgr_id()

检索当前正在执行此插件的管理器守护进程的名称(即活动管理器)。

返回类型:

str

返回:

str

MgrModule.get_daemon_health_metrics()

获取每个守护程序的健康指标列表。这包括 MON 和 OSD 守护程序中的 SLOW_OPS 健康指标,以及 OSD 的 PENDING_CREATING_PGS 健康指标。

返回类型:

Dict[str, List[Dict[str, Any]]]

公开健康检查

模块可以提出一流的 Ceph 健康检查,这些检查将在 ceph status 的输出和其他报告集群健康状况的地方报告。

如果您使用 set_health_checks 报告问题,请务必在问题解决后再次使用空字典调用它以清除您的健康检查。

MgrModule.set_health_checks(checks)

设置模块当前的健康检查映射。参数是检查名称到信息的字典,形式如下

{
  'CHECK_FOO': {
    'severity': 'warning',           # or 'error'
    'summary': 'summary string',
    'count': 4,                      # quantify badness
    'detail': [ 'list', 'of', 'detail', 'strings' ],
   },
  'CHECK_BAR': {
    'severity': 'error',
    'summary': 'bars are bad',
    'detail': [ 'too hard' ],
  },
}
参数:

list -- 健康检查字典的字典

返回类型:

None

如果监视器宕机了怎么办?

管理器守护进程从监视器获取大部分状态(例如集群映射)。如果监视器集群无法访问,则活动的管理器将继续运行,并保持其内存中最新的状态。

但是,如果您正在创建一个向用户显示集群状态的模块,那么您可能不希望通过显示过时的状态来误导他们。

要检查管理器守护进程当前是否与监视器集群有连接,请使用此函数

MgrModule.have_mon_connection()

检查此 ceph-mgr 守护进程是否与监视器有开放连接。如果没有,那么我们关于集群的信息很可能是过时的,并且/或者监视器集群已关闭。

返回类型:

bool

报告模块无法运行的情况

如果您的模块因任何原因(例如缺少依赖项)无法运行,则可以通过实现 can_run 函数来报告。

static MgrModule.can_run()

实现此函数以报告模块的依赖项是否满足。例如,如果模块需要导入特定的依赖项才能工作,则在文件作用域内对导入使用 try/except 块,然后在此处报告导入是否失败。

这将以阻塞方式从 C++ 代码调用,因此在此函数中不要执行任何可能阻塞的 I/O。

:return 一个由布尔值和解释字符串组成的 2 元组

返回类型:

Tuple[bool, str]

请注意,这仅在您的模块始终可以导入的情况下才能正常工作:如果您正在导入可能不存在的依赖项,请在 try/except 块中进行,以便即使依赖项不存在,您的模块也可以加载到足以使用 can_run 的程度。

发送命令

提供了一个非阻塞工具,用于向集群发送监视器命令。

MgrModule.send_command(result, svc_type, svc_id, command, tag, inbuf=None, *, one_shot=False)

由插件调用以向 mon 集群发送命令。

参数:
  • result (CommandResult) -- CommandResult 类的实例,与 MgrModule 在同一模块中定义。它充当完成并存储命令的输出。如果您想阻塞直到完成,请使用 CommandResult.wait()

  • svc_type (str) --

  • svc_id (str) --

  • command (str) -- JSON 序列化命令。它使用与 ceph 命令行相同的格式,即命令参数的字典,其中包含一个额外的 prefix 键,其中包含命令名称本身。请参阅 MonCommands.h 以获取可用命令及其预期参数。

  • tag (str) -- 用于非阻塞操作:当命令完成时,将触发 MgrModule 实例上的 notify() 回调,其中 notify_type 设置为“command”,notify_id 设置为命令的标签。

  • inbuf (str) -- 用于发送附加数据的输入缓冲区。

  • one_shot (bool) -- 一个仅限关键字的参数,用于在目标重置或拒绝重新连接时使命令以 EPIPE 终止

返回类型:

None

接收通知

当集群状态的关键部分(例如集群映射)更新时,管理器守护程序可以在活动模块上调用 notify 方法。

重要

只有明确声明对给定通知类型感兴趣的模块才会收到它。

要接收通知,模块必须声明 NOTIFY_TYPES 列表

NOTIFY_TYPES = [
    NotifyType.mon_map,
    NotifyType.osd_map,
    NotifyType.crush_map,
    ...
]

管理器将仅对该数组中列出的类型调用模块 notify 方法。在内部,它在调度通知之前检查 should_notify

通知不包含实际数据——它只是一个提示,告诉模块有变化。模块需要时使用 mgr.get(…) 检索新数据。

MgrModule.notify(notify_type, notify_id)

由 ceph-mgr 服务调用,通知 Python 插件新状态可用。此方法针对模块类 NOTIFY_TYPES 字符串列表中列出的 notify_types 调用。

参数:
  • notify_type (NotifyType) -- 指示通知类型字符串,例如 osd_map、mon_map、fs_map、mon_status、health、pg_summary、command、service_map

  • notify_id (str) -- 字符串(可能为空),可选指定要通知的实体。对于“command”通知,这设置为 send_command 的标签。

返回类型:

None

访问 RADOS 或 CephFS

如果您想使用 librados python API 访问存储在 Ceph 集群中的数据,您可以访问 MgrModule 实例的 rados 属性。这是一个 rados.Rados 实例,已为您使用 mgr 守护进程的现有 Ceph 上下文(C++ Ceph 代码的内部细节)构造。

始终使用此专门构造的 librados 实例,而不是手动构造一个。

同样,如果您使用 libcephfs 访问文件系统,那么请使用 libcephfs create_with_radosMgrModule.rados librados 实例构造它,从而继承正确的上下文。

请记住,您的模块可能在集群的其他部分宕机时运行:不要假设 librados 或 libcephfs 调用会立即返回——考虑是否使用超时或在集群其他部分未完全可用时阻塞。

实现备用模式

对于某些模块,在备用管理器守护程序和活动守护程序上运行都很有用。例如,HTTP 服务器可以有效地从备用管理器提供 HTTP 重定向响应,这样用户就可以将其浏览器指向任何管理器守护程序,而无需担心哪个是活动的。

备用管理器守护程序在每个模块中查找 StandbyModule 的子类。如果未找到该类,则在备用守护程序上根本不使用该模块。如果找到该类,则调用其 serve 方法。StandbyModule 的实现必须继承自 mgr_module.MgrStandbyModule

MgrStandbyModule 的接口与 MgrModule 相比受到很大限制——模块无法访问任何 Ceph 集群状态。serveshutdown 方法的使用方式与普通模块类相同。get_active_uri 方法使备用模块能够发现其活动对等方的地址以进行重定向。有关方法的完整列表,请参阅 Ceph 源代码中 MgrStandbyModule 的定义。

有关如何使用此接口的示例,请参阅 dashboard 模块的源代码。

模块间通信

模块可以调用其他模块的成员函数。

MgrModule.remote(module_name, method_name, *args, **kwargs)

调用另一个模块上的方法。所有参数以及另一个模块的返回值都必须可序列化。

限制:不要在被调用方法内导入任何模块。否则您将在 Python 2 中收到错误

RuntimeError('cannot unmarshal code objects in restricted execution mode',)
参数:
  • module_name (str) -- 其他模块的名称。如果模块未加载,则引发 ImportError 异常。

  • method_name (str) -- 方法名称。如果不存在,则引发 NameError 异常。

  • args (Any) -- 参数元组

  • kwargs (Any) -- 关键字参数字典

引发:
  • RuntimeError -- 方法中引发的任何错误都将转换为 RuntimeError

  • ImportError -- 没有此类模块

返回类型:

任意

请务必处理 ImportError,以应对所需模块未启用的情况。

如果远程方法引发 Python 异常,这将转换为调用端的 RuntimeError,其中消息字符串描述了最初抛出的异常。如果您的逻辑打算干净地处理某些错误,最好修改远程方法以返回错误值而不是引发异常。

撰写本文时,模块间调用是在没有复制或序列化的情况下实现的,因此当您返回一个 Python 对象时,您将该对象的引用返回给调用模块。建议不要依赖这种引用传递,因为将来实现可能会更改为序列化参数和返回值。

干净地关闭

如果模块实现了 serve() 方法,它也应该实现 shutdown() 方法以干净地关闭:否则行为不当的模块可能会阻止 ceph-mgr 的干净关闭。

限制

无法从模块的 __init__() 方法回调到 C++ 代码。例如,此时调用 self.get_module_option() 将导致 ceph-mgr 中断言失败。对于实现 serve() 方法的模块,通常在内部该方法中执行大多数初始化是有意义的。

调试

显然,我们总是可以使用日志工具来调试 ceph-mgr 模块。但我们中的一些人可能会怀念PDB和交互式 Python 解释器。是的,在开发 ceph-mgr 模块时,我们也可以拥有它们!ceph_mgr_repl.py 可以让您进入一个与 selftest 模块对话的交互式 shell。使用此工具,可以以与我们使用 Python 命令行解释器几乎相同的方式查看和修改 ceph-mgr 模块,并使用所有公开的工具。要使用 ceph_mgr_repl.py,我们需要

  1. 准备一个 Ceph 集群

  2. 启用 selftest 模块

  3. 设置必要的环境变量

  4. 启动工具

以下是一个示例会话,其中通过在提示符下输入 print(mgr.version) 查询 Ceph 版本。稍后导入 timeit 模块来测量 mgr.get_mgr_id() 的执行时间。

$ cd build
$ MDS=0 MGR=1 OSD=3 MON=1 ../src/vstart.sh -n -x
$ bin/ceph mgr module enable selftest
$ ../src/pybind/ceph_mgr_repl.py --show-env
   $ export PYTHONPATH=/home/me/ceph/src/pybind:/home/me/ceph/build/lib/cython_modules/lib.3:/home/me/ceph/src/python-common:$PYTHONPATH
   $ export LD_LIBRARY_PATH=/home/me/ceph/build/lib:$LD_LIBRARY_PATH
$ export PYTHONPATH=/home/me/ceph/src/pybind:/home/me/ceph/build/lib/cython_modules/lib.3:/home/me/ceph/src/python-common:$PYTHONPATH
$ export LD_LIBRARY_PATH=/home/me/ceph/build/lib:$LD_LIBRARY_PATH
$ ../src/pybind/ceph_mgr_repl.py
$ ../src/pybind/ceph_mgr_repl.py
Python 3.9.2 (default, Feb 28 2021, 17:03:44)
[GCC 10.2.1 20210110] on linux
Type "help", "copyright", "credits" or "license" for more information.
(MgrModuleInteractiveConsole)
[mgr self-test eval] >>> print(mgr.version)
ceph version Development (no_version) quincy (dev)
[mgr self-test eval] >>> from timeit import timeit
[mgr self-test eval] >>> timeit(mgr.get_mgr_id)
0.16303414600042743
[mgr self-test eval] >>>

如果您想使用此工具与 selftest 以外的 ceph-mgr 模块“对话”,您可以像将 mgr self-test eval 命令添加到 selftest 中一样,将命令添加到您要调试的模块中。或者我们可以通过将 eval() 方法提升为专门的 Mixin 类,并让您的 MgrModule 子类继承自它,从而简化此过程。并使用它定义一个命令。假设命令的前缀是 mgr my-module eval,您可以只输入

../src/pybind/ceph_mgr_repl.py --prefix "mgr my-module eval"

缺少什么吗?

ceph-mgr 的 Python 接口尚未最终确定。如果您有当前接口无法满足的需求,请在 ceph-devel 邮件列表中提出。虽然希望避免接口臃肿,但在有充分理由的情况下,将现有数据公开给 Python 代码通常并不困难。

由 Ceph 基金会为您呈现

Ceph 文档是由非营利性 Ceph 基金会 资助和托管的社区资源。如果您希望支持这项工作和我们的其他努力,请考虑 立即加入