Ceph RGW路由模块分析

做一个web服务必不可少的就是实现实现路由功能,也就是说将URL分配到对应的处理程序。常见的做法是实现一个处理器(handler)并为其绑定一个请求路径(例如:/aa/bb/cc),当客户端向指定路径发起请求的时候,web服务就会运行那个handler响应客户端的请求。为了处理客户端发过来的不同请求方法、不同参数、不同请求路径的请求,web服务程序需要实现很多功能复杂的handler。

Ceph RGW可以对外通过S3服务的形式提供访问存储资源的功能,S3也是一种基于HTTP的RESTful API,除了可以对存储空间(bucket)和对象(object)进行操作以外,RGW还提供了admin(相当于radosgw-admin的REST接口)、sts(Secure Token Service)、iam(Identity and Access Management)等服务,这篇文章研究是RGW的路由模块是如何实现这些服务的handler并处理URL的分配

之所以研究这个问题,是因为最近一年的工作都在魔改Ceph RGW的代码,实现一个我们自己的对象存储网关。一开始我的工作是实现一些外部代码以接入我们已有的服务或者新功能,后来开始上手修改RGW的内部功能,比如Bucket的创建、admin REST接口。

作为一个从零开始接触改造Ceph RGW代码的码农,在这里我通过一个改造接口的例子来分析RGW的路由模块。

接口说明

本文以RGW admin模块的查询用户信息的REST接口为例,通过我改造该接口的过程来分析说明RGW的其他服务的handler是如何实现的,每个服务的请求URL又是如何绑定到指定的handler。

先说明一下我对这个接口改造的目的,我们魔改Ceph RGW是为了将Ceph对象存储的元数据(对象名、大小,桶名、桶属性,用户数据等)从Rados中剖离出来单独存储到数据库中,这样我们就可以优化RGW对元数据的访问元数据性能(比如可以快速list海量的对象)甚至做到通过对象存储网关访问多个Ceph集群的存储资源。

我一拿到改造某个接口的需求,首先需要去Ceph的官方文档查一下该接口说明,以便我上RGW上测试一下这个接口作用或者效果是怎么样的。

获取用户信息的admin REST接口说明是这样的:

语法

GET /{admin}/user?format=json HTTP/1.1
Host: {fqdn}

请求参数

名称 类型 描述 是否必须
uid string 用户的id

通过说明可以知道该接口的请求路径是/admin/user,查询的时候需要带上一个uid参数来指定查询哪个用户的信息,例如我要查询uid为test的用户的信息,那我就先创建一个admin用户,然后用它的AK/SK来访问我部署的RGW,也就是发送一个GET请求到http://127.0.0.1:7480/amdin/user?uid=test,这样RGW就能返回test用户的信息给我。

RGW代码分析

通过接口说明知道了获取用户的admin REST接口是如何使用的,接下来就分析RGW是如何实现响应的逻辑。

《Ceph RGW整体结构,最全干货在这!》这一篇文章可以知道,首先RGW在启动的时候程序会为不同的API集注册MGR,也就是处理该类型API的handler的集合。每个API可以对应有多个MGR,每个MGR中维护该MRG所支持的handler。

注册admin REST接口这一类API的MGR这一步就是在src/rgw/rgw_main.cc这个文件的main函数中:

  if (apis_map.count("admin") > 0) {
    RGWRESTMgr_Admin *admin_resource = new RGWRESTMgr_Admin;
    admin_resource->register_resource("usage", new RGWRESTMgr_Usage);
    admin_resource->register_resource("user", new RGWRESTMgr_User);
    admin_resource->register_resource("bucket", new RGWRESTMgr_Bucket);
  
    /*Registering resource for /admin/metadata */
    admin_resource->register_resource("metadata", new RGWRESTMgr_Metadata);
    admin_resource->register_resource("log", new RGWRESTMgr_Log);
    admin_resource->register_resource("config", new RGWRESTMgr_Config);
    admin_resource->register_resource("realm", new RGWRESTMgr_Realm);
    rest.register_resource(g_conf()->rgw_admin_entry, admin_resource);
  }

从这段代码可以看出程序为admin REST接口注册很多个MGR,响应获取用户信息请求当然就是RGWRESTMgr_User,注册这个MGR时候指定的"user"字符串自然就是匹配了URL中的user,那么前面的admin部分又是在哪里做了匹配规则呢?答案就在倒数第二行代码中。程序在注册这些MGR的时候通过g_conf()->rgw_admin_entry为它们指定了一个匹配的前缀,这个rgw_admin_entry可以在src/common/options.cc这个代码中找到说明:

    Option("rgw_admin_entry", Option::TYPE_STR, Option::LEVEL_ADVANCED)
    .set_default("admin")
    .set_description("Path prefix to be used for accessing RGW RESTful admin API."),

读到这,大家应该就知道了RGWRESTMgr_User这个类会响应/amdin/user这个路径的请求,至于处理的是PUT请求,还是GET请求,可以再从它的代码里分析。

src/rgw/rgw_rest_user.h里面包含了RGWRESTMgr_User的代码:

class RGWRESTMgr_User : public RGWRESTMgr {
public:
  RGWRESTMgr_User() = default;
  ~RGWRESTMgr_User() override = default;

  RGWHandler_REST *get_handler(struct req_state*,
                               const rgw::auth::StrategyRegistry& auth_registry,
                               const std::string&) override {
    return new RGWHandler_User(auth_registry);
  }
};

从它的代码可以知道,该MGR只管理了一个名为RGWHandler_User的handler,该handler的代码也在同一个文件中,具体如下:

class RGWHandler_User : public RGWHandler_Auth_S3 {
protected:
  RGWOp *op_get() override;
  RGWOp *op_put() override;
  RGWOp *op_post() override;
  RGWOp *op_delete() override;
public:
  using RGWHandler_Auth_S3::RGWHandler_Auth_S3;
  ~RGWHandler_User() override = default;

  int read_permissions(RGWOp*) override {
    return 0;
  }
};

RGWHandler_User的代码可以看到,这个handler提供了对GET、PUT、POST、DELETE 4种方法请求/admin/user这个路径的支持,也就是4个RGWOp,处理获取用户信息的请求的op当然就是op_get()方法返回的op。该方法的实现在src/rgw/rgw_rest_user.cc的代码里,具体如下:

RGWOp *RGWHandler_User::op_get()
{
  if (s->info.args.sub_resource_exists("quota"))
    return new RGWOp_Quota_Info;

  if (s->info.args.sub_resource_exists("list"))
    return new RGWOp_User_List;

  return new RGWOp_User_Info;
}

从上面的代码可以看到该方法一共返回三个op,最后一个名为RGWOp_User_Info的op就是实现了响应获取用户信息的admin REST接口业务逻辑。至于剩余的两个op,从字面意思可以看出一个是返回的op是响应GET /{admin}/user?quota的请求,另一个返回的op是响应GET /{admin}/user?list的请求。比如我用admin用户的AK/SK发一个请求http://127.0.0.1:7480/amdin/user?list就能获得RGW全部创建用户的uid,奇怪的是这个接口在官方文档中并没有介绍。

接下来看看RGWOp_User_Info这个op,执行的业务逻辑在这个类的execute()方法中,代码在src/rgw/rgw_rest_user.cc文件中,具体的代码就不贴出来了,因为已经被我改动了。需要注意的是网上有已经有很多前辈解读过RGW处理请求流程的代码并进行了分享,例如用户通过S3上传一个对象到Ceph,RGW是如何处理这个流程的,大部分的文章都是从op这里的代码开始解读RGW做了什么。确实op里的execute()方法是大多数人关注的流程,但作为一个初学者懂得如何定位到一个接口对应的op同样很重要。

接下来还剩下一个问题没有解决,那就是这些op在何处被获取并调用其execute()方法来响应客户端的请求。

前面我们分析在rgw_main.cc的代码中完成了各种MGR的注册,那么这些注册好的MGR是怎么被调用起来的呢?需要注意的RGW不只实现了一种web框架(例如14.2.8版本的RGW有2个:civetweb、beast),这些web框架或者说web服务器实现了解析处理HTTP请求的功能,比如路由、请求参数解析等,RGW称其为前端(frontend)。

src/rgw/rgw_main.cc这个文件的main函数中,注册完各种MGR后就会根据配置初始化一个frontend,然后让它跑起来,具体看这一段代码:

if (fe == NULL) {
      dout(0) << "WARNING: skipping unknown framework: " << framework << dendl;
      continue;
    }

    dout(0) << "starting handler: " << fiter->first << dendl;
    int r = fe->init();
    if (r < 0) {
      derr << "ERROR: failed initializing frontend" << dendl;
      return -r;
    }
    r = fe->run();
    if (r < 0) {
      derr << "ERROR: failed run" << dendl;
      return -r;
    }

当frontend启动后就开始监听端口并捕获处理请求。每个frontend都实现了这一功能,它们的共同之处就是都调用下面这个方法来提取处理每次请求对于的MGR、handler和op:

/* process stream request */
extern int process_request(RGWRados* store,
                           RGWREST* rest,
                           RGWRequest* req,
                           const std::string& frontend_prefix,
                           const rgw_auth_registry_t& auth_registry,
                           RGWRestfulIO* client_io,
                           OpsLogSocket* olog,
                           optional_yield y,
                           rgw::dmclock::Scheduler *scheduler,
                           int* http_ret = nullptr);

该方法太长了,关键的地方在于下面这段代码:

  RGWOp* op = nullptr;
  int init_error = 0;
  bool should_log = false;
  RGWRESTMgr *mgr;
//首先解析URL获取对应的handler
  RGWHandler_REST *handler = rest->get_handler(store, s,
                                               auth_registry,
                                               frontend_prefix,
                                               client_io, &mgr, &init_error);
  rgw::dmclock::SchedulerCompleter c;
  if (init_error != 0) {
    abort_early(s, nullptr, init_error, nullptr);
    goto done;
  }
  dout(10) << "handler=" << typeid(*handler).name() << dendl;

  should_log = mgr->get_logging();

  ldpp_dout(s, 2) << "getting op " << s->op << dendl;
//拿到handler之后解析请求方法获取op
  op = handler->get_op(store);
  if (!op) {
    abort_early(s, NULL, -ERR_METHOD_NOT_ALLOWED, handler);
    goto done;
  }
//把op扔进调度队列,等着被调用op的execute()方法
  std::tie(ret,c) = schedule_request(scheduler, s, op);

题外话

经过上文的分析,可以知道/admin/...这条路径就是准备留给admin模块的REST接口的,熟悉的S3协议的同学可能知道获取一个对象的URL是/{bucket}/{object},那万一有一个桶的名字就叫admin,用户要请求这个桶里资源,这时RGW会怎么处理?其实RGW从建桶的时候就规定了不能创建名为admin的bucket(会报405 MethodNotAllowed),所以在/admin/...这条路径就永远留给admin模块了。

至于S3这一类型API路由分配还有MGR和handler是怎么注册的,大家按照上面的流程,顺着src/rgw/rgw_main.cc文件里面的这段代码:

  if (apis_map.count("s3") > 0 || s3website_enabled) {
    if (! swift_at_root) {
      rest.register_default_mgr(set_logging(rest_filter(store, RGW_REST_S3,
                                                        new RGWRESTMgr_S3(s3website_enabled, sts_enabled, iam_enabled, pubsub_enabled))));
    } else {
      derr << "Cannot have the S3 or S3 Website enabled together with "
           << "Swift API placed in the root of hierarchy" << dendl;
      return EINVAL;
    }
  }

看看RGWRESTMgr_S3这个类里面的内容就知道了。

posted @ 2021-12-21 10:02  共和国的工人  阅读(539)  评论(0)    收藏  举报