// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- // vim: ts=8 sw=2 smarttab ft=cpp #include "common/errno.h" #include "rgw_rest_realm.h" #include "rgw_rest_s3.h" #include "rgw_rest_config.h" #include "rgw_zone.h" #include "rgw_sal_rados.h" #include "services/svc_zone.h" #include "services/svc_mdlog.h" #include "include/ceph_assert.h" #define dout_subsys ceph_subsys_rgw // reject 'period push' if we would have to fetch too many intermediate periods static const uint32_t PERIOD_HISTORY_FETCH_MAX = 64; // base period op, shared between Get and Post class RGWOp_Period_Base : public RGWRESTOp { protected: RGWPeriod period; std::ostringstream error_stream; public: int verify_permission(optional_yield) override { return 0; } void send_response() override; }; // reply with the period object on success void RGWOp_Period_Base::send_response() { set_req_state_err(s, op_ret, error_stream.str()); dump_errno(s); if (op_ret < 0) { if (!s->err.message.empty()) { ldpp_dout(this, 4) << "Request failed with " << op_ret << ": " << s->err.message << dendl; } end_header(s); return; } encode_json("period", period, s->formatter); end_header(s, NULL, "application/json", s->formatter->get_len()); flusher.flush(); } // GET /admin/realm/period class RGWOp_Period_Get : public RGWOp_Period_Base { public: void execute(optional_yield y) override; int check_caps(const RGWUserCaps& caps) override { return caps.check_cap("zone", RGW_CAP_READ); } int verify_permission(optional_yield) override { return check_caps(s->user->get_caps()); } const char* name() const override { return "get_period"; } }; void RGWOp_Period_Get::execute(optional_yield y) { string realm_id, realm_name, period_id; epoch_t epoch = 0; RESTArgs::get_string(s, "realm_id", realm_id, &realm_id); RESTArgs::get_string(s, "realm_name", realm_name, &realm_name); RESTArgs::get_string(s, "period_id", period_id, &period_id); RESTArgs::get_uint32(s, "epoch", 0, &epoch); period.set_id(period_id); period.set_epoch(epoch); op_ret = period.init(this, store->ctx(), store->svc()->sysobj, realm_id, y, realm_name); if (op_ret < 0) ldpp_dout(this, 5) << "failed to read period" << dendl; } // POST /admin/realm/period class RGWOp_Period_Post : public RGWOp_Period_Base { public: void execute(optional_yield y) override; int check_caps(const RGWUserCaps& caps) override { return caps.check_cap("zone", RGW_CAP_WRITE); } int verify_permission(optional_yield) override { return check_caps(s->user->get_caps()); } const char* name() const override { return "post_period"; } }; void RGWOp_Period_Post::execute(optional_yield y) { auto cct = store->ctx(); // initialize the period without reading from rados period.init(this, cct, store->svc()->sysobj, y, false); // decode the period from input const auto max_size = cct->_conf->rgw_max_put_param_size; bool empty; op_ret = rgw_rest_get_json_input(cct, s, period, max_size, &empty); if (op_ret < 0) { ldpp_dout(this, -1) << "failed to decode period" << dendl; return; } // require period.realm_id to match our realm if (period.get_realm() != store->svc()->zone->get_realm().get_id()) { error_stream << "period with realm id " << period.get_realm() << " doesn't match current realm " << store->svc()->zone->get_realm().get_id() << std::endl; op_ret = -EINVAL; return; } // load the realm and current period from rados; there may be a more recent // period that we haven't restarted with yet. we also don't want to modify // the objects in use by RGWRados RGWRealm realm(period.get_realm()); op_ret = realm.init(this, cct, store->svc()->sysobj, y); if (op_ret < 0) { ldpp_dout(this, -1) << "failed to read current realm: " << cpp_strerror(-op_ret) << dendl; return; } RGWPeriod current_period; op_ret = current_period.init(this, cct, store->svc()->sysobj, realm.get_id(), y); if (op_ret < 0) { ldpp_dout(this, -1) << "failed to read current period: " << cpp_strerror(-op_ret) << dendl; return; } // if period id is empty, handle as 'period commit' if (period.get_id().empty()) { op_ret = period.commit(this, store, realm, current_period, error_stream, y); if (op_ret < 0) { ldpp_dout(this, -1) << "master zone failed to commit period" << dendl; } return; } // if it's not period commit, nobody is allowed to push to the master zone if (period.get_master_zone() == store->svc()->zone->get_zone_params().get_id()) { ldpp_dout(this, 10) << "master zone rejecting period id=" << period.get_id() << " epoch=" << period.get_epoch() << dendl; op_ret = -EINVAL; // XXX: error code return; } // write the period to rados op_ret = period.store_info(this, false, y); if (op_ret < 0) { ldpp_dout(this, -1) << "failed to store period " << period.get_id() << dendl; return; } // set as latest epoch op_ret = period.update_latest_epoch(this, period.get_epoch(), y); if (op_ret == -EEXIST) { // already have this epoch (or a more recent one) ldpp_dout(this, 4) << "already have epoch >= " << period.get_epoch() << " for period " << period.get_id() << dendl; op_ret = 0; return; } if (op_ret < 0) { ldpp_dout(this, -1) << "failed to set latest epoch" << dendl; return; } auto period_history = store->svc()->mdlog->get_period_history(); // decide whether we can set_current_period() or set_latest_epoch() if (period.get_id() != current_period.get_id()) { auto current_epoch = current_period.get_realm_epoch(); // discard periods in the past if (period.get_realm_epoch() < current_epoch) { ldpp_dout(this, 10) << "discarding period " << period.get_id() << " with realm epoch " << period.get_realm_epoch() << " older than current epoch " << current_epoch << dendl; // return success to ack that we have this period return; } // discard periods too far in the future if (period.get_realm_epoch() > current_epoch + PERIOD_HISTORY_FETCH_MAX) { ldpp_dout(this, -1) << "discarding period " << period.get_id() << " with realm epoch " << period.get_realm_epoch() << " too far in " "the future from current epoch " << current_epoch << dendl; op_ret = -ENOENT; // XXX: error code return; } // attach a copy of the period into the period history auto cursor = period_history->attach(this, RGWPeriod{period}, y); if (!cursor) { // we're missing some history between the new period and current_period op_ret = cursor.get_error(); ldpp_dout(this, -1) << "failed to collect the periods between current period " << current_period.get_id() << " (realm epoch " << current_epoch << ") and the new period " << period.get_id() << " (realm epoch " << period.get_realm_epoch() << "): " << cpp_strerror(-op_ret) << dendl; return; } if (cursor.has_next()) { // don't switch if we have a newer period in our history ldpp_dout(this, 4) << "attached period " << period.get_id() << " to history, but the history contains newer periods" << dendl; return; } // set as current period op_ret = realm.set_current_period(this, period, y); if (op_ret < 0) { ldpp_dout(this, -1) << "failed to update realm's current period" << dendl; return; } ldpp_dout(this, 4) << "period " << period.get_id() << " is newer than current period " << current_period.get_id() << ", updating realm's current period and notifying zone" << dendl; realm.notify_new_period(this, period, y); return; } // reflect the period into our local objects op_ret = period.reflect(this, y); if (op_ret < 0) { ldpp_dout(this, -1) << "failed to update local objects: " << cpp_strerror(-op_ret) << dendl; return; } ldpp_dout(this, 4) << "period epoch " << period.get_epoch() << " is newer than current epoch " << current_period.get_epoch() << ", updating period's latest epoch and notifying zone" << dendl; realm.notify_new_period(this, period, y); // update the period history period_history->insert(RGWPeriod{period}); } class RGWHandler_Period : public RGWHandler_Auth_S3 { protected: using RGWHandler_Auth_S3::RGWHandler_Auth_S3; RGWOp *op_get() override { return new RGWOp_Period_Get; } RGWOp *op_post() override { return new RGWOp_Period_Post; } }; class RGWRESTMgr_Period : public RGWRESTMgr { public: RGWHandler_REST* get_handler(rgw::sal::RGWRadosStore *store, struct req_state*, const rgw::auth::StrategyRegistry& auth_registry, const std::string&) override { return new RGWHandler_Period(auth_registry); } }; // GET /admin/realm class RGWOp_Realm_Get : public RGWRESTOp { std::unique_ptr realm; public: int check_caps(const RGWUserCaps& caps) override { return caps.check_cap("zone", RGW_CAP_READ); } int verify_permission(optional_yield) override { return check_caps(s->user->get_caps()); } void execute(optional_yield y) override; void send_response() override; const char* name() const override { return "get_realm"; } }; void RGWOp_Realm_Get::execute(optional_yield y) { string id; RESTArgs::get_string(s, "id", id, &id); string name; RESTArgs::get_string(s, "name", name, &name); // read realm realm.reset(new RGWRealm(id, name)); op_ret = realm->init(this, g_ceph_context, store->svc()->sysobj, y); if (op_ret < 0) ldpp_dout(this, -1) << "failed to read realm id=" << id << " name=" << name << dendl; } void RGWOp_Realm_Get::send_response() { set_req_state_err(s, op_ret); dump_errno(s); if (op_ret < 0) { end_header(s); return; } encode_json("realm", *realm, s->formatter); end_header(s, NULL, "application/json", s->formatter->get_len()); flusher.flush(); } // GET /admin/realm?list class RGWOp_Realm_List : public RGWRESTOp { std::string default_id; std::list realms; public: int check_caps(const RGWUserCaps& caps) override { return caps.check_cap("zone", RGW_CAP_READ); } int verify_permission(optional_yield) override { return check_caps(s->user->get_caps()); } void execute(optional_yield y) override; void send_response() override; const char* name() const override { return "list_realms"; } }; void RGWOp_Realm_List::execute(optional_yield y) { { // read default realm RGWRealm realm(store->ctx(), store->svc()->sysobj); [[maybe_unused]] int ret = realm.read_default_id(this, default_id, y); } op_ret = store->svc()->zone->list_realms(this, realms); if (op_ret < 0) ldpp_dout(this, -1) << "failed to list realms" << dendl; } void RGWOp_Realm_List::send_response() { set_req_state_err(s, op_ret); dump_errno(s); if (op_ret < 0) { end_header(s); return; } s->formatter->open_object_section("realms_list"); encode_json("default_info", default_id, s->formatter); encode_json("realms", realms, s->formatter); s->formatter->close_section(); end_header(s, NULL, "application/json", s->formatter->get_len()); flusher.flush(); } class RGWHandler_Realm : public RGWHandler_Auth_S3 { protected: using RGWHandler_Auth_S3::RGWHandler_Auth_S3; RGWOp *op_get() override { if (s->info.args.sub_resource_exists("list")) return new RGWOp_Realm_List; return new RGWOp_Realm_Get; } }; RGWRESTMgr_Realm::RGWRESTMgr_Realm() { // add the /admin/realm/period resource register_resource("period", new RGWRESTMgr_Period); } RGWHandler_REST* RGWRESTMgr_Realm::get_handler(rgw::sal::RGWRadosStore *store, struct req_state*, const rgw::auth::StrategyRegistry& auth_registry, const std::string&) { return new RGWHandler_Realm(auth_registry); }