runsisi's

technical notes

warning line abc 'xxx' in section 'global' redefined

2019-01-15 runsisi#debug#ceph

故障现象

~$ rbd ls
warning: line 23: 'debug_asok_assert_abort' in section 'global' redefined
~$ ceph daemon osd.0 help
Can't get admin socket path: unable to get conf option admin_socket for osd.0: warning: line 23: 'debug_asok_assert_abort' in section 'global' redefined

代码分析

参考如下代码,由于 conf->parse_config_files 传入的是 std::cerr,所以只要配置文件中同一节定义了同样的配置项,就肯定会在终端打印 warning: xxx redefined 这样的告警:

global_pre_init
  int ret = conf->parse_config_files(c_str_or_null(conf_file_list), &cerr, flags);
    md_config_t::parse_config_files_impl
      ConfFile::parse_file
        load_from_buffer
          if (cline->key.length() && warnings)
            *warnings << "warning: line " << line_no << ": '" << cline->key
                      << cur_section->first << "' redefined " << std::endl;

同时注意到代码中有如下的注释:

// replace an existing key/line in this section, so that
//  [mysection]
//    foo = 1
//    foo = 2
// will result in foo = 2.

因此理论上重复定义的配置项只会使用最后定义的那一个配置项的值,只要配置的值没有问题,重复配置本身并不会给软件运行带来什么影响。但是我们看到 ceph daemon 执行的时候实际上是失败了的,因此问题并不是像上面想像的那么简单,进一步分析 ceph 脚本的代码可知:

def ceph_conf(parsed_args, field, name):
    args = ['ceph-conf']
    args.extend(['--show-config-value', field])
    p = subprocess.Popen(
        args,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE)
    outdata, errdata = p.communicate()
    if len(errdata):
        raise RuntimeError('unable to get conf option %s for %s: %s' % (field, name, errdata))
    return outdata.rstrip()


def maybe_daemon_command(parsed_args, childargs):
    if parsed_args.admin_socket:
        sockpath = parsed_args.admin_socket
    elif len(childargs) > 0 and childargs[0] in ["daemon", "daemonperf"]:
        try:
            sockpath = ceph_conf(parsed_args, 'admin_socket', childargs[1])
        except Exception as e:
            print('Can\'t get admin socket path: ' + str(e), file=sys.stderr)
            return True, errno.EINVAL

当使用 ceph daemon 或者 ceph daemonperf 命令时,如果不显式指定 admin socket 的路径,ceph 脚本会尝试通过运行 ceph-conf 命令行工具得到 admin socket 的路径,但从上面的 global_pre_init 的代码可以看到 ceph-conf 在运行时会将告警打印到标准错误流中,此时 Python 代码中的 ceph_conf 函数将抛出异常,从而导致命令执行失败。

至于为何 ceph -s 或者自己写的基于 rados/rbd API 写的程序不报这个错,那是因为下面的代码的原因:

extern "C" int rados_conf_read_file(rados_t cluster, const char *path_list)
{
  librados::RadosClient *client = (librados::RadosClient *)cluster;
  md_config_t *conf = client->cct->_conf;
  ostringstream warnings;
  int ret = conf->parse_config_files(path_list, &warnings, 0);
  if (ret) {
    if (warnings.tellp() > 0)
      lderr(client->cct) << warnings.str() << dendl;
    return ret;
  }
  return 0;
}

由于重复的配置项并不是严格意义上的错误,因此 conf->parse_config_files 的返回值并不会返回真的错误,因此也就不可能在标准错误流中打印出来错误了。