【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.cc,http://ceph_fuse.cc和http://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
主要成员:
-
name:
-
type :参考 type_t {TYPE_UINT, TYPE_INT...}
-
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.
- 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