【ceph】ceph配置源码分析|common/config.*

ceph的配置项的构建思路

如果一个class中有成百上千个成员变量,有没有觉得当写类头文件都写到手痛,还要在构造函数里初始化它们,简直不敢再想下去了……

像下图这样

这只是头文件

还有实现的

接着还有get 和 set

然而,以上是最笨的方法~

那么问题来了,ceph的开发者是怎样做的呢?

利用c++的宏,在class的头文件定义成员变量前定义好要用的宏,如下

OPTION(name,ty,init)宏能根据ty,也就是成员变量的类型,在编译时生成成员变量,成员变量的最终定义是在common/config_opts.h下:

Class的构造函数初始化成员变量也是一样的手法:

只要改动common/config_opts.h下的OPTION语句就能实现增删成员变量了。

至于get 和 set 方法,用了一个巧妙的方法:直接对成员变量相对该class的偏移地址进行存取。

以下为例,如下图:

初始化全局数组config_optionsp时,每个config_option元素记录着md_config_t里的一个成员变量的名字,类型和偏移,如上图中的编号4小图。主要用的是stddef.h里的offsetof计算成员变量在class里的偏移值。

当调用get时进入到上图中小图1的函数,先找根据成员变量的名字找出config_option对象,再如小图2调用config_option的conf_ptr(小图3),其中md_conf_off是该成员变量在md_config_t类中的偏移量。这样就能直接存取成员变量,而且运行效率更高,同时又降低了开发的工作量,代码也更美观!

摘抄自:Ceph源码之 config https://blog.csdn.net/chunlu2438/article/details/100679794

代码

int main(int argc, const char **argv)

auto cct = global_init(&def_args, args,
             CEPH_ENTITY_TYPE_MON, CODE_ENVIRONMENT_DAEMON,
             flags, "mon_data");

void global_pre_init(std::vector < const char * > *alt_def_args,
             std::vector < const char* >& args,
             uint32_t module_type, code_environment_t code_env,
             int flags)

CephContext *cct = common_preinit(iparams, code_env, flags);

CephContext *cct = new CephContext(iparams.module_type, code_env, flags);

CephContext::CephContext(uint32_t module_type_,
                         enum code_environment_t code_env,
                         int init_flags_)
  : nref(1),
    _conf(new md_config_t(code_env == CODE_ENVIRONMENT_DAEMON)),
    _log(NULL),
{
}

 一,遍历ceph_options把option的name放入schema中。

二,把legacy_config_opts.h中的变量(展开)把名字和md_config_t::name结对生成map放入legacy_values中。

既:options.cc中的变量(名) 放入 schema。legacy_config_opts.h中的变量(名)放入 legacy_values。

ceph_options的来源是=build_options,build_options 就是把options.cc中的配置全部加入到 std::vector中返回给ceph_options, 见后面的代码

md_config_t::md_config_t(bool is_daemon)
{
 ……


  for (const auto &i : ceph_options) {
    if (schema.count(i.name)) {
      // We may be instantiated pre-logging so send 
      std::cerr << "Duplicate config key in schema: '" << i.name << "'"
                << std::endl;
      assert(false);
    }
    schema.insert({i.name, i});
  }

  // Populate list of legacy_values according to the OPTION() definitions
  // Note that this is just setting up our map of name->member ptr.  The
  // default values etc will get loaded in along with new-style data,
  // as all loads write to both the values map, and the legacy
  // members if present.
  legacy_values = {
#define OPTION(name, type) {std::string(STRINGIFY(name)), &md_config_t::name},
#define SAFE_OPTION(name, type) OPTION(name, type)
#include "common/legacy_config_opts.h"
#undef OPTION
#undef SAFE_OPTION
  };

  validate_schema();

……

}
 

static std::vector<Option> build_options()
{
  std::vector<Option> result = get_global_options();

  auto ingest = [&result](std::vector<Option>&& options, const char* svc) {
    for (const auto &o_in : options) {
      Option o(o_in);
      o.add_service(svc);
      result.push_back(o);
    }
  };

  ingest(get_rgw_options(), "rgw");
  ingest(get_rbd_options(), "rbd");
  ingest(get_rbd_mirror_options(), "rbd-mirror");
  ingest(get_mds_options(), "mds");
  ingest(get_mds_client_options(), "mds_client");

  return result;
}

const std::vector<Option> ceph_options = build_options();
 

  validate_schema();   

1、检查……   

2、检查确保legacy_config_opts.h中定义的变量,在options.cc中都有定义。


/**
 * Sanity check schema.  Assert out on failures, to ensure any bad changes
 * cannot possibly pass any testing and make it into a release.
 */
void md_config_t::validate_schema()
{
  for (const auto &i : schema) {
    const auto &opt = i.second;
    for (const auto &see_also_key : opt.see_also) {
      if (schema.count(see_also_key) == 0) {
        std::cerr << "Non-existent see-also key '" << see_also_key
                  << "' on option '" << opt.name << "'" << std::endl;
        assert(false);
      }
    }
  }

  for (const auto &i : legacy_values) {
    if (schema.count(i.first) == 0) {
      std::cerr << "Schema is missing legacy field '" << i.first << "'"
                << std::endl;
      assert(false);
    }
  }
}

src\common\options.cc里面还定义(实例化)一些变量,放入get_global_options、get_rgw_options、get_rbd_options、get_rbd_options……里:

std::vector<Option> get_global_options() {
  return std::vector<Option>({
    Option("host", Option::TYPE_STR, Option::LEVEL_BASIC)
    .set_description("local hostname")
    .set_long_description("if blank, ceph assumes the short hostname (hostname -s)")
    .add_service("common")
    .add_tag("network"),

    Option("fsid", Option::TYPE_UUID, Option::LEVEL_BASIC)
    .set_description("cluster fsid (uuid)")
    .add_service("common")
    .add_tag("service"),

    Option("public_addr", Option::TYPE_ADDR, Option::LEVEL_BASIC)
    .set_description("public-facing address to bind to")
    .add_service({"mon", "mds", "osd", "mgr"}),

    Option("public_bind_addr", Option::TYPE_ADDR, Option::LEVEL_ADVANCED)
    .set_default(entity_addr_t())
    .add_service("mon")
    .set_description(""),
……
}
std::vector<Option> get_rgw_options() {
  return std::vector<Option>({
    Option("rgw_acl_grants_max_num", Option::TYPE_INT, Option::LEVEL_ADVANCED)
    .set_default(100)
    .set_description("Max number of ACL grants in a single request"),

    Option("rgw_max_chunk_size", Option::TYPE_INT, Option::LEVEL_ADVANCED)
    .set_default(4_M)
    .set_description("Set RGW max chunk size")
    .set_long_description(
        "The chunk size is the size of RADOS I/O requests that RGW sends when accessing "
        "data objects. RGW read and write operation will never request more than this amount "
        "in a single request. This also defines the rgw object head size, as head operations "
        "need to be atomic, and anything larger than this would require more than a single "
        "operation."),

    Option("rgw_put_obj_min_window_size", Option::TYPE_INT, Option::LEVEL_ADVANCED)
    .set_default(16_M)
    .set_description("The minimum RADOS write window size (in bytes).")
    .set_long_description(
        "The window size determines the total concurrent RADOS writes of a single rgw object. "
        "When writing an object RGW will send multiple chunks to RADOS. The total size of the "
        "writes does not exceed the window size. The window size can be automatically "
        "in order to better utilize the pipe.")
    .add_see_also({"rgw_put_obj_max_window_size", "rgw_max_chunk_size"}),
……
}

然后都放入ceph_options 中

static std::vector<Option> build_options()
{
  std::vector<Option> result = get_global_options();

  auto ingest = [&result](std::vector<Option>&& options, const char* svc) {
    for (const auto &o_in : options) {
      Option o(o_in);
      o.add_service(svc);
      result.push_back(o);
    }
  };

  ingest(get_rgw_options(), "rgw");
  ingest(get_rbd_options(), "rbd");
  ingest(get_rbd_mirror_options(), "rbd-mirror");
  ingest(get_mds_options(), "mds");
  ingest(get_mds_client_options(), "mds_client");

  return result;
}

const std::vector<Option> ceph_options = build_options();

然后在 md_config_t中,把options.cc中定义/实例化的option的名字放入schema: schema.insert({i.name, i});

md_config_t::md_config_t(bool is_daemon)
  : cluster(""),
  lock("md_config_t", true, false)
{
  init_subsys();

  // Load the compile-time list of Option into
  // a map so that we can resolve keys quickly.
  for (const auto &i : ceph_options) {
    if (schema.count(i.name)) {
      // We may be instantiated pre-logging so send 
      std::cerr << "Duplicate config key in schema: '" << i.name << "'"
                << std::endl;
      assert(false);
    }
    schema.insert({i.name, i});
  }
……
}

 src\common\legacy_config_opts.h里面宏定义自己的变量:

/* note: no header guard */
OPTION(host, OPT_STR) // "" means that ceph will use short hostname
OPTION(public_addr, OPT_ADDR)
OPTION(public_bind_addr, OPT_ADDR)
OPTION(cluster_addr, OPT_ADDR)
OPTION(public_network, OPT_STR)
OPTION(cluster_network, OPT_STR)
OPTION(lockdep, OPT_BOOL)
OPTION(lockdep_force_backtrace, OPT_BOOL) // always gather current backtrace at every lock
OPTION(run_dir, OPT_STR)       // the "/var/run/ceph" dir, created on daemon startup
OPTION(admin_socket, OPT_STR) // default changed by common_preinit()
OPTION(admin_socket_mode, OPT_STR) // permission bits to set for admin socket file, e.g., "0775", "0755"
……

接着src\common\config.cc里面把legacy_config_opts.h里面宏定义自己的变量展开到 legacy_values里面:

#define _STR(x) #x
#define STRINGIFY(x) _STR(x)

md_config_t::md_config_t(bool is_daemon){
……

legacy_values = {
#define OPTION(name, type) \
    {std::string(STRINGIFY(name)), &md_config_t::name},
#define SAFE_OPTION(name, type) OPTION(name, type)
#include "common/legacy_config_opts.h"
#undef OPTION
#undef SAFE_OPTION
  };
……

}

ceph中的可配置参数(日志级别,缓存设置等等)都定义在http://options.cc中,可以通过ceph daemon命令来查看或者修改这些参数配置。

ceph daemon *** config show

例如:"log_file"参数可以配置日志文件的路径

Option("log_file", Option::TYPE_STR, Option::LEVEL_BASIC)
    .set_default("")
    .set_daemon_default("/var/log/ceph/$cluster-$name.log")
    .set_description("path to log file")
    .add_see_also({"log_to_stderr", "err_to_stderr", "log_to_syslog", "err_to_syslog"}),

参数是以Option结构体存在,Option结构体的定义如下

struct Option {
  enum type_t {                  // 参数值的类型         
    TYPE_UINT,
    TYPE_INT,
    TYPE_STR,
    TYPE_FLOAT,
    TYPE_BOOL,
    TYPE_ADDR,
    TYPE_UUID,
  };
  enum level_t {
    LEVEL_BASIC,
    LEVEL_ADVANCED,
    LEVEL_DEV,
  };
  using value_t = boost::variant<boost::blank, std::string, uint64_t, int64_t, double, bool, entity_addr_t, uuid_d>;
  const std::string name;                   // 参数名
  const type_t type;                        // 参数类型
  const level_t level;                      // 参数级别
  std::string desc;                         // 该参数的含义
  std::string long_desc;
  value_t value;                            // 参数值
  value_t daemon_value;                     // 有daemon的参数值
  std::vector<const char*> services;        // services即服务类型,比如"mds"
  std::vector<const char*> see_also;            // 该参数对应的类似的参数
  std::vector<const char*> enum_allowed;
  bool safe;
  ...  
}

http://options.cc中,定义的参数分为几类

std::vector<Option> get_global_options() { ... }

std::vector<Option> get_rgw_options() { ... }

static std::vector<Option> get_rbd_options() { ... } 

static std::vector<Option> get_rbd_mirror_options() { ... }

std::vector<Option> get_mds_options() { ... }

std::vector<Option> get_mds_client_options() { ... }

从函数名可以看出来:get_global_options函数中的参数是global类型的,即基本所有的模块都可以使用这些参数。get_rgw_options函数中的参数是给rgw模块使用。get_rbd_options函数和get_rbd_mirror_options函数中的参数是给rbd相关模块使用。get_mds_options函数中的参数是给mds模块使用。get_mds_client_options函数中的参数是给mds client模块使用。

全局变量ceph_options

ceph_options包含了所有的参数,

const std::vector<Option> ceph_options = build_options();

在build_options中将所有的Option类放入ceph_options这个vector里面。

static std::vector<Option> build_options()
{
  std::vector<Option> result = get_global_options();
  auto ingest = [&result](std::vector<Option>&& options, const char* svc) {
    for (auto &o : options) {
      o.add_service(svc);
      result.push_back(std::move(o));
    }
  };
  ingest(get_rgw_options(), "rgw");
  ingest(get_rbd_options(), "rbd");
  ingest(get_rbd_mirror_options(), "rbd-mirror");
  ingest(get_mds_options(), "mds");
  ingest(get_mds_client_options(), "mds_client");
  return result;
}

ceph_options = { Option{"log_file"}, Option("***")}

md_config_t

md_config_t结构体表示当前的Ceph配置,ceph_options中的参数都会存在这里。在md_config_t中有特殊的成员变量定义。

#define OPTION_OPT_INT(name) int64_t name;
#define OPTION_OPT_LONGLONG(name) int64_t name;
#define OPTION_OPT_STR(name) std::string name;
#define OPTION_OPT_DOUBLE(name) double name;
#define OPTION_OPT_FLOAT(name) double name;
#define OPTION_OPT_BOOL(name) bool name;
#define OPTION_OPT_ADDR(name) entity_addr_t name;
#define OPTION_OPT_U32(name) uint64_t name;
#define OPTION_OPT_U64(name) uint64_t name;
#define OPTION_OPT_UUID(name) uuid_d name;
#define OPTION(name, ty) \
  public:                      \
    OPTION_##ty(name)          
#define SAFE_OPTION(name, ty) \
  protected:                        \
    OPTION_##ty(name)               
#include "common/legacy_config_opts.h"
#undef OPTION_OPT_INT
#undef OPTION_OPT_LONGLONG
#undef OPTION_OPT_STR
#undef OPTION_OPT_DOUBLE
#undef OPTION_OPT_FLOAT
#undef OPTION_OPT_BOOL
#undef OPTION_OPT_ADDR
#undef OPTION_OPT_U32
#undef OPTION_OPT_U64
#undef OPTION_OPT_UUID
#undef OPTION
#undef SAFE_OPTION

common/legacy_config_opts.h中存的变量和common/http://options.cc一一对应,代码如下

/* note: no header guard */
OPTION(host, OPT_STR) // "" means that ceph will use short hostname
OPTION(public_addr, OPT_ADDR)
OPTION(public_bind_addr, OPT_ADDR)
OPTION(cluster_addr, OPT_ADDR)
OPTION(public_network, OPT_STR)
OPTION(cluster_network, OPT_STR)
OPTION(lockdep, OPT_BOOL)
OPTION(lockdep_force_backtrace, OPT_BOOL) // always gather current backtrace at every lock
OPTION(run_dir, OPT_STR)       // the "/var/run/ceph" dir, created on daemon startup
OPTION(admin_socket, OPT_STR) // default changed by common_preinit()
OPTION(admin_socket_mode, OPT_STR) // permission bits to set for admin socket file, e.g., "0775", "0755"
......

最终md_config_t结构体定义如下

struct md_config_t {
   ...
   typedef std::multimap <std::string, md_config_obs_t*> obs_map_t;
   std::map<std::string, md_config_t::member_ptr_t> legacy_values;
   std::map<std::string, const Option&> schema;
   std::map<std::string, Option::value_t> values;
private:
  ConfFile cf;
public:
  std::deque<std::string> parse_errors;
private:
  bool safe_to_start_threads = false;
  obs_map_t observers;
  changed_set_t changed;
public:
  ceph::logging::SubsystemMap subsys;
  EntityName name;
  string data_dir_option;  ///< data_dir config option, if any
  string cluster;           // cluster = "ceph"

// 下面的成员来自common/legacy_config_opts.h中
public:
   std::string host;
   entity_addr_t public_addr;
   entity_addr_t public_bind_addr;
   entity_addr_t cluster_addr;
   std::string public_network;
   std::string cluster_network;
   bool lockdep;
   bool lockdep_force_backtrace;
   std::string run_dir;
   std::string admin_socket;
   std::string admin_socket_mode;
   std::string log_file; 
   ...
protected:
   std::string ms_type; 
   std::string plugin_dir;
   ...

}

其中schema保存ceph_options中的所有Option参数,即

schema = { <"host", Options("host")>, ..., <"log_file", Option("log_file")>, ...}

legacy_values将ceph_options中的Option的name与对应的md_config_t中的成员指针作为key-value保存,即

legacy_values = {<"host", &md_config_t::host>, ... , <"log_file", &md_config_t::log_file>, ...}

values将ceph_options中的Option的name和value作为key-value保存。

libcephfs中:values = {<"host", "">, ..., <"log_file", "">, ...}

ceph-mds中:values = {<"host", "">, ..., <"log_file", "/var/log/ceph/$cluster-$name.log">, ...}

subsys用来存日志项,即

subsys = ceph::logging::SubsystemMap( m_subsys[ceph_subsys_ == 0] = Subsystem<name = "none", log_level = 0, gather_level = 5>, ... , m_subsys[ceph_subsys_crush == 3] = Subsystem<name = "crush", log_level = 1, gather_level = 1>, m_subsys[ceph_subsys_mds == 4] = Subsystem<name = "mds", log_level = 1, gather_level = 5>, ... }。

g_ceph_context和g_conf全局变量

一个模块的进程会有多个线程,比如ceph-mds,进程中有些内容需要整个进程中的所有线程都可以访问,比如参数配置和以及上下文内容,所以就有两个全局变量g_conf和g_ceph_context,在src/global/http://global_context.cc中定义如下

CephContext *g_ceph_context = NULL;
md_config_t *g_conf = NULL;

md_config_t 实例对象指针又作为CephContext 的成员,

class CephContext {
...
public:
  md_config_t *_conf;
...
}

每个模块启动时,都需要实例化CephContext,就不需要单独去实例化md_config_t。CephContext实例化函数流程如下:

在md_config_t的构造函数的入参为is_daemon,它是用来判断该模块是单独起一个守护线程,或者由别的线程去调用对外接口,不单独起线程。构造函数如下

md_config_t::md_config_t(bool is_daemon){ ... }

md_config_t的实例化处如下

 _conf(new md_config_t(code_env == CODE_ENVIRONMENT_DAEMON))

即判断code_env == CODE_ENVIRONMENT_DAEMON,在各个模块的main函数中有code_env的入参。即http://ceph_mds.cchttp://ceph_fuse.cchttp://ceph_mon.cc等中的md_config_t中is_daemon都是true,libcephfs的md_config_t中is_daemon是false。

参数配置方式

参数配置方式有两种:永久和临时。永久方式,就是在配置文件(如ceph.conf)中添加该参数的配置,重启进程后,参数就生效了。临时方式,就是通过ceph daemon命令设置内存中的参数。

修改ceph.conf中参数后怎么生效, 比如在ceph.conf中配置了

admin_socket = $rundir/$cluster-$id.$pid.asok

直接看代码,从http://ceph_mon.cc中开始看

auto cct = global_init(&def_args, args, CEPH_ENTITY_TYPE_MON, CODE_ENVIRONMENT_DAEMON,
			 flags, "mon_data")

global_init

boost::intrusive_ptr<CephContext> global_init(std::vector < const char * > *alt_def_args, std::vector < const char* >& args,
	    uint32_t module_type, code_environment_t code_env, int flags,
	    const char *data_dir_option, bool run_pre_init)
{ // module_type = CEPH_ENTITY_TYPE_MON, code_env = CODE_ENVIRONMENT_DAEMON, flags = 0
  // *data_dir_option = "mon_data", run_pre_init = true
  static bool first_run = true;
  if (run_pre_init) {
    global_pre_init(alt_def_args, args, module_type, code_env, flags);
  } else 
  ...
}

global_pre_init是关键函数:

void global_pre_init(std::vector < const char * > *alt_def_args, std::vector < const char* >& args,
		     uint32_t module_type, code_environment_t code_env, int flags)
{ // module_type = CEPH_ENTITY_TYPE_MON, code_env = CODE_ENVIRONMENT_DAEMON, flags = 0
  std::string conf_file_list;            // ceph.conf的路径
  std::string cluster = "";
  CephInitParameters iparams = ceph_argparse_early_args(args, module_type, &cluster, &conf_file_list);
  // common_preinit函数主要是去实例化CephContext
  CephContext *cct = common_preinit(iparams, code_env, flags);
  cct->_conf->cluster = cluster;         // cct->_conf->cluster = "ceph"
  global_init_set_globals(cct);
  md_config_t *conf = cct->_conf;
  if (alt_def_args)
    conf->parse_argv(*alt_def_args);  // alternative default args
  int ret = conf->parse_config_files(c_str_or_null(conf_file_list), &cerr, flags);
  ...
}

调用ceph_argparse_early_args来解析进程启动时设置的参数,比如ceph-mon启动时设置的参数如下

/usr/bin/ceph-mon -f --cluster ceph --id node1 --setuser ceph --setgroup ceph

ceph_argparse_early_args函数如下,

CephInitParameters ceph_argparse_early_args (std::vector<const char*>& args, uint32_t module_type,
	   std::string *cluster, std::string *conf_file_list)
{ //module_type = CEPH_ENTITY_TYPE_MON, *cluster = "", *conf_file_list = ""
  CephInitParameters iparams(module_type);
  // iparams里面的内容为class CephInitParameters { module_type = CEPH_ENTITY_TYPE_MON,
  //   name: struct EntityName { type = CEPH_ENTITY_TYPE_MON, id = "admin", type_id = "mon.admin" }}
  std::string val;
  vector<const char *> orig_args = args;
  for (std::vector<const char*>::iterator i = args.begin(); i != args.end(); ) {
    ...
   // 解析--conf,但是执行ceph-mon命令时,并未指定--conf,所以val为空字符串。
    else if (ceph_argparse_witharg(args, i, &val, "--conf", "-c", (char*)NULL)) {
      *conf_file_list = val;       
    }
    else if (ceph_argparse_witharg(args, i, &val, "--cluster", (char*)NULL)) {
      *cluster = val;         // *cluster = ceph
    }
    else if ((module_type != CEPH_ENTITY_TYPE_CLIENT) &&
	     (ceph_argparse_witharg(args, i, &val, "-i", (char*)NULL))) {
      iparams.name.set_id(val); 
    }
    else if (ceph_argparse_witharg(args, i, &val, "--id", "--user", (char*)NULL)) {
      iparams.name.set_id(val); // iparams.name中{_id = "node1", type_id = "mon.node1"}
    }
    ...
  return iparams;
}

common_preinit

CephContext *common_preinit(const CephInitParameters &iparams, enum code_environment_t code_env, int flags)
{ // code_env = CODE_ENVIRONMENT_DAEMON, flags = 0
  // 实例CephContext
  CephContext *cct = new CephContext(iparams.module_type, code_env, flags);
  md_config_t *conf = cct->_conf;
  conf->name = iparams.name;
  ...
  return cct;
}

最重要的就是parse_config_files

int md_config_t::parse_config_files(const char *conf_files, std::ostream *warnings, int flags)
{ // conf_files = NULL
  ...
  if (!conf_files) {
    const char *c = getenv("CEPH_CONF");       // c = NULL
    ...
    else {
      ...
      // CEPH_CONF_FILE_DEFAULT = "$data_dir/config, /etc/ceph/$cluster.conf, ~/.ceph/$cluster.conf, $cluster.conf"
      conf_files = CEPH_CONF_FILE_DEFAULT; // CEPH_CONF_FILE_DEFAULT就是配置文件的路径
    }
  }
  std::list<std::string> cfl;
  get_str_list(conf_files, cfl);     // 将conf_files中的字符串切割放在cfl中
  auto p = cfl.begin();
  while (p != cfl.end()) { ... }
  return parse_config_files_impl(cfl, warnings);
}

md_config_t::parse_config_files_impl

int md_config_t::parse_config_files_impl(const std::list<std::string> &conf_files,std::ostream *warnings)
{
  list<string>::const_iterator c;
  // 遍历conf_files,替换"$"后面的字符串,比如将$cluster替换成ceph
  for (c = conf_files.begin(); c != conf_files.end(); ++c) {
    cf.clear();
    string fn = *c;
    // 替换"$"后面的字符串
    expand_meta(fn, warnings);
    // 将ceph.conf(如/etc/ceph/ceph.conf)中的内容读上来保存在cf中
    int ret = cf.parse_file(fn.c_str(), &parse_errors, warnings);
    if (ret == 0) break;
  }

  std::vector <std::string> my_sections;
  // 获取要解析的部分,比如mon:<"mon.node1", "mon", global">, client: <"client.admin", "admin", "global">
  // 即ceph.conf中my_sections对应的部分才会解析
  _get_my_sections(my_sections);
  for (const auto &i : schema) { ... }
  //遍历subsys, 配置debug_**之类的
  for (size_t o = 0; o < subsys.get_num(); o++) { ... }
  ...
  return 0;
}

md_config_t::expand_meta函数用来替换"$"后面的字符串,比如admin_socket = $rundir/$cluster-$id.$pid.asok,调用expand_meta函数后admin_socket = /var/run/ceph/ceph-mon.node1.2093242.asok

摘抄自:ceph:可配置参数 https://zhuanlan.zhihu.com/p/110079635

1 介绍

    ceph里面的配置文件代码比较繁琐,分析清楚配置代码对未来排查问题大有益处。

2 启动分析

2.1 入口

    无论是osd、mgr还是mon等程序都需要加载配置,都有统一的入口,例如ceph-osd.cc在main函数中调用global_init--->global_pre_init函数进行配置的加载。

2.2 env_to_vec分析

ceph_mon.cc

int main(int argc, const char **argv)
{
  int err;

  bool mkfs = false;
  bool compact = false;
  bool force_sync = false;
  bool yes_really = false;
  std::string osdmapfn, inject_monmap, extract_monmap;

  vector<const char*> args;
  argv_to_vec(argc, argv, args);
  env_to_vec(args);

……
}

    split_dashdash把args根据"--"拆分成两段(注意:--不同于--param),前段为类型为vector的options, 后段为类型为vector的arguments。

(/opt/h3c/bin/ceph-mon --cluster ceph --mkfs -i rdma58 --keyring)

void env_to_vec(std::vector<const char*>& args, const char *name)
{
  if (!name)
    name = "CEPH_ARGS";

  bool dashdash = false;
  std::vector<const char*> options;
  std::vector<const char*> arguments;
  if (split_dashdash(args, options, arguments))
    dashdash = true;

  std::vector<const char*> env_options;
  std::vector<const char*> env_arguments;
  std::vector<const char*> env;

……

  g_str_vec_lock.lock();
  if (g_str_vec.empty()) {
    char *p = getenv(name);
    if (!p) {
      g_str_vec_lock.unlock();
      return;
    }
    get_str_vec(p, " ", g_str_vec);
  }

}


    get_str_vec。把环境变量值取出来:默认的环境变量是"CEPH_ARGS",可以通过参数来指定名字。以空格“ ” 为间隔,把环境变量拆分为一个个字符串到g_str_vec中。
    g_str_vec腾挪到env 的vector<const char*>中,同args一样以“--”进行拆分。
    分别聚合env和args的options和arguments到args中, 还是以“--”来间隔,无论是options还是arguments,env的参数都在前。

2.3 ceph_argparse_early_args

void global_pre_init(std::vector < const char * > *alt_def_args,
             std::vector < const char* >& args,
             uint32_t module_type, code_environment_t code_env,
             int flags)
{
  std::string conf_file_list;
  std::string cluster = "";
  CephInitParameters iparams = ceph_argparse_early_args(args, module_type,
                            &cluster, &conf_file_list);
  CephContext *cct = common_preinit(iparams, code_env, flags);
  cct->_conf->cluster = cluster;

……

}

    解析options来获取对应的值:
         a) -v 展示version
         b) -c 配置文件
         c) --cluster cluster
         d) -i 非client实体的id (client程序通过-i指定id会无效
         e) --id / --user 设置实体的id,会覆盖-i的选项
         f) --name/-n 通过名字来解析设置实体的id, 会覆盖-i 以及 --id/--user
         g)--show_args 显示参数 ,但是不会exit

3 配置类分析

3.1 ConfigProxy

    配置的代理类,实际是封装了:
         a) ConfigValues values;
         b) ObserverMgr<md_config_obs_t> obs_mgr;
         c) md_config_t config;

3.2 md_config_t

    主要包含了配置的元数据, 主要成员有:
         1) std::map<std::string, const Option&> schema;

        a) 构造函数中通过全局变量ceph_options=build_options()构造,在这个函数里面可以看到很多默认的值

         2) std::map<std::string, member_ptr_t> legacy_values;

3.3 Option

主要成员:

  1. name:

  2. type :参考 type_t {TYPE_UINT, TYPE_INT...}

  3. level: 参考level_t {basic, advanced, dev}

    3.1 basic: for users, configures some externally visible functional aspect

    3.2 advanced: for users, configures some internal behaviour

    3.3 Dev: not for users. May be dangerous, may not be documented.

    1. value_t value; value_t daemon_value;

3.4 ConfigValues

主要成员:

using values_t = std::map<std::string, map<int32_t,Option::value_t>>; // <name, <level, value_t>
values_t values;

3.5 类关系

通过ConfigProxy提供对外的接口:

1、如果是set类接口:先通过md_config_t获取option,通过option获取name、类型、默认值、校验等信息,解析value,最后设置到ConfigValues中。

2、如果是get类接口:

    2.1、流程也是从md_config_t到ConfigValues, 如果从ConfigValues不能获取到,则获取Option的默认值

    2.2、md_config_t::expand_meta 解析${var}到实际的值


作者:小跑001
链接:https://www.jianshu.com/p/fbfacbff1edf
 

读取和解析配置文件ceph.conf的代码

ceph_mon.cc

int main(int argc, const char **argv)

--->auto cct = global_init(&def_args, args,……)   

------>global_pre_init(alt_def_args, args, module_type, code_env, flags);

--------->ret = conf->parse_config_files(c_str_or_null(conf_file_list),

------------>parse_config_files_impl(cfl, warnings);

---------------->ret = _get_val_from_conf_file(my_sections, opt.name, val, false);

---------------->r = set_val_impl(val, opt, &error_message);

-------------------->values[opt.name] = new_value;

 auto legacy_ptr_iter = legacy_values.find(std::string(opt.name));
  if (legacy_ptr_iter != legacy_values.end()) {
    update_legacy_val(opt, legacy_ptr_iter->second);
  }

conf_file_list 由命令传入--conf 或为null,为null则读取环境变量CEPH_CONF定义路径下的ceph.conf:const char *c = getenv("CEPH_CONF");

int md_config_t::parse_config_files(const char *conf_files,
                    std::ostream *warnings,
                    int flags)
{

  if (!conf_files) {
    const char *c = getenv("CEPH_CONF");
    if (c) {
      conf_files = c;
    }
    else {
      if (flags & CINIT_FLAG_NO_DEFAULT_CONFIG_FILE)
    return 0;

// CEPH_CONF_FILE_DEFAULT = "$data_dir/config, /etc/ceph/$cluster.conf, ~/.ceph/$cluster.conf, $cluster.conf"
      conf_files = CEPH_CONF_FILE_DEFAULT; // CEPH_CONF_FILE_DEFAULT就是配置文件的路径

    }
  }



  std::list<std::string> cfl;
  get_str_list(conf_files, cfl);

 CEPH_CONF可以以;或,分割多个配置文件,get_str_list读取处理把多个配置文件放入litst cfl中:

   std::list<std::string> cfl;
  get_str_list(conf_files, cfl);

 cfl里面装的 使用变量定义的配置文件路径 翻译成真实路径?

  auto p = cfl.begin();
  while (p != cfl.end()) {
    // expand $data_dir?
    string &s = *p;
    if (s.find("$data_dir") != string::npos) {
      if (data_dir_option.length()) {
    list<const Option *> stack;
    expand_meta(s, NULL, stack, warnings);
    p++;
      } else {
    cfl.erase(p++);  // ignore this item
      }
    } else {
      ++p;
    }
  }

解析cfl里装的配置文件 

 return parse_config_files_impl(cfl, warnings);

 md_config_t::parse_config_files_impl

int md_config_t::parse_config_files_impl(const std::list<std::string> &conf_files,std::ostream *warnings)
{
  list<string>::const_iterator c;
  // 遍历conf_files,替换"$"后面的字符串,比如将$cluster替换成ceph
  for (c = conf_files.begin(); c != conf_files.end(); ++c) {
    cf.clear();
    string fn = *c;
    // 替换"$"后面的字符串
    expand_meta(fn, warnings);
    // 将ceph.conf(如/etc/ceph/ceph.conf)中的内容读上来保存在cf中
    int ret = cf.parse_file(fn.c_str(), &parse_errors, warnings);
    if (ret == 0) break;
  }

  std::vector <std::string> my_sections;
  // 获取要解析的部分,比如mon:<"mon.node1", "mon", global">, client: <"client.admin", "admin", "global">
  // 即ceph.conf中my_sections对应的部分才会解析
  _get_my_sections(my_sections);
  for (const auto &i : schema) { ... }
  //遍历subsys, 配置debug_**之类的
  for (size_t o = 0; o < subsys.get_num(); o++) { ... }
  ...
  return 0;
}

md_config_t::expand_meta函数用来替换"$"后面的字符串,比如admin_socket = $rundir/$cluster-$id.$pid.asok,调用expand_meta函数后admin_socket = /var/run/ceph/ceph-mon.node1.2093242.asok

posted on 2022-10-04 01:21  bdy  阅读(152)  评论(0编辑  收藏  举报

导航