// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- // vim: ts=8 sw=2 smarttab /* * Ceph - scalable distributed file system * * Copyright (C) 2004-2006 Sage Weil * Copyright (C) 2013,2014 Cloudwatt * * Author: Loic Dachary * * This is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License version 2.1, as published by the Free Software * Foundation. See file COPYING. * */ #include "boost/tuple/tuple.hpp" #include "boost/intrusive_ptr.hpp" #include "PG.h" #include "pg_scrubber.h" #include "PrimaryLogPG.h" #include "OSD.h" #include "PrimaryLogScrub.h" #include "OpRequest.h" #include "ScrubStore.h" #include "Session.h" #include "objclass/objclass.h" #include "osd/ClassHandler.h" #include "cls/cas/cls_cas_ops.h" #include "common/ceph_crypto.h" #include "common/errno.h" #include "common/scrub_types.h" #include "common/perf_counters.h" #include "messages/MOSDOp.h" #include "messages/MOSDBackoff.h" #include "messages/MOSDPGTrim.h" #include "messages/MOSDPGScan.h" #include "messages/MOSDRepScrub.h" #include "messages/MOSDPGBackfill.h" #include "messages/MOSDPGBackfillRemove.h" #include "messages/MOSDPGUpdateLogMissing.h" #include "messages/MOSDPGUpdateLogMissingReply.h" #include "messages/MCommandReply.h" #include "messages/MOSDScrubReserve.h" #include "common/EventTrace.h" #include "common/config.h" #include "include/compat.h" #include "mon/MonClient.h" #include "osdc/Objecter.h" #include "json_spirit/json_spirit_value.h" #include "json_spirit/json_spirit_reader.h" #include "include/ceph_assert.h" // json_spirit clobbers it #include "include/rados/rados_types.hpp" #ifdef WITH_LTTNG #include "tracing/osd.h" #else #define tracepoint(...) #endif #define dout_context cct #define dout_subsys ceph_subsys_osd #define DOUT_PREFIX_ARGS this, osd->whoami, get_osdmap() #undef dout_prefix #define dout_prefix _prefix(_dout, this) #include #include #include #ifdef HAVE_JAEGER #include "common/tracer.h" #endif #include MEMPOOL_DEFINE_OBJECT_FACTORY(PrimaryLogPG, replicatedpg, osd); using std::list; using std::ostream; using std::pair; using std::make_pair; using std::map; using std::ostringstream; using std::set; using std::string; using std::string_view; using std::stringstream; using std::unique_ptr; using std::vector; using ceph::bufferlist; using ceph::bufferptr; using ceph::Formatter; using ceph::decode; using ceph::decode_noclear; using ceph::encode; using ceph::encode_destructively; using namespace ceph::osd::scheduler; using TOPNSPC::common::cmd_getval; template static ostream& _prefix(std::ostream *_dout, T *pg) { return pg->gen_prefix(*_dout); } /** * The CopyCallback class defines an interface for completions to the * copy_start code. Users of the copy infrastructure must implement * one and give an instance of the class to start_copy. * * The implementer is responsible for making sure that the CopyCallback * can associate itself with the correct copy operation. */ class PrimaryLogPG::CopyCallback : public GenContext { protected: CopyCallback() {} /** * results.get<0>() is the return code: 0 for success; -ECANCELED if * the operation was cancelled by the local OSD; -errno for other issues. * results.get<1>() is a pointer to a CopyResults object, which you are * responsible for deleting. */ void finish(CopyCallbackResults results_) override = 0; public: /// Provide the final size of the copied object to the CopyCallback ~CopyCallback() override {} }; template class PrimaryLogPG::BlessedGenContext : public GenContext { PrimaryLogPGRef pg; unique_ptr> c; epoch_t e; public: BlessedGenContext(PrimaryLogPG *pg, GenContext *c, epoch_t e) : pg(pg), c(c), e(e) {} void finish(T t) override { std::scoped_lock locker{*pg}; if (pg->pg_has_reset_since(e)) c.reset(); else c.release()->complete(t); } bool sync_finish(T t) { // we assume here all blessed/wrapped Contexts can complete synchronously. c.release()->complete(t); return true; } }; GenContext *PrimaryLogPG::bless_gencontext( GenContext *c) { return new BlessedGenContext( this, c, get_osdmap_epoch()); } template class PrimaryLogPG::UnlockedBlessedGenContext : public GenContext { PrimaryLogPGRef pg; unique_ptr> c; epoch_t e; public: UnlockedBlessedGenContext(PrimaryLogPG *pg, GenContext *c, epoch_t e) : pg(pg), c(c), e(e) {} void finish(T t) override { if (pg->pg_has_reset_since(e)) c.reset(); else c.release()->complete(t); } bool sync_finish(T t) { // we assume here all blessed/wrapped Contexts can complete synchronously. c.release()->complete(t); return true; } }; GenContext *PrimaryLogPG::bless_unlocked_gencontext( GenContext *c) { return new UnlockedBlessedGenContext( this, c, get_osdmap_epoch()); } class PrimaryLogPG::BlessedContext : public Context { PrimaryLogPGRef pg; unique_ptr c; epoch_t e; public: BlessedContext(PrimaryLogPG *pg, Context *c, epoch_t e) : pg(pg), c(c), e(e) {} void finish(int r) override { std::scoped_lock locker{*pg}; if (pg->pg_has_reset_since(e)) c.reset(); else c.release()->complete(r); } bool sync_finish(int r) override { // we assume here all blessed/wrapped Contexts can complete synchronously. c.release()->complete(r); return true; } }; Context *PrimaryLogPG::bless_context(Context *c) { return new BlessedContext(this, c, get_osdmap_epoch()); } class PrimaryLogPG::C_PG_ObjectContext : public Context { PrimaryLogPGRef pg; ObjectContext *obc; public: C_PG_ObjectContext(PrimaryLogPG *p, ObjectContext *o) : pg(p), obc(o) {} void finish(int r) override { pg->object_context_destructor_callback(obc); } }; struct OnReadComplete : public Context { PrimaryLogPG *pg; PrimaryLogPG::OpContext *opcontext; OnReadComplete( PrimaryLogPG *pg, PrimaryLogPG::OpContext *ctx) : pg(pg), opcontext(ctx) {} void finish(int r) override { opcontext->finish_read(pg); } ~OnReadComplete() override {} }; class PrimaryLogPG::C_OSD_AppliedRecoveredObject : public Context { PrimaryLogPGRef pg; ObjectContextRef obc; public: C_OSD_AppliedRecoveredObject(PrimaryLogPG *p, ObjectContextRef o) : pg(p), obc(o) {} bool sync_finish(int r) override { pg->_applied_recovered_object(obc); return true; } void finish(int r) override { std::scoped_lock locker{*pg}; pg->_applied_recovered_object(obc); } }; class PrimaryLogPG::C_OSD_CommittedPushedObject : public Context { PrimaryLogPGRef pg; epoch_t epoch; eversion_t last_complete; public: C_OSD_CommittedPushedObject( PrimaryLogPG *p, epoch_t epoch, eversion_t lc) : pg(p), epoch(epoch), last_complete(lc) { } void finish(int r) override { pg->_committed_pushed_object(epoch, last_complete); } }; class PrimaryLogPG::C_OSD_AppliedRecoveredObjectReplica : public Context { PrimaryLogPGRef pg; public: explicit C_OSD_AppliedRecoveredObjectReplica(PrimaryLogPG *p) : pg(p) {} bool sync_finish(int r) override { pg->_applied_recovered_object_replica(); return true; } void finish(int r) override { std::scoped_lock locker{*pg}; pg->_applied_recovered_object_replica(); } }; // OpContext void PrimaryLogPG::OpContext::start_async_reads(PrimaryLogPG *pg) { inflightreads = 1; list, pair > > in; in.swap(pending_async_reads); pg->pgbackend->objects_read_async( obc->obs.oi.soid, in, new OnReadComplete(pg, this), pg->get_pool().fast_read); } void PrimaryLogPG::OpContext::finish_read(PrimaryLogPG *pg) { ceph_assert(inflightreads > 0); --inflightreads; if (async_reads_complete()) { ceph_assert(pg->in_progress_async_reads.size()); ceph_assert(pg->in_progress_async_reads.front().second == this); pg->in_progress_async_reads.pop_front(); // Restart the op context now that all reads have been // completed. Read failures will be handled by the op finisher pg->execute_ctx(this); } } class CopyFromCallback : public PrimaryLogPG::CopyCallback { public: PrimaryLogPG::CopyResults *results = nullptr; PrimaryLogPG::OpContext *ctx; OSDOp &osd_op; uint32_t truncate_seq; uint64_t truncate_size; bool have_truncate = false; CopyFromCallback(PrimaryLogPG::OpContext *ctx, OSDOp &osd_op) : ctx(ctx), osd_op(osd_op) { } ~CopyFromCallback() override {} void finish(PrimaryLogPG::CopyCallbackResults results_) override { results = results_.get<1>(); int r = results_.get<0>(); // Only use truncate_{seq,size} from the original object if the client // did not sent us these parameters if (!have_truncate) { truncate_seq = results->truncate_seq; truncate_size = results->truncate_size; } // for finish_copyfrom ctx->user_at_version = results->user_version; if (r >= 0) { ctx->pg->execute_ctx(ctx); } else { if (r != -ECANCELED) { // on cancel just toss it out; client resends if (ctx->op) ctx->pg->osd->reply_op_error(ctx->op, r); } else if (results->should_requeue) { if (ctx->op) ctx->pg->requeue_op(ctx->op); } ctx->pg->close_op_ctx(ctx); } } bool is_temp_obj_used() { return results->started_temp_obj; } uint64_t get_data_size() { return results->object_size; } void set_truncate(uint32_t seq, uint64_t size) { truncate_seq = seq; truncate_size = size; have_truncate = true; } }; struct CopyFromFinisher : public PrimaryLogPG::OpFinisher { CopyFromCallback *copy_from_callback; explicit CopyFromFinisher(CopyFromCallback *copy_from_callback) : copy_from_callback(copy_from_callback) { } int execute() override { // instance will be destructed after this method completes copy_from_callback->ctx->pg->finish_copyfrom(copy_from_callback); return 0; } }; // ====================== // PGBackend::Listener void PrimaryLogPG::on_local_recover( const hobject_t &hoid, const ObjectRecoveryInfo &_recovery_info, ObjectContextRef obc, bool is_delete, ObjectStore::Transaction *t ) { dout(10) << __func__ << ": " << hoid << dendl; ObjectRecoveryInfo recovery_info(_recovery_info); clear_object_snap_mapping(t, hoid); if (!is_delete && recovery_info.soid.is_snap()) { OSDriver::OSTransaction _t(osdriver.get_transaction(t)); set snaps; dout(20) << " snapset " << recovery_info.ss << dendl; auto p = recovery_info.ss.clone_snaps.find(hoid.snap); if (p != recovery_info.ss.clone_snaps.end()) { snaps.insert(p->second.begin(), p->second.end()); dout(20) << " snaps " << snaps << dendl; snap_mapper.add_oid( recovery_info.soid, snaps, &_t); } else { derr << __func__ << " " << hoid << " had no clone_snaps" << dendl; } } if (!is_delete && recovery_state.get_pg_log().get_missing().is_missing(recovery_info.soid) && recovery_state.get_pg_log().get_missing().get_items().find(recovery_info.soid)->second.need > recovery_info.version) { ceph_assert(is_primary()); const pg_log_entry_t *latest = recovery_state.get_pg_log().get_log().objects.find(recovery_info.soid)->second; if (latest->op == pg_log_entry_t::LOST_REVERT && latest->reverting_to == recovery_info.version) { dout(10) << " got old revert version " << recovery_info.version << " for " << *latest << dendl; recovery_info.version = latest->version; // update the attr to the revert event version recovery_info.oi.prior_version = recovery_info.oi.version; recovery_info.oi.version = latest->version; bufferlist bl; encode(recovery_info.oi, bl, get_osdmap()->get_features(CEPH_ENTITY_TYPE_OSD, nullptr)); ceph_assert(!pool.info.is_erasure()); t->setattr(coll, ghobject_t(recovery_info.soid), OI_ATTR, bl); if (obc) obc->attr_cache[OI_ATTR] = bl; } } // keep track of active pushes for scrub ++active_pushes; recovery_state.recover_got( recovery_info.soid, recovery_info.version, is_delete, *t); if (is_primary()) { if (!is_delete) { obc->obs.exists = true; bool got = obc->get_recovery_read(); ceph_assert(got); ceph_assert(recovering.count(obc->obs.oi.soid)); recovering[obc->obs.oi.soid] = obc; obc->obs.oi = recovery_info.oi; // may have been updated above } t->register_on_applied(new C_OSD_AppliedRecoveredObject(this, obc)); publish_stats_to_osd(); release_backoffs(hoid); if (!is_unreadable_object(hoid)) { auto unreadable_object_entry = waiting_for_unreadable_object.find(hoid); if (unreadable_object_entry != waiting_for_unreadable_object.end()) { dout(20) << " kicking unreadable waiters on " << hoid << dendl; requeue_ops(unreadable_object_entry->second); waiting_for_unreadable_object.erase(unreadable_object_entry); } } } else { t->register_on_applied( new C_OSD_AppliedRecoveredObjectReplica(this)); } t->register_on_commit( new C_OSD_CommittedPushedObject( this, get_osdmap_epoch(), info.last_complete)); } void PrimaryLogPG::on_global_recover( const hobject_t &soid, const object_stat_sum_t &stat_diff, bool is_delete) { recovery_state.object_recovered(soid, stat_diff); publish_stats_to_osd(); dout(10) << "pushed " << soid << " to all replicas" << dendl; auto i = recovering.find(soid); ceph_assert(i != recovering.end()); if (i->second && i->second->rwstate.recovery_read_marker) { // recover missing won't have had an obc, but it gets filled in // during on_local_recover ceph_assert(i->second); list requeue_list; i->second->drop_recovery_read(&requeue_list); requeue_ops(requeue_list); } backfills_in_flight.erase(soid); recovering.erase(i); finish_recovery_op(soid); release_backoffs(soid); auto degraded_object_entry = waiting_for_degraded_object.find(soid); if (degraded_object_entry != waiting_for_degraded_object.end()) { dout(20) << " kicking degraded waiters on " << soid << dendl; requeue_ops(degraded_object_entry->second); waiting_for_degraded_object.erase(degraded_object_entry); } auto unreadable_object_entry = waiting_for_unreadable_object.find(soid); if (unreadable_object_entry != waiting_for_unreadable_object.end()) { dout(20) << " kicking unreadable waiters on " << soid << dendl; requeue_ops(unreadable_object_entry->second); waiting_for_unreadable_object.erase(unreadable_object_entry); } finish_degraded_object(soid); } void PrimaryLogPG::schedule_recovery_work( GenContext *c) { osd->queue_recovery_context(this, c); } void PrimaryLogPG::replica_clear_repop_obc( const vector &logv, ObjectStore::Transaction &t) { for (auto &&e: logv) { /* Have to blast all clones, they share a snapset */ object_contexts.clear_range( e.soid.get_object_boundary(), e.soid.get_head()); ceph_assert( snapset_contexts.find(e.soid.get_head()) == snapset_contexts.end()); } } bool PrimaryLogPG::should_send_op( pg_shard_t peer, const hobject_t &hoid) { if (peer == get_primary()) return true; ceph_assert(recovery_state.has_peer_info(peer)); bool should_send = hoid.pool != (int64_t)info.pgid.pool() || hoid <= last_backfill_started || hoid <= recovery_state.get_peer_info(peer).last_backfill; if (!should_send) { ceph_assert(is_backfill_target(peer)); dout(10) << __func__ << " issue_repop shipping empty opt to osd." << peer << ", object " << hoid << " beyond std::max(last_backfill_started " << ", peer_info[peer].last_backfill " << recovery_state.get_peer_info(peer).last_backfill << ")" << dendl; return should_send; } if (is_async_recovery_target(peer) && recovery_state.get_peer_missing(peer).is_missing(hoid)) { should_send = false; dout(10) << __func__ << " issue_repop shipping empty opt to osd." << peer << ", object " << hoid << " which is pending recovery in async_recovery_targets" << dendl; } return should_send; } ConnectionRef PrimaryLogPG::get_con_osd_cluster( int peer, epoch_t from_epoch) { return osd->get_con_osd_cluster(peer, from_epoch); } PerfCounters *PrimaryLogPG::get_logger() { return osd->logger; } // ==================== // missing objects bool PrimaryLogPG::is_missing_object(const hobject_t& soid) const { return recovery_state.get_pg_log().get_missing().get_items().count(soid); } void PrimaryLogPG::maybe_kick_recovery( const hobject_t &soid) { eversion_t v; bool work_started = false; if (!recovery_state.get_missing_loc().needs_recovery(soid, &v)) return; map::const_iterator p = recovering.find(soid); if (p != recovering.end()) { dout(7) << "object " << soid << " v " << v << ", already recovering." << dendl; } else if (recovery_state.get_missing_loc().is_unfound(soid)) { dout(7) << "object " << soid << " v " << v << ", is unfound." << dendl; } else { dout(7) << "object " << soid << " v " << v << ", recovering." << dendl; PGBackend::RecoveryHandle *h = pgbackend->open_recovery_op(); if (is_missing_object(soid)) { recover_missing(soid, v, CEPH_MSG_PRIO_HIGH, h); } else if (recovery_state.get_missing_loc().is_deleted(soid)) { prep_object_replica_deletes(soid, v, h, &work_started); } else { prep_object_replica_pushes(soid, v, h, &work_started); } pgbackend->run_recovery_op(h, CEPH_MSG_PRIO_HIGH); } } void PrimaryLogPG::wait_for_unreadable_object( const hobject_t& soid, OpRequestRef op) { ceph_assert(is_unreadable_object(soid)); maybe_kick_recovery(soid); waiting_for_unreadable_object[soid].push_back(op); op->mark_delayed("waiting for missing object"); } bool PrimaryLogPG::is_degraded_or_backfilling_object(const hobject_t& soid) { /* The conditions below may clear (on_local_recover, before we queue * the transaction) before we actually requeue the degraded waiters * in on_global_recover after the transaction completes. */ if (waiting_for_degraded_object.count(soid)) return true; if (recovery_state.get_pg_log().get_missing().get_items().count(soid)) return true; ceph_assert(!get_acting_recovery_backfill().empty()); for (set::iterator i = get_acting_recovery_backfill().begin(); i != get_acting_recovery_backfill().end(); ++i) { if (*i == get_primary()) continue; pg_shard_t peer = *i; auto peer_missing_entry = recovery_state.get_peer_missing().find(peer); // If an object is missing on an async_recovery_target, return false. // This will not block the op and the object is async recovered later. if (peer_missing_entry != recovery_state.get_peer_missing().end() && peer_missing_entry->second.get_items().count(soid)) { if (is_async_recovery_target(peer)) continue; else return true; } // Object is degraded if after last_backfill AND // we are backfilling it if (is_backfill_target(peer) && recovery_state.get_peer_info(peer).last_backfill <= soid && last_backfill_started >= soid && backfills_in_flight.count(soid)) return true; } return false; } bool PrimaryLogPG::is_degraded_on_async_recovery_target(const hobject_t& soid) { for (auto &i: get_async_recovery_targets()) { auto peer_missing_entry = recovery_state.get_peer_missing().find(i); if (peer_missing_entry != recovery_state.get_peer_missing().end() && peer_missing_entry->second.get_items().count(soid)) { dout(30) << __func__ << " " << soid << dendl; return true; } } return false; } void PrimaryLogPG::wait_for_degraded_object(const hobject_t& soid, OpRequestRef op) { ceph_assert(is_degraded_or_backfilling_object(soid) || is_degraded_on_async_recovery_target(soid)); maybe_kick_recovery(soid); waiting_for_degraded_object[soid].push_back(op); op->mark_delayed("waiting for degraded object"); } void PrimaryLogPG::block_write_on_full_cache( const hobject_t& _oid, OpRequestRef op) { const hobject_t oid = _oid.get_head(); dout(20) << __func__ << ": blocking object " << oid << " on full cache" << dendl; objects_blocked_on_cache_full.insert(oid); waiting_for_cache_not_full.push_back(op); op->mark_delayed("waiting for cache not full"); } void PrimaryLogPG::block_for_clean( const hobject_t& oid, OpRequestRef op) { dout(20) << __func__ << ": blocking object " << oid << " on primary repair" << dendl; waiting_for_clean_to_primary_repair.push_back(op); op->mark_delayed("waiting for clean to repair"); } void PrimaryLogPG::block_write_on_snap_rollback( const hobject_t& oid, ObjectContextRef obc, OpRequestRef op) { dout(20) << __func__ << ": blocking object " << oid.get_head() << " on snap promotion " << obc->obs.oi.soid << dendl; // otherwise, we'd have blocked in do_op ceph_assert(oid.is_head()); ceph_assert(objects_blocked_on_snap_promotion.count(oid) == 0); objects_blocked_on_snap_promotion[oid] = obc; wait_for_blocked_object(obc->obs.oi.soid, op); } void PrimaryLogPG::block_write_on_degraded_snap( const hobject_t& snap, OpRequestRef op) { dout(20) << __func__ << ": blocking object " << snap.get_head() << " on degraded snap " << snap << dendl; // otherwise, we'd have blocked in do_op ceph_assert(objects_blocked_on_degraded_snap.count(snap.get_head()) == 0); objects_blocked_on_degraded_snap[snap.get_head()] = snap.snap; wait_for_degraded_object(snap, op); } bool PrimaryLogPG::maybe_await_blocked_head( const hobject_t &hoid, OpRequestRef op) { ObjectContextRef obc; obc = object_contexts.lookup(hoid.get_head()); if (obc) { if (obc->is_blocked()) { wait_for_blocked_object(obc->obs.oi.soid, op); return true; } else { return false; } } return false; } void PrimaryLogPG::wait_for_blocked_object(const hobject_t& soid, OpRequestRef op) { dout(10) << __func__ << " " << soid << " " << op << dendl; waiting_for_blocked_object[soid].push_back(op); op->mark_delayed("waiting for blocked object"); } void PrimaryLogPG::maybe_force_recovery() { // no force if not in degraded/recovery/backfill states if (!is_degraded() && !state_test(PG_STATE_RECOVERING | PG_STATE_RECOVERY_WAIT | PG_STATE_BACKFILLING | PG_STATE_BACKFILL_WAIT | PG_STATE_BACKFILL_TOOFULL)) return; if (recovery_state.get_pg_log().get_log().approx_size() < cct->_conf->osd_max_pg_log_entries * cct->_conf->osd_force_recovery_pg_log_entries_factor) return; // find the oldest missing object version_t min_version = recovery_state.get_pg_log().get_log().head.version; hobject_t soid; if (!recovery_state.get_pg_log().get_missing().get_rmissing().empty()) { min_version = recovery_state.get_pg_log().get_missing().get_rmissing().begin()->first; soid = recovery_state.get_pg_log().get_missing().get_rmissing().begin()->second; } ceph_assert(!get_acting_recovery_backfill().empty()); for (set::iterator it = get_acting_recovery_backfill().begin(); it != get_acting_recovery_backfill().end(); ++it) { if (*it == get_primary()) continue; pg_shard_t peer = *it; auto it_missing = recovery_state.get_peer_missing().find(peer); if (it_missing != recovery_state.get_peer_missing().end() && !it_missing->second.get_rmissing().empty()) { const auto& min_obj = recovery_state.get_peer_missing(peer).get_rmissing().begin(); dout(20) << __func__ << " peer " << peer << " min_version " << min_obj->first << " oid " << min_obj->second << dendl; if (min_version > min_obj->first) { min_version = min_obj->first; soid = min_obj->second; } } } // recover it if (soid != hobject_t()) maybe_kick_recovery(soid); } bool PrimaryLogPG::check_laggy(OpRequestRef& op) { if (!HAVE_FEATURE(recovery_state.get_min_upacting_features(), SERVER_OCTOPUS)) { dout(20) << __func__ << " not all upacting has SERVER_OCTOPUS" << dendl; return true; } if (state_test(PG_STATE_WAIT)) { dout(10) << __func__ << " PG is WAIT state" << dendl; } else if (!state_test(PG_STATE_LAGGY)) { auto mnow = osd->get_mnow(); auto ru = recovery_state.get_readable_until(); if (mnow <= ru) { // not laggy return true; } dout(10) << __func__ << " mnow " << mnow << " > readable_until " << ru << dendl; if (!is_primary()) { osd->reply_op_error(op, -EAGAIN); return false; } // go to laggy state state_set(PG_STATE_LAGGY); publish_stats_to_osd(); } dout(10) << __func__ << " not readable" << dendl; waiting_for_readable.push_back(op); op->mark_delayed("waiting for readable"); return false; } bool PrimaryLogPG::check_laggy_requeue(OpRequestRef& op) { if (!HAVE_FEATURE(recovery_state.get_min_upacting_features(), SERVER_OCTOPUS)) { return true; } if (!state_test(PG_STATE_WAIT) && !state_test(PG_STATE_LAGGY)) { return true; // not laggy } dout(10) << __func__ << " not readable" << dendl; waiting_for_readable.push_front(op); op->mark_delayed("waiting for readable"); return false; } void PrimaryLogPG::recheck_readable() { if (!is_wait() && !is_laggy()) { dout(20) << __func__ << " wasn't wait or laggy" << dendl; return; } auto mnow = osd->get_mnow(); bool pub = false; if (is_wait()) { auto prior_readable_until_ub = recovery_state.get_prior_readable_until_ub(); if (mnow < prior_readable_until_ub) { dout(10) << __func__ << " still wait (mnow " << mnow << " < prior_readable_until_ub " << prior_readable_until_ub << ")" << dendl; } else { dout(10) << __func__ << " no longer wait (mnow " << mnow << " >= prior_readable_until_ub " << prior_readable_until_ub << ")" << dendl; state_clear(PG_STATE_WAIT); recovery_state.clear_prior_readable_until_ub(); pub = true; } } if (is_laggy()) { auto ru = recovery_state.get_readable_until(); if (ru == ceph::signedspan::zero()) { dout(10) << __func__ << " still laggy (mnow " << mnow << ", readable_until zero)" << dendl; } else if (mnow >= ru) { dout(10) << __func__ << " still laggy (mnow " << mnow << " >= readable_until " << ru << ")" << dendl; } else { dout(10) << __func__ << " no longer laggy (mnow " << mnow << " < readable_until " << ru << ")" << dendl; state_clear(PG_STATE_LAGGY); pub = true; } } if (pub) { publish_stats_to_osd(); } if (!is_laggy() && !is_wait()) { requeue_ops(waiting_for_readable); } } bool PrimaryLogPG::pgls_filter(const PGLSFilter& filter, const hobject_t& sobj) { bufferlist bl; // If filter has expressed an interest in an xattr, load it. if (!filter.get_xattr().empty()) { int ret = pgbackend->objects_get_attr( sobj, filter.get_xattr(), &bl); dout(0) << "getattr (sobj=" << sobj << ", attr=" << filter.get_xattr() << ") returned " << ret << dendl; if (ret < 0) { if (ret != -ENODATA || filter.reject_empty_xattr()) { return false; } } } return filter.filter(sobj, bl); } std::pair> PrimaryLogPG::get_pgls_filter(bufferlist::const_iterator& iter) { string type; // storing non-const PGLSFilter for the sake of ::init() std::unique_ptr filter; try { decode(type, iter); } catch (ceph::buffer::error& e) { return { -EINVAL, nullptr }; } if (type.compare("plain") == 0) { filter = std::make_unique(); } else { std::size_t dot = type.find("."); if (dot == std::string::npos || dot == 0 || dot == type.size() - 1) { return { -EINVAL, nullptr }; } const std::string class_name = type.substr(0, dot); const std::string filter_name = type.substr(dot + 1); ClassHandler::ClassData *cls = NULL; int r = ClassHandler::get_instance().open_class(class_name, &cls); if (r != 0) { derr << "Error opening class '" << class_name << "': " << cpp_strerror(r) << dendl; if (r != -EPERM) // propagate permission error r = -EINVAL; return { r, nullptr }; } else { ceph_assert(cls); } ClassHandler::ClassFilter *class_filter = cls->get_filter(filter_name); if (class_filter == NULL) { derr << "Error finding filter '" << filter_name << "' in class " << class_name << dendl; return { -EINVAL, nullptr }; } filter.reset(class_filter->fn()); if (!filter) { // Object classes are obliged to return us something, but let's // give an error rather than asserting out. derr << "Buggy class " << class_name << " failed to construct " "filter " << filter_name << dendl; return { -EINVAL, nullptr }; } } ceph_assert(filter); int r = filter->init(iter); if (r < 0) { derr << "Error initializing filter " << type << ": " << cpp_strerror(r) << dendl; return { -EINVAL, nullptr }; } else { // Successfully constructed and initialized, return it. return std::make_pair(0, std::move(filter)); } } // ========================================================== void PrimaryLogPG::do_command( const string_view& orig_prefix, const cmdmap_t& cmdmap, const bufferlist& idata, std::function on_finish) { string format; cmd_getval(cmdmap, "format", format); std::unique_ptr f(Formatter::create( format, "json-pretty", "json-pretty")); int ret = 0; stringstream ss; // stderr error message stream bufferlist outbl; // if empty at end, we'll dump formatter as output // get final prefix: // - ceph pg foo -> prefix=pg, cmd=foo // - ceph tell foo -> prefix=foo string prefix(orig_prefix); string command; cmd_getval(cmdmap, "cmd", command); if (command.size()) { prefix = command; } if (prefix == "query") { f->open_object_section("pg"); f->dump_stream("snap_trimq") << snap_trimq; f->dump_unsigned("snap_trimq_len", snap_trimq.size()); recovery_state.dump_peering_state(f.get()); f->open_array_section("recovery_state"); handle_query_state(f.get()); f->close_section(); if (is_primary() && is_active() && m_scrubber) { m_scrubber->dump(f.get()); } f->open_object_section("agent_state"); if (agent_state) agent_state->dump(f.get()); f->close_section(); f->close_section(); } else if (prefix == "mark_unfound_lost") { string mulcmd; cmd_getval(cmdmap, "mulcmd", mulcmd); int mode = -1; if (mulcmd == "revert") { if (pool.info.is_erasure()) { ss << "mode must be 'delete' for ec pool"; ret = -EINVAL; goto out; } mode = pg_log_entry_t::LOST_REVERT; } else if (mulcmd == "delete") { mode = pg_log_entry_t::LOST_DELETE; } else { ss << "mode must be 'revert' or 'delete'; mark not yet implemented"; ret = -EINVAL; goto out; } ceph_assert(mode == pg_log_entry_t::LOST_REVERT || mode == pg_log_entry_t::LOST_DELETE); if (!is_primary()) { ss << "not primary"; ret = -EROFS; goto out; } uint64_t unfound = recovery_state.get_missing_loc().num_unfound(); if (!unfound) { ss << "pg has no unfound objects"; goto out; // make command idempotent } if (!recovery_state.all_unfound_are_queried_or_lost(get_osdmap())) { ss << "pg has " << unfound << " unfound objects but we haven't probed all sources, not marking lost"; ret = -EINVAL; goto out; } mark_all_unfound_lost(mode, on_finish); return; } else if (prefix == "list_unfound") { hobject_t offset; string offset_json; bool show_offset = false; if (cmd_getval(cmdmap, "offset", offset_json)) { json_spirit::Value v; try { if (!json_spirit::read(offset_json, v)) throw std::runtime_error("bad json"); offset.decode(v); } catch (std::runtime_error& e) { ss << "error parsing offset: " << e.what(); ret = -EINVAL; goto out; } show_offset = true; } f->open_object_section("missing"); if (show_offset) { f->open_object_section("offset"); offset.dump(f.get()); f->close_section(); } auto &needs_recovery_map = recovery_state.get_missing_loc() .get_needs_recovery(); f->dump_int("num_missing", needs_recovery_map.size()); f->dump_int("num_unfound", get_num_unfound()); map::const_iterator p = needs_recovery_map.upper_bound(offset); { f->open_array_section("objects"); int32_t num = 0; for (; p != needs_recovery_map.end() && num < cct->_conf->osd_command_max_records; ++p) { if (recovery_state.get_missing_loc().is_unfound(p->first)) { f->open_object_section("object"); { f->open_object_section("oid"); p->first.dump(f.get()); f->close_section(); } p->second.dump(f.get()); // have, need keys { f->open_array_section("locations"); for (auto &&r : recovery_state.get_missing_loc().get_locations( p->first)) { f->dump_stream("shard") << r; } f->close_section(); } f->close_section(); num++; } } f->close_section(); } // Get possible locations of missing objects from pg information PeeringState::QueryUnfound q(f.get()); recovery_state.handle_event(q, 0); f->dump_bool("more", p != needs_recovery_map.end()); f->close_section(); } else if (prefix == "scrub" || prefix == "deep_scrub") { bool deep = (prefix == "deep_scrub"); int64_t time; cmd_getval(cmdmap, "time", time, (int64_t)0); if (is_primary()) { const pg_pool_t *p = &pool.info; double pool_scrub_max_interval = 0; double scrub_max_interval; if (deep) { p->opts.get(pool_opts_t::DEEP_SCRUB_INTERVAL, &pool_scrub_max_interval); scrub_max_interval = pool_scrub_max_interval > 0 ? pool_scrub_max_interval : g_conf()->osd_deep_scrub_interval; } else { p->opts.get(pool_opts_t::SCRUB_MAX_INTERVAL, &pool_scrub_max_interval); scrub_max_interval = pool_scrub_max_interval > 0 ? pool_scrub_max_interval : g_conf()->osd_scrub_max_interval; } // Instead of marking must_scrub force a schedule scrub utime_t stamp = ceph_clock_now(); if (time == 0) stamp -= scrub_max_interval; else stamp -= (float)time; stamp -= 100.0; // push back last scrub more for good measure if (deep) { set_last_deep_scrub_stamp(stamp); } else { set_last_scrub_stamp(stamp); } f->open_object_section("result"); f->dump_bool("deep", deep); f->dump_stream("stamp") << stamp; f->close_section(); } else { ss << "Not primary"; ret = -EPERM; } outbl.append(ss.str()); } else { ret = -ENOSYS; ss << "prefix '" << prefix << "' not implemented"; } out: if (ret >= 0 && outbl.length() == 0) { f->flush(outbl); } on_finish(ret, ss.str(), outbl); } // ========================================================== void PrimaryLogPG::do_pg_op(OpRequestRef op) { const MOSDOp *m = static_cast(op->get_req()); ceph_assert(m->get_type() == CEPH_MSG_OSD_OP); dout(10) << "do_pg_op " << *m << dendl; op->mark_started(); int result = 0; string cname, mname; snapid_t snapid = m->get_snapid(); vector ops = m->ops; for (vector::iterator p = ops.begin(); p != ops.end(); ++p) { std::unique_ptr filter; OSDOp& osd_op = *p; auto bp = p->indata.cbegin(); switch (p->op.op) { case CEPH_OSD_OP_PGNLS_FILTER: try { decode(cname, bp); decode(mname, bp); } catch (const ceph::buffer::error& e) { dout(0) << "unable to decode PGLS_FILTER description in " << *m << dendl; result = -EINVAL; break; } std::tie(result, filter) = get_pgls_filter(bp); if (result < 0) break; ceph_assert(filter); // fall through case CEPH_OSD_OP_PGNLS: if (snapid != CEPH_NOSNAP) { result = -EINVAL; break; } if (get_osdmap()->raw_pg_to_pg(m->get_pg()) != info.pgid.pgid) { dout(10) << " pgnls pg=" << m->get_pg() << " " << get_osdmap()->raw_pg_to_pg(m->get_pg()) << " != " << info.pgid << dendl; result = 0; // hmm? } else { unsigned list_size = std::min(cct->_conf->osd_max_pgls, p->op.pgls.count); dout(10) << " pgnls pg=" << m->get_pg() << " count " << list_size << dendl; // read into a buffer vector sentries; pg_nls_response_t response; try { decode(response.handle, bp); } catch (const ceph::buffer::error& e) { dout(0) << "unable to decode PGNLS handle in " << *m << dendl; result = -EINVAL; break; } hobject_t next; hobject_t lower_bound = response.handle; hobject_t pg_start = info.pgid.pgid.get_hobj_start(); hobject_t pg_end = info.pgid.pgid.get_hobj_end(pool.info.get_pg_num()); dout(10) << " pgnls lower_bound " << lower_bound << " pg_end " << pg_end << dendl; if (((!lower_bound.is_max() && lower_bound >= pg_end) || (lower_bound != hobject_t() && lower_bound < pg_start))) { // this should only happen with a buggy client. dout(10) << "outside of PG bounds " << pg_start << " .. " << pg_end << dendl; result = -EINVAL; break; } hobject_t current = lower_bound; int r = pgbackend->objects_list_partial( current, list_size, list_size, &sentries, &next); if (r != 0) { result = -EINVAL; break; } map::const_iterator missing_iter = recovery_state.get_pg_log().get_missing().get_items().lower_bound(current); vector::iterator ls_iter = sentries.begin(); hobject_t _max = hobject_t::get_max(); while (1) { const hobject_t &mcand = missing_iter == recovery_state.get_pg_log().get_missing().get_items().end() ? _max : missing_iter->first; const hobject_t &lcand = ls_iter == sentries.end() ? _max : *ls_iter; hobject_t candidate; if (mcand == lcand) { candidate = mcand; if (!mcand.is_max()) { ++ls_iter; ++missing_iter; } } else if (mcand < lcand) { candidate = mcand; ceph_assert(!mcand.is_max()); ++missing_iter; } else { candidate = lcand; ceph_assert(!lcand.is_max()); ++ls_iter; } dout(10) << " pgnls candidate 0x" << std::hex << candidate.get_hash() << " vs lower bound 0x" << lower_bound.get_hash() << std::dec << dendl; if (candidate >= next) { break; } if (response.entries.size() == list_size) { next = candidate; break; } if (candidate.snap != CEPH_NOSNAP) continue; // skip internal namespace if (candidate.get_namespace() == cct->_conf->osd_hit_set_namespace) continue; if (recovery_state.get_missing_loc().is_deleted(candidate)) continue; // skip wrong namespace if (m->get_hobj().nspace != librados::all_nspaces && candidate.get_namespace() != m->get_hobj().nspace) continue; if (filter && !pgls_filter(*filter, candidate)) continue; dout(20) << "pgnls item 0x" << std::hex << candidate.get_hash() << ", rev 0x" << hobject_t::_reverse_bits(candidate.get_hash()) << std::dec << " " << candidate.oid.name << dendl; librados::ListObjectImpl item; item.nspace = candidate.get_namespace(); item.oid = candidate.oid.name; item.locator = candidate.get_key(); response.entries.push_back(item); } if (next.is_max() && missing_iter == recovery_state.get_pg_log().get_missing().get_items().end() && ls_iter == sentries.end()) { result = 1; // Set response.handle to the start of the next PG according // to the object sort order. response.handle = info.pgid.pgid.get_hobj_end(pool.info.get_pg_num()); } else { response.handle = next; } dout(10) << "pgnls handle=" << response.handle << dendl; encode(response, osd_op.outdata); dout(10) << " pgnls result=" << result << " outdata.length()=" << osd_op.outdata.length() << dendl; } break; case CEPH_OSD_OP_PGLS_FILTER: try { decode(cname, bp); decode(mname, bp); } catch (const ceph::buffer::error& e) { dout(0) << "unable to decode PGLS_FILTER description in " << *m << dendl; result = -EINVAL; break; } std::tie(result, filter) = get_pgls_filter(bp); if (result < 0) break; ceph_assert(filter); // fall through case CEPH_OSD_OP_PGLS: if (snapid != CEPH_NOSNAP) { result = -EINVAL; break; } if (get_osdmap()->raw_pg_to_pg(m->get_pg()) != info.pgid.pgid) { dout(10) << " pgls pg=" << m->get_pg() << " " << get_osdmap()->raw_pg_to_pg(m->get_pg()) << " != " << info.pgid << dendl; result = 0; // hmm? } else { unsigned list_size = std::min(cct->_conf->osd_max_pgls, p->op.pgls.count); dout(10) << " pgls pg=" << m->get_pg() << " count " << list_size << dendl; // read into a buffer vector sentries; pg_ls_response_t response; try { decode(response.handle, bp); } catch (const ceph::buffer::error& e) { dout(0) << "unable to decode PGLS handle in " << *m << dendl; result = -EINVAL; break; } hobject_t next; hobject_t current = response.handle; int r = pgbackend->objects_list_partial( current, list_size, list_size, &sentries, &next); if (r != 0) { result = -EINVAL; break; } ceph_assert(snapid == CEPH_NOSNAP || recovery_state.get_pg_log().get_missing().get_items().empty()); map::const_iterator missing_iter = recovery_state.get_pg_log().get_missing().get_items().lower_bound(current); vector::iterator ls_iter = sentries.begin(); hobject_t _max = hobject_t::get_max(); while (1) { const hobject_t &mcand = missing_iter == recovery_state.get_pg_log().get_missing().get_items().end() ? _max : missing_iter->first; const hobject_t &lcand = ls_iter == sentries.end() ? _max : *ls_iter; hobject_t candidate; if (mcand == lcand) { candidate = mcand; if (!mcand.is_max()) { ++ls_iter; ++missing_iter; } } else if (mcand < lcand) { candidate = mcand; ceph_assert(!mcand.is_max()); ++missing_iter; } else { candidate = lcand; ceph_assert(!lcand.is_max()); ++ls_iter; } if (candidate >= next) { break; } if (response.entries.size() == list_size) { next = candidate; break; } if (candidate.snap != CEPH_NOSNAP) continue; // skip wrong namespace if (candidate.get_namespace() != m->get_hobj().nspace) continue; if (recovery_state.get_missing_loc().is_deleted(candidate)) continue; if (filter && !pgls_filter(*filter, candidate)) continue; response.entries.push_back(make_pair(candidate.oid, candidate.get_key())); } if (next.is_max() && missing_iter == recovery_state.get_pg_log().get_missing().get_items().end() && ls_iter == sentries.end()) { result = 1; } response.handle = next; encode(response, osd_op.outdata); dout(10) << " pgls result=" << result << " outdata.length()=" << osd_op.outdata.length() << dendl; } break; case CEPH_OSD_OP_PG_HITSET_LS: { list< pair > ls; for (list::const_iterator p = info.hit_set.history.begin(); p != info.hit_set.history.end(); ++p) ls.push_back(make_pair(p->begin, p->end)); if (hit_set) ls.push_back(make_pair(hit_set_start_stamp, utime_t())); encode(ls, osd_op.outdata); } break; case CEPH_OSD_OP_PG_HITSET_GET: { utime_t stamp(osd_op.op.hit_set_get.stamp); if (hit_set_start_stamp && stamp >= hit_set_start_stamp) { // read the current in-memory HitSet, not the version we've // checkpointed. if (!hit_set) { result= -ENOENT; break; } encode(*hit_set, osd_op.outdata); result = osd_op.outdata.length(); } else { // read an archived HitSet. hobject_t oid; for (list::const_iterator p = info.hit_set.history.begin(); p != info.hit_set.history.end(); ++p) { if (stamp >= p->begin && stamp <= p->end) { oid = get_hit_set_archive_object(p->begin, p->end, p->using_gmt); break; } } if (oid == hobject_t()) { result = -ENOENT; break; } if (!pool.info.is_replicated()) { // FIXME: EC not supported yet result = -EOPNOTSUPP; break; } if (is_unreadable_object(oid)) { wait_for_unreadable_object(oid, op); return; } result = osd->store->read(ch, ghobject_t(oid), 0, 0, osd_op.outdata); } } break; case CEPH_OSD_OP_SCRUBLS: result = do_scrub_ls(m, &osd_op); break; default: result = -EINVAL; break; } if (result < 0) break; } // reply MOSDOpReply *reply = new MOSDOpReply(m, 0, get_osdmap_epoch(), CEPH_OSD_FLAG_ACK | CEPH_OSD_FLAG_ONDISK, false); reply->claim_op_out_data(ops); reply->set_result(result); reply->set_reply_versions(info.last_update, info.last_user_version); osd->send_message_osd_client(reply, m->get_connection()); } int PrimaryLogPG::do_scrub_ls(const MOSDOp *m, OSDOp *osd_op) { if (m->get_pg() != info.pgid.pgid) { dout(10) << " scrubls pg=" << m->get_pg() << " != " << info.pgid << dendl; return -EINVAL; // hmm? } auto bp = osd_op->indata.cbegin(); scrub_ls_arg_t arg; try { arg.decode(bp); } catch (ceph::buffer::error&) { dout(10) << " corrupted scrub_ls_arg_t" << dendl; return -EINVAL; } int r = 0; scrub_ls_result_t result = {.interval = info.history.same_interval_since}; if (arg.interval != 0 && arg.interval != info.history.same_interval_since) { r = -EAGAIN; } else { bool store_queried = m_scrubber && m_scrubber->get_store_errors(arg, result); if (store_queried) { encode(result, osd_op->outdata); } else { // the scrubber's store is not initialized r = -ENOENT; } } return r; } /** * Releases locks * * @param manager [in] manager with locks to release */ void PrimaryLogPG::release_object_locks( ObcLockManager &lock_manager) { std::list > > to_req; bool requeue_recovery = false; bool requeue_snaptrim = false; lock_manager.put_locks( &to_req, &requeue_recovery, &requeue_snaptrim); if (requeue_recovery) queue_recovery(); if (requeue_snaptrim) snap_trimmer_machine.process_event(TrimWriteUnblocked()); if (!to_req.empty()) { // requeue at front of scrub blocking queue if we are blocked by scrub for (auto &&p: to_req) { if (m_scrubber->write_blocked_by_scrub(p.first->obs.oi.soid.get_head())) { for (auto& op : p.second) { op->mark_delayed("waiting for scrub"); } waiting_for_scrub.splice( waiting_for_scrub.begin(), p.second, p.second.begin(), p.second.end()); } else if (is_laggy()) { for (auto& op : p.second) { op->mark_delayed("waiting for readable"); } waiting_for_readable.splice( waiting_for_readable.begin(), p.second, p.second.begin(), p.second.end()); } else { requeue_ops(p.second); } } } } PrimaryLogPG::PrimaryLogPG(OSDService *o, OSDMapRef curmap, const PGPool &_pool, const map& ec_profile, spg_t p) : PG(o, curmap, _pool, p), pgbackend( PGBackend::build_pg_backend( _pool.info, ec_profile, this, coll_t(p), ch, o->store, cct)), object_contexts(o->cct, o->cct->_conf->osd_pg_object_context_cache_count), new_backfill(false), temp_seq(0), snap_trimmer_machine(this) { recovery_state.set_backend_predicates( pgbackend->get_is_readable_predicate(), pgbackend->get_is_recoverable_predicate()); snap_trimmer_machine.initiate(); m_scrubber = make_unique(this); } PrimaryLogPG::~PrimaryLogPG() { m_scrubber.reset(); } void PrimaryLogPG::get_src_oloc(const object_t& oid, const object_locator_t& oloc, object_locator_t& src_oloc) { src_oloc = oloc; if (oloc.key.empty()) src_oloc.key = oid.name; } void PrimaryLogPG::handle_backoff(OpRequestRef& op) { auto m = op->get_req(); auto session = ceph::ref_cast(m->get_connection()->get_priv()); if (!session) return; // drop it. hobject_t begin = info.pgid.pgid.get_hobj_start(); hobject_t end = info.pgid.pgid.get_hobj_end(pool.info.get_pg_num()); if (begin < m->begin) { begin = m->begin; } if (end > m->end) { end = m->end; } dout(10) << __func__ << " backoff ack id " << m->id << " [" << begin << "," << end << ")" << dendl; session->ack_backoff(cct, m->pgid, m->id, begin, end); } void PrimaryLogPG::do_request( OpRequestRef& op, ThreadPool::TPHandle &handle) { if (op->osd_trace) { op->pg_trace.init("pg op", &trace_endpoint, &op->osd_trace); op->pg_trace.event("do request"); } #ifdef HAVE_JAEGER if (op->osd_parent_span) { auto do_req_span = jaeger_tracing::child_span(__func__, op->osd_parent_span); } #endif // make sure we have a new enough map auto p = waiting_for_map.find(op->get_source()); if (p != waiting_for_map.end()) { // preserve ordering dout(20) << __func__ << " waiting_for_map " << p->first << " not empty, queueing" << dendl; p->second.push_back(op); op->mark_delayed("waiting_for_map not empty"); return; } if (!have_same_or_newer_map(op->min_epoch)) { dout(20) << __func__ << " min " << op->min_epoch << ", queue on waiting_for_map " << op->get_source() << dendl; waiting_for_map[op->get_source()].push_back(op); op->mark_delayed("op must wait for map"); osd->request_osdmap_update(op->min_epoch); return; } if (can_discard_request(op)) { return; } // pg-wide backoffs const Message *m = op->get_req(); int msg_type = m->get_type(); if (m->get_connection()->has_feature(CEPH_FEATURE_RADOS_BACKOFF)) { auto session = ceph::ref_cast(m->get_connection()->get_priv()); if (!session) return; // drop it. if (msg_type == CEPH_MSG_OSD_OP) { if (session->check_backoff(cct, info.pgid, info.pgid.pgid.get_hobj_start(), m)) { return; } bool backoff = is_down() || is_incomplete() || (!is_active() && is_peered()); if (g_conf()->osd_backoff_on_peering && !backoff) { if (is_peering()) { backoff = true; } } if (backoff) { add_pg_backoff(session); return; } } // pg backoff acks at pg-level if (msg_type == CEPH_MSG_OSD_BACKOFF) { const MOSDBackoff *ba = static_cast(m); if (ba->begin != ba->end) { handle_backoff(op); return; } } } if (!is_peered()) { // Delay unless PGBackend says it's ok if (pgbackend->can_handle_while_inactive(op)) { bool handled = pgbackend->handle_message(op); ceph_assert(handled); return; } else { waiting_for_peered.push_back(op); op->mark_delayed("waiting for peered"); return; } } if (recovery_state.needs_flush()) { dout(20) << "waiting for flush on " << op << dendl; waiting_for_flush.push_back(op); op->mark_delayed("waiting for flush"); return; } ceph_assert(is_peered() && !recovery_state.needs_flush()); if (pgbackend->handle_message(op)) return; switch (msg_type) { case CEPH_MSG_OSD_OP: case CEPH_MSG_OSD_BACKOFF: if (!is_active()) { dout(20) << " peered, not active, waiting for active on " << op << dendl; waiting_for_active.push_back(op); op->mark_delayed("waiting for active"); return; } switch (msg_type) { case CEPH_MSG_OSD_OP: // verify client features if ((pool.info.has_tiers() || pool.info.is_tier()) && !op->has_feature(CEPH_FEATURE_OSD_CACHEPOOL)) { osd->reply_op_error(op, -EOPNOTSUPP); return; } do_op(op); break; case CEPH_MSG_OSD_BACKOFF: // object-level backoff acks handled in osdop context handle_backoff(op); break; } break; case MSG_OSD_PG_SCAN: do_scan(op, handle); break; case MSG_OSD_PG_BACKFILL: do_backfill(op); break; case MSG_OSD_PG_BACKFILL_REMOVE: do_backfill_remove(op); break; case MSG_OSD_SCRUB_RESERVE: { if (!m_scrubber) { osd->reply_op_error(op, -EAGAIN); return; } auto m = op->get_req(); switch (m->type) { case MOSDScrubReserve::REQUEST: m_scrubber->handle_scrub_reserve_request(op); break; case MOSDScrubReserve::GRANT: m_scrubber->handle_scrub_reserve_grant(op, m->from); break; case MOSDScrubReserve::REJECT: m_scrubber->handle_scrub_reserve_reject(op, m->from); break; case MOSDScrubReserve::RELEASE: m_scrubber->handle_scrub_reserve_release(op); break; } } break; case MSG_OSD_REP_SCRUB: replica_scrub(op, handle); break; case MSG_OSD_REP_SCRUBMAP: do_replica_scrub_map(op); break; case MSG_OSD_PG_UPDATE_LOG_MISSING: do_update_log_missing(op); break; case MSG_OSD_PG_UPDATE_LOG_MISSING_REPLY: do_update_log_missing_reply(op); break; default: ceph_abort_msg("bad message type in do_request"); } } /** do_op - do an op * pg lock will be held (if multithreaded) * osd_lock NOT held. */ void PrimaryLogPG::do_op(OpRequestRef& op) { FUNCTRACE(cct); // NOTE: take a non-const pointer here; we must be careful not to // change anything that will break other reads on m (operator<<). MOSDOp *m = static_cast(op->get_nonconst_req()); ceph_assert(m->get_type() == CEPH_MSG_OSD_OP); if (m->finish_decode()) { op->reset_desc(); // for TrackedOp m->clear_payload(); } dout(20) << __func__ << ": op " << *m << dendl; const hobject_t head = m->get_hobj().get_head(); if (!info.pgid.pgid.contains( info.pgid.pgid.get_split_bits(pool.info.get_pg_num()), head)) { derr << __func__ << " " << info.pgid.pgid << " does not contain " << head << " pg_num " << pool.info.get_pg_num() << " hash " << std::hex << head.get_hash() << std::dec << dendl; osd->clog->warn() << info.pgid.pgid << " does not contain " << head << " op " << *m; ceph_assert(!cct->_conf->osd_debug_misdirected_ops); return; } bool can_backoff = m->get_connection()->has_feature(CEPH_FEATURE_RADOS_BACKOFF); ceph::ref_t session; if (can_backoff) { session = static_cast(m->get_connection()->get_priv().get()); if (!session.get()) { dout(10) << __func__ << " no session" << dendl; return; } if (session->check_backoff(cct, info.pgid, head, m)) { return; } } if (m->has_flag(CEPH_OSD_FLAG_PARALLELEXEC)) { // not implemented. dout(20) << __func__ << ": PARALLELEXEC not implemented " << *m << dendl; osd->reply_op_error(op, -EINVAL); return; } { int r = op->maybe_init_op_info(*get_osdmap()); if (r) { osd->reply_op_error(op, r); return; } } if ((m->get_flags() & (CEPH_OSD_FLAG_BALANCE_READS | CEPH_OSD_FLAG_LOCALIZE_READS)) && op->may_read() && !(op->may_write() || op->may_cache())) { // balanced reads; any replica will do if (!(is_primary() || is_nonprimary())) { osd->handle_misdirected_op(this, op); return; } } else { // normal case; must be primary if (!is_primary()) { osd->handle_misdirected_op(this, op); return; } } if (!check_laggy(op)) { return; } if (!op_has_sufficient_caps(op)) { osd->reply_op_error(op, -EPERM); return; } if (op->includes_pg_op()) { return do_pg_op(op); } // object name too long? if (m->get_oid().name.size() > cct->_conf->osd_max_object_name_len) { dout(4) << "do_op name is longer than " << cct->_conf->osd_max_object_name_len << " bytes" << dendl; osd->reply_op_error(op, -ENAMETOOLONG); return; } if (m->get_hobj().get_key().size() > cct->_conf->osd_max_object_name_len) { dout(4) << "do_op locator is longer than " << cct->_conf->osd_max_object_name_len << " bytes" << dendl; osd->reply_op_error(op, -ENAMETOOLONG); return; } if (m->get_hobj().nspace.size() > cct->_conf->osd_max_object_namespace_len) { dout(4) << "do_op namespace is longer than " << cct->_conf->osd_max_object_namespace_len << " bytes" << dendl; osd->reply_op_error(op, -ENAMETOOLONG); return; } if (m->get_hobj().oid.name.empty()) { dout(4) << "do_op empty oid name is not allowed" << dendl; osd->reply_op_error(op, -EINVAL); return; } if (int r = osd->store->validate_hobject_key(head)) { dout(4) << "do_op object " << head << " invalid for backing store: " << r << dendl; osd->reply_op_error(op, r); return; } // blocklisted? if (get_osdmap()->is_blocklisted(m->get_source_addr())) { dout(10) << "do_op " << m->get_source_addr() << " is blocklisted" << dendl; osd->reply_op_error(op, -EBLOCKLISTED); return; } // order this op as a write? bool write_ordered = op->rwordered(); // discard due to cluster full transition? (we discard any op that // originates before the cluster or pool is marked full; the client // will resend after the full flag is removed or if they expect the // op to succeed despite being full). The except is FULL_FORCE and // FULL_TRY ops, which there is no reason to discard because they // bypass all full checks anyway. If this op isn't write or // read-ordered, we skip. // FIXME: we exclude mds writes for now. if (write_ordered && !(m->get_source().is_mds() || m->has_flag(CEPH_OSD_FLAG_FULL_TRY) || m->has_flag(CEPH_OSD_FLAG_FULL_FORCE)) && info.history.last_epoch_marked_full > m->get_map_epoch()) { dout(10) << __func__ << " discarding op sent before full " << m << " " << *m << dendl; return; } // mds should have stopped writing before this point. // We can't allow OSD to become non-startable even if mds // could be writing as part of file removals. if (write_ordered && osd->check_failsafe_full(get_dpp()) && !m->has_flag(CEPH_OSD_FLAG_FULL_TRY)) { dout(10) << __func__ << " fail-safe full check failed, dropping request." << dendl; return; } int64_t poolid = get_pgid().pool(); if (op->may_write()) { const pg_pool_t *pi = get_osdmap()->get_pg_pool(poolid); if (!pi) { return; } // invalid? if (m->get_snapid() != CEPH_NOSNAP) { dout(20) << __func__ << ": write to clone not valid " << *m << dendl; osd->reply_op_error(op, -EINVAL); return; } // too big? if (cct->_conf->osd_max_write_size && m->get_data_len() > cct->_conf->osd_max_write_size << 20) { // journal can't hold commit! derr << "do_op msg data len " << m->get_data_len() << " > osd_max_write_size " << (cct->_conf->osd_max_write_size << 20) << " on " << *m << dendl; osd->reply_op_error(op, -OSD_WRITETOOBIG); return; } } dout(10) << "do_op " << *m << (op->may_write() ? " may_write" : "") << (op->may_read() ? " may_read" : "") << (op->may_cache() ? " may_cache" : "") << " -> " << (write_ordered ? "write-ordered" : "read-ordered") << " flags " << ceph_osd_flag_string(m->get_flags()) << dendl; #ifdef HAVE_JAEGER if (op->osd_parent_span) { auto do_op_span = jaeger_tracing::child_span(__func__, op->osd_parent_span); } #endif // missing object? if (is_unreadable_object(head)) { if (!is_primary()) { osd->reply_op_error(op, -EAGAIN); return; } if (can_backoff && (g_conf()->osd_backoff_on_degraded || (g_conf()->osd_backoff_on_unfound && recovery_state.get_missing_loc().is_unfound(head)))) { add_backoff(session, head, head); maybe_kick_recovery(head); } else { wait_for_unreadable_object(head, op); } return; } if (write_ordered) { // degraded object? if (is_degraded_or_backfilling_object(head)) { if (can_backoff && g_conf()->osd_backoff_on_degraded) { add_backoff(session, head, head); maybe_kick_recovery(head); } else { wait_for_degraded_object(head, op); } return; } if (m_scrubber->is_scrub_active() && m_scrubber->write_blocked_by_scrub(head)) { dout(20) << __func__ << ": waiting for scrub" << dendl; waiting_for_scrub.push_back(op); op->mark_delayed("waiting for scrub"); return; } if (!check_laggy_requeue(op)) { return; } // blocked on snap? if (auto blocked_iter = objects_blocked_on_degraded_snap.find(head); blocked_iter != std::end(objects_blocked_on_degraded_snap)) { hobject_t to_wait_on(head); to_wait_on.snap = blocked_iter->second; wait_for_degraded_object(to_wait_on, op); return; } if (auto blocked_snap_promote_iter = objects_blocked_on_snap_promotion.find(head); blocked_snap_promote_iter != std::end(objects_blocked_on_snap_promotion)) { wait_for_blocked_object(blocked_snap_promote_iter->second->obs.oi.soid, op); return; } if (objects_blocked_on_cache_full.count(head)) { block_write_on_full_cache(head, op); return; } } // dup/resent? if (op->may_write() || op->may_cache()) { // warning: we will get back *a* request for this reqid, but not // necessarily the most recent. this happens with flush and // promote ops, but we can't possible have both in our log where // the original request is still not stable on disk, so for our // purposes here it doesn't matter which one we get. eversion_t version; version_t user_version; int return_code = 0; vector op_returns; bool got = check_in_progress_op( m->get_reqid(), &version, &user_version, &return_code, &op_returns); if (got) { dout(3) << __func__ << " dup " << m->get_reqid() << " version " << version << dendl; if (already_complete(version)) { osd->reply_op_error(op, return_code, version, user_version, op_returns); } else { dout(10) << " waiting for " << version << " to commit" << dendl; // always queue ondisk waiters, so that we can requeue if needed waiting_for_ondisk[version].emplace_back(op, user_version, return_code, op_returns); op->mark_delayed("waiting for ondisk"); } return; } } ObjectContextRef obc; bool can_create = op->may_write(); hobject_t missing_oid; // kludge around the fact that LIST_SNAPS sets CEPH_SNAPDIR for LIST_SNAPS const hobject_t& oid = m->get_snapid() == CEPH_SNAPDIR ? head : m->get_hobj(); // make sure LIST_SNAPS is on CEPH_SNAPDIR and nothing else for (vector::iterator p = m->ops.begin(); p != m->ops.end(); ++p) { OSDOp& osd_op = *p; if (osd_op.op.op == CEPH_OSD_OP_LIST_SNAPS) { if (m->get_snapid() != CEPH_SNAPDIR) { dout(10) << "LIST_SNAPS with incorrect context" << dendl; osd->reply_op_error(op, -EINVAL); return; } } else { if (m->get_snapid() == CEPH_SNAPDIR) { dout(10) << "non-LIST_SNAPS on snapdir" << dendl; osd->reply_op_error(op, -EINVAL); return; } } } // io blocked on obc? if (!m->has_flag(CEPH_OSD_FLAG_FLUSH) && maybe_await_blocked_head(oid, op)) { return; } if (!is_primary()) { if (!recovery_state.can_serve_replica_read(oid)) { dout(20) << __func__ << ": unstable write on replica, bouncing to primary " << *m << dendl; osd->reply_op_error(op, -EAGAIN); return; } dout(20) << __func__ << ": serving replica read on oid " << oid << dendl; } int r = find_object_context( oid, &obc, can_create, m->has_flag(CEPH_OSD_FLAG_MAP_SNAP_CLONE), &missing_oid); // LIST_SNAPS needs the ssc too if (obc && m->get_snapid() == CEPH_SNAPDIR && !obc->ssc) { obc->ssc = get_snapset_context(oid, true); } if (r == -EAGAIN) { // If we're not the primary of this OSD, we just return -EAGAIN. Otherwise, // we have to wait for the object. if (is_primary()) { // missing the specific snap we need; requeue and wait. ceph_assert(!op->may_write()); // only happens on a read/cache wait_for_unreadable_object(missing_oid, op); return; } } else if (r == 0) { if (is_unreadable_object(obc->obs.oi.soid)) { dout(10) << __func__ << ": clone " << obc->obs.oi.soid << " is unreadable, waiting" << dendl; wait_for_unreadable_object(obc->obs.oi.soid, op); return; } // degraded object? (the check above was for head; this could be a clone) if (write_ordered && obc->obs.oi.soid.snap != CEPH_NOSNAP && is_degraded_or_backfilling_object(obc->obs.oi.soid)) { dout(10) << __func__ << ": clone " << obc->obs.oi.soid << " is degraded, waiting" << dendl; wait_for_degraded_object(obc->obs.oi.soid, op); return; } } bool in_hit_set = false; if (hit_set) { if (obc.get()) { if (obc->obs.oi.soid != hobject_t() && hit_set->contains(obc->obs.oi.soid)) in_hit_set = true; } else { if (missing_oid != hobject_t() && hit_set->contains(missing_oid)) in_hit_set = true; } if (!op->hitset_inserted) { hit_set->insert(oid); op->hitset_inserted = true; if (hit_set->is_full() || hit_set_start_stamp + pool.info.hit_set_period <= m->get_recv_stamp()) { hit_set_persist(); } } } if (agent_state) { if (agent_choose_mode(false, op)) return; } if (obc.get() && obc->obs.exists && obc->obs.oi.has_manifest()) { if (recover_adjacent_clones(obc, op)) { return; } if (maybe_handle_manifest(op, write_ordered, obc)) return; } if (maybe_handle_cache(op, write_ordered, obc, r, missing_oid, false, in_hit_set)) return; if (r && (r != -ENOENT || !obc)) { // copy the reqids for copy get on ENOENT if (r == -ENOENT && (m->ops[0].op.op == CEPH_OSD_OP_COPY_GET)) { fill_in_copy_get_noent(op, oid, m->ops[0]); return; } dout(20) << __func__ << ": find_object_context got error " << r << dendl; if (op->may_write() && get_osdmap()->require_osd_release >= ceph_release_t::kraken) { record_write_error(op, oid, nullptr, r); } else { osd->reply_op_error(op, r); } return; } // make sure locator is consistent object_locator_t oloc(obc->obs.oi.soid); if (m->get_object_locator() != oloc) { dout(10) << " provided locator " << m->get_object_locator() << " != object's " << obc->obs.oi.soid << dendl; osd->clog->warn() << "bad locator " << m->get_object_locator() << " on object " << oloc << " op " << *m; } // io blocked on obc? if (obc->is_blocked() && !m->has_flag(CEPH_OSD_FLAG_FLUSH)) { wait_for_blocked_object(obc->obs.oi.soid, op); return; } dout(25) << __func__ << " oi " << obc->obs.oi << dendl; OpContext *ctx = new OpContext(op, m->get_reqid(), &m->ops, obc, this); if (m->has_flag(CEPH_OSD_FLAG_SKIPRWLOCKS)) { dout(20) << __func__ << ": skipping rw locks" << dendl; } else if (m->get_flags() & CEPH_OSD_FLAG_FLUSH) { dout(20) << __func__ << ": part of flush, will ignore write lock" << dendl; // verify there is in fact a flush in progress // FIXME: we could make this a stronger test. map::iterator p = flush_ops.find(obc->obs.oi.soid); if (p == flush_ops.end()) { dout(10) << __func__ << " no flush in progress, aborting" << dendl; reply_ctx(ctx, -EINVAL); return; } } else if (!get_rw_locks(write_ordered, ctx)) { dout(20) << __func__ << " waiting for rw locks " << dendl; op->mark_delayed("waiting for rw locks"); close_op_ctx(ctx); return; } dout(20) << __func__ << " obc " << *obc << dendl; if (r) { dout(20) << __func__ << " returned an error: " << r << dendl; if (op->may_write() && get_osdmap()->require_osd_release >= ceph_release_t::kraken) { record_write_error(op, oid, nullptr, r, ctx->op->allows_returnvec() ? ctx : nullptr); } else { osd->reply_op_error(op, r); } close_op_ctx(ctx); return; } if (m->has_flag(CEPH_OSD_FLAG_IGNORE_CACHE)) { ctx->ignore_cache = true; } if ((op->may_read()) && (obc->obs.oi.is_lost())) { // This object is lost. Reading from it returns an error. dout(20) << __func__ << ": object " << obc->obs.oi.soid << " is lost" << dendl; reply_ctx(ctx, -ENFILE); return; } if (!op->may_write() && !op->may_cache() && (!obc->obs.exists || ((m->get_snapid() != CEPH_SNAPDIR) && obc->obs.oi.is_whiteout()))) { // copy the reqids for copy get on ENOENT if (m->ops[0].op.op == CEPH_OSD_OP_COPY_GET) { fill_in_copy_get_noent(op, oid, m->ops[0]); close_op_ctx(ctx); return; } reply_ctx(ctx, -ENOENT); return; } op->mark_started(); execute_ctx(ctx); utime_t prepare_latency = ceph_clock_now(); prepare_latency -= op->get_dequeued_time(); osd->logger->tinc(l_osd_op_prepare_lat, prepare_latency); if (op->may_read() && op->may_write()) { osd->logger->tinc(l_osd_op_rw_prepare_lat, prepare_latency); } else if (op->may_read()) { osd->logger->tinc(l_osd_op_r_prepare_lat, prepare_latency); } else if (op->may_write() || op->may_cache()) { osd->logger->tinc(l_osd_op_w_prepare_lat, prepare_latency); } // force recovery of the oldest missing object if too many logs maybe_force_recovery(); } PrimaryLogPG::cache_result_t PrimaryLogPG::maybe_handle_manifest_detail( OpRequestRef op, bool write_ordered, ObjectContextRef obc) { ceph_assert(obc); if (op->get_req()->get_flags() & CEPH_OSD_FLAG_IGNORE_REDIRECT) { dout(20) << __func__ << ": ignoring redirect due to flag" << dendl; return cache_result_t::NOOP; } // if it is write-ordered and blocked, stop now if (obc->is_blocked() && write_ordered) { // we're already doing something with this object dout(20) << __func__ << " blocked on " << obc->obs.oi.soid << dendl; return cache_result_t::NOOP; } vector ops = op->get_req()->ops; for (vector::iterator p = ops.begin(); p != ops.end(); ++p) { OSDOp& osd_op = *p; ceph_osd_op& op = osd_op.op; if (op.op == CEPH_OSD_OP_SET_REDIRECT || op.op == CEPH_OSD_OP_SET_CHUNK || op.op == CEPH_OSD_OP_UNSET_MANIFEST || op.op == CEPH_OSD_OP_TIER_PROMOTE || op.op == CEPH_OSD_OP_TIER_FLUSH || op.op == CEPH_OSD_OP_TIER_EVICT) { return cache_result_t::NOOP; } } switch (obc->obs.oi.manifest.type) { case object_manifest_t::TYPE_REDIRECT: if (op->may_write() || write_ordered) { do_proxy_write(op, obc); } else { // promoted object if (obc->obs.oi.size != 0) { return cache_result_t::NOOP; } do_proxy_read(op, obc); } return cache_result_t::HANDLED_PROXY; case object_manifest_t::TYPE_CHUNKED: { if (can_proxy_chunked_read(op, obc)) { map::iterator p = flush_ops.find(obc->obs.oi.soid); if (p != flush_ops.end()) { do_proxy_chunked_op(op, obc->obs.oi.soid, obc, true); return cache_result_t::HANDLED_PROXY; } do_proxy_chunked_op(op, obc->obs.oi.soid, obc, write_ordered); return cache_result_t::HANDLED_PROXY; } MOSDOp *m = static_cast(op->get_nonconst_req()); ceph_assert(m->get_type() == CEPH_MSG_OSD_OP); hobject_t head = m->get_hobj(); if (is_degraded_or_backfilling_object(head)) { dout(20) << __func__ << ": " << head << " is degraded, waiting" << dendl; wait_for_degraded_object(head, op); return cache_result_t::BLOCKED_RECOVERY; } if (m_scrubber->write_blocked_by_scrub(head)) { dout(20) << __func__ << ": waiting for scrub" << dendl; waiting_for_scrub.push_back(op); op->mark_delayed("waiting for scrub"); return cache_result_t::BLOCKED_RECOVERY; } if (!check_laggy_requeue(op)) { return cache_result_t::BLOCKED_RECOVERY; } for (auto& p : obc->obs.oi.manifest.chunk_map) { if (p.second.is_missing()) { auto m = op->get_req(); const object_locator_t oloc = m->get_object_locator(); promote_object(obc, obc->obs.oi.soid, oloc, op, NULL); return cache_result_t::BLOCKED_PROMOTE; } } return cache_result_t::NOOP; } default: ceph_abort_msg("unrecognized manifest type"); } return cache_result_t::NOOP; } void PrimaryLogPG::record_write_error(OpRequestRef op, const hobject_t &soid, MOSDOpReply *orig_reply, int r, OpContext *ctx_for_op_returns) { dout(20) << __func__ << " r=" << r << dendl; ceph_assert(op->may_write()); const osd_reqid_t &reqid = op->get_req()->get_reqid(); mempool::osd_pglog::list entries; entries.push_back(pg_log_entry_t(pg_log_entry_t::ERROR, soid, get_next_version(), eversion_t(), 0, reqid, utime_t(), r)); if (ctx_for_op_returns) { entries.back().set_op_returns(*ctx_for_op_returns->ops); dout(20) << __func__ << " op_returns=" << entries.back().op_returns << dendl; } struct OnComplete { PrimaryLogPG *pg; OpRequestRef op; boost::intrusive_ptr orig_reply; int r; OnComplete( PrimaryLogPG *pg, OpRequestRef op, MOSDOpReply *orig_reply, int r) : pg(pg), op(op), orig_reply(orig_reply, false /* take over ref */), r(r) {} void operator()() { ldpp_dout(pg, 20) << "finished " << __func__ << " r=" << r << dendl; auto m = op->get_req(); MOSDOpReply *reply = orig_reply.detach(); ldpp_dout(pg, 10) << " sending commit on " << *m << " " << reply << dendl; pg->osd->send_message_osd_client(reply, m->get_connection()); } }; ObcLockManager lock_manager; submit_log_entries( entries, std::move(lock_manager), std::optional >( OnComplete(this, op, orig_reply, r)), op, r); } PrimaryLogPG::cache_result_t PrimaryLogPG::maybe_handle_cache_detail( OpRequestRef op, bool write_ordered, ObjectContextRef obc, int r, hobject_t missing_oid, bool must_promote, bool in_hit_set, ObjectContextRef *promote_obc) { // return quickly if caching is not enabled if (pool.info.cache_mode == pg_pool_t::CACHEMODE_NONE) return cache_result_t::NOOP; if (op && op->get_req() && op->get_req()->get_type() == CEPH_MSG_OSD_OP && (op->get_req()->get_flags() & CEPH_OSD_FLAG_IGNORE_CACHE)) { dout(20) << __func__ << ": ignoring cache due to flag" << dendl; return cache_result_t::NOOP; } must_promote = must_promote || op->need_promote(); if (obc) dout(25) << __func__ << " " << obc->obs.oi << " " << (obc->obs.exists ? "exists" : "DNE") << " missing_oid " << missing_oid << " must_promote " << (int)must_promote << " in_hit_set " << (int)in_hit_set << dendl; else dout(25) << __func__ << " (no obc)" << " missing_oid " << missing_oid << " must_promote " << (int)must_promote << " in_hit_set " << (int)in_hit_set << dendl; // if it is write-ordered and blocked, stop now if (obc.get() && obc->is_blocked() && write_ordered) { // we're already doing something with this object dout(20) << __func__ << " blocked on " << obc->obs.oi.soid << dendl; return cache_result_t::NOOP; } if (r == -ENOENT && missing_oid == hobject_t()) { // we know this object is logically absent (e.g., an undefined clone) return cache_result_t::NOOP; } if (obc.get() && obc->obs.exists) { osd->logger->inc(l_osd_op_cache_hit); return cache_result_t::NOOP; } if (!is_primary()) { dout(20) << __func__ << " cache miss; ask the primary" << dendl; osd->reply_op_error(op, -EAGAIN); return cache_result_t::REPLIED_WITH_EAGAIN; } if (missing_oid == hobject_t() && obc.get()) { missing_oid = obc->obs.oi.soid; } auto m = op->get_req(); const object_locator_t oloc = m->get_object_locator(); if (op->need_skip_handle_cache()) { return cache_result_t::NOOP; } OpRequestRef promote_op; switch (pool.info.cache_mode) { case pg_pool_t::CACHEMODE_WRITEBACK: if (agent_state && agent_state->evict_mode == TierAgentState::EVICT_MODE_FULL) { if (!op->may_write() && !op->may_cache() && !write_ordered && !must_promote) { dout(20) << __func__ << " cache pool full, proxying read" << dendl; do_proxy_read(op); return cache_result_t::HANDLED_PROXY; } dout(20) << __func__ << " cache pool full, waiting" << dendl; block_write_on_full_cache(missing_oid, op); return cache_result_t::BLOCKED_FULL; } if (must_promote || (!hit_set && !op->need_skip_promote())) { promote_object(obc, missing_oid, oloc, op, promote_obc); return cache_result_t::BLOCKED_PROMOTE; } if (op->may_write() || op->may_cache()) { do_proxy_write(op); // Promote too? if (!op->need_skip_promote() && maybe_promote(obc, missing_oid, oloc, in_hit_set, pool.info.min_write_recency_for_promote, OpRequestRef(), promote_obc)) { return cache_result_t::BLOCKED_PROMOTE; } return cache_result_t::HANDLED_PROXY; } else { do_proxy_read(op); // Avoid duplicate promotion if (obc.get() && obc->is_blocked()) { if (promote_obc) *promote_obc = obc; return cache_result_t::BLOCKED_PROMOTE; } // Promote too? if (!op->need_skip_promote()) { (void)maybe_promote(obc, missing_oid, oloc, in_hit_set, pool.info.min_read_recency_for_promote, promote_op, promote_obc); } return cache_result_t::HANDLED_PROXY; } ceph_abort_msg("unreachable"); return cache_result_t::NOOP; case pg_pool_t::CACHEMODE_READONLY: // TODO: clean this case up if (!obc.get() && r == -ENOENT) { // we don't have the object and op's a read promote_object(obc, missing_oid, oloc, op, promote_obc); return cache_result_t::BLOCKED_PROMOTE; } if (!r) { // it must be a write do_cache_redirect(op); return cache_result_t::HANDLED_REDIRECT; } // crap, there was a failure of some kind return cache_result_t::NOOP; case pg_pool_t::CACHEMODE_FORWARD: // this mode is deprecated; proxy instead case pg_pool_t::CACHEMODE_PROXY: if (!must_promote) { if (op->may_write() || op->may_cache() || write_ordered) { do_proxy_write(op); return cache_result_t::HANDLED_PROXY; } else { do_proxy_read(op); return cache_result_t::HANDLED_PROXY; } } // ugh, we're forced to promote. if (agent_state && agent_state->evict_mode == TierAgentState::EVICT_MODE_FULL) { dout(20) << __func__ << " cache pool full, waiting" << dendl; block_write_on_full_cache(missing_oid, op); return cache_result_t::BLOCKED_FULL; } promote_object(obc, missing_oid, oloc, op, promote_obc); return cache_result_t::BLOCKED_PROMOTE; case pg_pool_t::CACHEMODE_READFORWARD: // this mode is deprecated; proxy instead case pg_pool_t::CACHEMODE_READPROXY: // Do writeback to the cache tier for writes if (op->may_write() || write_ordered || must_promote) { if (agent_state && agent_state->evict_mode == TierAgentState::EVICT_MODE_FULL) { dout(20) << __func__ << " cache pool full, waiting" << dendl; block_write_on_full_cache(missing_oid, op); return cache_result_t::BLOCKED_FULL; } promote_object(obc, missing_oid, oloc, op, promote_obc); return cache_result_t::BLOCKED_PROMOTE; } // If it is a read, we can read, we need to proxy it do_proxy_read(op); return cache_result_t::HANDLED_PROXY; default: ceph_abort_msg("unrecognized cache_mode"); } return cache_result_t::NOOP; } bool PrimaryLogPG::maybe_promote(ObjectContextRef obc, const hobject_t& missing_oid, const object_locator_t& oloc, bool in_hit_set, uint32_t recency, OpRequestRef promote_op, ObjectContextRef *promote_obc) { dout(20) << __func__ << " missing_oid " << missing_oid << " in_hit_set " << in_hit_set << dendl; switch (recency) { case 0: break; case 1: // Check if in the current hit set if (in_hit_set) { break; } else { // not promoting return false; } break; default: { unsigned count = (int)in_hit_set; if (count) { // Check if in other hit sets const hobject_t& oid = obc.get() ? obc->obs.oi.soid : missing_oid; for (map::reverse_iterator itor = agent_state->hit_set_map.rbegin(); itor != agent_state->hit_set_map.rend(); ++itor) { if (!itor->second->contains(oid)) { break; } ++count; if (count >= recency) { break; } } } if (count >= recency) { break; } return false; // not promoting } break; } if (osd->promote_throttle()) { dout(10) << __func__ << " promote throttled" << dendl; return false; } promote_object(obc, missing_oid, oloc, promote_op, promote_obc); return true; } void PrimaryLogPG::do_cache_redirect(OpRequestRef op) { auto m = op->get_req(); int flags = m->get_flags() & (CEPH_OSD_FLAG_ACK|CEPH_OSD_FLAG_ONDISK); MOSDOpReply *reply = new MOSDOpReply(m, -ENOENT, get_osdmap_epoch(), flags, false); request_redirect_t redir(m->get_object_locator(), pool.info.tier_of); reply->set_redirect(redir); dout(10) << "sending redirect to pool " << pool.info.tier_of << " for op " << op << dendl; m->get_connection()->send_message(reply); return; } struct C_ProxyRead : public Context { PrimaryLogPGRef pg; hobject_t oid; epoch_t last_peering_reset; ceph_tid_t tid; PrimaryLogPG::ProxyReadOpRef prdop; utime_t start; C_ProxyRead(PrimaryLogPG *p, hobject_t o, epoch_t lpr, const PrimaryLogPG::ProxyReadOpRef& prd) : pg(p), oid(o), last_peering_reset(lpr), tid(0), prdop(prd), start(ceph_clock_now()) {} void finish(int r) override { if (prdop->canceled) return; std::scoped_lock locker{*pg}; if (prdop->canceled) { return; } if (last_peering_reset == pg->get_last_peering_reset()) { pg->finish_proxy_read(oid, tid, r); pg->osd->logger->tinc(l_osd_tier_r_lat, ceph_clock_now() - start); } } }; struct C_ProxyChunkRead : public Context { PrimaryLogPGRef pg; hobject_t oid; epoch_t last_peering_reset; ceph_tid_t tid; PrimaryLogPG::ProxyReadOpRef prdop; utime_t start; ObjectOperation *obj_op; int op_index = 0; uint64_t req_offset = 0; ObjectContextRef obc; uint64_t req_total_len = 0; C_ProxyChunkRead(PrimaryLogPG *p, hobject_t o, epoch_t lpr, const PrimaryLogPG::ProxyReadOpRef& prd) : pg(p), oid(o), last_peering_reset(lpr), tid(0), prdop(prd), start(ceph_clock_now()), obj_op(NULL) {} void finish(int r) override { if (prdop->canceled) return; std::scoped_lock locker{*pg}; if (prdop->canceled) { return; } if (last_peering_reset == pg->get_last_peering_reset()) { if (r >= 0) { if (!prdop->ops[op_index].outdata.length()) { ceph_assert(req_total_len); bufferlist list; bufferptr bptr(req_total_len); list.push_back(std::move(bptr)); prdop->ops[op_index].outdata.append(list); } ceph_assert(obj_op); uint64_t copy_offset; if (req_offset >= prdop->ops[op_index].op.extent.offset) { copy_offset = req_offset - prdop->ops[op_index].op.extent.offset; } else { copy_offset = 0; } prdop->ops[op_index].outdata.begin(copy_offset).copy_in( obj_op->ops[0].outdata.length(), obj_op->ops[0].outdata.c_str()); } pg->finish_proxy_read(oid, tid, r); pg->osd->logger->tinc(l_osd_tier_r_lat, ceph_clock_now() - start); if (obj_op) { delete obj_op; } } } }; void PrimaryLogPG::do_proxy_read(OpRequestRef op, ObjectContextRef obc) { // NOTE: non-const here because the ProxyReadOp needs mutable refs to // stash the result in the request's OSDOp vector MOSDOp *m = static_cast(op->get_nonconst_req()); object_locator_t oloc; hobject_t soid; /* extensible tier */ if (obc && obc->obs.exists && obc->obs.oi.has_manifest()) { switch (obc->obs.oi.manifest.type) { case object_manifest_t::TYPE_REDIRECT: oloc = object_locator_t(obc->obs.oi.manifest.redirect_target); soid = obc->obs.oi.manifest.redirect_target; break; default: ceph_abort_msg("unrecognized manifest type"); } } else { /* proxy */ soid = m->get_hobj(); oloc = object_locator_t(m->get_object_locator()); oloc.pool = pool.info.tier_of; } unsigned flags = CEPH_OSD_FLAG_IGNORE_CACHE | CEPH_OSD_FLAG_IGNORE_OVERLAY; // pass through some original flags that make sense. // - leave out redirection and balancing flags since we are // already proxying through the primary // - leave off read/write/exec flags that are derived from the op flags |= m->get_flags() & (CEPH_OSD_FLAG_RWORDERED | CEPH_OSD_FLAG_ORDERSNAP | CEPH_OSD_FLAG_ENFORCE_SNAPC | CEPH_OSD_FLAG_MAP_SNAP_CLONE); dout(10) << __func__ << " Start proxy read for " << *m << dendl; ProxyReadOpRef prdop(std::make_shared(op, soid, m->ops)); ObjectOperation obj_op; obj_op.dup(prdop->ops); if (pool.info.cache_mode == pg_pool_t::CACHEMODE_WRITEBACK && (agent_state && agent_state->evict_mode != TierAgentState::EVICT_MODE_FULL)) { for (unsigned i = 0; i < obj_op.ops.size(); i++) { ceph_osd_op op = obj_op.ops[i].op; switch (op.op) { case CEPH_OSD_OP_READ: case CEPH_OSD_OP_SYNC_READ: case CEPH_OSD_OP_SPARSE_READ: case CEPH_OSD_OP_CHECKSUM: case CEPH_OSD_OP_CMPEXT: op.flags = (op.flags | CEPH_OSD_OP_FLAG_FADVISE_SEQUENTIAL) & ~(CEPH_OSD_OP_FLAG_FADVISE_DONTNEED | CEPH_OSD_OP_FLAG_FADVISE_NOCACHE); } } } C_ProxyRead *fin = new C_ProxyRead(this, soid, get_last_peering_reset(), prdop); ceph_tid_t tid = osd->objecter->read( soid.oid, oloc, obj_op, m->get_snapid(), NULL, flags, new C_OnFinisher(fin, osd->get_objecter_finisher(get_pg_shard())), &prdop->user_version, &prdop->data_offset, m->get_features()); fin->tid = tid; prdop->objecter_tid = tid; proxyread_ops[tid] = prdop; in_progress_proxy_ops[soid].push_back(op); } void PrimaryLogPG::finish_proxy_read(hobject_t oid, ceph_tid_t tid, int r) { dout(10) << __func__ << " " << oid << " tid " << tid << " " << cpp_strerror(r) << dendl; map::iterator p = proxyread_ops.find(tid); if (p == proxyread_ops.end()) { dout(10) << __func__ << " no proxyread_op found" << dendl; return; } ProxyReadOpRef prdop = p->second; if (tid != prdop->objecter_tid) { dout(10) << __func__ << " tid " << tid << " != prdop " << prdop << " tid " << prdop->objecter_tid << dendl; return; } if (oid != prdop->soid) { dout(10) << __func__ << " oid " << oid << " != prdop " << prdop << " soid " << prdop->soid << dendl; return; } proxyread_ops.erase(tid); map>::iterator q = in_progress_proxy_ops.find(oid); if (q == in_progress_proxy_ops.end()) { dout(10) << __func__ << " no in_progress_proxy_ops found" << dendl; return; } ceph_assert(q->second.size()); list::iterator it = std::find(q->second.begin(), q->second.end(), prdop->op); ceph_assert(it != q->second.end()); OpRequestRef op = *it; q->second.erase(it); if (q->second.size() == 0) { in_progress_proxy_ops.erase(oid); } else if (std::find(q->second.begin(), q->second.end(), prdop->op) != q->second.end()) { /* multiple read case */ dout(20) << __func__ << " " << oid << " is not completed " << dendl; return; } osd->logger->inc(l_osd_tier_proxy_read); auto m = op->get_req(); OpContext *ctx = new OpContext(op, m->get_reqid(), &prdop->ops, this); ctx->reply = new MOSDOpReply(m, 0, get_osdmap_epoch(), 0, false); ctx->user_at_version = prdop->user_version; ctx->data_off = prdop->data_offset; ctx->ignore_log_op_stats = true; complete_read_ctx(r, ctx); } void PrimaryLogPG::kick_proxy_ops_blocked(hobject_t& soid) { map>::iterator p = in_progress_proxy_ops.find(soid); if (p == in_progress_proxy_ops.end()) return; list& ls = p->second; dout(10) << __func__ << " " << soid << " requeuing " << ls.size() << " requests" << dendl; requeue_ops(ls); in_progress_proxy_ops.erase(p); } void PrimaryLogPG::cancel_proxy_read(ProxyReadOpRef prdop, vector *tids) { dout(10) << __func__ << " " << prdop->soid << dendl; prdop->canceled = true; // cancel objecter op, if we can if (prdop->objecter_tid) { tids->push_back(prdop->objecter_tid); for (uint32_t i = 0; i < prdop->ops.size(); i++) { prdop->ops[i].outdata.clear(); } proxyread_ops.erase(prdop->objecter_tid); prdop->objecter_tid = 0; } } void PrimaryLogPG::cancel_proxy_ops(bool requeue, vector *tids) { dout(10) << __func__ << dendl; // cancel proxy reads map::iterator p = proxyread_ops.begin(); while (p != proxyread_ops.end()) { cancel_proxy_read((p++)->second, tids); } // cancel proxy writes map::iterator q = proxywrite_ops.begin(); while (q != proxywrite_ops.end()) { cancel_proxy_write((q++)->second, tids); } if (requeue) { map>::iterator p = in_progress_proxy_ops.begin(); while (p != in_progress_proxy_ops.end()) { list& ls = p->second; dout(10) << __func__ << " " << p->first << " requeuing " << ls.size() << " requests" << dendl; requeue_ops(ls); in_progress_proxy_ops.erase(p++); } } else { in_progress_proxy_ops.clear(); } } struct C_ProxyWrite_Commit : public Context { PrimaryLogPGRef pg; hobject_t oid; epoch_t last_peering_reset; ceph_tid_t tid; PrimaryLogPG::ProxyWriteOpRef pwop; C_ProxyWrite_Commit(PrimaryLogPG *p, hobject_t o, epoch_t lpr, const PrimaryLogPG::ProxyWriteOpRef& pw) : pg(p), oid(o), last_peering_reset(lpr), tid(0), pwop(pw) {} void finish(int r) override { if (pwop->canceled) return; std::scoped_lock locker{*pg}; if (pwop->canceled) { return; } if (last_peering_reset == pg->get_last_peering_reset()) { pg->finish_proxy_write(oid, tid, r); } } }; void PrimaryLogPG::do_proxy_write(OpRequestRef op, ObjectContextRef obc) { // NOTE: non-const because ProxyWriteOp takes a mutable ref MOSDOp *m = static_cast(op->get_nonconst_req()); object_locator_t oloc; SnapContext snapc(m->get_snap_seq(), m->get_snaps()); hobject_t soid; /* extensible tier */ if (obc && obc->obs.exists && obc->obs.oi.has_manifest()) { switch (obc->obs.oi.manifest.type) { case object_manifest_t::TYPE_REDIRECT: oloc = object_locator_t(obc->obs.oi.manifest.redirect_target); soid = obc->obs.oi.manifest.redirect_target; break; default: ceph_abort_msg("unrecognized manifest type"); } } else { /* proxy */ soid = m->get_hobj(); oloc = object_locator_t(m->get_object_locator()); oloc.pool = pool.info.tier_of; } unsigned flags = CEPH_OSD_FLAG_IGNORE_CACHE | CEPH_OSD_FLAG_IGNORE_OVERLAY; if (!(op->may_write() || op->may_cache())) { flags |= CEPH_OSD_FLAG_RWORDERED; } if (op->allows_returnvec()) { flags |= CEPH_OSD_FLAG_RETURNVEC; } dout(10) << __func__ << " Start proxy write for " << *m << dendl; ProxyWriteOpRef pwop(std::make_shared(op, soid, m->ops, m->get_reqid())); pwop->ctx = new OpContext(op, m->get_reqid(), &pwop->ops, this); pwop->mtime = m->get_mtime(); ObjectOperation obj_op; obj_op.dup(pwop->ops); C_ProxyWrite_Commit *fin = new C_ProxyWrite_Commit( this, soid, get_last_peering_reset(), pwop); ceph_tid_t tid = osd->objecter->mutate( soid.oid, oloc, obj_op, snapc, ceph::real_clock::from_ceph_timespec(pwop->mtime), flags, new C_OnFinisher(fin, osd->get_objecter_finisher(get_pg_shard())), &pwop->user_version, pwop->reqid); fin->tid = tid; pwop->objecter_tid = tid; proxywrite_ops[tid] = pwop; in_progress_proxy_ops[soid].push_back(op); } void PrimaryLogPG::do_proxy_chunked_op(OpRequestRef op, const hobject_t& missing_oid, ObjectContextRef obc, bool write_ordered) { MOSDOp *m = static_cast(op->get_nonconst_req()); OSDOp *osd_op = NULL; for (unsigned int i = 0; i < m->ops.size(); i++) { osd_op = &m->ops[i]; uint64_t cursor = osd_op->op.extent.offset; uint64_t op_length = osd_op->op.extent.offset + osd_op->op.extent.length; uint64_t chunk_length = 0, chunk_index = 0, req_len = 0; object_manifest_t *manifest = &obc->obs.oi.manifest; map > chunk_read; while (cursor < op_length) { chunk_index = 0; chunk_length = 0; /* find the right chunk position for cursor */ for (auto &p : manifest->chunk_map) { if (p.first <= cursor && p.first + p.second.length > cursor) { chunk_length = p.second.length; chunk_index = p.first; break; } } /* no index */ if (!chunk_index && !chunk_length) { if (cursor == osd_op->op.extent.offset) { OpContext *ctx = new OpContext(op, m->get_reqid(), &m->ops, this); ctx->reply = new MOSDOpReply(m, 0, get_osdmap_epoch(), 0, false); ctx->data_off = osd_op->op.extent.offset; ctx->ignore_log_op_stats = true; complete_read_ctx(0, ctx); } break; } uint64_t next_length = chunk_length; /* the size to read -> | op length | */ /* | a chunk | */ if (cursor + next_length > op_length) { next_length = op_length - cursor; } /* the size to read -> | op length | */ /* | a chunk | */ if (cursor + next_length > chunk_index + chunk_length) { next_length = chunk_index + chunk_length - cursor; } chunk_read[cursor] = {{chunk_index, next_length}}; cursor += next_length; } req_len = cursor - osd_op->op.extent.offset; for (auto &p : chunk_read) { auto chunks = p.second.begin(); dout(20) << __func__ << " chunk_index: " << chunks->first << " next_length: " << chunks->second << " cursor: " << p.first << dendl; do_proxy_chunked_read(op, obc, i, chunks->first, p.first, chunks->second, req_len, write_ordered); } } } struct RefCountCallback : public Context { public: PrimaryLogPG::OpContext *ctx; OSDOp& osd_op; bool requeue = false; RefCountCallback(PrimaryLogPG::OpContext *ctx, OSDOp &osd_op) : ctx(ctx), osd_op(osd_op) {} void finish(int r) override { // NB: caller must already have pg->lock held ctx->obc->stop_block(); ctx->pg->kick_object_context_blocked(ctx->obc); if (r >= 0) { osd_op.rval = 0; ctx->pg->execute_ctx(ctx); } else { // on cancel simply toss op out, // or requeue as requested if (r != -ECANCELED) { if (ctx->op) ctx->pg->osd->reply_op_error(ctx->op, r); } else if (requeue) { if (ctx->op) ctx->pg->requeue_op(ctx->op); } ctx->pg->close_op_ctx(ctx); } } void set_requeue(bool rq) { requeue = rq; } }; struct SetManifestFinisher : public PrimaryLogPG::OpFinisher { OSDOp& osd_op; explicit SetManifestFinisher(OSDOp& osd_op) : osd_op(osd_op) { } int execute() override { return osd_op.rval; } }; struct C_SetManifestRefCountDone : public Context { PrimaryLogPGRef pg; PrimaryLogPG::ManifestOpRef mop; hobject_t soid; C_SetManifestRefCountDone(PrimaryLogPG *p, PrimaryLogPG::ManifestOpRef mop, hobject_t soid) : pg(p), mop(mop), soid(soid) {} void finish(int r) override { if (r == -ECANCELED) return; std::scoped_lock locker{*pg}; auto it = pg->manifest_ops.find(soid); if (it == pg->manifest_ops.end()) { // raced with cancel_manifest_ops return; } if (it->second->cb) { it->second->cb->complete(r); } pg->manifest_ops.erase(it); mop.reset(); } }; struct C_SetDedupChunks : public Context { PrimaryLogPGRef pg; hobject_t oid; epoch_t last_peering_reset; ceph_tid_t tid; uint64_t offset; C_SetDedupChunks(PrimaryLogPG *p, hobject_t o, epoch_t lpr, uint64_t offset) : pg(p), oid(o), last_peering_reset(lpr), tid(0), offset(offset) {} void finish(int r) override { if (r == -ECANCELED) return; std::scoped_lock locker{*pg}; if (last_peering_reset != pg->get_last_peering_reset()) { return; } pg->finish_set_dedup(oid, r, tid, offset); } }; void PrimaryLogPG::cancel_manifest_ops(bool requeue, vector *tids) { dout(10) << __func__ << dendl; auto p = manifest_ops.begin(); while (p != manifest_ops.end()) { auto mop = p->second; // cancel objecter op, if we can if (mop->objecter_tid) { tids->push_back(mop->objecter_tid); mop->objecter_tid = 0; } if (mop->cb) { mop->cb->set_requeue(requeue); mop->cb->complete(-ECANCELED); } manifest_ops.erase(p++); } } int PrimaryLogPG::get_manifest_ref_count(ObjectContextRef obc, std::string& fp_oid, OpRequestRef op) { int cnt = 0; // head for (auto &p : obc->obs.oi.manifest.chunk_map) { if (p.second.oid.oid.name == fp_oid) { cnt++; } } // snap SnapSet& ss = obc->ssc->snapset; const OSDMapRef& osdmap = get_osdmap(); for (vector::const_reverse_iterator p = ss.clones.rbegin(); p != ss.clones.rend(); ++p) { object_ref_delta_t refs; ObjectContextRef obc_l = nullptr; ObjectContextRef obc_g = nullptr; hobject_t clone_oid = obc->obs.oi.soid; clone_oid.snap = *p; if (osdmap->in_removed_snaps_queue(info.pgid.pgid.pool(), *p)) { return -EBUSY; } ObjectContextRef clone_obc = get_object_context(clone_oid, false); if (!clone_obc) { break; } if (recover_adjacent_clones(clone_obc, op)) { return -EAGAIN; } get_adjacent_clones(clone_obc, obc_l, obc_g); clone_obc->obs.oi.manifest.calc_refs_to_inc_on_set( obc_g ? &(obc_g->obs.oi.manifest) : nullptr , nullptr, refs); for (auto p = refs.begin(); p != refs.end(); ++p) { if (p->first.oid.name == fp_oid && p->second > 0) { cnt += p->second; } } } return cnt; } bool PrimaryLogPG::recover_adjacent_clones(ObjectContextRef obc, OpRequestRef op) { if (!obc->ssc || !obc->ssc->snapset.clones.size()) { return false; } MOSDOp *m = static_cast(op->get_nonconst_req()); bool has_manifest_op = std::any_of( begin(m->ops), end(m->ops), [](const auto& osd_op) { return osd_op.op.op == CEPH_OSD_OP_SET_CHUNK; }); if (!obc->obs.oi.manifest.is_chunked() && !has_manifest_op) { return false; } ceph_assert(op); const SnapSet& snapset = obc->ssc->snapset; auto s = std::find(snapset.clones.begin(), snapset.clones.end(), obc->obs.oi.soid.snap); auto is_unreadable_snap = [this, obc, &snapset, op](auto iter) -> bool { hobject_t cid = obc->obs.oi.soid; cid.snap = (iter == snapset.clones.end()) ? snapid_t(CEPH_NOSNAP) : *iter; if (is_unreadable_object(cid)) { dout(10) << __func__ << ": clone " << cid << " is unreadable, waiting" << dendl; wait_for_unreadable_object(cid, op); return true; } return false; }; if (s != snapset.clones.begin()) { if (is_unreadable_snap(s - 1)) { return true; } } if (s != snapset.clones.end()) { if (is_unreadable_snap(s + 1)) { return true; } } return false; } ObjectContextRef PrimaryLogPG::get_prev_clone_obc(ObjectContextRef obc) { auto s = std::find(obc->ssc->snapset.clones.begin(), obc->ssc->snapset.clones.end(), obc->obs.oi.soid.snap); if (s != obc->ssc->snapset.clones.begin()) { auto s_iter = s - 1; hobject_t cid = obc->obs.oi.soid; object_ref_delta_t refs; cid.snap = *s_iter; ObjectContextRef cobc = get_object_context(cid, false, NULL); ceph_assert(cobc); return cobc; } return nullptr; } void PrimaryLogPG::dec_refcount(const hobject_t& soid, const object_ref_delta_t& refs) { for (auto p = refs.begin(); p != refs.end(); ++p) { int dec_ref_count = p->second; ceph_assert(dec_ref_count < 0); while (dec_ref_count < 0) { dout(10) << __func__ << ": decrement reference on offset oid: " << p->first << dendl; refcount_manifest(soid, p->first, refcount_t::DECREMENT_REF, NULL, std::nullopt); dec_ref_count++; } } } void PrimaryLogPG::get_adjacent_clones(ObjectContextRef src_obc, ObjectContextRef& _l, ObjectContextRef& _g) { const SnapSet& snapset = src_obc->ssc->snapset; const object_info_t& oi = src_obc->obs.oi; auto get_context = [this, &oi, &snapset](auto iter) -> ObjectContextRef { hobject_t cid = oi.soid; cid.snap = (iter == snapset.clones.end()) ? snapid_t(CEPH_NOSNAP) : *iter; ObjectContextRef obc = get_object_context(cid, false, NULL); ceph_assert(obc); return obc; }; // check adjacent clones auto s = std::find(snapset.clones.begin(), snapset.clones.end(), oi.soid.snap); // We *must* find the clone iff it's not head, // let s == snapset.clones.end() mean head ceph_assert((s == snapset.clones.end()) == oi.soid.is_head()); if (s != snapset.clones.begin()) { _l = get_context(s - 1); } if (s != snapset.clones.end()) { _g = get_context(s + 1); } } bool PrimaryLogPG::inc_refcount_by_set(OpContext* ctx, object_manifest_t& set_chunk, OSDOp& osd_op) { object_ref_delta_t refs; ObjectContextRef obc_l, obc_g; get_adjacent_clones(ctx->obc, obc_l, obc_g); set_chunk.calc_refs_to_inc_on_set( obc_l ? &(obc_l->obs.oi.manifest) : nullptr, obc_g ? &(obc_g->obs.oi.manifest) : nullptr, refs); if (!refs.is_empty()) { /* This is called by set-chunk, so we only consider a single chunk for the time being */ ceph_assert(refs.size() == 1); auto p = refs.begin(); int inc_ref_count = p->second; if (inc_ref_count > 0) { /* * In set-chunk case, the first thing we should do is to increment * the reference the targe object has prior to update object_manifest in object_info_t. * So, call directly refcount_manifest. */ ManifestOpRef mop = std::make_shared(new RefCountCallback(ctx, osd_op)); C_SetManifestRefCountDone* fin = new C_SetManifestRefCountDone(this, mop, ctx->obs->oi.soid); ceph_tid_t tid = refcount_manifest(ctx->obs->oi.soid, p->first, refcount_t::INCREMENT_REF, fin, std::nullopt); mop->objecter_tid = tid; manifest_ops[ctx->obs->oi.soid] = mop; ctx->obc->start_block(); return true; } else if (inc_ref_count < 0) { hobject_t src = ctx->obs->oi.soid; hobject_t tgt = p->first; ctx->register_on_commit( [src, tgt, this](){ refcount_manifest(src, tgt, refcount_t::DECREMENT_REF, NULL, std::nullopt); }); return false; } } return false; } void PrimaryLogPG::dec_refcount_by_dirty(OpContext* ctx) { object_ref_delta_t refs; ObjectContextRef cobc = nullptr; ObjectContextRef obc = ctx->obc; for (auto &p : ctx->obs->oi.manifest.chunk_map) { if (!ctx->clean_regions.is_clean_region(p.first, p.second.length)) { ctx->new_obs.oi.manifest.chunk_map.erase(p.first); if (ctx->new_obs.oi.manifest.chunk_map.empty()) { ctx->new_obs.oi.manifest.type = object_manifest_t::TYPE_NONE; ctx->new_obs.oi.clear_flag(object_info_t::FLAG_MANIFEST); ctx->delta_stats.num_objects_manifest--; } } } // Look over previous snapshot, then figure out whether updated chunk needs to be deleted cobc = get_prev_clone_obc(obc); obc->obs.oi.manifest.calc_refs_to_drop_on_modify( cobc ? &cobc->obs.oi.manifest : nullptr, ctx->clean_regions, refs); if (!refs.is_empty()) { hobject_t soid = obc->obs.oi.soid; ctx->register_on_commit( [soid, this, refs](){ dec_refcount(soid, refs); }); } } void PrimaryLogPG::dec_all_refcount_manifest(const object_info_t& oi, OpContext* ctx) { ceph_assert(oi.has_manifest()); ceph_assert(ctx->obc->ssc); if (oi.manifest.is_chunked()) { object_ref_delta_t refs; ObjectContextRef obc_l, obc_g; get_adjacent_clones(ctx->obc, obc_l, obc_g); oi.manifest.calc_refs_to_drop_on_removal( obc_l ? &(obc_l->obs.oi.manifest) : nullptr, obc_g ? &(obc_g->obs.oi.manifest) : nullptr, refs); if (!refs.is_empty()) { hobject_t soid = ctx->obc->obs.oi.soid; ctx->register_on_commit( [soid, this, refs](){ dec_refcount(soid, refs); }); } } else if (oi.manifest.is_redirect() && oi.test_flag(object_info_t::FLAG_REDIRECT_HAS_REFERENCE)) { ctx->register_on_commit( [oi, this](){ refcount_manifest(oi.soid, oi.manifest.redirect_target, refcount_t::DECREMENT_REF, NULL, std::nullopt); }); } } ceph_tid_t PrimaryLogPG::refcount_manifest(hobject_t src_soid, hobject_t tgt_soid, refcount_t type, Context *cb, std::optional chunk) { unsigned flags = CEPH_OSD_FLAG_IGNORE_CACHE | CEPH_OSD_FLAG_IGNORE_OVERLAY | CEPH_OSD_FLAG_RWORDERED; dout(10) << __func__ << " Start refcount from " << src_soid << " to " << tgt_soid << dendl; ObjectOperation obj_op; bufferlist in; if (type == refcount_t::INCREMENT_REF) { cls_cas_chunk_get_ref_op call; call.source = src_soid.get_head(); ::encode(call, in); obj_op.call("cas", "chunk_get_ref", in); } else if (type == refcount_t::DECREMENT_REF) { cls_cas_chunk_put_ref_op call; call.source = src_soid.get_head(); ::encode(call, in); obj_op.call("cas", "chunk_put_ref", in); } else if (type == refcount_t::CREATE_OR_GET_REF) { cls_cas_chunk_create_or_get_ref_op get_call; get_call.source = src_soid.get_head(); ceph_assert(chunk); get_call.data = move(*chunk); ::encode(get_call, in); obj_op.call("cas", "chunk_create_or_get_ref", in); } else { ceph_assert(0 == "unrecognized type"); } Context *c = nullptr; if (cb) { c = new C_OnFinisher(cb, osd->get_objecter_finisher(get_pg_shard())); } object_locator_t oloc(tgt_soid); ObjectContextRef src_obc = get_object_context(src_soid, false, NULL); ceph_assert(src_obc); auto tid = osd->objecter->mutate( tgt_soid.oid, oloc, obj_op, SnapContext(), ceph::real_clock::from_ceph_timespec(src_obc->obs.oi.mtime), flags, c); return tid; } void PrimaryLogPG::do_proxy_chunked_read(OpRequestRef op, ObjectContextRef obc, int op_index, uint64_t chunk_index, uint64_t req_offset, uint64_t req_length, uint64_t req_total_len, bool write_ordered) { MOSDOp *m = static_cast(op->get_nonconst_req()); object_manifest_t *manifest = &obc->obs.oi.manifest; if (!manifest->chunk_map.count(chunk_index)) { return; } uint64_t chunk_length = manifest->chunk_map[chunk_index].length; hobject_t soid = manifest->chunk_map[chunk_index].oid; hobject_t ori_soid = m->get_hobj(); object_locator_t oloc(soid); unsigned flags = CEPH_OSD_FLAG_IGNORE_CACHE | CEPH_OSD_FLAG_IGNORE_OVERLAY; if (write_ordered) { flags |= CEPH_OSD_FLAG_RWORDERED; } if (!chunk_length || soid == hobject_t()) { return; } /* same as do_proxy_read() */ flags |= m->get_flags() & (CEPH_OSD_FLAG_RWORDERED | CEPH_OSD_FLAG_ORDERSNAP | CEPH_OSD_FLAG_ENFORCE_SNAPC | CEPH_OSD_FLAG_MAP_SNAP_CLONE); dout(10) << __func__ << " Start do chunk proxy read for " << *m << " index: " << op_index << " oid: " << soid.oid.name << " req_offset: " << req_offset << " req_length: " << req_length << dendl; ProxyReadOpRef prdop(std::make_shared(op, ori_soid, m->ops)); ObjectOperation *pobj_op = new ObjectOperation; OSDOp &osd_op = pobj_op->add_op(m->ops[op_index].op.op); if (chunk_index <= req_offset) { osd_op.op.extent.offset = manifest->chunk_map[chunk_index].offset + req_offset - chunk_index; } else { ceph_abort_msg("chunk_index > req_offset"); } osd_op.op.extent.length = req_length; ObjectOperation obj_op; obj_op.dup(pobj_op->ops); C_ProxyChunkRead *fin = new C_ProxyChunkRead(this, ori_soid, get_last_peering_reset(), prdop); fin->obj_op = pobj_op; fin->op_index = op_index; fin->req_offset = req_offset; fin->obc = obc; fin->req_total_len = req_total_len; ceph_tid_t tid = osd->objecter->read( soid.oid, oloc, obj_op, m->get_snapid(), NULL, flags, new C_OnFinisher(fin, osd->get_objecter_finisher(get_pg_shard())), &prdop->user_version, &prdop->data_offset, m->get_features()); fin->tid = tid; prdop->objecter_tid = tid; proxyread_ops[tid] = prdop; in_progress_proxy_ops[ori_soid].push_back(op); } bool PrimaryLogPG::can_proxy_chunked_read(OpRequestRef op, ObjectContextRef obc) { MOSDOp *m = static_cast(op->get_nonconst_req()); OSDOp *osd_op = NULL; bool ret = true; for (unsigned int i = 0; i < m->ops.size(); i++) { osd_op = &m->ops[i]; ceph_osd_op op = osd_op->op; switch (op.op) { case CEPH_OSD_OP_READ: case CEPH_OSD_OP_SYNC_READ: { uint64_t cursor = osd_op->op.extent.offset; uint64_t remain = osd_op->op.extent.length; /* requested chunks exist in chunk_map ? */ for (auto &p : obc->obs.oi.manifest.chunk_map) { if (p.first <= cursor && p.first + p.second.length > cursor) { if (!p.second.is_missing()) { return false; } if (p.second.length >= remain) { remain = 0; break; } else { remain = remain - p.second.length; } cursor += p.second.length; } } if (remain) { dout(20) << __func__ << " requested chunks don't exist in chunk_map " << dendl; return false; } continue; } default: return false; } } return ret; } void PrimaryLogPG::finish_proxy_write(hobject_t oid, ceph_tid_t tid, int r) { dout(10) << __func__ << " " << oid << " tid " << tid << " " << cpp_strerror(r) << dendl; map::iterator p = proxywrite_ops.find(tid); if (p == proxywrite_ops.end()) { dout(10) << __func__ << " no proxywrite_op found" << dendl; return; } ProxyWriteOpRef pwop = p->second; ceph_assert(tid == pwop->objecter_tid); ceph_assert(oid == pwop->soid); proxywrite_ops.erase(tid); map >::iterator q = in_progress_proxy_ops.find(oid); if (q == in_progress_proxy_ops.end()) { dout(10) << __func__ << " no in_progress_proxy_ops found" << dendl; delete pwop->ctx; pwop->ctx = NULL; return; } list& in_progress_op = q->second; ceph_assert(in_progress_op.size()); list::iterator it = std::find(in_progress_op.begin(), in_progress_op.end(), pwop->op); ceph_assert(it != in_progress_op.end()); in_progress_op.erase(it); if (in_progress_op.size() == 0) { in_progress_proxy_ops.erase(oid); } else if (std::find(in_progress_op.begin(), in_progress_op.end(), pwop->op) != in_progress_op.end()) { if (pwop->ctx) delete pwop->ctx; pwop->ctx = NULL; dout(20) << __func__ << " " << oid << " tid " << tid << " in_progress_op size: " << in_progress_op.size() << dendl; return; } osd->logger->inc(l_osd_tier_proxy_write); auto m = pwop->op->get_req(); ceph_assert(m != NULL); if (!pwop->sent_reply) { // send commit. assert(pwop->ctx->reply == nullptr); MOSDOpReply *reply = new MOSDOpReply(m, r, get_osdmap_epoch(), 0, true /* we claim it below */); reply->set_reply_versions(eversion_t(), pwop->user_version); reply->add_flags(CEPH_OSD_FLAG_ACK | CEPH_OSD_FLAG_ONDISK); reply->claim_op_out_data(pwop->ops); dout(10) << " sending commit on " << pwop << " " << reply << dendl; osd->send_message_osd_client(reply, m->get_connection()); pwop->sent_reply = true; pwop->ctx->op->mark_commit_sent(); } delete pwop->ctx; pwop->ctx = NULL; } void PrimaryLogPG::cancel_proxy_write(ProxyWriteOpRef pwop, vector *tids) { dout(10) << __func__ << " " << pwop->soid << dendl; pwop->canceled = true; // cancel objecter op, if we can if (pwop->objecter_tid) { tids->push_back(pwop->objecter_tid); delete pwop->ctx; pwop->ctx = NULL; proxywrite_ops.erase(pwop->objecter_tid); pwop->objecter_tid = 0; } } class PromoteCallback: public PrimaryLogPG::CopyCallback { ObjectContextRef obc; PrimaryLogPG *pg; utime_t start; public: PromoteCallback(ObjectContextRef obc_, PrimaryLogPG *pg_) : obc(obc_), pg(pg_), start(ceph_clock_now()) {} void finish(PrimaryLogPG::CopyCallbackResults results) override { PrimaryLogPG::CopyResults *results_data = results.get<1>(); int r = results.get<0>(); pg->finish_promote(r, results_data, obc); pg->osd->logger->tinc(l_osd_tier_promote_lat, ceph_clock_now() - start); } }; class PromoteManifestCallback: public PrimaryLogPG::CopyCallback { ObjectContextRef obc; PrimaryLogPG *pg; utime_t start; PrimaryLogPG::OpContext *ctx; PrimaryLogPG::CopyCallbackResults promote_results; public: PromoteManifestCallback(ObjectContextRef obc_, PrimaryLogPG *pg_, PrimaryLogPG::OpContext *ctx = NULL) : obc(obc_), pg(pg_), start(ceph_clock_now()), ctx(ctx) {} void finish(PrimaryLogPG::CopyCallbackResults results) override { PrimaryLogPG::CopyResults *results_data = results.get<1>(); int r = results.get<0>(); if (ctx) { promote_results = results; pg->execute_ctx(ctx); } else { pg->finish_promote_manifest(r, results_data, obc); } pg->osd->logger->tinc(l_osd_tier_promote_lat, ceph_clock_now() - start); } friend struct PromoteFinisher; }; struct PromoteFinisher : public PrimaryLogPG::OpFinisher { PromoteManifestCallback *promote_callback; explicit PromoteFinisher(PromoteManifestCallback *promote_callback) : promote_callback(promote_callback) { } int execute() override { if (promote_callback->ctx->obc->obs.oi.manifest.is_redirect()) { promote_callback->ctx->pg->finish_promote(promote_callback->promote_results.get<0>(), promote_callback->promote_results.get<1>(), promote_callback->obc); } else if (promote_callback->ctx->obc->obs.oi.manifest.is_chunked()) { promote_callback->ctx->pg->finish_promote_manifest(promote_callback->promote_results.get<0>(), promote_callback->promote_results.get<1>(), promote_callback->obc); } else { ceph_abort_msg("unrecognized manifest type"); } return 0; } }; void PrimaryLogPG::promote_object(ObjectContextRef obc, const hobject_t& missing_oid, const object_locator_t& oloc, OpRequestRef op, ObjectContextRef *promote_obc) { hobject_t hoid = obc ? obc->obs.oi.soid : missing_oid; ceph_assert(hoid != hobject_t()); if (m_scrubber->write_blocked_by_scrub(hoid)) { dout(10) << __func__ << " " << hoid << " blocked by scrub" << dendl; if (op) { waiting_for_scrub.push_back(op); op->mark_delayed("waiting for scrub"); dout(10) << __func__ << " " << hoid << " placing op in waiting_for_scrub" << dendl; } else { dout(10) << __func__ << " " << hoid << " no op, dropping on the floor" << dendl; } return; } if (op && !check_laggy_requeue(op)) { return; } if (!obc) { // we need to create an ObjectContext ceph_assert(missing_oid != hobject_t()); obc = get_object_context(missing_oid, true); } if (promote_obc) *promote_obc = obc; /* * Before promote complete, if there are proxy-reads for the object, * for this case we don't use DONTNEED. */ unsigned src_fadvise_flags = LIBRADOS_OP_FLAG_FADVISE_SEQUENTIAL; map>::iterator q = in_progress_proxy_ops.find(obc->obs.oi.soid); if (q == in_progress_proxy_ops.end()) { src_fadvise_flags |= LIBRADOS_OP_FLAG_FADVISE_DONTNEED; } CopyCallback *cb; object_locator_t my_oloc; hobject_t src_hoid; if (!obc->obs.oi.has_manifest()) { my_oloc = oloc; my_oloc.pool = pool.info.tier_of; src_hoid = obc->obs.oi.soid; cb = new PromoteCallback(obc, this); } else { if (obc->obs.oi.manifest.is_chunked()) { src_hoid = obc->obs.oi.soid; cb = new PromoteManifestCallback(obc, this); } else if (obc->obs.oi.manifest.is_redirect()) { object_locator_t src_oloc(obc->obs.oi.manifest.redirect_target); my_oloc = src_oloc; src_hoid = obc->obs.oi.manifest.redirect_target; cb = new PromoteCallback(obc, this); } else { ceph_abort_msg("unrecognized manifest type"); } } unsigned flags = CEPH_OSD_COPY_FROM_FLAG_IGNORE_OVERLAY | CEPH_OSD_COPY_FROM_FLAG_IGNORE_CACHE | CEPH_OSD_COPY_FROM_FLAG_MAP_SNAP_CLONE | CEPH_OSD_COPY_FROM_FLAG_RWORDERED; start_copy(cb, obc, src_hoid, my_oloc, 0, flags, obc->obs.oi.soid.snap == CEPH_NOSNAP, src_fadvise_flags, 0); ceph_assert(obc->is_blocked()); if (op) wait_for_blocked_object(obc->obs.oi.soid, op); recovery_state.update_stats( [](auto &history, auto &stats) { stats.stats.sum.num_promote++; return false; }); } void PrimaryLogPG::execute_ctx(OpContext *ctx) { FUNCTRACE(cct); dout(10) << __func__ << " " << ctx << dendl; ctx->reset_obs(ctx->obc); ctx->update_log_only = false; // reset in case finish_copyfrom() is re-running execute_ctx OpRequestRef op = ctx->op; auto m = op->get_req(); ObjectContextRef obc = ctx->obc; const hobject_t& soid = obc->obs.oi.soid; // this method must be idempotent since we may call it several times // before we finally apply the resulting transaction. ctx->op_t.reset(new PGTransaction); if (op->may_write() || op->may_cache()) { // snap if (!(m->has_flag(CEPH_OSD_FLAG_ENFORCE_SNAPC)) && pool.info.is_pool_snaps_mode()) { // use pool's snapc ctx->snapc = pool.snapc; } else { // client specified snapc ctx->snapc.seq = m->get_snap_seq(); ctx->snapc.snaps = m->get_snaps(); filter_snapc(ctx->snapc.snaps); } if ((m->has_flag(CEPH_OSD_FLAG_ORDERSNAP)) && ctx->snapc.seq < obc->ssc->snapset.seq) { dout(10) << " ORDERSNAP flag set and snapc seq " << ctx->snapc.seq << " < snapset seq " << obc->ssc->snapset.seq << " on " << obc->obs.oi.soid << dendl; reply_ctx(ctx, -EOLDSNAPC); return; } // version ctx->at_version = get_next_version(); ctx->mtime = m->get_mtime(); dout(10) << __func__ << " " << soid << " " << *ctx->ops << " ov " << obc->obs.oi.version << " av " << ctx->at_version << " snapc " << ctx->snapc << " snapset " << obc->ssc->snapset << dendl; } else { dout(10) << __func__ << " " << soid << " " << *ctx->ops << " ov " << obc->obs.oi.version << dendl; } if (!ctx->user_at_version) ctx->user_at_version = obc->obs.oi.user_version; dout(30) << __func__ << " user_at_version " << ctx->user_at_version << dendl; { #ifdef WITH_LTTNG osd_reqid_t reqid = ctx->op->get_reqid(); #endif tracepoint(osd, prepare_tx_enter, reqid.name._type, reqid.name._num, reqid.tid, reqid.inc); } #ifdef HAVE_JAEGER if (ctx->op->osd_parent_span) { auto execute_span = jaeger_tracing::child_span(__func__, ctx->op->osd_parent_span); } #endif int result = prepare_transaction(ctx); { #ifdef WITH_LTTNG osd_reqid_t reqid = ctx->op->get_reqid(); #endif tracepoint(osd, prepare_tx_exit, reqid.name._type, reqid.name._num, reqid.tid, reqid.inc); } bool pending_async_reads = !ctx->pending_async_reads.empty(); if (result == -EINPROGRESS || pending_async_reads) { // come back later. if (pending_async_reads) { ceph_assert(pool.info.is_erasure()); in_progress_async_reads.push_back(make_pair(op, ctx)); ctx->start_async_reads(this); } return; } if (result == -EAGAIN) { // clean up after the ctx close_op_ctx(ctx); return; } bool ignore_out_data = false; if (!ctx->op_t->empty() && op->may_write() && result >= 0) { // successful update if (ctx->op->allows_returnvec()) { // enforce reasonable bound on the return buffer sizes for (auto& i : *ctx->ops) { if (i.outdata.length() > cct->_conf->osd_max_write_op_reply_len) { dout(10) << __func__ << " op " << i << " outdata overflow" << dendl; result = -EOVERFLOW; // overall result is overflow i.rval = -EOVERFLOW; i.outdata.clear(); } } } else { // legacy behavior -- zero result and return data etc. ignore_out_data = true; result = 0; } } // prepare the reply ctx->reply = new MOSDOpReply(m, result, get_osdmap_epoch(), 0, ignore_out_data); dout(20) << __func__ << " alloc reply " << ctx->reply << " result " << result << dendl; // read or error? if ((ctx->op_t->empty() || result < 0) && !ctx->update_log_only) { // finish side-effects if (result >= 0) do_osd_op_effects(ctx, m->get_connection()); complete_read_ctx(result, ctx); return; } ctx->reply->set_reply_versions(ctx->at_version, ctx->user_at_version); ceph_assert(op->may_write() || op->may_cache()); // trim log? recovery_state.update_trim_to(); // verify that we are doing this in order? if (cct->_conf->osd_debug_op_order && m->get_source().is_client() && !pool.info.is_tier() && !pool.info.has_tiers()) { map& cm = debug_op_order[obc->obs.oi.soid]; ceph_tid_t t = m->get_tid(); client_t n = m->get_source().num(); map::iterator p = cm.find(n); if (p == cm.end()) { dout(20) << " op order client." << n << " tid " << t << " (first)" << dendl; cm[n] = t; } else { dout(20) << " op order client." << n << " tid " << t << " last was " << p->second << dendl; if (p->second > t) { derr << "bad op order, already applied " << p->second << " > this " << t << dendl; ceph_abort_msg("out of order op"); } p->second = t; } } if (ctx->update_log_only) { if (result >= 0) do_osd_op_effects(ctx, m->get_connection()); dout(20) << __func__ << " update_log_only -- result=" << result << dendl; // save just what we need from ctx MOSDOpReply *reply = ctx->reply; ctx->reply = nullptr; reply->get_header().data_off = (ctx->data_off ? *ctx->data_off : 0); if (result == -ENOENT) { reply->set_enoent_reply_versions(info.last_update, info.last_user_version); } reply->add_flags(CEPH_OSD_FLAG_ACK | CEPH_OSD_FLAG_ONDISK); // append to pg log for dup detection - don't save buffers for now record_write_error(op, soid, reply, result, ctx->op->allows_returnvec() ? ctx : nullptr); close_op_ctx(ctx); return; } // no need to capture PG ref, repop cancel will handle that // Can capture the ctx by pointer, it's owned by the repop ctx->register_on_commit( [m, ctx, this](){ if (ctx->op) log_op_stats(*ctx->op, ctx->bytes_written, ctx->bytes_read); if (m && !ctx->sent_reply) { MOSDOpReply *reply = ctx->reply; ctx->reply = nullptr; reply->add_flags(CEPH_OSD_FLAG_ACK | CEPH_OSD_FLAG_ONDISK); dout(10) << " sending reply on " << *m << " " << reply << dendl; osd->send_message_osd_client(reply, m->get_connection()); ctx->sent_reply = true; ctx->op->mark_commit_sent(); } }); ctx->register_on_success( [ctx, this]() { do_osd_op_effects( ctx, ctx->op ? ctx->op->get_req()->get_connection() : ConnectionRef()); }); ctx->register_on_finish( [ctx]() { delete ctx; }); // issue replica writes ceph_tid_t rep_tid = osd->get_tid(); RepGather *repop = new_repop(ctx, obc, rep_tid); issue_repop(repop, ctx); eval_repop(repop); repop->put(); } void PrimaryLogPG::close_op_ctx(OpContext *ctx) { release_object_locks(ctx->lock_manager); ctx->op_t.reset(); for (auto p = ctx->on_finish.begin(); p != ctx->on_finish.end(); ctx->on_finish.erase(p++)) { (*p)(); } delete ctx; } void PrimaryLogPG::reply_ctx(OpContext *ctx, int r) { if (ctx->op) osd->reply_op_error(ctx->op, r); close_op_ctx(ctx); } void PrimaryLogPG::log_op_stats(const OpRequest& op, const uint64_t inb, const uint64_t outb) { auto m = op.get_req(); const utime_t now = ceph_clock_now(); const utime_t latency = now - m->get_recv_stamp(); const utime_t process_latency = now - op.get_dequeued_time(); osd->logger->inc(l_osd_op); osd->logger->inc(l_osd_op_outb, outb); osd->logger->inc(l_osd_op_inb, inb); osd->logger->tinc(l_osd_op_lat, latency); osd->logger->tinc(l_osd_op_process_lat, process_latency); if (op.may_read() && op.may_write()) { osd->logger->inc(l_osd_op_rw); osd->logger->inc(l_osd_op_rw_inb, inb); osd->logger->inc(l_osd_op_rw_outb, outb); osd->logger->tinc(l_osd_op_rw_lat, latency); osd->logger->hinc(l_osd_op_rw_lat_inb_hist, latency.to_nsec(), inb); osd->logger->hinc(l_osd_op_rw_lat_outb_hist, latency.to_nsec(), outb); osd->logger->tinc(l_osd_op_rw_process_lat, process_latency); } else if (op.may_read()) { osd->logger->inc(l_osd_op_r); osd->logger->inc(l_osd_op_r_outb, outb); osd->logger->tinc(l_osd_op_r_lat, latency); osd->logger->hinc(l_osd_op_r_lat_outb_hist, latency.to_nsec(), outb); osd->logger->tinc(l_osd_op_r_process_lat, process_latency); } else if (op.may_write() || op.may_cache()) { osd->logger->inc(l_osd_op_w); osd->logger->inc(l_osd_op_w_inb, inb); osd->logger->tinc(l_osd_op_w_lat, latency); osd->logger->hinc(l_osd_op_w_lat_inb_hist, latency.to_nsec(), inb); osd->logger->tinc(l_osd_op_w_process_lat, process_latency); } else { ceph_abort(); } dout(15) << "log_op_stats " << *m << " inb " << inb << " outb " << outb << " lat " << latency << dendl; if (m_dynamic_perf_stats.is_enabled()) { m_dynamic_perf_stats.add(osd, info, op, inb, outb, latency); } } void PrimaryLogPG::set_dynamic_perf_stats_queries( const std::list &queries) { m_dynamic_perf_stats.set_queries(queries); } void PrimaryLogPG::get_dynamic_perf_stats(DynamicPerfStats *stats) { std::swap(m_dynamic_perf_stats, *stats); } void PrimaryLogPG::do_scan( OpRequestRef op, ThreadPool::TPHandle &handle) { auto m = op->get_req(); ceph_assert(m->get_type() == MSG_OSD_PG_SCAN); dout(10) << "do_scan " << *m << dendl; op->mark_started(); switch (m->op) { case MOSDPGScan::OP_SCAN_GET_DIGEST: { auto dpp = get_dpp(); if (osd->check_backfill_full(dpp)) { dout(1) << __func__ << ": Canceling backfill: Full." << dendl; queue_peering_event( PGPeeringEventRef( std::make_shared( get_osdmap_epoch(), get_osdmap_epoch(), PeeringState::BackfillTooFull()))); return; } BackfillInterval bi; bi.begin = m->begin; // No need to flush, there won't be any in progress writes occuring // past m->begin scan_range( cct->_conf->osd_backfill_scan_min, cct->_conf->osd_backfill_scan_max, &bi, handle); MOSDPGScan *reply = new MOSDPGScan( MOSDPGScan::OP_SCAN_DIGEST, pg_whoami, get_osdmap_epoch(), m->query_epoch, spg_t(info.pgid.pgid, get_primary().shard), bi.begin, bi.end); encode(bi.objects, reply->get_data()); osd->send_message_osd_cluster(reply, m->get_connection()); } break; case MOSDPGScan::OP_SCAN_DIGEST: { pg_shard_t from = m->from; // Check that from is in backfill_targets vector ceph_assert(is_backfill_target(from)); BackfillInterval& bi = peer_backfill_info[from]; bi.begin = m->begin; bi.end = m->end; auto p = m->get_data().cbegin(); // take care to preserve ordering! bi.clear_objects(); decode_noclear(bi.objects, p); dout(10) << __func__ << " bi.begin=" << bi.begin << " bi.end=" << bi.end << " bi.objects.size()=" << bi.objects.size() << dendl; if (waiting_on_backfill.erase(from)) { if (waiting_on_backfill.empty()) { ceph_assert( peer_backfill_info.size() == get_backfill_targets().size()); finish_recovery_op(hobject_t::get_max()); } } else { // we canceled backfill for a while due to a too full, and this // is an extra response from a non-too-full peer dout(20) << __func__ << " canceled backfill (too full?)" << dendl; } } break; } } void PrimaryLogPG::do_backfill(OpRequestRef op) { auto m = op->get_req(); ceph_assert(m->get_type() == MSG_OSD_PG_BACKFILL); dout(10) << "do_backfill " << *m << dendl; op->mark_started(); switch (m->op) { case MOSDPGBackfill::OP_BACKFILL_FINISH: { ceph_assert(cct->_conf->osd_kill_backfill_at != 1); MOSDPGBackfill *reply = new MOSDPGBackfill( MOSDPGBackfill::OP_BACKFILL_FINISH_ACK, get_osdmap_epoch(), m->query_epoch, spg_t(info.pgid.pgid, get_primary().shard)); reply->set_priority(get_recovery_op_priority()); osd->send_message_osd_cluster(reply, m->get_connection()); queue_peering_event( PGPeeringEventRef( std::make_shared( get_osdmap_epoch(), get_osdmap_epoch(), RecoveryDone()))); } // fall-thru case MOSDPGBackfill::OP_BACKFILL_PROGRESS: { ceph_assert(cct->_conf->osd_kill_backfill_at != 2); ObjectStore::Transaction t; recovery_state.update_backfill_progress( m->last_backfill, m->stats, m->op == MOSDPGBackfill::OP_BACKFILL_PROGRESS, t); int tr = osd->store->queue_transaction(ch, std::move(t), NULL); ceph_assert(tr == 0); } break; case MOSDPGBackfill::OP_BACKFILL_FINISH_ACK: { ceph_assert(is_primary()); ceph_assert(cct->_conf->osd_kill_backfill_at != 3); finish_recovery_op(hobject_t::get_max()); } break; } } void PrimaryLogPG::do_backfill_remove(OpRequestRef op) { const MOSDPGBackfillRemove *m = static_cast( op->get_req()); ceph_assert(m->get_type() == MSG_OSD_PG_BACKFILL_REMOVE); dout(7) << __func__ << " " << m->ls << dendl; op->mark_started(); ObjectStore::Transaction t; for (auto& p : m->ls) { if (is_remote_backfilling()) { struct stat st; int r = osd->store->stat(ch, ghobject_t(p.first, ghobject_t::NO_GEN, pg_whoami.shard) , &st); if (r == 0) { sub_local_num_bytes(st.st_size); int64_t usersize; if (pool.info.is_erasure()) { bufferlist bv; int r = osd->store->getattr( ch, ghobject_t(p.first, ghobject_t::NO_GEN, pg_whoami.shard), OI_ATTR, bv); if (r >= 0) { object_info_t oi(bv); usersize = oi.size * pgbackend->get_ec_data_chunk_count(); } else { dout(0) << __func__ << " " << ghobject_t(p.first, ghobject_t::NO_GEN, pg_whoami.shard) << " can't get object info" << dendl; usersize = 0; } } else { usersize = st.st_size; } sub_num_bytes(usersize); dout(10) << __func__ << " " << ghobject_t(p.first, ghobject_t::NO_GEN, pg_whoami.shard) << " sub actual data by " << st.st_size << " sub num_bytes by " << usersize << dendl; } } remove_snap_mapped_object(t, p.first); } int r = osd->store->queue_transaction(ch, std::move(t), NULL); ceph_assert(r == 0); } int PrimaryLogPG::trim_object( bool first, const hobject_t &coid, snapid_t snap_to_trim, PrimaryLogPG::OpContextUPtr *ctxp) { *ctxp = NULL; // load clone info bufferlist bl; ObjectContextRef obc = get_object_context(coid, false, NULL); if (!obc || !obc->ssc || !obc->ssc->exists) { osd->clog->error() << __func__ << ": Can not trim " << coid << " repair needed " << (obc ? "(no obc->ssc or !exists)" : "(no obc)"); return -ENOENT; } hobject_t head_oid = coid.get_head(); ObjectContextRef head_obc = get_object_context(head_oid, false); if (!head_obc) { osd->clog->error() << __func__ << ": Can not trim " << coid << " repair needed, no snapset obc for " << head_oid; return -ENOENT; } SnapSet& snapset = obc->ssc->snapset; object_info_t &coi = obc->obs.oi; auto citer = snapset.clone_snaps.find(coid.snap); if (citer == snapset.clone_snaps.end()) { osd->clog->error() << "No clone_snaps in snapset " << snapset << " for object " << coid << "\n"; return -ENOENT; } set old_snaps(citer->second.begin(), citer->second.end()); if (old_snaps.empty()) { osd->clog->error() << "No object info snaps for object " << coid; return -ENOENT; } dout(10) << coid << " old_snaps " << old_snaps << " old snapset " << snapset << dendl; if (snapset.seq == 0) { osd->clog->error() << "No snapset.seq for object " << coid; return -ENOENT; } set new_snaps; const OSDMapRef& osdmap = get_osdmap(); for (set::iterator i = old_snaps.begin(); i != old_snaps.end(); ++i) { if (!osdmap->in_removed_snaps_queue(info.pgid.pgid.pool(), *i) && *i != snap_to_trim) { new_snaps.insert(*i); } } vector::iterator p = snapset.clones.end(); if (new_snaps.empty()) { p = std::find(snapset.clones.begin(), snapset.clones.end(), coid.snap); if (p == snapset.clones.end()) { osd->clog->error() << "Snap " << coid.snap << " not in clones"; return -ENOENT; } } OpContextUPtr ctx = simple_opc_create(obc); ctx->head_obc = head_obc; if (!ctx->lock_manager.get_snaptrimmer_write( coid, obc, first)) { close_op_ctx(ctx.release()); dout(10) << __func__ << ": Unable to get a wlock on " << coid << dendl; return -ENOLCK; } if (!ctx->lock_manager.get_snaptrimmer_write( head_oid, head_obc, first)) { close_op_ctx(ctx.release()); dout(10) << __func__ << ": Unable to get a wlock on " << head_oid << dendl; return -ENOLCK; } ctx->at_version = get_next_version(); PGTransaction *t = ctx->op_t.get(); if (new_snaps.empty()) { // remove clone dout(10) << coid << " snaps " << old_snaps << " -> " << new_snaps << " ... deleting" << dendl; // ...from snapset ceph_assert(p != snapset.clones.end()); snapid_t last = coid.snap; ctx->delta_stats.num_bytes -= snapset.get_clone_bytes(last); if (p != snapset.clones.begin()) { // not the oldest... merge overlap into next older clone vector::iterator n = p - 1; hobject_t prev_coid = coid; prev_coid.snap = *n; bool adjust_prev_bytes = is_present_clone(prev_coid); if (adjust_prev_bytes) ctx->delta_stats.num_bytes -= snapset.get_clone_bytes(*n); snapset.clone_overlap[*n].intersection_of( snapset.clone_overlap[*p]); if (adjust_prev_bytes) ctx->delta_stats.num_bytes += snapset.get_clone_bytes(*n); } ctx->delta_stats.num_objects--; if (coi.is_dirty()) ctx->delta_stats.num_objects_dirty--; if (coi.is_omap()) ctx->delta_stats.num_objects_omap--; if (coi.is_whiteout()) { dout(20) << __func__ << " trimming whiteout on " << coid << dendl; ctx->delta_stats.num_whiteouts--; } ctx->delta_stats.num_object_clones--; if (coi.is_cache_pinned()) ctx->delta_stats.num_objects_pinned--; if (coi.has_manifest()) { dec_all_refcount_manifest(coi, ctx.get()); ctx->delta_stats.num_objects_manifest--; } obc->obs.exists = false; snapset.clones.erase(p); snapset.clone_overlap.erase(last); snapset.clone_size.erase(last); snapset.clone_snaps.erase(last); ctx->log.push_back( pg_log_entry_t( pg_log_entry_t::DELETE, coid, ctx->at_version, ctx->obs->oi.version, 0, osd_reqid_t(), ctx->mtime, 0) ); t->remove(coid); t->update_snaps( coid, old_snaps, new_snaps); coi = object_info_t(coid); ctx->at_version.version++; } else { // save adjusted snaps for this object dout(10) << coid << " snaps " << old_snaps << " -> " << new_snaps << dendl; snapset.clone_snaps[coid.snap] = vector(new_snaps.rbegin(), new_snaps.rend()); // we still do a 'modify' event on this object just to trigger a // snapmapper.update ... :( coi.prior_version = coi.version; coi.version = ctx->at_version; bl.clear(); encode(coi, bl, get_osdmap()->get_features(CEPH_ENTITY_TYPE_OSD, nullptr)); t->setattr(coid, OI_ATTR, bl); ctx->log.push_back( pg_log_entry_t( pg_log_entry_t::MODIFY, coid, coi.version, coi.prior_version, 0, osd_reqid_t(), ctx->mtime, 0) ); ctx->at_version.version++; t->update_snaps( coid, old_snaps, new_snaps); } // save head snapset dout(10) << coid << " new snapset " << snapset << " on " << head_obc->obs.oi << dendl; if (snapset.clones.empty() && (head_obc->obs.oi.is_whiteout() && !(head_obc->obs.oi.is_dirty() && pool.info.is_tier()) && !head_obc->obs.oi.is_cache_pinned())) { // NOTE: this arguably constitutes minor interference with the // tiering agent if this is a cache tier since a snap trim event // is effectively evicting a whiteout we might otherwise want to // keep around. dout(10) << coid << " removing " << head_oid << dendl; ctx->log.push_back( pg_log_entry_t( pg_log_entry_t::DELETE, head_oid, ctx->at_version, head_obc->obs.oi.version, 0, osd_reqid_t(), ctx->mtime, 0) ); dout(10) << "removing snap head" << dendl; object_info_t& oi = head_obc->obs.oi; ctx->delta_stats.num_objects--; if (oi.is_dirty()) { ctx->delta_stats.num_objects_dirty--; } if (oi.is_omap()) ctx->delta_stats.num_objects_omap--; if (oi.is_whiteout()) { dout(20) << __func__ << " trimming whiteout on " << oi.soid << dendl; ctx->delta_stats.num_whiteouts--; } if (oi.is_cache_pinned()) { ctx->delta_stats.num_objects_pinned--; } if (oi.has_manifest()) { ctx->delta_stats.num_objects_manifest--; dec_all_refcount_manifest(oi, ctx.get()); } head_obc->obs.exists = false; head_obc->obs.oi = object_info_t(head_oid); t->remove(head_oid); } else { if (get_osdmap()->require_osd_release < ceph_release_t::octopus) { // filter SnapSet::snaps for the benefit of pre-octopus // peers. This is perhaps overly conservative in that I'm not // certain they need this, but let's be conservative here. dout(10) << coid << " filtering snapset on " << head_oid << dendl; snapset.filter(pool.info); } else { snapset.snaps.clear(); } dout(10) << coid << " writing updated snapset on " << head_oid << ", snapset is " << snapset << dendl; ctx->log.push_back( pg_log_entry_t( pg_log_entry_t::MODIFY, head_oid, ctx->at_version, head_obc->obs.oi.version, 0, osd_reqid_t(), ctx->mtime, 0) ); head_obc->obs.oi.prior_version = head_obc->obs.oi.version; head_obc->obs.oi.version = ctx->at_version; map attrs; bl.clear(); encode(snapset, bl); attrs[SS_ATTR] = std::move(bl); bl.clear(); encode(head_obc->obs.oi, bl, get_osdmap()->get_features(CEPH_ENTITY_TYPE_OSD, nullptr)); attrs[OI_ATTR] = std::move(bl); t->setattrs(head_oid, attrs); } *ctxp = std::move(ctx); return 0; } void PrimaryLogPG::kick_snap_trim() { ceph_assert(is_active()); ceph_assert(is_primary()); if (is_clean() && !state_test(PG_STATE_PREMERGE) && !snap_trimq.empty()) { if (get_osdmap()->test_flag(CEPH_OSDMAP_NOSNAPTRIM)) { dout(10) << __func__ << ": nosnaptrim set, not kicking" << dendl; } else { dout(10) << __func__ << ": clean and snaps to trim, kicking" << dendl; snap_trimmer_machine.process_event(KickTrim()); } } } void PrimaryLogPG::snap_trimmer_scrub_complete() { if (is_primary() && is_active() && is_clean() && !snap_trimq.empty()) { dout(10) << "scrub finished - requeuing snap_trimmer" << dendl; snap_trimmer_machine.process_event(ScrubComplete()); } } void PrimaryLogPG::snap_trimmer(epoch_t queued) { if (recovery_state.is_deleting() || pg_has_reset_since(queued)) { return; } ceph_assert(is_primary()); dout(10) << "snap_trimmer posting" << dendl; snap_trimmer_machine.process_event(DoSnapWork()); dout(10) << "snap_trimmer complete" << dendl; return; } int PrimaryLogPG::do_xattr_cmp_u64(int op, __u64 v1, bufferlist& xattr) { __u64 v2; string v2s(xattr.c_str(), xattr.length()); if (v2s.length()) v2 = strtoull(v2s.c_str(), NULL, 10); else v2 = 0; dout(20) << "do_xattr_cmp_u64 '" << v1 << "' vs '" << v2 << "' op " << op << dendl; switch (op) { case CEPH_OSD_CMPXATTR_OP_EQ: return (v1 == v2); case CEPH_OSD_CMPXATTR_OP_NE: return (v1 != v2); case CEPH_OSD_CMPXATTR_OP_GT: return (v1 > v2); case CEPH_OSD_CMPXATTR_OP_GTE: return (v1 >= v2); case CEPH_OSD_CMPXATTR_OP_LT: return (v1 < v2); case CEPH_OSD_CMPXATTR_OP_LTE: return (v1 <= v2); default: return -EINVAL; } } int PrimaryLogPG::do_xattr_cmp_str(int op, string& v1s, bufferlist& xattr) { string v2s(xattr.c_str(), xattr.length()); dout(20) << "do_xattr_cmp_str '" << v1s << "' vs '" << v2s << "' op " << op << dendl; switch (op) { case CEPH_OSD_CMPXATTR_OP_EQ: return (v1s.compare(v2s) == 0); case CEPH_OSD_CMPXATTR_OP_NE: return (v1s.compare(v2s) != 0); case CEPH_OSD_CMPXATTR_OP_GT: return (v1s.compare(v2s) > 0); case CEPH_OSD_CMPXATTR_OP_GTE: return (v1s.compare(v2s) >= 0); case CEPH_OSD_CMPXATTR_OP_LT: return (v1s.compare(v2s) < 0); case CEPH_OSD_CMPXATTR_OP_LTE: return (v1s.compare(v2s) <= 0); default: return -EINVAL; } } int PrimaryLogPG::do_writesame(OpContext *ctx, OSDOp& osd_op) { ceph_osd_op& op = osd_op.op; vector write_ops(1); OSDOp& write_op = write_ops[0]; uint64_t write_length = op.writesame.length; int result = 0; if (!write_length) return 0; if (!op.writesame.data_length || write_length % op.writesame.data_length) return -EINVAL; if (op.writesame.data_length != osd_op.indata.length()) { derr << "invalid length ws data length " << op.writesame.data_length << " actual len " << osd_op.indata.length() << dendl; return -EINVAL; } while (write_length) { write_op.indata.append(osd_op.indata); write_length -= op.writesame.data_length; } write_op.op.op = CEPH_OSD_OP_WRITE; write_op.op.extent.offset = op.writesame.offset; write_op.op.extent.length = op.writesame.length; result = do_osd_ops(ctx, write_ops); if (result < 0) derr << "do_writesame do_osd_ops failed " << result << dendl; return result; } // ======================================================================== // low level osd ops int PrimaryLogPG::do_tmap2omap(OpContext *ctx, unsigned flags) { dout(20) << " convert tmap to omap for " << ctx->new_obs.oi.soid << dendl; bufferlist header, vals; int r = _get_tmap(ctx, &header, &vals); if (r < 0) { if (r == -ENODATA && (flags & CEPH_OSD_TMAP2OMAP_NULLOK)) r = 0; return r; } vector ops(3); ops[0].op.op = CEPH_OSD_OP_TRUNCATE; ops[0].op.extent.offset = 0; ops[0].op.extent.length = 0; ops[1].op.op = CEPH_OSD_OP_OMAPSETHEADER; ops[1].indata = std::move(header); ops[2].op.op = CEPH_OSD_OP_OMAPSETVALS; ops[2].indata = std::move(vals); return do_osd_ops(ctx, ops); } int PrimaryLogPG::do_tmapup_slow(OpContext *ctx, bufferlist::const_iterator& bp, OSDOp& osd_op, bufferlist& bl) { // decode bufferlist header; map m; if (bl.length()) { auto p = bl.cbegin(); decode(header, p); decode(m, p); ceph_assert(p.end()); } // do the update(s) while (!bp.end()) { __u8 op; string key; decode(op, bp); switch (op) { case CEPH_OSD_TMAP_SET: // insert key { decode(key, bp); bufferlist data; decode(data, bp); m[key] = data; } break; case CEPH_OSD_TMAP_RM: // remove key decode(key, bp); if (!m.count(key)) { return -ENOENT; } m.erase(key); break; case CEPH_OSD_TMAP_RMSLOPPY: // remove key decode(key, bp); m.erase(key); break; case CEPH_OSD_TMAP_HDR: // update header { decode(header, bp); } break; default: return -EINVAL; } } // reencode bufferlist obl; encode(header, obl); encode(m, obl); // write it out vector nops(1); OSDOp& newop = nops[0]; newop.op.op = CEPH_OSD_OP_WRITEFULL; newop.op.extent.offset = 0; newop.op.extent.length = obl.length(); newop.indata = obl; do_osd_ops(ctx, nops); return 0; } int PrimaryLogPG::do_tmapup(OpContext *ctx, bufferlist::const_iterator& bp, OSDOp& osd_op) { bufferlist::const_iterator orig_bp = bp; int result = 0; if (bp.end()) { dout(10) << "tmapup is a no-op" << dendl; } else { // read the whole object vector nops(1); OSDOp& newop = nops[0]; newop.op.op = CEPH_OSD_OP_READ; newop.op.extent.offset = 0; newop.op.extent.length = 0; result = do_osd_ops(ctx, nops); dout(10) << "tmapup read " << newop.outdata.length() << dendl; dout(30) << " starting is \n"; newop.outdata.hexdump(*_dout); *_dout << dendl; auto ip = newop.outdata.cbegin(); bufferlist obl; dout(30) << "the update command is: \n"; osd_op.indata.hexdump(*_dout); *_dout << dendl; // header bufferlist header; __u32 nkeys = 0; if (newop.outdata.length()) { decode(header, ip); decode(nkeys, ip); } dout(10) << "tmapup header " << header.length() << dendl; if (!bp.end() && *bp == CEPH_OSD_TMAP_HDR) { ++bp; decode(header, bp); dout(10) << "tmapup new header " << header.length() << dendl; } encode(header, obl); dout(20) << "tmapup initial nkeys " << nkeys << dendl; // update keys bufferlist newkeydata; string nextkey, last_in_key; bufferlist nextval; bool have_next = false; if (!ip.end()) { have_next = true; decode(nextkey, ip); decode(nextval, ip); } while (!bp.end() && !result) { __u8 op; string key; try { decode(op, bp); decode(key, bp); } catch (ceph::buffer::error& e) { return -EINVAL; } if (key < last_in_key) { dout(5) << "tmapup warning: key '" << key << "' < previous key '" << last_in_key << "', falling back to an inefficient (unsorted) update" << dendl; bp = orig_bp; return do_tmapup_slow(ctx, bp, osd_op, newop.outdata); } last_in_key = key; dout(10) << "tmapup op " << (int)op << " key " << key << dendl; // skip existing intervening keys bool key_exists = false; while (have_next && !key_exists) { dout(20) << " (have_next=" << have_next << " nextkey=" << nextkey << ")" << dendl; if (nextkey > key) break; if (nextkey < key) { // copy untouched. encode(nextkey, newkeydata); encode(nextval, newkeydata); dout(20) << " keep " << nextkey << " " << nextval.length() << dendl; } else { // don't copy; discard old value. and stop. dout(20) << " drop " << nextkey << " " << nextval.length() << dendl; key_exists = true; nkeys--; } if (!ip.end()) { decode(nextkey, ip); decode(nextval, ip); } else { have_next = false; } } if (op == CEPH_OSD_TMAP_SET) { bufferlist val; try { decode(val, bp); } catch (ceph::buffer::error& e) { return -EINVAL; } encode(key, newkeydata); encode(val, newkeydata); dout(20) << " set " << key << " " << val.length() << dendl; nkeys++; } else if (op == CEPH_OSD_TMAP_CREATE) { if (key_exists) { return -EEXIST; } bufferlist val; try { decode(val, bp); } catch (ceph::buffer::error& e) { return -EINVAL; } encode(key, newkeydata); encode(val, newkeydata); dout(20) << " create " << key << " " << val.length() << dendl; nkeys++; } else if (op == CEPH_OSD_TMAP_RM) { // do nothing. if (!key_exists) { return -ENOENT; } } else if (op == CEPH_OSD_TMAP_RMSLOPPY) { // do nothing } else { dout(10) << " invalid tmap op " << (int)op << dendl; return -EINVAL; } } // copy remaining if (have_next) { encode(nextkey, newkeydata); encode(nextval, newkeydata); dout(20) << " keep " << nextkey << " " << nextval.length() << dendl; } if (!ip.end()) { bufferlist rest; rest.substr_of(newop.outdata, ip.get_off(), newop.outdata.length() - ip.get_off()); dout(20) << " keep trailing " << rest.length() << " at " << newkeydata.length() << dendl; newkeydata.claim_append(rest); } // encode final key count + key data dout(20) << "tmapup final nkeys " << nkeys << dendl; encode(nkeys, obl); obl.claim_append(newkeydata); if (0) { dout(30) << " final is \n"; obl.hexdump(*_dout); *_dout << dendl; // sanity check auto tp = obl.cbegin(); bufferlist h; decode(h, tp); map d; decode(d, tp); ceph_assert(tp.end()); dout(0) << " **** debug sanity check, looks ok ****" << dendl; } // write it out if (!result) { dout(20) << "tmapput write " << obl.length() << dendl; newop.op.op = CEPH_OSD_OP_WRITEFULL; newop.op.extent.offset = 0; newop.op.extent.length = obl.length(); newop.indata = obl; do_osd_ops(ctx, nops); } } return result; } static int check_offset_and_length(uint64_t offset, uint64_t length, uint64_t max, DoutPrefixProvider *dpp) { if (offset >= max || length > max || offset + length > max) { ldpp_dout(dpp, 10) << __func__ << " " << "osd_max_object_size: " << max << "; Hard limit of object size is 4GB." << dendl; return -EFBIG; } return 0; } struct FillInVerifyExtent : public Context { ceph_le64 *r; int32_t *rval; bufferlist *outdatap; std::optional maybe_crc; uint64_t size; OSDService *osd; hobject_t soid; uint32_t flags; FillInVerifyExtent(ceph_le64 *r, int32_t *rv, bufferlist *blp, std::optional mc, uint64_t size, OSDService *osd, hobject_t soid, uint32_t flags) : r(r), rval(rv), outdatap(blp), maybe_crc(mc), size(size), osd(osd), soid(soid), flags(flags) {} void finish(int len) override { if (len < 0) { *rval = len; return; } *r = len; *rval = 0; // whole object? can we verify the checksum? if (maybe_crc && *r == size) { uint32_t crc = outdatap->crc32c(-1); if (maybe_crc != crc) { osd->clog->error() << std::hex << " full-object read crc 0x" << crc << " != expected 0x" << *maybe_crc << std::dec << " on " << soid; if (!(flags & CEPH_OSD_OP_FLAG_FAILOK)) { *rval = -EIO; *r = 0; } } } } }; struct ToSparseReadResult : public Context { int* result; bufferlist* data_bl; uint64_t data_offset; ceph_le64* len; ToSparseReadResult(int* result, bufferlist* bl, uint64_t offset, ceph_le64* len) : result(result), data_bl(bl), data_offset(offset),len(len) {} void finish(int r) override { if (r < 0) { *result = r; return; } *result = 0; *len = r; bufferlist outdata; map extents = {{data_offset, r}}; encode(extents, outdata); encode_destructively(*data_bl, outdata); data_bl->swap(outdata); } }; template static string list_keys(const map& m) { string s; for (typename map::const_iterator itr = m.begin(); itr != m.end(); ++itr) { if (!s.empty()) { s.push_back(','); } s.append(itr->first); } return s; } template static string list_entries(const T& m) { string s; for (typename T::const_iterator itr = m.begin(); itr != m.end(); ++itr) { if (!s.empty()) { s.push_back(','); } s.append(*itr); } return s; } void PrimaryLogPG::maybe_create_new_object( OpContext *ctx, bool ignore_transaction) { ObjectState& obs = ctx->new_obs; if (!obs.exists) { ctx->delta_stats.num_objects++; obs.exists = true; ceph_assert(!obs.oi.is_whiteout()); obs.oi.new_object(); if (!ignore_transaction) ctx->op_t->create(obs.oi.soid); } else if (obs.oi.is_whiteout()) { dout(10) << __func__ << " clearing whiteout on " << obs.oi.soid << dendl; ctx->new_obs.oi.clear_flag(object_info_t::FLAG_WHITEOUT); --ctx->delta_stats.num_whiteouts; } } struct ReadFinisher : public PrimaryLogPG::OpFinisher { OSDOp& osd_op; explicit ReadFinisher(OSDOp& osd_op) : osd_op(osd_op) { } int execute() override { return osd_op.rval; } }; struct C_ChecksumRead : public Context { PrimaryLogPG *primary_log_pg; OSDOp &osd_op; Checksummer::CSumType csum_type; bufferlist init_value_bl; ceph_le64 read_length; bufferlist read_bl; Context *fill_extent_ctx; C_ChecksumRead(PrimaryLogPG *primary_log_pg, OSDOp &osd_op, Checksummer::CSumType csum_type, bufferlist &&init_value_bl, std::optional maybe_crc, uint64_t size, OSDService *osd, hobject_t soid, uint32_t flags) : primary_log_pg(primary_log_pg), osd_op(osd_op), csum_type(csum_type), init_value_bl(std::move(init_value_bl)), fill_extent_ctx(new FillInVerifyExtent(&read_length, &osd_op.rval, &read_bl, maybe_crc, size, osd, soid, flags)) { } ~C_ChecksumRead() override { delete fill_extent_ctx; } void finish(int r) override { fill_extent_ctx->complete(r); fill_extent_ctx = nullptr; if (osd_op.rval >= 0) { bufferlist::const_iterator init_value_bl_it = init_value_bl.begin(); osd_op.rval = primary_log_pg->finish_checksum(osd_op, csum_type, &init_value_bl_it, read_bl); } } }; int PrimaryLogPG::do_checksum(OpContext *ctx, OSDOp& osd_op, bufferlist::const_iterator *bl_it) { dout(20) << __func__ << dendl; auto& op = osd_op.op; if (op.checksum.chunk_size > 0) { if (op.checksum.length == 0) { dout(10) << __func__ << ": length required when chunk size provided" << dendl; return -EINVAL; } if (op.checksum.length % op.checksum.chunk_size != 0) { dout(10) << __func__ << ": length not aligned to chunk size" << dendl; return -EINVAL; } } auto& oi = ctx->new_obs.oi; if (op.checksum.offset == 0 && op.checksum.length == 0) { // zeroed offset+length implies checksum whole object op.checksum.length = oi.size; } else if (op.checksum.offset >= oi.size) { // read size was trimmed to zero, do nothing // see PrimaryLogPG::do_read return 0; } else if (op.extent.offset + op.extent.length > oi.size) { op.extent.length = oi.size - op.extent.offset; if (op.checksum.chunk_size > 0 && op.checksum.length % op.checksum.chunk_size != 0) { dout(10) << __func__ << ": length (trimmed to 0x" << std::hex << op.checksum.length << ") not aligned to chunk size 0x" << op.checksum.chunk_size << std::dec << dendl; return -EINVAL; } } Checksummer::CSumType csum_type; switch (op.checksum.type) { case CEPH_OSD_CHECKSUM_OP_TYPE_XXHASH32: csum_type = Checksummer::CSUM_XXHASH32; break; case CEPH_OSD_CHECKSUM_OP_TYPE_XXHASH64: csum_type = Checksummer::CSUM_XXHASH64; break; case CEPH_OSD_CHECKSUM_OP_TYPE_CRC32C: csum_type = Checksummer::CSUM_CRC32C; break; default: dout(10) << __func__ << ": unknown crc type (" << static_cast(op.checksum.type) << ")" << dendl; return -EINVAL; } size_t csum_init_value_size = Checksummer::get_csum_init_value_size(csum_type); if (bl_it->get_remaining() < csum_init_value_size) { dout(10) << __func__ << ": init value not provided" << dendl; return -EINVAL; } bufferlist init_value_bl; init_value_bl.substr_of(bl_it->get_bl(), bl_it->get_off(), csum_init_value_size); *bl_it += csum_init_value_size; if (pool.info.is_erasure() && op.checksum.length > 0) { // If there is a data digest and it is possible we are reading // entire object, pass the digest. std::optional maybe_crc; if (oi.is_data_digest() && op.checksum.offset == 0 && op.checksum.length >= oi.size) { maybe_crc = oi.data_digest; } // async read auto& soid = oi.soid; auto checksum_ctx = new C_ChecksumRead(this, osd_op, csum_type, std::move(init_value_bl), maybe_crc, oi.size, osd, soid, op.flags); ctx->pending_async_reads.push_back({ {op.checksum.offset, op.checksum.length, op.flags}, {&checksum_ctx->read_bl, checksum_ctx}}); dout(10) << __func__ << ": async_read noted for " << soid << dendl; ctx->op_finishers[ctx->current_osd_subop_num].reset( new ReadFinisher(osd_op)); return -EINPROGRESS; } // sync read std::vector read_ops(1); auto& read_op = read_ops[0]; if (op.checksum.length > 0) { read_op.op.op = CEPH_OSD_OP_READ; read_op.op.flags = op.flags; read_op.op.extent.offset = op.checksum.offset; read_op.op.extent.length = op.checksum.length; read_op.op.extent.truncate_size = 0; read_op.op.extent.truncate_seq = 0; int r = do_osd_ops(ctx, read_ops); if (r < 0) { derr << __func__ << ": do_osd_ops failed: " << cpp_strerror(r) << dendl; return r; } } bufferlist::const_iterator init_value_bl_it = init_value_bl.begin(); return finish_checksum(osd_op, csum_type, &init_value_bl_it, read_op.outdata); } int PrimaryLogPG::finish_checksum(OSDOp& osd_op, Checksummer::CSumType csum_type, bufferlist::const_iterator *init_value_bl_it, const bufferlist &read_bl) { dout(20) << __func__ << dendl; auto& op = osd_op.op; if (op.checksum.length > 0 && read_bl.length() != op.checksum.length) { derr << __func__ << ": bytes read " << read_bl.length() << " != " << op.checksum.length << dendl; return -EINVAL; } size_t csum_chunk_size = (op.checksum.chunk_size != 0 ? op.checksum.chunk_size : read_bl.length()); uint32_t csum_count = (csum_chunk_size > 0 ? read_bl.length() / csum_chunk_size : 0); bufferlist csum; bufferptr csum_data; if (csum_count > 0) { size_t csum_value_size = Checksummer::get_csum_value_size(csum_type); csum_data = ceph::buffer::create(csum_value_size * csum_count); csum_data.zero(); csum.append(csum_data); switch (csum_type) { case Checksummer::CSUM_XXHASH32: { Checksummer::xxhash32::init_value_t init_value; decode(init_value, *init_value_bl_it); Checksummer::calculate( init_value, csum_chunk_size, 0, read_bl.length(), read_bl, &csum_data); } break; case Checksummer::CSUM_XXHASH64: { Checksummer::xxhash64::init_value_t init_value; decode(init_value, *init_value_bl_it); Checksummer::calculate( init_value, csum_chunk_size, 0, read_bl.length(), read_bl, &csum_data); } break; case Checksummer::CSUM_CRC32C: { Checksummer::crc32c::init_value_t init_value; decode(init_value, *init_value_bl_it); Checksummer::calculate( init_value, csum_chunk_size, 0, read_bl.length(), read_bl, &csum_data); } break; default: break; } } encode(csum_count, osd_op.outdata); osd_op.outdata.claim_append(csum); return 0; } struct C_ExtentCmpRead : public Context { PrimaryLogPG *primary_log_pg; OSDOp &osd_op; ceph_le64 read_length{}; bufferlist read_bl; Context *fill_extent_ctx; C_ExtentCmpRead(PrimaryLogPG *primary_log_pg, OSDOp &osd_op, std::optional maybe_crc, uint64_t size, OSDService *osd, hobject_t soid, uint32_t flags) : primary_log_pg(primary_log_pg), osd_op(osd_op), fill_extent_ctx(new FillInVerifyExtent(&read_length, &osd_op.rval, &read_bl, maybe_crc, size, osd, soid, flags)) { } ~C_ExtentCmpRead() override { delete fill_extent_ctx; } void finish(int r) override { if (r == -ENOENT) { osd_op.rval = 0; read_bl.clear(); delete fill_extent_ctx; } else { fill_extent_ctx->complete(r); } fill_extent_ctx = nullptr; if (osd_op.rval >= 0) { osd_op.rval = primary_log_pg->finish_extent_cmp(osd_op, read_bl); } } }; int PrimaryLogPG::do_extent_cmp(OpContext *ctx, OSDOp& osd_op) { dout(20) << __func__ << dendl; ceph_osd_op& op = osd_op.op; auto& oi = ctx->new_obs.oi; uint64_t size = oi.size; if ((oi.truncate_seq < op.extent.truncate_seq) && (op.extent.offset + op.extent.length > op.extent.truncate_size)) { size = op.extent.truncate_size; } if (op.extent.offset >= size) { op.extent.length = 0; } else if (op.extent.offset + op.extent.length > size) { op.extent.length = size - op.extent.offset; } if (op.extent.length == 0) { dout(20) << __func__ << " zero length extent" << dendl; return finish_extent_cmp(osd_op, bufferlist{}); } else if (!ctx->obs->exists || ctx->obs->oi.is_whiteout()) { dout(20) << __func__ << " object DNE" << dendl; return finish_extent_cmp(osd_op, {}); } else if (pool.info.is_erasure()) { // If there is a data digest and it is possible we are reading // entire object, pass the digest. std::optional maybe_crc; if (oi.is_data_digest() && op.checksum.offset == 0 && op.checksum.length >= oi.size) { maybe_crc = oi.data_digest; } // async read auto& soid = oi.soid; auto extent_cmp_ctx = new C_ExtentCmpRead(this, osd_op, maybe_crc, oi.size, osd, soid, op.flags); ctx->pending_async_reads.push_back({ {op.extent.offset, op.extent.length, op.flags}, {&extent_cmp_ctx->read_bl, extent_cmp_ctx}}); dout(10) << __func__ << ": async_read noted for " << soid << dendl; ctx->op_finishers[ctx->current_osd_subop_num].reset( new ReadFinisher(osd_op)); return -EINPROGRESS; } // sync read vector read_ops(1); OSDOp& read_op = read_ops[0]; read_op.op.op = CEPH_OSD_OP_SYNC_READ; read_op.op.extent.offset = op.extent.offset; read_op.op.extent.length = op.extent.length; read_op.op.extent.truncate_seq = op.extent.truncate_seq; read_op.op.extent.truncate_size = op.extent.truncate_size; int result = do_osd_ops(ctx, read_ops); if (result < 0) { derr << __func__ << " failed " << result << dendl; return result; } return finish_extent_cmp(osd_op, read_op.outdata); } int PrimaryLogPG::finish_extent_cmp(OSDOp& osd_op, const bufferlist &read_bl) { for (uint64_t idx = 0; idx < osd_op.indata.length(); ++idx) { char read_byte = (idx < read_bl.length() ? read_bl[idx] : 0); if (osd_op.indata[idx] != read_byte) { return (-MAX_ERRNO - idx); } } return 0; } int PrimaryLogPG::do_read(OpContext *ctx, OSDOp& osd_op) { dout(20) << __func__ << dendl; auto& op = osd_op.op; auto& oi = ctx->new_obs.oi; auto& soid = oi.soid; __u32 seq = oi.truncate_seq; uint64_t size = oi.size; bool trimmed_read = false; dout(30) << __func__ << " oi.size: " << oi.size << dendl; dout(30) << __func__ << " oi.truncate_seq: " << oi.truncate_seq << dendl; dout(30) << __func__ << " op.extent.truncate_seq: " << op.extent.truncate_seq << dendl; dout(30) << __func__ << " op.extent.truncate_size: " << op.extent.truncate_size << dendl; // are we beyond truncate_size? if ( (seq < op.extent.truncate_seq) && (op.extent.offset + op.extent.length > op.extent.truncate_size) && (size > op.extent.truncate_size) ) size = op.extent.truncate_size; if (op.extent.length == 0) //length is zero mean read the whole object op.extent.length = size; if (op.extent.offset >= size) { op.extent.length = 0; trimmed_read = true; } else if (op.extent.offset + op.extent.length > size) { op.extent.length = size - op.extent.offset; trimmed_read = true; } dout(30) << __func__ << "op.extent.length is now " << op.extent.length << dendl; // read into a buffer int result = 0; if (trimmed_read && op.extent.length == 0) { // read size was trimmed to zero and it is expected to do nothing // a read operation of 0 bytes does *not* do nothing, this is why // the trimmed_read boolean is needed } else if (pool.info.is_erasure()) { // The initialisation below is required to silence a false positive // -Wmaybe-uninitialized warning std::optional maybe_crc; // If there is a data digest and it is possible we are reading // entire object, pass the digest. FillInVerifyExtent will // will check the oi.size again. if (oi.is_data_digest() && op.extent.offset == 0 && op.extent.length >= oi.size) maybe_crc = oi.data_digest; ctx->pending_async_reads.push_back( make_pair( boost::make_tuple(op.extent.offset, op.extent.length, op.flags), make_pair(&osd_op.outdata, new FillInVerifyExtent(&op.extent.length, &osd_op.rval, &osd_op.outdata, maybe_crc, oi.size, osd, soid, op.flags)))); dout(10) << " async_read noted for " << soid << dendl; ctx->op_finishers[ctx->current_osd_subop_num].reset( new ReadFinisher(osd_op)); } else { int r = pgbackend->objects_read_sync( soid, op.extent.offset, op.extent.length, op.flags, &osd_op.outdata); // whole object? can we verify the checksum? if (r >= 0 && op.extent.offset == 0 && (uint64_t)r == oi.size && oi.is_data_digest()) { uint32_t crc = osd_op.outdata.crc32c(-1); if (oi.data_digest != crc) { osd->clog->error() << info.pgid << std::hex << " full-object read crc 0x" << crc << " != expected 0x" << oi.data_digest << std::dec << " on " << soid; r = -EIO; // try repair later } } if (r == -EIO) { r = rep_repair_primary_object(soid, ctx); } if (r >= 0) op.extent.length = r; else if (r == -EAGAIN) { result = -EAGAIN; } else { result = r; op.extent.length = 0; } dout(10) << " read got " << r << " / " << op.extent.length << " bytes from obj " << soid << dendl; } if (result >= 0) { ctx->delta_stats.num_rd_kb += shift_round_up(op.extent.length, 10); ctx->delta_stats.num_rd++; } return result; } int PrimaryLogPG::do_sparse_read(OpContext *ctx, OSDOp& osd_op) { dout(20) << __func__ << dendl; auto& op = osd_op.op; auto& oi = ctx->new_obs.oi; auto& soid = oi.soid; if (op.extent.truncate_seq) { dout(0) << "sparse_read does not support truncation sequence " << dendl; return -EINVAL; } ++ctx->num_read; if (pool.info.is_erasure()) { // translate sparse read to a normal one if not supported uint64_t offset = op.extent.offset; uint64_t length = op.extent.length; if (offset > oi.size) { length = 0; } else if (offset + length > oi.size) { length = oi.size - offset; } if (length > 0) { ctx->pending_async_reads.push_back( make_pair( boost::make_tuple(offset, length, op.flags), make_pair( &osd_op.outdata, new ToSparseReadResult(&osd_op.rval, &osd_op.outdata, offset, &op.extent.length)))); dout(10) << " async_read (was sparse_read) noted for " << soid << dendl; ctx->op_finishers[ctx->current_osd_subop_num].reset( new ReadFinisher(osd_op)); } else { dout(10) << " sparse read ended up empty for " << soid << dendl; map extents; encode(extents, osd_op.outdata); } } else { // read into a buffer map m; int r = osd->store->fiemap(ch, ghobject_t(soid, ghobject_t::NO_GEN, info.pgid.shard), op.extent.offset, op.extent.length, m); if (r < 0) { return r; } bufferlist data_bl; r = pgbackend->objects_readv_sync(soid, std::move(m), op.flags, &data_bl); if (r == -EIO) { r = rep_repair_primary_object(soid, ctx); } if (r < 0) { return r; } // Why SPARSE_READ need checksum? In fact, librbd always use sparse-read. // Maybe at first, there is no much whole objects. With continued use, more // and more whole object exist. So from this point, for spare-read add // checksum make sense. if ((uint64_t)r == oi.size && oi.is_data_digest()) { uint32_t crc = data_bl.crc32c(-1); if (oi.data_digest != crc) { osd->clog->error() << info.pgid << std::hex << " full-object read crc 0x" << crc << " != expected 0x" << oi.data_digest << std::dec << " on " << soid; r = rep_repair_primary_object(soid, ctx); if (r < 0) { return r; } } } op.extent.length = r; encode(m, osd_op.outdata); // re-encode since it might be modified ::encode_destructively(data_bl, osd_op.outdata); dout(10) << " sparse_read got " << r << " bytes from object " << soid << dendl; } ctx->delta_stats.num_rd_kb += shift_round_up(op.extent.length, 10); ctx->delta_stats.num_rd++; return 0; } int PrimaryLogPG::do_osd_ops(OpContext *ctx, vector& ops) { int result = 0; SnapSetContext *ssc = ctx->obc->ssc; ObjectState& obs = ctx->new_obs; object_info_t& oi = obs.oi; const hobject_t& soid = oi.soid; const bool skip_data_digest = osd->store->has_builtin_csum() && osd->osd_skip_data_digest; PGTransaction* t = ctx->op_t.get(); dout(10) << "do_osd_op " << soid << " " << ops << dendl; #ifdef HAVE_JAEGER if (ctx->op->osd_parent_span) { auto do_osd_op_span = jaeger_tracing::child_span(__func__, ctx->op->osd_parent_span); } #endif ctx->current_osd_subop_num = 0; for (auto p = ops.begin(); p != ops.end(); ++p, ctx->current_osd_subop_num++, ctx->processed_subop_count++) { OSDOp& osd_op = *p; ceph_osd_op& op = osd_op.op; OpFinisher* op_finisher = nullptr; { auto op_finisher_it = ctx->op_finishers.find(ctx->current_osd_subop_num); if (op_finisher_it != ctx->op_finishers.end()) { op_finisher = op_finisher_it->second.get(); } } // TODO: check endianness (ceph_le32 vs uint32_t, etc.) // The fields in ceph_osd_op are little-endian (according to the definition in rados.h), // but the code in this function seems to treat them as native-endian. What should the // tracepoints do? tracepoint(osd, do_osd_op_pre, soid.oid.name.c_str(), soid.snap.val, op.op, ceph_osd_op_name(op.op), op.flags); dout(10) << "do_osd_op " << osd_op << dendl; auto bp = osd_op.indata.cbegin(); // user-visible modifcation? switch (op.op) { // non user-visible modifications case CEPH_OSD_OP_WATCH: case CEPH_OSD_OP_CACHE_EVICT: case CEPH_OSD_OP_CACHE_FLUSH: case CEPH_OSD_OP_CACHE_TRY_FLUSH: case CEPH_OSD_OP_UNDIRTY: case CEPH_OSD_OP_COPY_FROM: // we handle user_version update explicitly case CEPH_OSD_OP_COPY_FROM2: case CEPH_OSD_OP_CACHE_PIN: case CEPH_OSD_OP_CACHE_UNPIN: case CEPH_OSD_OP_SET_REDIRECT: case CEPH_OSD_OP_SET_CHUNK: case CEPH_OSD_OP_TIER_PROMOTE: case CEPH_OSD_OP_TIER_FLUSH: case CEPH_OSD_OP_TIER_EVICT: break; default: if (op.op & CEPH_OSD_OP_MODE_WR) ctx->user_modify = true; } // munge -1 truncate to 0 truncate if (ceph_osd_op_uses_extent(op.op) && op.extent.truncate_seq == 1 && op.extent.truncate_size == (-1ULL)) { op.extent.truncate_size = 0; op.extent.truncate_seq = 0; } // munge ZERO -> TRUNCATE? (don't munge to DELETE or we risk hosing attributes) if (op.op == CEPH_OSD_OP_ZERO && obs.exists && op.extent.offset < static_cast(osd->osd_max_object_size) && op.extent.length >= 1 && op.extent.length <= static_cast(osd->osd_max_object_size) && op.extent.offset + op.extent.length >= oi.size) { if (op.extent.offset >= oi.size) { // no-op goto fail; } dout(10) << " munging ZERO " << op.extent.offset << "~" << op.extent.length << " -> TRUNCATE " << op.extent.offset << " (old size is " << oi.size << ")" << dendl; op.op = CEPH_OSD_OP_TRUNCATE; } switch (op.op) { // --- READS --- case CEPH_OSD_OP_CMPEXT: ++ctx->num_read; tracepoint(osd, do_osd_op_pre_extent_cmp, soid.oid.name.c_str(), soid.snap.val, oi.size, oi.truncate_seq, op.extent.offset, op.extent.length, op.extent.truncate_size, op.extent.truncate_seq); if (op_finisher == nullptr) { result = do_extent_cmp(ctx, osd_op); } else { result = op_finisher->execute(); } break; case CEPH_OSD_OP_SYNC_READ: if (pool.info.is_erasure()) { result = -EOPNOTSUPP; break; } // fall through case CEPH_OSD_OP_READ: ++ctx->num_read; tracepoint(osd, do_osd_op_pre_read, soid.oid.name.c_str(), soid.snap.val, oi.size, oi.truncate_seq, op.extent.offset, op.extent.length, op.extent.truncate_size, op.extent.truncate_seq); if (op_finisher == nullptr) { if (!ctx->data_off) { ctx->data_off = op.extent.offset; } result = do_read(ctx, osd_op); } else { result = op_finisher->execute(); } break; case CEPH_OSD_OP_CHECKSUM: ++ctx->num_read; { tracepoint(osd, do_osd_op_pre_checksum, soid.oid.name.c_str(), soid.snap.val, oi.size, oi.truncate_seq, op.checksum.type, op.checksum.offset, op.checksum.length, op.checksum.chunk_size); if (op_finisher == nullptr) { result = do_checksum(ctx, osd_op, &bp); } else { result = op_finisher->execute(); } } break; /* map extents */ case CEPH_OSD_OP_MAPEXT: tracepoint(osd, do_osd_op_pre_mapext, soid.oid.name.c_str(), soid.snap.val, op.extent.offset, op.extent.length); if (pool.info.is_erasure()) { result = -EOPNOTSUPP; break; } ++ctx->num_read; { // read into a buffer bufferlist bl; int r = osd->store->fiemap(ch, ghobject_t(soid, ghobject_t::NO_GEN, info.pgid.shard), op.extent.offset, op.extent.length, bl); osd_op.outdata = std::move(bl); if (r < 0) result = r; else ctx->delta_stats.num_rd_kb += shift_round_up(bl.length(), 10); ctx->delta_stats.num_rd++; dout(10) << " map_extents done on object " << soid << dendl; } break; /* map extents */ case CEPH_OSD_OP_SPARSE_READ: tracepoint(osd, do_osd_op_pre_sparse_read, soid.oid.name.c_str(), soid.snap.val, oi.size, oi.truncate_seq, op.extent.offset, op.extent.length, op.extent.truncate_size, op.extent.truncate_seq); if (op_finisher == nullptr) { result = do_sparse_read(ctx, osd_op); } else { result = op_finisher->execute(); } break; case CEPH_OSD_OP_CALL: { string cname, mname; bufferlist indata; try { bp.copy(op.cls.class_len, cname); bp.copy(op.cls.method_len, mname); bp.copy(op.cls.indata_len, indata); } catch (ceph::buffer::error& e) { dout(10) << "call unable to decode class + method + indata" << dendl; dout(30) << "in dump: "; osd_op.indata.hexdump(*_dout); *_dout << dendl; result = -EINVAL; tracepoint(osd, do_osd_op_pre_call, soid.oid.name.c_str(), soid.snap.val, "???", "???"); break; } tracepoint(osd, do_osd_op_pre_call, soid.oid.name.c_str(), soid.snap.val, cname.c_str(), mname.c_str()); ClassHandler::ClassData *cls; result = ClassHandler::get_instance().open_class(cname, &cls); ceph_assert(result == 0); // init_op_flags() already verified this works. ClassHandler::ClassMethod *method = cls->get_method(mname); if (!method) { dout(10) << "call method " << cname << "." << mname << " does not exist" << dendl; result = -EOPNOTSUPP; break; } int flags = method->get_flags(); if (flags & CLS_METHOD_WR) ctx->user_modify = true; bufferlist outdata; dout(10) << "call method " << cname << "." << mname << dendl; int prev_rd = ctx->num_read; int prev_wr = ctx->num_write; result = method->exec((cls_method_context_t)&ctx, indata, outdata); if (ctx->num_read > prev_rd && !(flags & CLS_METHOD_RD)) { derr << "method " << cname << "." << mname << " tried to read object but is not marked RD" << dendl; result = -EIO; break; } if (ctx->num_write > prev_wr && !(flags & CLS_METHOD_WR)) { derr << "method " << cname << "." << mname << " tried to update object but is not marked WR" << dendl; result = -EIO; break; } dout(10) << "method called response length=" << outdata.length() << dendl; op.extent.length = outdata.length(); osd_op.outdata.claim_append(outdata); dout(30) << "out dump: "; osd_op.outdata.hexdump(*_dout); *_dout << dendl; } break; case CEPH_OSD_OP_STAT: // note: stat does not require RD { tracepoint(osd, do_osd_op_pre_stat, soid.oid.name.c_str(), soid.snap.val); if (obs.exists && !oi.is_whiteout()) { encode(oi.size, osd_op.outdata); encode(oi.mtime, osd_op.outdata); dout(10) << "stat oi has " << oi.size << " " << oi.mtime << dendl; } else { result = -ENOENT; dout(10) << "stat oi object does not exist" << dendl; } ctx->delta_stats.num_rd++; } break; case CEPH_OSD_OP_ISDIRTY: ++ctx->num_read; { tracepoint(osd, do_osd_op_pre_isdirty, soid.oid.name.c_str(), soid.snap.val); bool is_dirty = obs.oi.is_dirty(); encode(is_dirty, osd_op.outdata); ctx->delta_stats.num_rd++; result = 0; } break; case CEPH_OSD_OP_UNDIRTY: ++ctx->num_write; result = 0; { tracepoint(osd, do_osd_op_pre_undirty, soid.oid.name.c_str(), soid.snap.val); if (oi.is_dirty()) { ctx->undirty = true; // see make_writeable() ctx->modify = true; ctx->delta_stats.num_wr++; } } break; case CEPH_OSD_OP_CACHE_TRY_FLUSH: ++ctx->num_write; result = 0; { tracepoint(osd, do_osd_op_pre_try_flush, soid.oid.name.c_str(), soid.snap.val); if (ctx->lock_type != RWState::RWNONE) { dout(10) << "cache-try-flush without SKIPRWLOCKS flag set" << dendl; result = -EINVAL; break; } if (pool.info.cache_mode == pg_pool_t::CACHEMODE_NONE || obs.oi.has_manifest()) { result = -EINVAL; break; } if (!obs.exists) { result = 0; break; } if (oi.is_cache_pinned()) { dout(10) << "cache-try-flush on a pinned object, consider unpin this object first" << dendl; result = -EPERM; break; } if (oi.is_dirty()) { result = start_flush(ctx->op, ctx->obc, false, NULL, std::nullopt); if (result == -EINPROGRESS) result = -EAGAIN; } else { result = 0; } } break; case CEPH_OSD_OP_CACHE_FLUSH: ++ctx->num_write; result = 0; { tracepoint(osd, do_osd_op_pre_cache_flush, soid.oid.name.c_str(), soid.snap.val); if (ctx->lock_type == RWState::RWNONE) { dout(10) << "cache-flush with SKIPRWLOCKS flag set" << dendl; result = -EINVAL; break; } if (pool.info.cache_mode == pg_pool_t::CACHEMODE_NONE || obs.oi.has_manifest()) { result = -EINVAL; break; } if (!obs.exists) { result = 0; break; } if (oi.is_cache_pinned()) { dout(10) << "cache-flush on a pinned object, consider unpin this object first" << dendl; result = -EPERM; break; } hobject_t missing; if (oi.is_dirty()) { result = start_flush(ctx->op, ctx->obc, true, &missing, std::nullopt); if (result == -EINPROGRESS) result = -EAGAIN; } else { result = 0; } // Check special return value which has set missing_return if (result == -ENOENT) { dout(10) << __func__ << " CEPH_OSD_OP_CACHE_FLUSH got ENOENT" << dendl; ceph_assert(!missing.is_min()); wait_for_unreadable_object(missing, ctx->op); // Error code which is used elsewhere when wait_for_unreadable_object() is used result = -EAGAIN; } } break; case CEPH_OSD_OP_CACHE_EVICT: ++ctx->num_write; result = 0; { tracepoint(osd, do_osd_op_pre_cache_evict, soid.oid.name.c_str(), soid.snap.val); if (pool.info.cache_mode == pg_pool_t::CACHEMODE_NONE || obs.oi.has_manifest()) { result = -EINVAL; break; } if (!obs.exists) { result = 0; break; } if (oi.is_cache_pinned()) { dout(10) << "cache-evict on a pinned object, consider unpin this object first" << dendl; result = -EPERM; break; } if (oi.is_dirty()) { result = -EBUSY; break; } if (!oi.watchers.empty()) { result = -EBUSY; break; } if (soid.snap == CEPH_NOSNAP) { result = _verify_no_head_clones(soid, ssc->snapset); if (result < 0) break; } result = _delete_oid(ctx, true, false); if (result >= 0) { // mark that this is a cache eviction to avoid triggering normal // make_writeable() clone creation in finish_ctx() ctx->cache_operation = true; } osd->logger->inc(l_osd_tier_evict); } break; case CEPH_OSD_OP_GETXATTR: ++ctx->num_read; { string aname; bp.copy(op.xattr.name_len, aname); tracepoint(osd, do_osd_op_pre_getxattr, soid.oid.name.c_str(), soid.snap.val, aname.c_str()); string name = "_" + aname; int r = getattr_maybe_cache( ctx->obc, name, &(osd_op.outdata)); if (r >= 0) { op.xattr.value_len = osd_op.outdata.length(); result = 0; ctx->delta_stats.num_rd_kb += shift_round_up(osd_op.outdata.length(), 10); } else result = r; ctx->delta_stats.num_rd++; } break; case CEPH_OSD_OP_GETXATTRS: ++ctx->num_read; { tracepoint(osd, do_osd_op_pre_getxattrs, soid.oid.name.c_str(), soid.snap.val); map out; result = getattrs_maybe_cache( ctx->obc, &out); bufferlist bl; encode(out, bl); ctx->delta_stats.num_rd_kb += shift_round_up(bl.length(), 10); ctx->delta_stats.num_rd++; osd_op.outdata.claim_append(bl); } break; case CEPH_OSD_OP_CMPXATTR: ++ctx->num_read; { string aname; bp.copy(op.xattr.name_len, aname); tracepoint(osd, do_osd_op_pre_cmpxattr, soid.oid.name.c_str(), soid.snap.val, aname.c_str()); string name = "_" + aname; name[op.xattr.name_len + 1] = 0; bufferlist xattr; result = getattr_maybe_cache( ctx->obc, name, &xattr); if (result < 0 && result != -EEXIST && result != -ENODATA) break; ctx->delta_stats.num_rd++; ctx->delta_stats.num_rd_kb += shift_round_up(xattr.length(), 10); switch (op.xattr.cmp_mode) { case CEPH_OSD_CMPXATTR_MODE_STRING: { string val; bp.copy(op.xattr.value_len, val); val[op.xattr.value_len] = 0; dout(10) << "CEPH_OSD_OP_CMPXATTR name=" << name << " val=" << val << " op=" << (int)op.xattr.cmp_op << " mode=" << (int)op.xattr.cmp_mode << dendl; result = do_xattr_cmp_str(op.xattr.cmp_op, val, xattr); } break; case CEPH_OSD_CMPXATTR_MODE_U64: { uint64_t u64val; try { decode(u64val, bp); } catch (ceph::buffer::error& e) { result = -EINVAL; goto fail; } dout(10) << "CEPH_OSD_OP_CMPXATTR name=" << name << " val=" << u64val << " op=" << (int)op.xattr.cmp_op << " mode=" << (int)op.xattr.cmp_mode << dendl; result = do_xattr_cmp_u64(op.xattr.cmp_op, u64val, xattr); } break; default: dout(10) << "bad cmp mode " << (int)op.xattr.cmp_mode << dendl; result = -EINVAL; } if (!result) { dout(10) << "comparison returned false" << dendl; result = -ECANCELED; break; } if (result < 0) { dout(10) << "comparison returned " << result << " " << cpp_strerror(-result) << dendl; break; } dout(10) << "comparison returned true" << dendl; } break; case CEPH_OSD_OP_ASSERT_VER: ++ctx->num_read; { uint64_t ver = op.assert_ver.ver; tracepoint(osd, do_osd_op_pre_assert_ver, soid.oid.name.c_str(), soid.snap.val, ver); if (!ver) result = -EINVAL; else if (ver < oi.user_version) result = -ERANGE; else if (ver > oi.user_version) result = -EOVERFLOW; } break; case CEPH_OSD_OP_LIST_WATCHERS: ++ctx->num_read; { tracepoint(osd, do_osd_op_pre_list_watchers, soid.oid.name.c_str(), soid.snap.val); obj_list_watch_response_t resp; map, watch_info_t>::const_iterator oi_iter; for (oi_iter = oi.watchers.begin(); oi_iter != oi.watchers.end(); ++oi_iter) { dout(20) << "key cookie=" << oi_iter->first.first << " entity=" << oi_iter->first.second << " " << oi_iter->second << dendl; ceph_assert(oi_iter->first.first == oi_iter->second.cookie); ceph_assert(oi_iter->first.second.is_client()); watch_item_t wi(oi_iter->first.second, oi_iter->second.cookie, oi_iter->second.timeout_seconds, oi_iter->second.addr); resp.entries.push_back(wi); } resp.encode(osd_op.outdata, ctx->get_features()); result = 0; ctx->delta_stats.num_rd++; break; } case CEPH_OSD_OP_LIST_SNAPS: ++ctx->num_read; { tracepoint(osd, do_osd_op_pre_list_snaps, soid.oid.name.c_str(), soid.snap.val); obj_list_snap_response_t resp; if (!ssc) { ssc = ctx->obc->ssc = get_snapset_context(soid, false); } ceph_assert(ssc); dout(20) << " snapset " << ssc->snapset << dendl; int clonecount = ssc->snapset.clones.size(); clonecount++; // for head resp.clones.reserve(clonecount); for (auto clone_iter = ssc->snapset.clones.begin(); clone_iter != ssc->snapset.clones.end(); ++clone_iter) { clone_info ci; ci.cloneid = *clone_iter; hobject_t clone_oid = soid; clone_oid.snap = *clone_iter; auto p = ssc->snapset.clone_snaps.find(*clone_iter); if (p == ssc->snapset.clone_snaps.end()) { osd->clog->error() << "osd." << osd->whoami << ": inconsistent clone_snaps found for oid " << soid << " clone " << *clone_iter << " snapset " << ssc->snapset; result = -EINVAL; break; } for (auto q = p->second.rbegin(); q != p->second.rend(); ++q) { ci.snaps.push_back(*q); } dout(20) << " clone " << *clone_iter << " snaps " << ci.snaps << dendl; map >::const_iterator coi; coi = ssc->snapset.clone_overlap.find(ci.cloneid); if (coi == ssc->snapset.clone_overlap.end()) { osd->clog->error() << "osd." << osd->whoami << ": inconsistent clone_overlap found for oid " << soid << " clone " << *clone_iter; result = -EINVAL; break; } const interval_set &o = coi->second; ci.overlap.reserve(o.num_intervals()); for (interval_set::const_iterator r = o.begin(); r != o.end(); ++r) { ci.overlap.push_back(pair(r.get_start(), r.get_len())); } map::const_iterator si; si = ssc->snapset.clone_size.find(ci.cloneid); if (si == ssc->snapset.clone_size.end()) { osd->clog->error() << "osd." << osd->whoami << ": inconsistent clone_size found for oid " << soid << " clone " << *clone_iter; result = -EINVAL; break; } ci.size = si->second; resp.clones.push_back(ci); } if (result < 0) { break; } if (!ctx->obc->obs.oi.is_whiteout()) { ceph_assert(obs.exists); clone_info ci; ci.cloneid = CEPH_NOSNAP; //Size for HEAD is oi.size ci.size = oi.size; resp.clones.push_back(ci); } resp.seq = ssc->snapset.seq; resp.encode(osd_op.outdata); result = 0; ctx->delta_stats.num_rd++; break; } case CEPH_OSD_OP_NOTIFY: ++ctx->num_read; { uint32_t timeout; bufferlist bl; try { uint32_t ver; // obsolete decode(ver, bp); decode(timeout, bp); decode(bl, bp); } catch (const ceph::buffer::error &e) { timeout = 0; } tracepoint(osd, do_osd_op_pre_notify, soid.oid.name.c_str(), soid.snap.val, timeout); if (!timeout) timeout = cct->_conf->osd_default_notify_timeout; notify_info_t n; n.timeout = timeout; n.notify_id = osd->get_next_id(get_osdmap_epoch()); n.cookie = op.notify.cookie; n.bl = bl; ctx->notifies.push_back(n); // return our unique notify id to the client encode(n.notify_id, osd_op.outdata); } break; case CEPH_OSD_OP_NOTIFY_ACK: ++ctx->num_read; { try { uint64_t notify_id = 0; uint64_t watch_cookie = 0; decode(notify_id, bp); decode(watch_cookie, bp); bufferlist reply_bl; if (!bp.end()) { decode(reply_bl, bp); } tracepoint(osd, do_osd_op_pre_notify_ack, soid.oid.name.c_str(), soid.snap.val, notify_id, watch_cookie, "Y"); OpContext::NotifyAck ack(notify_id, watch_cookie, reply_bl); ctx->notify_acks.push_back(ack); } catch (const ceph::buffer::error &e) { tracepoint(osd, do_osd_op_pre_notify_ack, soid.oid.name.c_str(), soid.snap.val, op.watch.cookie, 0, "N"); OpContext::NotifyAck ack( // op.watch.cookie is actually the notify_id for historical reasons op.watch.cookie ); ctx->notify_acks.push_back(ack); } } break; case CEPH_OSD_OP_SETALLOCHINT: ++ctx->num_write; result = 0; { tracepoint(osd, do_osd_op_pre_setallochint, soid.oid.name.c_str(), soid.snap.val, op.alloc_hint.expected_object_size, op.alloc_hint.expected_write_size); maybe_create_new_object(ctx); oi.expected_object_size = op.alloc_hint.expected_object_size; oi.expected_write_size = op.alloc_hint.expected_write_size; oi.alloc_hint_flags = op.alloc_hint.flags; t->set_alloc_hint(soid, op.alloc_hint.expected_object_size, op.alloc_hint.expected_write_size, op.alloc_hint.flags); } break; // --- WRITES --- // -- object data -- case CEPH_OSD_OP_WRITE: ++ctx->num_write; result = 0; { // write __u32 seq = oi.truncate_seq; tracepoint(osd, do_osd_op_pre_write, soid.oid.name.c_str(), soid.snap.val, oi.size, seq, op.extent.offset, op.extent.length, op.extent.truncate_size, op.extent.truncate_seq); if (op.extent.length != osd_op.indata.length()) { result = -EINVAL; break; } if (pool.info.has_flag(pg_pool_t::FLAG_WRITE_FADVISE_DONTNEED)) op.flags = op.flags | CEPH_OSD_OP_FLAG_FADVISE_DONTNEED; if (pool.info.requires_aligned_append() && (op.extent.offset % pool.info.required_alignment() != 0)) { result = -EOPNOTSUPP; break; } if (!obs.exists) { if (pool.info.requires_aligned_append() && op.extent.offset) { result = -EOPNOTSUPP; break; } } else if (op.extent.offset != oi.size && pool.info.requires_aligned_append()) { result = -EOPNOTSUPP; break; } if (seq && (seq > op.extent.truncate_seq) && (op.extent.offset + op.extent.length > oi.size)) { // old write, arrived after trimtrunc op.extent.length = (op.extent.offset > oi.size ? 0 : oi.size - op.extent.offset); dout(10) << " old truncate_seq " << op.extent.truncate_seq << " < current " << seq << ", adjusting write length to " << op.extent.length << dendl; bufferlist t; t.substr_of(osd_op.indata, 0, op.extent.length); osd_op.indata.swap(t); } if (op.extent.truncate_seq > seq) { // write arrives before trimtrunc if (obs.exists && !oi.is_whiteout()) { dout(10) << " truncate_seq " << op.extent.truncate_seq << " > current " << seq << ", truncating to " << op.extent.truncate_size << dendl; t->truncate(soid, op.extent.truncate_size); oi.truncate_seq = op.extent.truncate_seq; oi.truncate_size = op.extent.truncate_size; if (oi.size > op.extent.truncate_size) { interval_set trim; trim.insert(op.extent.truncate_size, oi.size - op.extent.truncate_size); ctx->modified_ranges.union_of(trim); ctx->clean_regions.mark_data_region_dirty(op.extent.truncate_size, oi.size - op.extent.truncate_size); oi.clear_data_digest(); } if (op.extent.truncate_size != oi.size) { truncate_update_size_and_usage(ctx->delta_stats, oi, op.extent.truncate_size); } } else { dout(10) << " truncate_seq " << op.extent.truncate_seq << " > current " << seq << ", but object is new" << dendl; oi.truncate_seq = op.extent.truncate_seq; oi.truncate_size = op.extent.truncate_size; } } result = check_offset_and_length( op.extent.offset, op.extent.length, static_cast(osd->osd_max_object_size), get_dpp()); if (result < 0) break; maybe_create_new_object(ctx); if (op.extent.length == 0) { if (op.extent.offset > oi.size) { t->truncate( soid, op.extent.offset); truncate_update_size_and_usage(ctx->delta_stats, oi, op.extent.offset); } else { t->nop(soid); } } else { t->write( soid, op.extent.offset, op.extent.length, osd_op.indata, op.flags); } if (op.extent.offset == 0 && op.extent.length >= oi.size && !skip_data_digest) { obs.oi.set_data_digest(osd_op.indata.crc32c(-1)); } else if (op.extent.offset == oi.size && obs.oi.is_data_digest()) { if (skip_data_digest) { obs.oi.clear_data_digest(); } else { obs.oi.set_data_digest(osd_op.indata.crc32c(obs.oi.data_digest)); } } else { obs.oi.clear_data_digest(); } write_update_size_and_usage(ctx->delta_stats, oi, ctx->modified_ranges, op.extent.offset, op.extent.length); ctx->clean_regions.mark_data_region_dirty(op.extent.offset, op.extent.length); dout(10) << "clean_regions modified" << ctx->clean_regions << dendl; } break; case CEPH_OSD_OP_WRITEFULL: ++ctx->num_write; result = 0; { // write full object tracepoint(osd, do_osd_op_pre_writefull, soid.oid.name.c_str(), soid.snap.val, oi.size, 0, op.extent.length); if (op.extent.length != osd_op.indata.length()) { result = -EINVAL; break; } result = check_offset_and_length( 0, op.extent.length, static_cast(osd->osd_max_object_size), get_dpp()); if (result < 0) break; if (pool.info.has_flag(pg_pool_t::FLAG_WRITE_FADVISE_DONTNEED)) op.flags = op.flags | CEPH_OSD_OP_FLAG_FADVISE_DONTNEED; maybe_create_new_object(ctx); if (pool.info.is_erasure()) { t->truncate(soid, 0); } else if (obs.exists && op.extent.length < oi.size) { t->truncate(soid, op.extent.length); } if (op.extent.length) { t->write(soid, 0, op.extent.length, osd_op.indata, op.flags); } if (!skip_data_digest) { obs.oi.set_data_digest(osd_op.indata.crc32c(-1)); } else { obs.oi.clear_data_digest(); } ctx->clean_regions.mark_data_region_dirty(0, std::max((uint64_t)op.extent.length, oi.size)); write_update_size_and_usage(ctx->delta_stats, oi, ctx->modified_ranges, 0, op.extent.length, true); } break; case CEPH_OSD_OP_WRITESAME: ++ctx->num_write; tracepoint(osd, do_osd_op_pre_writesame, soid.oid.name.c_str(), soid.snap.val, oi.size, op.writesame.offset, op.writesame.length, op.writesame.data_length); result = do_writesame(ctx, osd_op); break; case CEPH_OSD_OP_ROLLBACK : ++ctx->num_write; tracepoint(osd, do_osd_op_pre_rollback, soid.oid.name.c_str(), soid.snap.val); result = _rollback_to(ctx, op); break; case CEPH_OSD_OP_ZERO: tracepoint(osd, do_osd_op_pre_zero, soid.oid.name.c_str(), soid.snap.val, op.extent.offset, op.extent.length); if (pool.info.requires_aligned_append()) { result = -EOPNOTSUPP; break; } ++ctx->num_write; { // zero result = check_offset_and_length( op.extent.offset, op.extent.length, static_cast(osd->osd_max_object_size), get_dpp()); if (result < 0) break; ceph_assert(op.extent.length); if (obs.exists && !oi.is_whiteout()) { t->zero(soid, op.extent.offset, op.extent.length); interval_set ch; ch.insert(op.extent.offset, op.extent.length); ctx->modified_ranges.union_of(ch); ctx->clean_regions.mark_data_region_dirty(op.extent.offset, op.extent.length); ctx->delta_stats.num_wr++; oi.clear_data_digest(); } else { // no-op } } break; case CEPH_OSD_OP_CREATE: ++ctx->num_write; result = 0; { tracepoint(osd, do_osd_op_pre_create, soid.oid.name.c_str(), soid.snap.val); if (obs.exists && !oi.is_whiteout() && (op.flags & CEPH_OSD_OP_FLAG_EXCL)) { result = -EEXIST; /* this is an exclusive create */ } else { if (osd_op.indata.length()) { auto p = osd_op.indata.cbegin(); string category; try { decode(category, p); } catch (ceph::buffer::error& e) { result = -EINVAL; goto fail; } // category is no longer implemented. } maybe_create_new_object(ctx); t->nop(soid); } } break; case CEPH_OSD_OP_TRIMTRUNC: op.extent.offset = op.extent.truncate_size; // falling through case CEPH_OSD_OP_TRUNCATE: tracepoint(osd, do_osd_op_pre_truncate, soid.oid.name.c_str(), soid.snap.val, oi.size, oi.truncate_seq, op.extent.offset, op.extent.length, op.extent.truncate_size, op.extent.truncate_seq); if (pool.info.requires_aligned_append()) { result = -EOPNOTSUPP; break; } ++ctx->num_write; result = 0; { // truncate if (!obs.exists || oi.is_whiteout()) { dout(10) << " object dne, truncate is a no-op" << dendl; break; } result = check_offset_and_length( op.extent.offset, op.extent.length, static_cast(osd->osd_max_object_size), get_dpp()); if (result < 0) break; if (op.extent.truncate_seq) { ceph_assert(op.extent.offset == op.extent.truncate_size); if (op.extent.truncate_seq <= oi.truncate_seq) { dout(10) << " truncate seq " << op.extent.truncate_seq << " <= current " << oi.truncate_seq << ", no-op" << dendl; break; // old } dout(10) << " truncate seq " << op.extent.truncate_seq << " > current " << oi.truncate_seq << ", truncating" << dendl; oi.truncate_seq = op.extent.truncate_seq; oi.truncate_size = op.extent.truncate_size; } maybe_create_new_object(ctx); t->truncate(soid, op.extent.offset); if (oi.size > op.extent.offset) { interval_set trim; trim.insert(op.extent.offset, oi.size-op.extent.offset); ctx->modified_ranges.union_of(trim); ctx->clean_regions.mark_data_region_dirty(op.extent.offset, oi.size - op.extent.offset); } else if (oi.size < op.extent.offset) { ctx->clean_regions.mark_data_region_dirty(oi.size, op.extent.offset - oi.size); } if (op.extent.offset != oi.size) { truncate_update_size_and_usage(ctx->delta_stats, oi, op.extent.offset); } ctx->delta_stats.num_wr++; // do no set exists, or we will break above DELETE -> TRUNCATE munging. oi.clear_data_digest(); } break; case CEPH_OSD_OP_DELETE: ++ctx->num_write; result = 0; tracepoint(osd, do_osd_op_pre_delete, soid.oid.name.c_str(), soid.snap.val); { result = _delete_oid(ctx, false, ctx->ignore_cache); } break; case CEPH_OSD_OP_WATCH: ++ctx->num_write; result = 0; { tracepoint(osd, do_osd_op_pre_watch, soid.oid.name.c_str(), soid.snap.val, op.watch.cookie, op.watch.op); if (!obs.exists) { result = -ENOENT; break; } result = 0; uint64_t cookie = op.watch.cookie; entity_name_t entity = ctx->reqid.name; ObjectContextRef obc = ctx->obc; dout(10) << "watch " << ceph_osd_watch_op_name(op.watch.op) << ": ctx->obc=" << (void *)obc.get() << " cookie=" << cookie << " oi.version=" << oi.version.version << " ctx->at_version=" << ctx->at_version << dendl; dout(10) << "watch: oi.user_version=" << oi.user_version<< dendl; dout(10) << "watch: peer_addr=" << ctx->op->get_req()->get_connection()->get_peer_addr() << dendl; uint32_t timeout = cct->_conf->osd_client_watch_timeout; if (op.watch.timeout != 0) { timeout = op.watch.timeout; } watch_info_t w(cookie, timeout, ctx->op->get_req()->get_connection()->get_peer_addr()); if (op.watch.op == CEPH_OSD_WATCH_OP_WATCH || op.watch.op == CEPH_OSD_WATCH_OP_LEGACY_WATCH) { if (oi.watchers.count(make_pair(cookie, entity))) { dout(10) << " found existing watch " << w << " by " << entity << dendl; } else { dout(10) << " registered new watch " << w << " by " << entity << dendl; oi.watchers[make_pair(cookie, entity)] = w; t->nop(soid); // make sure update the object_info on disk! } bool will_ping = (op.watch.op == CEPH_OSD_WATCH_OP_WATCH); ctx->watch_connects.push_back(make_pair(w, will_ping)); } else if (op.watch.op == CEPH_OSD_WATCH_OP_RECONNECT) { if (!oi.watchers.count(make_pair(cookie, entity))) { result = -ENOTCONN; break; } dout(10) << " found existing watch " << w << " by " << entity << dendl; ctx->watch_connects.push_back(make_pair(w, true)); } else if (op.watch.op == CEPH_OSD_WATCH_OP_PING) { /* Note: WATCH with PING doesn't cause may_write() to return true, * so if there is nothing else in the transaction, this is going * to run do_osd_op_effects, but not write out a log entry */ if (!oi.watchers.count(make_pair(cookie, entity))) { result = -ENOTCONN; break; } map,WatchRef>::iterator p = obc->watchers.find(make_pair(cookie, entity)); if (p == obc->watchers.end() || !p->second->is_connected()) { // client needs to reconnect result = -ETIMEDOUT; break; } dout(10) << " found existing watch " << w << " by " << entity << dendl; p->second->got_ping(ceph_clock_now()); result = 0; } else if (op.watch.op == CEPH_OSD_WATCH_OP_UNWATCH) { map, watch_info_t>::iterator oi_iter = oi.watchers.find(make_pair(cookie, entity)); if (oi_iter != oi.watchers.end()) { dout(10) << " removed watch " << oi_iter->second << " by " << entity << dendl; oi.watchers.erase(oi_iter); t->nop(soid); // update oi on disk ctx->watch_disconnects.push_back( watch_disconnect_t(cookie, entity, false)); } else { dout(10) << " can't remove: no watch by " << entity << dendl; } } } break; case CEPH_OSD_OP_CACHE_PIN: tracepoint(osd, do_osd_op_pre_cache_pin, soid.oid.name.c_str(), soid.snap.val); if ((!pool.info.is_tier() || pool.info.cache_mode == pg_pool_t::CACHEMODE_NONE)) { result = -EINVAL; dout(10) << " pin object is only allowed on the cache tier " << dendl; break; } ++ctx->num_write; result = 0; { if (!obs.exists || oi.is_whiteout()) { result = -ENOENT; break; } if (!oi.is_cache_pinned()) { oi.set_flag(object_info_t::FLAG_CACHE_PIN); ctx->modify = true; ctx->delta_stats.num_objects_pinned++; ctx->delta_stats.num_wr++; } } break; case CEPH_OSD_OP_CACHE_UNPIN: tracepoint(osd, do_osd_op_pre_cache_unpin, soid.oid.name.c_str(), soid.snap.val); if ((!pool.info.is_tier() || pool.info.cache_mode == pg_pool_t::CACHEMODE_NONE)) { result = -EINVAL; dout(10) << " pin object is only allowed on the cache tier " << dendl; break; } ++ctx->num_write; result = 0; { if (!obs.exists || oi.is_whiteout()) { result = -ENOENT; break; } if (oi.is_cache_pinned()) { oi.clear_flag(object_info_t::FLAG_CACHE_PIN); ctx->modify = true; ctx->delta_stats.num_objects_pinned--; ctx->delta_stats.num_wr++; } } break; case CEPH_OSD_OP_SET_REDIRECT: ++ctx->num_write; result = 0; { if (pool.info.is_tier()) { result = -EINVAL; break; } if (!obs.exists) { result = -ENOENT; break; } if (get_osdmap()->require_osd_release < ceph_release_t::luminous) { result = -EOPNOTSUPP; break; } object_t target_name; object_locator_t target_oloc; snapid_t target_snapid = (uint64_t)op.copy_from.snapid; version_t target_version = op.copy_from.src_version; try { decode(target_name, bp); decode(target_oloc, bp); } catch (ceph::buffer::error& e) { result = -EINVAL; goto fail; } pg_t raw_pg; result = get_osdmap()->object_locator_to_pg(target_name, target_oloc, raw_pg); if (result < 0) { dout(5) << " pool information is invalid: " << result << dendl; break; } hobject_t target(target_name, target_oloc.key, target_snapid, raw_pg.ps(), raw_pg.pool(), target_oloc.nspace); if (target == soid) { dout(20) << " set-redirect self is invalid" << dendl; result = -EINVAL; break; } bool need_reference = (osd_op.op.flags & CEPH_OSD_OP_FLAG_WITH_REFERENCE); bool has_reference = (oi.flags & object_info_t::FLAG_REDIRECT_HAS_REFERENCE); if (has_reference) { result = -EINVAL; dout(5) << " the object is already a manifest " << dendl; break; } if (op_finisher == nullptr && need_reference) { // start ctx->op_finishers[ctx->current_osd_subop_num].reset( new SetManifestFinisher(osd_op)); ManifestOpRef mop = std::make_shared(new RefCountCallback(ctx, osd_op)); C_SetManifestRefCountDone* fin = new C_SetManifestRefCountDone(this, mop, soid); ceph_tid_t tid = refcount_manifest(soid, target, refcount_t::INCREMENT_REF, fin, std::nullopt); mop->objecter_tid = tid; manifest_ops[soid] = mop; ctx->obc->start_block(); result = -EINPROGRESS; } else { // finish if (op_finisher) { result = op_finisher->execute(); ceph_assert(result == 0); } if (!oi.has_manifest() && !oi.manifest.is_redirect()) ctx->delta_stats.num_objects_manifest++; oi.set_flag(object_info_t::FLAG_MANIFEST); oi.manifest.redirect_target = target; oi.manifest.type = object_manifest_t::TYPE_REDIRECT; t->truncate(soid, 0); ctx->clean_regions.mark_data_region_dirty(0, oi.size); if (oi.is_omap() && pool.info.supports_omap()) { t->omap_clear(soid); obs.oi.clear_omap_digest(); obs.oi.clear_flag(object_info_t::FLAG_OMAP); ctx->clean_regions.mark_omap_dirty(); } write_update_size_and_usage(ctx->delta_stats, oi, ctx->modified_ranges, 0, oi.size, false); ctx->delta_stats.num_bytes -= oi.size; oi.size = 0; oi.new_object(); oi.user_version = target_version; ctx->user_at_version = target_version; /* rm_attrs */ map rmattrs; result = getattrs_maybe_cache(ctx->obc, &rmattrs); if (result < 0) { dout(10) << __func__ << " error: " << cpp_strerror(result) << dendl; return result; } map::iterator iter; for (iter = rmattrs.begin(); iter != rmattrs.end(); ++iter) { const string& name = iter->first; t->rmattr(soid, name); } if (!has_reference && need_reference) { oi.set_flag(object_info_t::FLAG_REDIRECT_HAS_REFERENCE); } dout(10) << "set-redirect oid:" << oi.soid << " user_version: " << oi.user_version << dendl; if (op_finisher) { ctx->op_finishers.erase(ctx->current_osd_subop_num); } } } break; case CEPH_OSD_OP_SET_CHUNK: ++ctx->num_write; result = 0; { if (pool.info.is_tier()) { result = -EINVAL; break; } if (!obs.exists) { result = -ENOENT; break; } if (get_osdmap()->require_osd_release < ceph_release_t::luminous) { result = -EOPNOTSUPP; break; } if (oi.manifest.is_redirect()) { result = -EINVAL; goto fail; } object_locator_t tgt_oloc; uint64_t src_offset, src_length, tgt_offset; object_t tgt_name; try { decode(src_offset, bp); decode(src_length, bp); decode(tgt_oloc, bp); decode(tgt_name, bp); decode(tgt_offset, bp); } catch (ceph::buffer::error& e) { result = -EINVAL; goto fail; } if (!src_length) { result = -EINVAL; goto fail; } if (src_offset + src_length > oi.size) { result = -ERANGE; goto fail; } if (!(osd_op.op.flags & CEPH_OSD_OP_FLAG_WITH_REFERENCE)) { result = -EOPNOTSUPP; break; } if (pool.info.is_erasure()) { result = -EOPNOTSUPP; break; } for (auto &p : oi.manifest.chunk_map) { interval_set chunk; chunk.insert(p.first, p.second.length); if (chunk.intersects(src_offset, src_length)) { dout(20) << __func__ << " overlapped !! offset: " << src_offset << " length: " << src_length << " chunk_info: " << p << dendl; result = -EOPNOTSUPP; goto fail; } } pg_t raw_pg; chunk_info_t chunk_info; result = get_osdmap()->object_locator_to_pg(tgt_name, tgt_oloc, raw_pg); if (result < 0) { dout(5) << " pool information is invalid: " << result << dendl; break; } hobject_t target(tgt_name, tgt_oloc.key, snapid_t(), raw_pg.ps(), raw_pg.pool(), tgt_oloc.nspace); bool has_reference = (oi.manifest.chunk_map.find(src_offset) != oi.manifest.chunk_map.end()) && (oi.manifest.chunk_map[src_offset].test_flag(chunk_info_t::FLAG_HAS_REFERENCE)); if (has_reference) { result = -EINVAL; dout(5) << " the object is already a manifest " << dendl; break; } chunk_info.oid = target; chunk_info.offset = tgt_offset; chunk_info.length = src_length; if (op_finisher == nullptr) { // start ctx->op_finishers[ctx->current_osd_subop_num].reset( new SetManifestFinisher(osd_op)); object_manifest_t set_chunk; bool need_inc_ref = false; set_chunk.chunk_map[src_offset] = chunk_info; need_inc_ref = inc_refcount_by_set(ctx, set_chunk, osd_op); if (need_inc_ref) { result = -EINPROGRESS; break; } } if (op_finisher) { result = op_finisher->execute(); ceph_assert(result == 0); } oi.manifest.chunk_map[src_offset] = chunk_info; if (!oi.has_manifest() && !oi.manifest.is_chunked()) ctx->delta_stats.num_objects_manifest++; oi.set_flag(object_info_t::FLAG_MANIFEST); oi.manifest.type = object_manifest_t::TYPE_CHUNKED; if (!has_reference) { oi.manifest.chunk_map[src_offset].set_flag(chunk_info_t::FLAG_HAS_REFERENCE); } ctx->modify = true; ctx->cache_operation = true; dout(10) << "set-chunked oid:" << oi.soid << " user_version: " << oi.user_version << " chunk_info: " << chunk_info << dendl; if (op_finisher) { ctx->op_finishers.erase(ctx->current_osd_subop_num); } } break; case CEPH_OSD_OP_TIER_PROMOTE: ++ctx->num_write; result = 0; { if (pool.info.is_tier()) { result = -EINVAL; break; } if (!obs.exists) { result = -ENOENT; break; } if (get_osdmap()->require_osd_release < ceph_release_t::luminous) { result = -EOPNOTSUPP; break; } if (!obs.oi.has_manifest()) { result = 0; break; } if (op_finisher == nullptr) { PromoteManifestCallback *cb; object_locator_t my_oloc; hobject_t src_hoid; if (obs.oi.manifest.is_chunked()) { src_hoid = obs.oi.soid; } else if (obs.oi.manifest.is_redirect()) { object_locator_t src_oloc(obs.oi.manifest.redirect_target); my_oloc = src_oloc; src_hoid = obs.oi.manifest.redirect_target; } else { ceph_abort_msg("unrecognized manifest type"); } cb = new PromoteManifestCallback(ctx->obc, this, ctx); ctx->op_finishers[ctx->current_osd_subop_num].reset( new PromoteFinisher(cb)); unsigned flags = CEPH_OSD_COPY_FROM_FLAG_IGNORE_OVERLAY | CEPH_OSD_COPY_FROM_FLAG_IGNORE_CACHE | CEPH_OSD_COPY_FROM_FLAG_MAP_SNAP_CLONE | CEPH_OSD_COPY_FROM_FLAG_RWORDERED; unsigned src_fadvise_flags = LIBRADOS_OP_FLAG_FADVISE_SEQUENTIAL; start_copy(cb, ctx->obc, src_hoid, my_oloc, 0, flags, obs.oi.soid.snap == CEPH_NOSNAP, src_fadvise_flags, 0); dout(10) << "tier-promote oid:" << oi.soid << " manifest: " << obs.oi.manifest << dendl; result = -EINPROGRESS; } else { result = op_finisher->execute(); ceph_assert(result == 0); ctx->op_finishers.erase(ctx->current_osd_subop_num); } } break; case CEPH_OSD_OP_TIER_FLUSH: ++ctx->num_write; result = 0; { if (pool.info.is_tier()) { result = -EINVAL; break; } if (!obs.exists) { result = -ENOENT; break; } if (get_osdmap()->require_osd_release < ceph_release_t::octopus) { result = -EOPNOTSUPP; break; } if (!obs.oi.has_manifest()) { result = 0; break; } if (oi.is_dirty()) { result = start_flush(ctx->op, ctx->obc, true, NULL, std::nullopt); if (result == -EINPROGRESS) result = -EAGAIN; } else { result = 0; } } break; case CEPH_OSD_OP_TIER_EVICT: ++ctx->num_write; result = 0; { if (pool.info.is_tier()) { result = -EINVAL; break; } if (!obs.exists) { result = -ENOENT; break; } if (get_osdmap()->require_osd_release < ceph_release_t::octopus) { result = -EOPNOTSUPP; break; } if (!obs.oi.has_manifest()) { result = -EINVAL; break; } // The chunks already has a reference, so it is just enough to invoke truncate if necessary uint64_t chunk_length = 0; for (auto p : obs.oi.manifest.chunk_map) { chunk_length += p.second.length; } if (chunk_length == obs.oi.size) { for (auto &p : obs.oi.manifest.chunk_map) { p.second.set_flag(chunk_info_t::FLAG_MISSING); } // punch hole t->zero(soid, 0, oi.size); oi.clear_data_digest(); ctx->delta_stats.num_wr++; ctx->cache_operation = true; } osd->logger->inc(l_osd_tier_evict); } break; case CEPH_OSD_OP_UNSET_MANIFEST: ++ctx->num_write; result = 0; { if (pool.info.is_tier()) { result = -EINVAL; break; } if (!obs.exists) { result = -ENOENT; break; } if (!oi.has_manifest()) { result = -EOPNOTSUPP; break; } if (get_osdmap()->require_osd_release < ceph_release_t::luminous) { result = -EOPNOTSUPP; break; } dec_all_refcount_manifest(oi, ctx); oi.clear_flag(object_info_t::FLAG_MANIFEST); oi.manifest = object_manifest_t(); ctx->delta_stats.num_objects_manifest--; ctx->delta_stats.num_wr++; ctx->modify = true; } break; // -- object attrs -- case CEPH_OSD_OP_SETXATTR: ++ctx->num_write; result = 0; { if (cct->_conf->osd_max_attr_size > 0 && op.xattr.value_len > cct->_conf->osd_max_attr_size) { tracepoint(osd, do_osd_op_pre_setxattr, soid.oid.name.c_str(), soid.snap.val, "???"); result = -EFBIG; break; } unsigned max_name_len = std::min(osd->store->get_max_attr_name_length(), cct->_conf->osd_max_attr_name_len); if (op.xattr.name_len > max_name_len) { result = -ENAMETOOLONG; break; } maybe_create_new_object(ctx); string aname; bp.copy(op.xattr.name_len, aname); tracepoint(osd, do_osd_op_pre_setxattr, soid.oid.name.c_str(), soid.snap.val, aname.c_str()); string name = "_" + aname; bufferlist bl; bp.copy(op.xattr.value_len, bl); t->setattr(soid, name, bl); ctx->delta_stats.num_wr++; } break; case CEPH_OSD_OP_RMXATTR: ++ctx->num_write; result = 0; { string aname; bp.copy(op.xattr.name_len, aname); tracepoint(osd, do_osd_op_pre_rmxattr, soid.oid.name.c_str(), soid.snap.val, aname.c_str()); if (!obs.exists || oi.is_whiteout()) { result = -ENOENT; break; } string name = "_" + aname; t->rmattr(soid, name); ctx->delta_stats.num_wr++; } break; // -- fancy writers -- case CEPH_OSD_OP_APPEND: { tracepoint(osd, do_osd_op_pre_append, soid.oid.name.c_str(), soid.snap.val, oi.size, oi.truncate_seq, op.extent.offset, op.extent.length, op.extent.truncate_size, op.extent.truncate_seq); // just do it inline; this works because we are happy to execute // fancy op on replicas as well. vector nops(1); OSDOp& newop = nops[0]; newop.op.op = CEPH_OSD_OP_WRITE; newop.op.extent.offset = oi.size; newop.op.extent.length = op.extent.length; newop.op.extent.truncate_seq = oi.truncate_seq; newop.indata = osd_op.indata; result = do_osd_ops(ctx, nops); osd_op.outdata = std::move(newop.outdata); } break; case CEPH_OSD_OP_STARTSYNC: result = 0; t->nop(soid); break; // -- trivial map -- case CEPH_OSD_OP_TMAPGET: tracepoint(osd, do_osd_op_pre_tmapget, soid.oid.name.c_str(), soid.snap.val); if (pool.info.is_erasure()) { result = -EOPNOTSUPP; break; } { vector nops(1); OSDOp& newop = nops[0]; newop.op.op = CEPH_OSD_OP_SYNC_READ; newop.op.extent.offset = 0; newop.op.extent.length = 0; result = do_osd_ops(ctx, nops); osd_op.outdata = std::move(newop.outdata); } break; case CEPH_OSD_OP_TMAPPUT: tracepoint(osd, do_osd_op_pre_tmapput, soid.oid.name.c_str(), soid.snap.val); if (pool.info.is_erasure()) { result = -EOPNOTSUPP; break; } { //_dout_lock.Lock(); //osd_op.data.hexdump(*_dout); //_dout_lock.Unlock(); // verify sort order bool unsorted = false; if (true) { bufferlist header; decode(header, bp); uint32_t n; decode(n, bp); string last_key; while (n--) { string key; decode(key, bp); dout(10) << "tmapput key " << key << dendl; bufferlist val; decode(val, bp); if (key < last_key) { dout(10) << "TMAPPUT is unordered; resorting" << dendl; unsorted = true; break; } last_key = key; } } // write it vector nops(1); OSDOp& newop = nops[0]; newop.op.op = CEPH_OSD_OP_WRITEFULL; newop.op.extent.offset = 0; newop.op.extent.length = osd_op.indata.length(); newop.indata = osd_op.indata; if (unsorted) { bp = osd_op.indata.begin(); bufferlist header; map m; decode(header, bp); decode(m, bp); ceph_assert(bp.end()); bufferlist newbl; encode(header, newbl); encode(m, newbl); newop.indata = newbl; } result = do_osd_ops(ctx, nops); ceph_assert(result == 0); } break; case CEPH_OSD_OP_TMAPUP: tracepoint(osd, do_osd_op_pre_tmapup, soid.oid.name.c_str(), soid.snap.val); if (pool.info.is_erasure()) { result = -EOPNOTSUPP; break; } ++ctx->num_write; result = do_tmapup(ctx, bp, osd_op); break; case CEPH_OSD_OP_TMAP2OMAP: ++ctx->num_write; tracepoint(osd, do_osd_op_pre_tmap2omap, soid.oid.name.c_str(), soid.snap.val); result = do_tmap2omap(ctx, op.tmap2omap.flags); break; // OMAP Read ops case CEPH_OSD_OP_OMAPGETKEYS: ++ctx->num_read; { string start_after; uint64_t max_return; try { decode(start_after, bp); decode(max_return, bp); } catch (ceph::buffer::error& e) { result = -EINVAL; tracepoint(osd, do_osd_op_pre_omapgetkeys, soid.oid.name.c_str(), soid.snap.val, "???", 0); goto fail; } if (max_return > cct->_conf->osd_max_omap_entries_per_request) { max_return = cct->_conf->osd_max_omap_entries_per_request; } tracepoint(osd, do_osd_op_pre_omapgetkeys, soid.oid.name.c_str(), soid.snap.val, start_after.c_str(), max_return); bufferlist bl; uint32_t num = 0; bool truncated = false; if (oi.is_omap()) { ObjectMap::ObjectMapIterator iter = osd->store->get_omap_iterator( ch, ghobject_t(soid) ); ceph_assert(iter); iter->upper_bound(start_after); for (num = 0; iter->valid(); ++num, iter->next()) { if (num >= max_return || bl.length() >= cct->_conf->osd_max_omap_bytes_per_request) { truncated = true; break; } encode(iter->key(), bl); } } // else return empty out_set encode(num, osd_op.outdata); osd_op.outdata.claim_append(bl); encode(truncated, osd_op.outdata); ctx->delta_stats.num_rd_kb += shift_round_up(osd_op.outdata.length(), 10); ctx->delta_stats.num_rd++; } break; case CEPH_OSD_OP_OMAPGETVALS: ++ctx->num_read; { string start_after; uint64_t max_return; string filter_prefix; try { decode(start_after, bp); decode(max_return, bp); decode(filter_prefix, bp); } catch (ceph::buffer::error& e) { result = -EINVAL; tracepoint(osd, do_osd_op_pre_omapgetvals, soid.oid.name.c_str(), soid.snap.val, "???", 0, "???"); goto fail; } if (max_return > cct->_conf->osd_max_omap_entries_per_request) { max_return = cct->_conf->osd_max_omap_entries_per_request; } tracepoint(osd, do_osd_op_pre_omapgetvals, soid.oid.name.c_str(), soid.snap.val, start_after.c_str(), max_return, filter_prefix.c_str()); uint32_t num = 0; bool truncated = false; bufferlist bl; if (oi.is_omap()) { ObjectMap::ObjectMapIterator iter = osd->store->get_omap_iterator( ch, ghobject_t(soid) ); if (!iter) { result = -ENOENT; goto fail; } iter->upper_bound(start_after); if (filter_prefix > start_after) iter->lower_bound(filter_prefix); for (num = 0; iter->valid() && iter->key().substr(0, filter_prefix.size()) == filter_prefix; ++num, iter->next()) { dout(20) << "Found key " << iter->key() << dendl; if (num >= max_return || bl.length() >= cct->_conf->osd_max_omap_bytes_per_request) { truncated = true; break; } encode(iter->key(), bl); encode(iter->value(), bl); } } // else return empty out_set encode(num, osd_op.outdata); osd_op.outdata.claim_append(bl); encode(truncated, osd_op.outdata); ctx->delta_stats.num_rd_kb += shift_round_up(osd_op.outdata.length(), 10); ctx->delta_stats.num_rd++; } break; case CEPH_OSD_OP_OMAPGETHEADER: tracepoint(osd, do_osd_op_pre_omapgetheader, soid.oid.name.c_str(), soid.snap.val); if (!oi.is_omap()) { // return empty header break; } ++ctx->num_read; { osd->store->omap_get_header(ch, ghobject_t(soid), &osd_op.outdata); ctx->delta_stats.num_rd_kb += shift_round_up(osd_op.outdata.length(), 10); ctx->delta_stats.num_rd++; } break; case CEPH_OSD_OP_OMAPGETVALSBYKEYS: ++ctx->num_read; { set keys_to_get; try { decode(keys_to_get, bp); } catch (ceph::buffer::error& e) { result = -EINVAL; tracepoint(osd, do_osd_op_pre_omapgetvalsbykeys, soid.oid.name.c_str(), soid.snap.val, "???"); goto fail; } tracepoint(osd, do_osd_op_pre_omapgetvalsbykeys, soid.oid.name.c_str(), soid.snap.val, list_entries(keys_to_get).c_str()); map out; if (oi.is_omap()) { osd->store->omap_get_values(ch, ghobject_t(soid), keys_to_get, &out); } // else return empty omap entries encode(out, osd_op.outdata); ctx->delta_stats.num_rd_kb += shift_round_up(osd_op.outdata.length(), 10); ctx->delta_stats.num_rd++; } break; case CEPH_OSD_OP_OMAP_CMP: ++ctx->num_read; { if (!obs.exists || oi.is_whiteout()) { result = -ENOENT; tracepoint(osd, do_osd_op_pre_omap_cmp, soid.oid.name.c_str(), soid.snap.val, "???"); break; } map > assertions; try { decode(assertions, bp); } catch (ceph::buffer::error& e) { result = -EINVAL; tracepoint(osd, do_osd_op_pre_omap_cmp, soid.oid.name.c_str(), soid.snap.val, "???"); goto fail; } tracepoint(osd, do_osd_op_pre_omap_cmp, soid.oid.name.c_str(), soid.snap.val, list_keys(assertions).c_str()); map out; if (oi.is_omap()) { set to_get; for (map >::iterator i = assertions.begin(); i != assertions.end(); ++i) to_get.insert(i->first); int r = osd->store->omap_get_values(ch, ghobject_t(soid), to_get, &out); if (r < 0) { result = r; break; } } // else leave out empty //Should set num_rd_kb based on encode length of map ctx->delta_stats.num_rd++; int r = 0; bufferlist empty; for (map >::iterator i = assertions.begin(); i != assertions.end(); ++i) { auto out_entry = out.find(i->first); bufferlist &bl = (out_entry != out.end()) ? out_entry->second : empty; switch (i->second.second) { case CEPH_OSD_CMPXATTR_OP_EQ: if (!(bl == i->second.first)) { r = -ECANCELED; } break; case CEPH_OSD_CMPXATTR_OP_LT: if (!(bl < i->second.first)) { r = -ECANCELED; } break; case CEPH_OSD_CMPXATTR_OP_GT: if (!(bl > i->second.first)) { r = -ECANCELED; } break; default: r = -EINVAL; break; } if (r < 0) break; } if (r < 0) { result = r; } } break; // OMAP Write ops case CEPH_OSD_OP_OMAPSETVALS: if (!pool.info.supports_omap()) { result = -EOPNOTSUPP; tracepoint(osd, do_osd_op_pre_omapsetvals, soid.oid.name.c_str(), soid.snap.val); break; } ++ctx->num_write; result = 0; { maybe_create_new_object(ctx); bufferlist to_set_bl; try { decode_str_str_map_to_bl(bp, &to_set_bl); } catch (ceph::buffer::error& e) { result = -EINVAL; tracepoint(osd, do_osd_op_pre_omapsetvals, soid.oid.name.c_str(), soid.snap.val); goto fail; } tracepoint(osd, do_osd_op_pre_omapsetvals, soid.oid.name.c_str(), soid.snap.val); if (cct->_conf->subsys.should_gather()) { dout(20) << "setting vals: " << dendl; map to_set; bufferlist::const_iterator pt = to_set_bl.begin(); decode(to_set, pt); for (map::iterator i = to_set.begin(); i != to_set.end(); ++i) { dout(20) << "\t" << i->first << dendl; } } t->omap_setkeys(soid, to_set_bl); ctx->clean_regions.mark_omap_dirty(); ctx->delta_stats.num_wr++; ctx->delta_stats.num_wr_kb += shift_round_up(to_set_bl.length(), 10); } obs.oi.set_flag(object_info_t::FLAG_OMAP); obs.oi.clear_omap_digest(); break; case CEPH_OSD_OP_OMAPSETHEADER: tracepoint(osd, do_osd_op_pre_omapsetheader, soid.oid.name.c_str(), soid.snap.val); if (!pool.info.supports_omap()) { result = -EOPNOTSUPP; break; } ++ctx->num_write; result = 0; { maybe_create_new_object(ctx); t->omap_setheader(soid, osd_op.indata); ctx->clean_regions.mark_omap_dirty(); ctx->delta_stats.num_wr++; } obs.oi.set_flag(object_info_t::FLAG_OMAP); obs.oi.clear_omap_digest(); break; case CEPH_OSD_OP_OMAPCLEAR: tracepoint(osd, do_osd_op_pre_omapclear, soid.oid.name.c_str(), soid.snap.val); if (!pool.info.supports_omap()) { result = -EOPNOTSUPP; break; } ++ctx->num_write; result = 0; { if (!obs.exists || oi.is_whiteout()) { result = -ENOENT; break; } if (oi.is_omap()) { t->omap_clear(soid); ctx->clean_regions.mark_omap_dirty(); ctx->delta_stats.num_wr++; obs.oi.clear_omap_digest(); obs.oi.clear_flag(object_info_t::FLAG_OMAP); } } break; case CEPH_OSD_OP_OMAPRMKEYS: if (!pool.info.supports_omap()) { result = -EOPNOTSUPP; tracepoint(osd, do_osd_op_pre_omaprmkeys, soid.oid.name.c_str(), soid.snap.val); break; } ++ctx->num_write; result = 0; { if (!obs.exists || oi.is_whiteout()) { result = -ENOENT; tracepoint(osd, do_osd_op_pre_omaprmkeys, soid.oid.name.c_str(), soid.snap.val); break; } bufferlist to_rm_bl; try { decode_str_set_to_bl(bp, &to_rm_bl); } catch (ceph::buffer::error& e) { result = -EINVAL; tracepoint(osd, do_osd_op_pre_omaprmkeys, soid.oid.name.c_str(), soid.snap.val); goto fail; } tracepoint(osd, do_osd_op_pre_omaprmkeys, soid.oid.name.c_str(), soid.snap.val); t->omap_rmkeys(soid, to_rm_bl); ctx->clean_regions.mark_omap_dirty(); ctx->delta_stats.num_wr++; } obs.oi.clear_omap_digest(); break; case CEPH_OSD_OP_OMAPRMKEYRANGE: tracepoint(osd, do_osd_op_pre_omaprmkeyrange, soid.oid.name.c_str(), soid.snap.val); if (!pool.info.supports_omap()) { result = -EOPNOTSUPP; break; } ++ctx->num_write; result = 0; { if (!obs.exists || oi.is_whiteout()) { result = -ENOENT; break; } std::string key_begin, key_end; try { decode(key_begin, bp); decode(key_end, bp); } catch (ceph::buffer::error& e) { result = -EINVAL; goto fail; } t->omap_rmkeyrange(soid, key_begin, key_end); ctx->clean_regions.mark_omap_dirty(); ctx->delta_stats.num_wr++; } obs.oi.clear_omap_digest(); break; case CEPH_OSD_OP_COPY_GET: ++ctx->num_read; tracepoint(osd, do_osd_op_pre_copy_get, soid.oid.name.c_str(), soid.snap.val); if (op_finisher == nullptr) { result = do_copy_get(ctx, bp, osd_op, ctx->obc); } else { result = op_finisher->execute(); } break; case CEPH_OSD_OP_COPY_FROM: case CEPH_OSD_OP_COPY_FROM2: ++ctx->num_write; result = 0; { object_t src_name; object_locator_t src_oloc; uint32_t truncate_seq = 0; uint64_t truncate_size = 0; bool have_truncate = false; snapid_t src_snapid = (uint64_t)op.copy_from.snapid; version_t src_version = op.copy_from.src_version; if ((op.op == CEPH_OSD_OP_COPY_FROM2) && (op.copy_from.flags & ~CEPH_OSD_COPY_FROM_FLAGS)) { dout(20) << "invalid copy-from2 flags 0x" << std::hex << (int)op.copy_from.flags << std::dec << dendl; result = -EINVAL; break; } try { decode(src_name, bp); decode(src_oloc, bp); // check if client sent us truncate_seq and truncate_size if ((op.op == CEPH_OSD_OP_COPY_FROM2) && (op.copy_from.flags & CEPH_OSD_COPY_FROM_FLAG_TRUNCATE_SEQ)) { decode(truncate_seq, bp); decode(truncate_size, bp); have_truncate = true; } } catch (ceph::buffer::error& e) { result = -EINVAL; tracepoint(osd, do_osd_op_pre_copy_from, soid.oid.name.c_str(), soid.snap.val, "???", 0, "???", "???", 0, src_snapid, src_version); goto fail; } tracepoint(osd, do_osd_op_pre_copy_from, soid.oid.name.c_str(), soid.snap.val, src_name.name.c_str(), src_oloc.pool, src_oloc.key.c_str(), src_oloc.nspace.c_str(), src_oloc.hash, src_snapid, src_version); if (op_finisher == nullptr) { // start pg_t raw_pg; get_osdmap()->object_locator_to_pg(src_name, src_oloc, raw_pg); hobject_t src(src_name, src_oloc.key, src_snapid, raw_pg.ps(), raw_pg.pool(), src_oloc.nspace); if (src == soid) { dout(20) << " copy from self is invalid" << dendl; result = -EINVAL; break; } CopyFromCallback *cb = new CopyFromCallback(ctx, osd_op); if (have_truncate) cb->set_truncate(truncate_seq, truncate_size); ctx->op_finishers[ctx->current_osd_subop_num].reset( new CopyFromFinisher(cb)); start_copy(cb, ctx->obc, src, src_oloc, src_version, op.copy_from.flags, false, op.copy_from.src_fadvise_flags, op.flags); result = -EINPROGRESS; } else { // finish result = op_finisher->execute(); ceph_assert(result == 0); // COPY_FROM cannot be executed multiple times -- it must restart ctx->op_finishers.erase(ctx->current_osd_subop_num); } } break; default: tracepoint(osd, do_osd_op_pre_unknown, soid.oid.name.c_str(), soid.snap.val, op.op, ceph_osd_op_name(op.op)); dout(1) << "unrecognized osd op " << op.op << " " << ceph_osd_op_name(op.op) << dendl; result = -EOPNOTSUPP; } fail: osd_op.rval = result; tracepoint(osd, do_osd_op_post, soid.oid.name.c_str(), soid.snap.val, op.op, ceph_osd_op_name(op.op), op.flags, result); if (result < 0 && (op.flags & CEPH_OSD_OP_FLAG_FAILOK) && result != -EAGAIN && result != -EINPROGRESS) result = 0; if (result < 0) break; } if (result < 0) { dout(10) << __func__ << " error: " << cpp_strerror(result) << dendl; } return result; } int PrimaryLogPG::_get_tmap(OpContext *ctx, bufferlist *header, bufferlist *vals) { if (ctx->new_obs.oi.size == 0) { dout(20) << "unable to get tmap for zero sized " << ctx->new_obs.oi.soid << dendl; return -ENODATA; } vector nops(1); OSDOp &newop = nops[0]; newop.op.op = CEPH_OSD_OP_TMAPGET; do_osd_ops(ctx, nops); try { bufferlist::const_iterator i = newop.outdata.begin(); decode(*header, i); (*vals).substr_of(newop.outdata, i.get_off(), i.get_remaining()); } catch (...) { dout(20) << "unsuccessful at decoding tmap for " << ctx->new_obs.oi.soid << dendl; return -EINVAL; } dout(20) << "successful at decoding tmap for " << ctx->new_obs.oi.soid << dendl; return 0; } int PrimaryLogPG::_verify_no_head_clones(const hobject_t& soid, const SnapSet& ss) { // verify that all clones have been evicted dout(20) << __func__ << " verifying clones are absent " << ss << dendl; for (vector::const_iterator p = ss.clones.begin(); p != ss.clones.end(); ++p) { hobject_t clone_oid = soid; clone_oid.snap = *p; if (is_missing_object(clone_oid)) return -EBUSY; ObjectContextRef clone_obc = get_object_context(clone_oid, false); if (clone_obc && clone_obc->obs.exists) { dout(10) << __func__ << " cannot evict head before clone " << clone_oid << dendl; return -EBUSY; } if (copy_ops.count(clone_oid)) { dout(10) << __func__ << " cannot evict head, pending promote on clone " << clone_oid << dendl; return -EBUSY; } } return 0; } inline int PrimaryLogPG::_delete_oid( OpContext *ctx, bool no_whiteout, // no whiteouts, no matter what. bool try_no_whiteout) // try not to whiteout { SnapSet& snapset = ctx->new_snapset; ObjectState& obs = ctx->new_obs; object_info_t& oi = obs.oi; const hobject_t& soid = oi.soid; PGTransaction* t = ctx->op_t.get(); // cache: cache: set whiteout on delete? bool whiteout = false; if (pool.info.cache_mode != pg_pool_t::CACHEMODE_NONE && !no_whiteout && !try_no_whiteout) { whiteout = true; } // in luminous or later, we can't delete the head if there are // clones. we trust the caller passing no_whiteout has already // verified they don't exist. if (!snapset.clones.empty() || (!ctx->snapc.snaps.empty() && ctx->snapc.snaps[0] > snapset.seq)) { if (no_whiteout) { dout(20) << __func__ << " has or will have clones but no_whiteout=1" << dendl; } else { dout(20) << __func__ << " has or will have clones; will whiteout" << dendl; whiteout = true; } } dout(20) << __func__ << " " << soid << " whiteout=" << (int)whiteout << " no_whiteout=" << (int)no_whiteout << " try_no_whiteout=" << (int)try_no_whiteout << dendl; if (!obs.exists || (obs.oi.is_whiteout() && whiteout)) return -ENOENT; t->remove(soid); if (oi.size > 0) { interval_set ch; ch.insert(0, oi.size); ctx->modified_ranges.union_of(ch); ctx->clean_regions.mark_data_region_dirty(0, oi.size); } ctx->clean_regions.mark_omap_dirty(); ctx->delta_stats.num_wr++; if (soid.is_snap()) { ceph_assert(ctx->obc->ssc->snapset.clone_overlap.count(soid.snap)); ctx->delta_stats.num_bytes -= ctx->obc->ssc->snapset.get_clone_bytes(soid.snap); } else { ctx->delta_stats.num_bytes -= oi.size; } oi.size = 0; oi.new_object(); // disconnect all watchers for (map, watch_info_t>::iterator p = oi.watchers.begin(); p != oi.watchers.end(); ++p) { dout(20) << __func__ << " will disconnect watcher " << p->first << dendl; ctx->watch_disconnects.push_back( watch_disconnect_t(p->first.first, p->first.second, true)); } oi.watchers.clear(); if (oi.has_manifest()) { ctx->delta_stats.num_objects_manifest--; dec_all_refcount_manifest(oi, ctx); } if (whiteout) { dout(20) << __func__ << " setting whiteout on " << soid << dendl; oi.set_flag(object_info_t::FLAG_WHITEOUT); ctx->delta_stats.num_whiteouts++; t->create(soid); osd->logger->inc(l_osd_tier_whiteout); return 0; } // delete the head ctx->delta_stats.num_objects--; if (soid.is_snap()) ctx->delta_stats.num_object_clones--; if (oi.is_whiteout()) { dout(20) << __func__ << " deleting whiteout on " << soid << dendl; ctx->delta_stats.num_whiteouts--; oi.clear_flag(object_info_t::FLAG_WHITEOUT); } if (oi.is_cache_pinned()) { ctx->delta_stats.num_objects_pinned--; } obs.exists = false; return 0; } int PrimaryLogPG::_rollback_to(OpContext *ctx, ceph_osd_op& op) { SnapSet& snapset = ctx->new_snapset; ObjectState& obs = ctx->new_obs; object_info_t& oi = obs.oi; const hobject_t& soid = oi.soid; PGTransaction* t = ctx->op_t.get(); snapid_t snapid = (uint64_t)op.snap.snapid; hobject_t missing_oid; dout(10) << "_rollback_to " << soid << " snapid " << snapid << dendl; ObjectContextRef rollback_to; int ret = find_object_context( hobject_t(soid.oid, soid.get_key(), snapid, soid.get_hash(), info.pgid.pool(), soid.get_namespace()), &rollback_to, false, false, &missing_oid); if (ret == -EAGAIN) { /* clone must be missing */ ceph_assert(is_degraded_or_backfilling_object(missing_oid) || is_degraded_on_async_recovery_target(missing_oid)); dout(20) << "_rollback_to attempted to roll back to a missing or backfilling clone " << missing_oid << " (requested snapid: ) " << snapid << dendl; block_write_on_degraded_snap(missing_oid, ctx->op); return ret; } { ObjectContextRef promote_obc; cache_result_t tier_mode_result; if (obs.exists && obs.oi.has_manifest()) { tier_mode_result = maybe_handle_manifest_detail( ctx->op, true, rollback_to); } else { tier_mode_result = maybe_handle_cache_detail( ctx->op, true, rollback_to, ret, missing_oid, true, false, &promote_obc); } switch (tier_mode_result) { case cache_result_t::NOOP: break; case cache_result_t::BLOCKED_PROMOTE: ceph_assert(promote_obc); block_write_on_snap_rollback(soid, promote_obc, ctx->op); return -EAGAIN; case cache_result_t::BLOCKED_FULL: block_write_on_full_cache(soid, ctx->op); return -EAGAIN; case cache_result_t::REPLIED_WITH_EAGAIN: ceph_abort_msg("this can't happen, no rollback on replica"); default: ceph_abort_msg("must promote was set, other values are not valid"); return -EAGAIN; } } if (ret == -ENOENT || (rollback_to && rollback_to->obs.oi.is_whiteout())) { // there's no snapshot here, or there's no object. // if there's no snapshot, we delete the object; otherwise, do nothing. dout(20) << "_rollback_to deleting head on " << soid.oid << " because got ENOENT|whiteout on find_object_context" << dendl; if (ctx->obc->obs.oi.watchers.size()) { // Cannot delete an object with watchers ret = -EBUSY; } else { _delete_oid(ctx, false, false); ret = 0; } } else if (ret) { // ummm....huh? It *can't* return anything else at time of writing. ceph_abort_msg("unexpected error code in _rollback_to"); } else { //we got our context, let's use it to do the rollback! hobject_t& rollback_to_sobject = rollback_to->obs.oi.soid; if (is_degraded_or_backfilling_object(rollback_to_sobject) || is_degraded_on_async_recovery_target(rollback_to_sobject)) { dout(20) << "_rollback_to attempted to roll back to a degraded object " << rollback_to_sobject << " (requested snapid: ) " << snapid << dendl; block_write_on_degraded_snap(rollback_to_sobject, ctx->op); ret = -EAGAIN; } else if (rollback_to->obs.oi.soid.snap == CEPH_NOSNAP) { // rolling back to the head; we just need to clone it. ctx->modify = true; } else { /* 1) Delete current head * 2) Clone correct snapshot into head * 3) Calculate clone_overlaps by following overlaps * forward from rollback snapshot */ dout(10) << "_rollback_to deleting " << soid.oid << " and rolling back to old snap" << dendl; if (obs.exists) { t->remove(soid); } t->clone(soid, rollback_to_sobject); t->add_obc(rollback_to); map >::iterator iter = snapset.clone_overlap.lower_bound(snapid); ceph_assert(iter != snapset.clone_overlap.end()); interval_set overlaps = iter->second; for ( ; iter != snapset.clone_overlap.end(); ++iter) overlaps.intersection_of(iter->second); if (obs.oi.size > 0) { interval_set modified; modified.insert(0, obs.oi.size); overlaps.intersection_of(modified); modified.subtract(overlaps); ctx->modified_ranges.union_of(modified); } // Adjust the cached objectcontext maybe_create_new_object(ctx, true); ctx->delta_stats.num_bytes -= obs.oi.size; ctx->delta_stats.num_bytes += rollback_to->obs.oi.size; ctx->clean_regions.mark_data_region_dirty(0, std::max(obs.oi.size, rollback_to->obs.oi.size)); ctx->clean_regions.mark_omap_dirty(); obs.oi.size = rollback_to->obs.oi.size; if (rollback_to->obs.oi.is_data_digest()) obs.oi.set_data_digest(rollback_to->obs.oi.data_digest); else obs.oi.clear_data_digest(); if (rollback_to->obs.oi.is_omap_digest()) obs.oi.set_omap_digest(rollback_to->obs.oi.omap_digest); else obs.oi.clear_omap_digest(); if (rollback_to->obs.oi.is_omap()) { dout(10) << __func__ << " setting omap flag on " << obs.oi.soid << dendl; obs.oi.set_flag(object_info_t::FLAG_OMAP); } else { dout(10) << __func__ << " clearing omap flag on " << obs.oi.soid << dendl; obs.oi.clear_flag(object_info_t::FLAG_OMAP); } } } return ret; } void PrimaryLogPG::_make_clone( OpContext *ctx, PGTransaction* t, ObjectContextRef obc, const hobject_t& head, const hobject_t& coid, object_info_t *poi) { bufferlist bv; encode(*poi, bv, get_osdmap()->get_features(CEPH_ENTITY_TYPE_OSD, nullptr)); t->clone(coid, head); setattr_maybe_cache(obc, t, OI_ATTR, bv); rmattr_maybe_cache(obc, t, SS_ATTR); } void PrimaryLogPG::make_writeable(OpContext *ctx) { const hobject_t& soid = ctx->obs->oi.soid; SnapContext& snapc = ctx->snapc; // clone? ceph_assert(soid.snap == CEPH_NOSNAP); dout(20) << "make_writeable " << soid << " snapset=" << ctx->new_snapset << " snapc=" << snapc << dendl; bool was_dirty = ctx->obc->obs.oi.is_dirty(); if (ctx->new_obs.exists) { // we will mark the object dirty if (ctx->undirty && was_dirty) { dout(20) << " clearing DIRTY flag" << dendl; ceph_assert(ctx->new_obs.oi.is_dirty()); ctx->new_obs.oi.clear_flag(object_info_t::FLAG_DIRTY); --ctx->delta_stats.num_objects_dirty; osd->logger->inc(l_osd_tier_clean); } else if (!was_dirty && !ctx->undirty) { dout(20) << " setting DIRTY flag" << dendl; ctx->new_obs.oi.set_flag(object_info_t::FLAG_DIRTY); ++ctx->delta_stats.num_objects_dirty; osd->logger->inc(l_osd_tier_dirty); } } else { if (was_dirty) { dout(20) << " deletion, decrementing num_dirty and clearing flag" << dendl; ctx->new_obs.oi.clear_flag(object_info_t::FLAG_DIRTY); --ctx->delta_stats.num_objects_dirty; } } if ((ctx->new_obs.exists && ctx->new_obs.oi.is_omap()) && (!ctx->obc->obs.exists || !ctx->obc->obs.oi.is_omap())) { ++ctx->delta_stats.num_objects_omap; } if ((!ctx->new_obs.exists || !ctx->new_obs.oi.is_omap()) && (ctx->obc->obs.exists && ctx->obc->obs.oi.is_omap())) { --ctx->delta_stats.num_objects_omap; } if (ctx->new_snapset.seq > snapc.seq) { dout(10) << " op snapset is old" << dendl; } if ((ctx->obs->exists && !ctx->obs->oi.is_whiteout()) && // head exist(ed) snapc.snaps.size() && // there are snaps !ctx->cache_operation && snapc.snaps[0] > ctx->new_snapset.seq) { // existing object is old // clone hobject_t coid = soid; coid.snap = snapc.seq; unsigned l; for (l = 1; l < snapc.snaps.size() && snapc.snaps[l] > ctx->new_snapset.seq; l++) ; vector snaps(l); for (unsigned i=0; iclone_obc = object_contexts.lookup_or_create(static_snap_oi.soid); ctx->clone_obc->destructor_callback = new C_PG_ObjectContext(this, ctx->clone_obc.get()); ctx->clone_obc->obs.oi = static_snap_oi; ctx->clone_obc->obs.exists = true; ctx->clone_obc->ssc = ctx->obc->ssc; ctx->clone_obc->ssc->ref++; if (pool.info.is_erasure()) ctx->clone_obc->attr_cache = ctx->obc->attr_cache; snap_oi = &ctx->clone_obc->obs.oi; if (ctx->obc->obs.oi.has_manifest()) { if ((ctx->obc->obs.oi.flags & object_info_t::FLAG_REDIRECT_HAS_REFERENCE) && ctx->obc->obs.oi.manifest.is_redirect()) { snap_oi->set_flag(object_info_t::FLAG_MANIFEST); snap_oi->manifest.type = object_manifest_t::TYPE_REDIRECT; snap_oi->manifest.redirect_target = ctx->obc->obs.oi.manifest.redirect_target; } else if (ctx->obc->obs.oi.manifest.is_chunked()) { snap_oi->set_flag(object_info_t::FLAG_MANIFEST); snap_oi->manifest.type = object_manifest_t::TYPE_CHUNKED; snap_oi->manifest.chunk_map = ctx->obc->obs.oi.manifest.chunk_map; } else { ceph_abort_msg("unrecognized manifest type"); } } bool got = ctx->lock_manager.get_write_greedy( coid, ctx->clone_obc, ctx->op); ceph_assert(got); dout(20) << " got greedy write on clone_obc " << *ctx->clone_obc << dendl; } else { snap_oi = &static_snap_oi; } snap_oi->version = ctx->at_version; snap_oi->prior_version = ctx->obs->oi.version; snap_oi->copy_user_bits(ctx->obs->oi); _make_clone(ctx, ctx->op_t.get(), ctx->clone_obc, soid, coid, snap_oi); ctx->delta_stats.num_objects++; if (snap_oi->is_dirty()) { ctx->delta_stats.num_objects_dirty++; osd->logger->inc(l_osd_tier_dirty); } if (snap_oi->is_omap()) ctx->delta_stats.num_objects_omap++; if (snap_oi->is_cache_pinned()) ctx->delta_stats.num_objects_pinned++; if (snap_oi->has_manifest()) ctx->delta_stats.num_objects_manifest++; ctx->delta_stats.num_object_clones++; ctx->new_snapset.clones.push_back(coid.snap); ctx->new_snapset.clone_size[coid.snap] = ctx->obs->oi.size; ctx->new_snapset.clone_snaps[coid.snap] = snaps; // clone_overlap should contain an entry for each clone // (an empty interval_set if there is no overlap) ctx->new_snapset.clone_overlap[coid.snap]; if (ctx->obs->oi.size) ctx->new_snapset.clone_overlap[coid.snap].insert(0, ctx->obs->oi.size); // log clone dout(10) << " cloning v " << ctx->obs->oi.version << " to " << coid << " v " << ctx->at_version << " snaps=" << snaps << " snapset=" << ctx->new_snapset << dendl; ctx->log.push_back(pg_log_entry_t( pg_log_entry_t::CLONE, coid, ctx->at_version, ctx->obs->oi.version, ctx->obs->oi.user_version, osd_reqid_t(), ctx->new_obs.oi.mtime, 0)); encode(snaps, ctx->log.back().snaps); ctx->at_version.version++; } // update most recent clone_overlap and usage stats if (ctx->new_snapset.clones.size() > 0) { // the clone_overlap is difference of range between head and clones. // we need to check whether the most recent clone exists, if it's // been evicted, it's not included in the stats, but the clone_overlap // is still exist in the snapset, so we should update the // clone_overlap to make it sense. hobject_t last_clone_oid = soid; last_clone_oid.snap = ctx->new_snapset.clone_overlap.rbegin()->first; interval_set &newest_overlap = ctx->new_snapset.clone_overlap.rbegin()->second; ctx->modified_ranges.intersection_of(newest_overlap); if (is_present_clone(last_clone_oid)) { // modified_ranges is still in use by the clone ctx->delta_stats.num_bytes += ctx->modified_ranges.size(); } newest_overlap.subtract(ctx->modified_ranges); } if (snapc.seq > ctx->new_snapset.seq) { // update snapset with latest snap context ctx->new_snapset.seq = snapc.seq; if (get_osdmap()->require_osd_release < ceph_release_t::octopus) { ctx->new_snapset.snaps = snapc.snaps; } else { ctx->new_snapset.snaps.clear(); } } dout(20) << "make_writeable " << soid << " done, snapset=" << ctx->new_snapset << dendl; } void PrimaryLogPG::write_update_size_and_usage(object_stat_sum_t& delta_stats, object_info_t& oi, interval_set& modified, uint64_t offset, uint64_t length, bool write_full) { interval_set ch; if (write_full) { if (oi.size) ch.insert(0, oi.size); } else if (length) ch.insert(offset, length); modified.union_of(ch); if (write_full || (offset + length > oi.size && length)) { uint64_t new_size = offset + length; delta_stats.num_bytes -= oi.size; delta_stats.num_bytes += new_size; oi.size = new_size; } delta_stats.num_wr++; delta_stats.num_wr_kb += shift_round_up(length, 10); } void PrimaryLogPG::truncate_update_size_and_usage( object_stat_sum_t& delta_stats, object_info_t& oi, uint64_t truncate_size) { if (oi.size != truncate_size) { delta_stats.num_bytes -= oi.size; delta_stats.num_bytes += truncate_size; oi.size = truncate_size; } } void PrimaryLogPG::complete_disconnect_watches( ObjectContextRef obc, const list &to_disconnect) { for (list::const_iterator i = to_disconnect.begin(); i != to_disconnect.end(); ++i) { pair watcher(i->cookie, i->name); auto watchers_entry = obc->watchers.find(watcher); if (watchers_entry != obc->watchers.end()) { WatchRef watch = watchers_entry->second; dout(10) << "do_osd_op_effects disconnect watcher " << watcher << dendl; obc->watchers.erase(watcher); watch->remove(i->send_disconnect); } else { dout(10) << "do_osd_op_effects disconnect failed to find watcher " << watcher << dendl; } } } void PrimaryLogPG::do_osd_op_effects(OpContext *ctx, const ConnectionRef& conn) { entity_name_t entity = ctx->reqid.name; dout(15) << "do_osd_op_effects " << entity << " con " << conn.get() << dendl; // disconnects first complete_disconnect_watches(ctx->obc, ctx->watch_disconnects); ceph_assert(conn); auto session = conn->get_priv(); if (!session) return; for (list >::iterator i = ctx->watch_connects.begin(); i != ctx->watch_connects.end(); ++i) { pair watcher(i->first.cookie, entity); dout(15) << "do_osd_op_effects applying watch connect on session " << session.get() << " watcher " << watcher << dendl; WatchRef watch; if (ctx->obc->watchers.count(watcher)) { dout(15) << "do_osd_op_effects found existing watch watcher " << watcher << dendl; watch = ctx->obc->watchers[watcher]; } else { dout(15) << "do_osd_op_effects new watcher " << watcher << dendl; watch = Watch::makeWatchRef( this, osd, ctx->obc, i->first.timeout_seconds, i->first.cookie, entity, conn->get_peer_addr()); ctx->obc->watchers.insert( make_pair( watcher, watch)); } watch->connect(conn, i->second); } for (list::iterator p = ctx->notifies.begin(); p != ctx->notifies.end(); ++p) { dout(10) << "do_osd_op_effects, notify " << *p << dendl; ConnectionRef conn(ctx->op->get_req()->get_connection()); NotifyRef notif( Notify::makeNotifyRef( conn, ctx->reqid.name.num(), p->bl, p->timeout, p->cookie, p->notify_id, ctx->obc->obs.oi.user_version, osd)); for (map, WatchRef>::iterator i = ctx->obc->watchers.begin(); i != ctx->obc->watchers.end(); ++i) { dout(10) << "starting notify on watch " << i->first << dendl; i->second->start_notify(notif); } notif->init(); } for (list::iterator p = ctx->notify_acks.begin(); p != ctx->notify_acks.end(); ++p) { if (p->watch_cookie) dout(10) << "notify_ack " << make_pair(*(p->watch_cookie), p->notify_id) << dendl; else dout(10) << "notify_ack " << make_pair("NULL", p->notify_id) << dendl; for (map, WatchRef>::iterator i = ctx->obc->watchers.begin(); i != ctx->obc->watchers.end(); ++i) { if (i->first.second != entity) continue; if (p->watch_cookie && *(p->watch_cookie) != i->first.first) continue; dout(10) << "acking notify on watch " << i->first << dendl; i->second->notify_ack(p->notify_id, p->reply_bl); } } } hobject_t PrimaryLogPG::generate_temp_object(const hobject_t& target) { ostringstream ss; ss << "temp_" << info.pgid << "_" << get_role() << "_" << osd->monc->get_global_id() << "_" << (++temp_seq); hobject_t hoid = target.make_temp_hobject(ss.str()); dout(20) << __func__ << " " << hoid << dendl; return hoid; } hobject_t PrimaryLogPG::get_temp_recovery_object( const hobject_t& target, eversion_t version) { ostringstream ss; ss << "temp_recovering_" << info.pgid // (note this includes the shardid) << "_" << version << "_" << info.history.same_interval_since << "_" << target.snap; // pgid + version + interval + snapid is unique, and short hobject_t hoid = target.make_temp_hobject(ss.str()); dout(20) << __func__ << " " << hoid << dendl; return hoid; } int PrimaryLogPG::prepare_transaction(OpContext *ctx) { ceph_assert(!ctx->ops->empty()); // valid snap context? if (!ctx->snapc.is_valid()) { dout(10) << " invalid snapc " << ctx->snapc << dendl; return -EINVAL; } // prepare the actual mutation int result = do_osd_ops(ctx, *ctx->ops); if (result < 0) { if (ctx->op->may_write() && get_osdmap()->require_osd_release >= ceph_release_t::kraken) { // need to save the error code in the pg log, to detect dup ops, // but do nothing else ctx->update_log_only = true; } return result; } // read-op? write-op noop? done? if (ctx->op_t->empty() && !ctx->modify) { if (ctx->pending_async_reads.empty()) unstable_stats.add(ctx->delta_stats); if (ctx->op->may_write() && get_osdmap()->require_osd_release >= ceph_release_t::kraken) { ctx->update_log_only = true; } return result; } // check for full if ((ctx->delta_stats.num_bytes > 0 || ctx->delta_stats.num_objects > 0) && // FIXME: keys? pool.info.has_flag(pg_pool_t::FLAG_FULL)) { auto m = ctx->op->get_req(); if (ctx->reqid.name.is_mds() || // FIXME: ignore MDS for now m->has_flag(CEPH_OSD_FLAG_FULL_FORCE)) { dout(20) << __func__ << " full, but proceeding due to FULL_FORCE or MDS" << dendl; } else if (m->has_flag(CEPH_OSD_FLAG_FULL_TRY)) { // they tried, they failed. dout(20) << __func__ << " full, replying to FULL_TRY op" << dendl; return pool.info.has_flag(pg_pool_t::FLAG_FULL_QUOTA) ? -EDQUOT : -ENOSPC; } else { // drop request dout(20) << __func__ << " full, dropping request (bad client)" << dendl; return -EAGAIN; } } const hobject_t& soid = ctx->obs->oi.soid; // clone, if necessary if (soid.snap == CEPH_NOSNAP) make_writeable(ctx); finish_ctx(ctx, ctx->new_obs.exists ? pg_log_entry_t::MODIFY : pg_log_entry_t::DELETE, result); return result; } void PrimaryLogPG::finish_ctx(OpContext *ctx, int log_op_type, int result) { const hobject_t& soid = ctx->obs->oi.soid; dout(20) << __func__ << " " << soid << " " << ctx << " op " << pg_log_entry_t::get_op_name(log_op_type) << dendl; utime_t now = ceph_clock_now(); #ifdef HAVE_JAEGER if (ctx->op->osd_parent_span) { auto finish_ctx_span = jaeger_tracing::child_span(__func__, ctx->op->osd_parent_span); } #endif // Drop the reference if deduped chunk is modified if (ctx->new_obs.oi.is_dirty() && (ctx->obs->oi.has_manifest() && ctx->obs->oi.manifest.is_chunked()) && // If a clone is creating, ignore dropping the reference for manifest object !ctx->delta_stats.num_object_clones && ctx->new_obs.oi.size != 0 && // missing, redirect and delete !ctx->cache_operation && log_op_type != pg_log_entry_t::PROMOTE) { dec_refcount_by_dirty(ctx); } // finish and log the op. if (ctx->user_modify) { // update the user_version for any modify ops, except for the watch op ctx->user_at_version = std::max(info.last_user_version, ctx->new_obs.oi.user_version) + 1; /* In order for new clients and old clients to interoperate properly * when exchanging versions, we need to lower bound the user_version * (which our new clients pay proper attention to) * by the at_version (which is all the old clients can ever see). */ if (ctx->at_version.version > ctx->user_at_version) ctx->user_at_version = ctx->at_version.version; ctx->new_obs.oi.user_version = ctx->user_at_version; } ctx->bytes_written = ctx->op_t->get_bytes_written(); if (ctx->new_obs.exists) { ctx->new_obs.oi.version = ctx->at_version; ctx->new_obs.oi.prior_version = ctx->obs->oi.version; ctx->new_obs.oi.last_reqid = ctx->reqid; if (ctx->mtime != utime_t()) { ctx->new_obs.oi.mtime = ctx->mtime; dout(10) << " set mtime to " << ctx->new_obs.oi.mtime << dendl; ctx->new_obs.oi.local_mtime = now; } else { dout(10) << " mtime unchanged at " << ctx->new_obs.oi.mtime << dendl; } // object_info_t map attrs; bufferlist bv(sizeof(ctx->new_obs.oi)); encode(ctx->new_obs.oi, bv, get_osdmap()->get_features(CEPH_ENTITY_TYPE_OSD, nullptr)); attrs[OI_ATTR] = std::move(bv); // snapset if (soid.snap == CEPH_NOSNAP) { dout(10) << " final snapset " << ctx->new_snapset << " in " << soid << dendl; bufferlist bss; encode(ctx->new_snapset, bss); attrs[SS_ATTR] = std::move(bss); } else { dout(10) << " no snapset (this is a clone)" << dendl; } ctx->op_t->setattrs(soid, attrs); } else { // reset cached oi ctx->new_obs.oi = object_info_t(ctx->obc->obs.oi.soid); } // append to log ctx->log.push_back( pg_log_entry_t(log_op_type, soid, ctx->at_version, ctx->obs->oi.version, ctx->user_at_version, ctx->reqid, ctx->mtime, (ctx->op && ctx->op->allows_returnvec()) ? result : 0)); if (ctx->op && ctx->op->allows_returnvec()) { // also the per-op values ctx->log.back().set_op_returns(*ctx->ops); dout(20) << __func__ << " op_returns " << ctx->log.back().op_returns << dendl; } ctx->log.back().clean_regions = ctx->clean_regions; dout(20) << __func__ << " object " << soid << " marks clean_regions " << ctx->log.back().clean_regions << dendl; if (soid.snap < CEPH_NOSNAP) { switch (log_op_type) { case pg_log_entry_t::MODIFY: case pg_log_entry_t::PROMOTE: case pg_log_entry_t::CLEAN: dout(20) << __func__ << " encoding snaps from " << ctx->new_snapset << dendl; encode(ctx->new_snapset.clone_snaps[soid.snap], ctx->log.back().snaps); break; default: break; } } if (!ctx->extra_reqids.empty()) { dout(20) << __func__ << " extra_reqids " << ctx->extra_reqids << " " << ctx->extra_reqid_return_codes << dendl; ctx->log.back().extra_reqids.swap(ctx->extra_reqids); ctx->log.back().extra_reqid_return_codes.swap(ctx->extra_reqid_return_codes); } // apply new object state. ctx->obc->obs = ctx->new_obs; if (soid.is_head() && !ctx->obc->obs.exists) { ctx->obc->ssc->exists = false; ctx->obc->ssc->snapset = SnapSet(); } else { ctx->obc->ssc->exists = true; ctx->obc->ssc->snapset = ctx->new_snapset; } } void PrimaryLogPG::apply_stats( const hobject_t &soid, const object_stat_sum_t &delta_stats) { recovery_state.apply_op_stats(soid, delta_stats); for (set::const_iterator i = get_backfill_targets().begin(); i != get_backfill_targets().end(); ++i) { pg_shard_t bt = *i; const pg_info_t& pinfo = recovery_state.get_peer_info(bt); if (soid > pinfo.last_backfill && soid <= last_backfill_started) { pending_backfill_updates[soid].stats.add(delta_stats); } } m_scrubber->stats_of_handled_objects(delta_stats, soid); } void PrimaryLogPG::complete_read_ctx(int result, OpContext *ctx) { auto m = ctx->op->get_req(); ceph_assert(ctx->async_reads_complete()); for (auto p = ctx->ops->begin(); p != ctx->ops->end() && result >= 0; ++p) { if (p->rval < 0 && !(p->op.flags & CEPH_OSD_OP_FLAG_FAILOK)) { result = p->rval; break; } ctx->bytes_read += p->outdata.length(); } ctx->reply->get_header().data_off = (ctx->data_off ? *ctx->data_off : 0); MOSDOpReply *reply = ctx->reply; ctx->reply = nullptr; if (result >= 0) { if (!ctx->ignore_log_op_stats) { log_op_stats(*ctx->op, ctx->bytes_written, ctx->bytes_read); publish_stats_to_osd(); } // on read, return the current object version if (ctx->obs) { reply->set_reply_versions(eversion_t(), ctx->obs->oi.user_version); } else { reply->set_reply_versions(eversion_t(), ctx->user_at_version); } } else if (result == -ENOENT) { // on ENOENT, set a floor for what the next user version will be. reply->set_enoent_reply_versions(info.last_update, info.last_user_version); } reply->set_result(result); reply->add_flags(CEPH_OSD_FLAG_ACK | CEPH_OSD_FLAG_ONDISK); osd->send_message_osd_client(reply, m->get_connection()); close_op_ctx(ctx); } // ======================================================================== // copyfrom struct C_Copyfrom : public Context { PrimaryLogPGRef pg; hobject_t oid; epoch_t last_peering_reset; ceph_tid_t tid; PrimaryLogPG::CopyOpRef cop; // used for keeping the cop alive C_Copyfrom(PrimaryLogPG *p, hobject_t o, epoch_t lpr, const PrimaryLogPG::CopyOpRef& c) : pg(p), oid(o), last_peering_reset(lpr), tid(0), cop(c) {} void finish(int r) override { if (r == -ECANCELED) return; std::scoped_lock l{*pg}; if (last_peering_reset == pg->get_last_peering_reset()) { pg->process_copy_chunk(oid, tid, r); cop.reset(); } } }; struct C_CopyFrom_AsyncReadCb : public Context { OSDOp *osd_op; object_copy_data_t reply_obj; uint64_t features; size_t len; C_CopyFrom_AsyncReadCb(OSDOp *osd_op, uint64_t features) : osd_op(osd_op), features(features), len(0) {} void finish(int r) override { osd_op->rval = r; if (r < 0) { return; } ceph_assert(len > 0); ceph_assert(len <= reply_obj.data.length()); bufferlist bl; bl.substr_of(reply_obj.data, 0, len); reply_obj.data.swap(bl); encode(reply_obj, osd_op->outdata, features); } }; struct C_CopyChunk : public Context { PrimaryLogPGRef pg; hobject_t oid; epoch_t last_peering_reset; ceph_tid_t tid; PrimaryLogPG::CopyOpRef cop; // used for keeping the cop alive uint64_t offset = 0; C_CopyChunk(PrimaryLogPG *p, hobject_t o, epoch_t lpr, const PrimaryLogPG::CopyOpRef& c) : pg(p), oid(o), last_peering_reset(lpr), tid(0), cop(c) {} void finish(int r) override { if (r == -ECANCELED) return; std::scoped_lock l{*pg}; if (last_peering_reset == pg->get_last_peering_reset()) { pg->process_copy_chunk_manifest(oid, tid, r, offset); cop.reset(); } } }; int PrimaryLogPG::do_copy_get(OpContext *ctx, bufferlist::const_iterator& bp, OSDOp& osd_op, ObjectContextRef &obc) { object_info_t& oi = obc->obs.oi; hobject_t& soid = oi.soid; int result = 0; object_copy_cursor_t cursor; uint64_t out_max; try { decode(cursor, bp); decode(out_max, bp); } catch (ceph::buffer::error& e) { result = -EINVAL; return result; } const MOSDOp *op = reinterpret_cast(ctx->op->get_req()); uint64_t features = op->get_features(); bool async_read_started = false; object_copy_data_t _reply_obj; C_CopyFrom_AsyncReadCb *cb = nullptr; if (pool.info.is_erasure()) { cb = new C_CopyFrom_AsyncReadCb(&osd_op, features); } object_copy_data_t &reply_obj = cb ? cb->reply_obj : _reply_obj; // size, mtime reply_obj.size = oi.size; reply_obj.mtime = oi.mtime; ceph_assert(obc->ssc); if (soid.snap < CEPH_NOSNAP) { auto p = obc->ssc->snapset.clone_snaps.find(soid.snap); ceph_assert(p != obc->ssc->snapset.clone_snaps.end()); // warn? reply_obj.snaps = p->second; } else { reply_obj.snap_seq = obc->ssc->snapset.seq; } if (oi.is_data_digest()) { reply_obj.flags |= object_copy_data_t::FLAG_DATA_DIGEST; reply_obj.data_digest = oi.data_digest; } if (oi.is_omap_digest()) { reply_obj.flags |= object_copy_data_t::FLAG_OMAP_DIGEST; reply_obj.omap_digest = oi.omap_digest; } reply_obj.truncate_seq = oi.truncate_seq; reply_obj.truncate_size = oi.truncate_size; // attrs map& out_attrs = reply_obj.attrs; if (!cursor.attr_complete) { result = getattrs_maybe_cache( ctx->obc, &out_attrs); if (result < 0) { if (cb) { delete cb; } return result; } cursor.attr_complete = true; dout(20) << " got attrs" << dendl; } int64_t left = out_max - osd_op.outdata.length(); // data bufferlist& bl = reply_obj.data; if (left > 0 && !cursor.data_complete) { if (cursor.data_offset < oi.size) { uint64_t max_read = std::min(oi.size - cursor.data_offset, (uint64_t)left); if (cb) { async_read_started = true; ctx->pending_async_reads.push_back( make_pair( boost::make_tuple(cursor.data_offset, max_read, osd_op.op.flags), make_pair(&bl, cb))); cb->len = max_read; ctx->op_finishers[ctx->current_osd_subop_num].reset( new ReadFinisher(osd_op)); result = -EINPROGRESS; dout(10) << __func__ << ": async_read noted for " << soid << dendl; } else { result = pgbackend->objects_read_sync( oi.soid, cursor.data_offset, max_read, osd_op.op.flags, &bl); if (result < 0) return result; } left -= max_read; cursor.data_offset += max_read; } if (cursor.data_offset == oi.size) { cursor.data_complete = true; dout(20) << " got data" << dendl; } ceph_assert(cursor.data_offset <= oi.size); } // omap uint32_t omap_keys = 0; if (!pool.info.supports_omap() || !oi.is_omap()) { cursor.omap_complete = true; } else { if (left > 0 && !cursor.omap_complete) { ceph_assert(cursor.data_complete); if (cursor.omap_offset.empty()) { osd->store->omap_get_header(ch, ghobject_t(oi.soid), &reply_obj.omap_header); } bufferlist omap_data; ObjectMap::ObjectMapIterator iter = osd->store->get_omap_iterator(ch, ghobject_t(oi.soid)); ceph_assert(iter); iter->upper_bound(cursor.omap_offset); for (; iter->valid(); iter->next()) { ++omap_keys; encode(iter->key(), omap_data); encode(iter->value(), omap_data); left -= iter->key().length() + 4 + iter->value().length() + 4; if (left <= 0) break; } if (omap_keys) { encode(omap_keys, reply_obj.omap_data); reply_obj.omap_data.claim_append(omap_data); } if (iter->valid()) { cursor.omap_offset = iter->key(); } else { cursor.omap_complete = true; dout(20) << " got omap" << dendl; } } } if (cursor.is_complete()) { // include reqids only in the final step. this is a bit fragile // but it works... recovery_state.get_pg_log().get_log().get_object_reqids(ctx->obc->obs.oi.soid, 10, &reply_obj.reqids, &reply_obj.reqid_return_codes); dout(20) << " got reqids" << dendl; } dout(20) << " cursor.is_complete=" << cursor.is_complete() << " " << out_attrs.size() << " attrs" << " " << bl.length() << " bytes" << " " << reply_obj.omap_header.length() << " omap header bytes" << " " << reply_obj.omap_data.length() << " omap data bytes in " << omap_keys << " keys" << " " << reply_obj.reqids.size() << " reqids" << dendl; reply_obj.cursor = cursor; if (!async_read_started) { encode(reply_obj, osd_op.outdata, features); } if (cb && !async_read_started) { delete cb; } if (result > 0) { result = 0; } return result; } void PrimaryLogPG::fill_in_copy_get_noent(OpRequestRef& op, hobject_t oid, OSDOp& osd_op) { const MOSDOp *m = static_cast(op->get_req()); uint64_t features = m->get_features(); object_copy_data_t reply_obj; recovery_state.get_pg_log().get_log().get_object_reqids(oid, 10, &reply_obj.reqids, &reply_obj.reqid_return_codes); dout(20) << __func__ << " got reqids " << reply_obj.reqids << dendl; encode(reply_obj, osd_op.outdata, features); osd_op.rval = -ENOENT; MOSDOpReply *reply = new MOSDOpReply(m, 0, get_osdmap_epoch(), 0, false); reply->set_result(-ENOENT); reply->add_flags(CEPH_OSD_FLAG_ACK | CEPH_OSD_FLAG_ONDISK); osd->send_message_osd_client(reply, m->get_connection()); } void PrimaryLogPG::start_copy(CopyCallback *cb, ObjectContextRef obc, hobject_t src, object_locator_t oloc, version_t version, unsigned flags, bool mirror_snapset, unsigned src_obj_fadvise_flags, unsigned dest_obj_fadvise_flags) { const hobject_t& dest = obc->obs.oi.soid; dout(10) << __func__ << " " << dest << " from " << src << " " << oloc << " v" << version << " flags " << flags << (mirror_snapset ? " mirror_snapset" : "") << dendl; ceph_assert(!mirror_snapset || src.snap == CEPH_NOSNAP); // cancel a previous in-progress copy? if (copy_ops.count(dest)) { // FIXME: if the src etc match, we could avoid restarting from the // beginning. CopyOpRef cop = copy_ops[dest]; vector tids; cancel_copy(cop, false, &tids); osd->objecter->op_cancel(tids, -ECANCELED); } CopyOpRef cop(std::make_shared(cb, obc, src, oloc, version, flags, mirror_snapset, src_obj_fadvise_flags, dest_obj_fadvise_flags)); copy_ops[dest] = cop; obc->start_block(); if (!obc->obs.oi.has_manifest()) { _copy_some(obc, cop); } else { if (obc->obs.oi.manifest.is_redirect()) { _copy_some(obc, cop); } else if (obc->obs.oi.manifest.is_chunked()) { auto p = obc->obs.oi.manifest.chunk_map.begin(); _copy_some_manifest(obc, cop, p->first); } else { ceph_abort_msg("unrecognized manifest type"); } } } void PrimaryLogPG::_copy_some(ObjectContextRef obc, CopyOpRef cop) { dout(10) << __func__ << " " << *obc << " " << cop << dendl; unsigned flags = 0; if (cop->flags & CEPH_OSD_COPY_FROM_FLAG_FLUSH) flags |= CEPH_OSD_FLAG_FLUSH; if (cop->flags & CEPH_OSD_COPY_FROM_FLAG_IGNORE_CACHE) flags |= CEPH_OSD_FLAG_IGNORE_CACHE; if (cop->flags & CEPH_OSD_COPY_FROM_FLAG_IGNORE_OVERLAY) flags |= CEPH_OSD_FLAG_IGNORE_OVERLAY; if (cop->flags & CEPH_OSD_COPY_FROM_FLAG_MAP_SNAP_CLONE) flags |= CEPH_OSD_FLAG_MAP_SNAP_CLONE; if (cop->flags & CEPH_OSD_COPY_FROM_FLAG_RWORDERED) flags |= CEPH_OSD_FLAG_RWORDERED; C_GatherBuilder gather(cct); if (cop->cursor.is_initial() && cop->mirror_snapset) { // list snaps too. ceph_assert(cop->src.snap == CEPH_NOSNAP); ObjectOperation op; op.list_snaps(&cop->results.snapset, NULL); ceph_tid_t tid = osd->objecter->read(cop->src.oid, cop->oloc, op, CEPH_SNAPDIR, NULL, flags, gather.new_sub(), NULL); cop->objecter_tid2 = tid; } ObjectOperation op; if (cop->results.user_version) { op.assert_version(cop->results.user_version); } else { // we should learn the version after the first chunk, if we didn't know // it already! ceph_assert(cop->cursor.is_initial()); } op.copy_get(&cop->cursor, get_copy_chunk_size(), &cop->results.object_size, &cop->results.mtime, &cop->attrs, &cop->data, &cop->omap_header, &cop->omap_data, &cop->results.snaps, &cop->results.snap_seq, &cop->results.flags, &cop->results.source_data_digest, &cop->results.source_omap_digest, &cop->results.reqids, &cop->results.reqid_return_codes, &cop->results.truncate_seq, &cop->results.truncate_size, &cop->rval); op.set_last_op_flags(cop->src_obj_fadvise_flags); C_Copyfrom *fin = new C_Copyfrom(this, obc->obs.oi.soid, get_last_peering_reset(), cop); gather.set_finisher(new C_OnFinisher(fin, osd->get_objecter_finisher(get_pg_shard()))); ceph_tid_t tid = osd->objecter->read(cop->src.oid, cop->oloc, op, cop->src.snap, NULL, flags, gather.new_sub(), // discover the object version if we don't know it yet cop->results.user_version ? NULL : &cop->results.user_version); fin->tid = tid; cop->objecter_tid = tid; gather.activate(); } void PrimaryLogPG::_copy_some_manifest(ObjectContextRef obc, CopyOpRef cop, uint64_t start_offset) { dout(10) << __func__ << " " << *obc << " " << cop << dendl; unsigned flags = 0; if (cop->flags & CEPH_OSD_COPY_FROM_FLAG_FLUSH) flags |= CEPH_OSD_FLAG_FLUSH; if (cop->flags & CEPH_OSD_COPY_FROM_FLAG_IGNORE_CACHE) flags |= CEPH_OSD_FLAG_IGNORE_CACHE; if (cop->flags & CEPH_OSD_COPY_FROM_FLAG_IGNORE_OVERLAY) flags |= CEPH_OSD_FLAG_IGNORE_OVERLAY; if (cop->flags & CEPH_OSD_COPY_FROM_FLAG_MAP_SNAP_CLONE) flags |= CEPH_OSD_FLAG_MAP_SNAP_CLONE; if (cop->flags & CEPH_OSD_COPY_FROM_FLAG_RWORDERED) flags |= CEPH_OSD_FLAG_RWORDERED; int num_chunks = 0; uint64_t last_offset = 0, chunks_size = 0; object_manifest_t *manifest = &obc->obs.oi.manifest; map::iterator iter = manifest->chunk_map.find(start_offset); for (;iter != manifest->chunk_map.end(); ++iter) { num_chunks++; chunks_size += iter->second.length; last_offset = iter->first; if (get_copy_chunk_size() < chunks_size) { break; } } cop->num_chunk = num_chunks; cop->start_offset = start_offset; cop->last_offset = last_offset; dout(20) << __func__ << " oid " << obc->obs.oi.soid << " num_chunks: " << num_chunks << " start_offset: " << start_offset << " chunks_size: " << chunks_size << " last_offset: " << last_offset << dendl; iter = manifest->chunk_map.find(start_offset); for (;iter != manifest->chunk_map.end(); ++iter) { uint64_t obj_offset = iter->first; uint64_t length = manifest->chunk_map[iter->first].length; hobject_t soid = manifest->chunk_map[iter->first].oid; object_locator_t oloc(soid); CopyCallback * cb = NULL; CopyOpRef sub_cop(std::make_shared(cb, ObjectContextRef(), cop->src, oloc, cop->results.user_version, cop->flags, cop->mirror_snapset, cop->src_obj_fadvise_flags, cop->dest_obj_fadvise_flags)); sub_cop->cursor.data_offset = obj_offset; cop->chunk_cops[obj_offset] = sub_cop; int s = sub_cop->chunk_ops.size(); sub_cop->chunk_ops.resize(s+1); sub_cop->chunk_ops[s].op.op = CEPH_OSD_OP_READ; sub_cop->chunk_ops[s].op.extent.offset = manifest->chunk_map[iter->first].offset; sub_cop->chunk_ops[s].op.extent.length = length; ObjectOperation op; op.dup(sub_cop->chunk_ops); if (cop->results.user_version) { op.assert_version(cop->results.user_version); } else { // we should learn the version after the first chunk, if we didn't know // it already! ceph_assert(cop->cursor.is_initial()); } op.set_last_op_flags(cop->src_obj_fadvise_flags); C_CopyChunk *fin = new C_CopyChunk(this, obc->obs.oi.soid, get_last_peering_reset(), cop); fin->offset = obj_offset; ceph_tid_t tid = osd->objecter->read( soid.oid, oloc, op, sub_cop->src.snap, NULL, flags, new C_OnFinisher(fin, osd->get_objecter_finisher(get_pg_shard())), // discover the object version if we don't know it yet sub_cop->results.user_version ? NULL : &sub_cop->results.user_version); fin->tid = tid; sub_cop->objecter_tid = tid; dout(20) << __func__ << " tgt_oid: " << soid.oid << " tgt_offset: " << manifest->chunk_map[iter->first].offset << " length: " << length << " pool id: " << oloc.pool << " tid: " << tid << dendl; if (last_offset < iter->first) { break; } } } void PrimaryLogPG::process_copy_chunk(hobject_t oid, ceph_tid_t tid, int r) { dout(10) << __func__ << " " << oid << " tid " << tid << " " << cpp_strerror(r) << dendl; map::iterator p = copy_ops.find(oid); if (p == copy_ops.end()) { dout(10) << __func__ << " no copy_op found" << dendl; return; } CopyOpRef cop = p->second; if (tid != cop->objecter_tid) { dout(10) << __func__ << " tid " << tid << " != cop " << cop << " tid " << cop->objecter_tid << dendl; return; } if (cop->omap_data.length() || cop->omap_header.length()) cop->results.has_omap = true; if (r >= 0 && !pool.info.supports_omap() && (cop->omap_data.length() || cop->omap_header.length())) { r = -EOPNOTSUPP; } cop->objecter_tid = 0; cop->objecter_tid2 = 0; // assume this ordered before us (if it happened) ObjectContextRef& cobc = cop->obc; if (r < 0) goto out; ceph_assert(cop->rval >= 0); if (oid.snap < CEPH_NOSNAP && !cop->results.snaps.empty()) { // verify snap hasn't been deleted vector::iterator p = cop->results.snaps.begin(); while (p != cop->results.snaps.end()) { // make best effort to sanitize snaps/clones. if (get_osdmap()->in_removed_snaps_queue(info.pgid.pgid.pool(), *p)) { dout(10) << __func__ << " clone snap " << *p << " has been deleted" << dendl; for (vector::iterator q = p + 1; q != cop->results.snaps.end(); ++q) *(q - 1) = *q; cop->results.snaps.resize(cop->results.snaps.size() - 1); } else { ++p; } } if (cop->results.snaps.empty()) { dout(10) << __func__ << " no more snaps for " << oid << dendl; r = -ENOENT; goto out; } } ceph_assert(cop->rval >= 0); if (!cop->temp_cursor.data_complete) { cop->results.data_digest = cop->data.crc32c(cop->results.data_digest); } if (pool.info.supports_omap() && !cop->temp_cursor.omap_complete) { if (cop->omap_header.length()) { cop->results.omap_digest = cop->omap_header.crc32c(cop->results.omap_digest); } if (cop->omap_data.length()) { bufferlist keys; keys.substr_of(cop->omap_data, 4, cop->omap_data.length() - 4); cop->results.omap_digest = keys.crc32c(cop->results.omap_digest); } } if (!cop->temp_cursor.attr_complete) { for (map::iterator p = cop->attrs.begin(); p != cop->attrs.end(); ++p) { cop->results.attrs[string("_") + p->first] = p->second; } cop->attrs.clear(); } if (!cop->cursor.is_complete()) { // write out what we have so far if (cop->temp_cursor.is_initial()) { ceph_assert(!cop->results.started_temp_obj); cop->results.started_temp_obj = true; cop->results.temp_oid = generate_temp_object(oid); dout(20) << __func__ << " using temp " << cop->results.temp_oid << dendl; } ObjectContextRef tempobc = get_object_context(cop->results.temp_oid, true); OpContextUPtr ctx = simple_opc_create(tempobc); if (cop->temp_cursor.is_initial()) { ctx->new_temp_oid = cop->results.temp_oid; } _write_copy_chunk(cop, ctx->op_t.get()); simple_opc_submit(std::move(ctx)); dout(10) << __func__ << " fetching more" << dendl; _copy_some(cobc, cop); return; } // verify digests? if (cop->results.is_data_digest() || cop->results.is_omap_digest()) { dout(20) << __func__ << std::hex << " got digest: rx data 0x" << cop->results.data_digest << " omap 0x" << cop->results.omap_digest << ", source: data 0x" << cop->results.source_data_digest << " omap 0x" << cop->results.source_omap_digest << std::dec << " flags " << cop->results.flags << dendl; } if (cop->results.is_data_digest() && cop->results.data_digest != cop->results.source_data_digest) { derr << __func__ << std::hex << " data digest 0x" << cop->results.data_digest << " != source 0x" << cop->results.source_data_digest << std::dec << dendl; osd->clog->error() << info.pgid << " copy from " << cop->src << " to " << cop->obc->obs.oi.soid << std::hex << " data digest 0x" << cop->results.data_digest << " != source 0x" << cop->results.source_data_digest << std::dec; r = -EIO; goto out; } if (cop->results.is_omap_digest() && cop->results.omap_digest != cop->results.source_omap_digest) { derr << __func__ << std::hex << " omap digest 0x" << cop->results.omap_digest << " != source 0x" << cop->results.source_omap_digest << std::dec << dendl; osd->clog->error() << info.pgid << " copy from " << cop->src << " to " << cop->obc->obs.oi.soid << std::hex << " omap digest 0x" << cop->results.omap_digest << " != source 0x" << cop->results.source_omap_digest << std::dec; r = -EIO; goto out; } if (cct->_conf->osd_debug_inject_copyfrom_error) { derr << __func__ << " injecting copyfrom failure" << dendl; r = -EIO; goto out; } cop->results.fill_in_final_tx = std::function( [this, &cop /* avoid ref cycle */](PGTransaction *t) { ObjectState& obs = cop->obc->obs; if (cop->temp_cursor.is_initial()) { dout(20) << "fill_in_final_tx: writing " << "directly to final object" << dendl; // write directly to final object cop->results.temp_oid = obs.oi.soid; _write_copy_chunk(cop, t); } else { // finish writing to temp object, then move into place dout(20) << "fill_in_final_tx: writing to temp object" << dendl; if (obs.oi.has_manifest() && obs.oi.manifest.is_redirect() && obs.exists) { /* In redirect manifest case, the object exists in the upper tier. * So, to avoid a conflict when rename() is called, remove existing * object first */ t->remove(obs.oi.soid); } _write_copy_chunk(cop, t); t->rename(obs.oi.soid, cop->results.temp_oid); } t->setattrs(obs.oi.soid, cop->results.attrs); }); dout(20) << __func__ << " success; committing" << dendl; out: dout(20) << __func__ << " complete r = " << cpp_strerror(r) << dendl; CopyCallbackResults results(r, &cop->results); cop->cb->complete(results); copy_ops.erase(cobc->obs.oi.soid); cobc->stop_block(); if (r < 0 && cop->results.started_temp_obj) { dout(10) << __func__ << " deleting partial temp object " << cop->results.temp_oid << dendl; ObjectContextRef tempobc = get_object_context(cop->results.temp_oid, true); OpContextUPtr ctx = simple_opc_create(tempobc); ctx->op_t->remove(cop->results.temp_oid); ctx->discard_temp_oid = cop->results.temp_oid; simple_opc_submit(std::move(ctx)); } // cancel and requeue proxy ops on this object if (!r) { cancel_and_requeue_proxy_ops(cobc->obs.oi.soid); } kick_object_context_blocked(cobc); } void PrimaryLogPG::process_copy_chunk_manifest(hobject_t oid, ceph_tid_t tid, int r, uint64_t offset) { dout(10) << __func__ << " " << oid << " tid " << tid << " " << cpp_strerror(r) << dendl; map::iterator p = copy_ops.find(oid); if (p == copy_ops.end()) { dout(10) << __func__ << " no copy_op found" << dendl; return; } CopyOpRef obj_cop = p->second; CopyOpRef chunk_cop = obj_cop->chunk_cops[offset]; if (tid != chunk_cop->objecter_tid) { dout(10) << __func__ << " tid " << tid << " != cop " << chunk_cop << " tid " << chunk_cop->objecter_tid << dendl; return; } if (chunk_cop->omap_data.length() || chunk_cop->omap_header.length()) { r = -EOPNOTSUPP; } chunk_cop->objecter_tid = 0; chunk_cop->objecter_tid2 = 0; // assume this ordered before us (if it happened) ObjectContextRef& cobc = obj_cop->obc; OSDOp &chunk_data = chunk_cop->chunk_ops[0]; if (r < 0) { obj_cop->failed = true; goto out; } if (obj_cop->failed) { return; } if (!chunk_data.outdata.length()) { r = -EIO; obj_cop->failed = true; goto out; } obj_cop->num_chunk--; /* check all of the copyop are completed */ if (obj_cop->num_chunk) { dout(20) << __func__ << " num_chunk: " << obj_cop->num_chunk << dendl; return; } { OpContextUPtr ctx = simple_opc_create(obj_cop->obc); if (!ctx->lock_manager.take_write_lock( obj_cop->obc->obs.oi.soid, obj_cop->obc)) { // recovery op can take read lock. // so need to wait for recovery completion r = -EAGAIN; obj_cop->failed = true; close_op_ctx(ctx.release()); goto out; } dout(20) << __func__ << " took lock on obc, " << obj_cop->obc->rwstate << dendl; PGTransaction *t = ctx->op_t.get(); ObjectState& obs = ctx->new_obs; for (auto p : obj_cop->chunk_cops) { OSDOp &sub_chunk = p.second->chunk_ops[0]; t->write(cobc->obs.oi.soid, p.second->cursor.data_offset, sub_chunk.outdata.length(), sub_chunk.outdata, p.second->dest_obj_fadvise_flags); dout(20) << __func__ << " offset: " << p.second->cursor.data_offset << " length: " << sub_chunk.outdata.length() << dendl; write_update_size_and_usage(ctx->delta_stats, obs.oi, ctx->modified_ranges, p.second->cursor.data_offset, sub_chunk.outdata.length()); obs.oi.manifest.chunk_map[p.second->cursor.data_offset].clear_flag(chunk_info_t::FLAG_MISSING); ctx->clean_regions.mark_data_region_dirty(p.second->cursor.data_offset, sub_chunk.outdata.length()); sub_chunk.outdata.clear(); } obs.oi.clear_data_digest(); ctx->at_version = get_next_version(); finish_ctx(ctx.get(), pg_log_entry_t::PROMOTE); simple_opc_submit(std::move(ctx)); auto p = cobc->obs.oi.manifest.chunk_map.rbegin(); /* check remaining work */ if (p != cobc->obs.oi.manifest.chunk_map.rend()) { if (obj_cop->last_offset >= p->first + p->second.length) { for (auto &en : cobc->obs.oi.manifest.chunk_map) { if (obj_cop->last_offset < en.first) { _copy_some_manifest(cobc, obj_cop, en.first); return; } } } } } out: dout(20) << __func__ << " complete r = " << cpp_strerror(r) << dendl; CopyCallbackResults results(r, &obj_cop->results); obj_cop->cb->complete(results); copy_ops.erase(cobc->obs.oi.soid); cobc->stop_block(); // cancel and requeue proxy ops on this object if (!r) { cancel_and_requeue_proxy_ops(cobc->obs.oi.soid); } kick_object_context_blocked(cobc); } void PrimaryLogPG::cancel_and_requeue_proxy_ops(hobject_t oid) { vector tids; for (map::iterator it = proxyread_ops.begin(); it != proxyread_ops.end();) { if (it->second->soid == oid) { cancel_proxy_read((it++)->second, &tids); } else { ++it; } } for (map::iterator it = proxywrite_ops.begin(); it != proxywrite_ops.end();) { if (it->second->soid == oid) { cancel_proxy_write((it++)->second, &tids); } else { ++it; } } osd->objecter->op_cancel(tids, -ECANCELED); kick_proxy_ops_blocked(oid); } void PrimaryLogPG::_write_copy_chunk(CopyOpRef cop, PGTransaction *t) { dout(20) << __func__ << " " << cop << " " << cop->attrs.size() << " attrs" << " " << cop->data.length() << " bytes" << " " << cop->omap_header.length() << " omap header bytes" << " " << cop->omap_data.length() << " omap data bytes" << dendl; if (!cop->temp_cursor.attr_complete) { t->create(cop->results.temp_oid); } if (!cop->temp_cursor.data_complete) { ceph_assert(cop->data.length() + cop->temp_cursor.data_offset == cop->cursor.data_offset); if (pool.info.required_alignment() && !cop->cursor.data_complete) { /** * Trim off the unaligned bit at the end, we'll adjust cursor.data_offset * to pick it up on the next pass. */ ceph_assert(cop->temp_cursor.data_offset % pool.info.required_alignment() == 0); if (cop->data.length() % pool.info.required_alignment() != 0) { uint64_t to_trim = cop->data.length() % pool.info.required_alignment(); bufferlist bl; bl.substr_of(cop->data, 0, cop->data.length() - to_trim); cop->data.swap(bl); cop->cursor.data_offset -= to_trim; ceph_assert(cop->data.length() + cop->temp_cursor.data_offset == cop->cursor.data_offset); } } if (cop->data.length()) { t->write( cop->results.temp_oid, cop->temp_cursor.data_offset, cop->data.length(), cop->data, cop->dest_obj_fadvise_flags); } cop->data.clear(); } if (pool.info.supports_omap()) { if (!cop->temp_cursor.omap_complete) { if (cop->omap_header.length()) { t->omap_setheader( cop->results.temp_oid, cop->omap_header); cop->omap_header.clear(); } if (cop->omap_data.length()) { map omap; bufferlist::const_iterator p = cop->omap_data.begin(); decode(omap, p); t->omap_setkeys(cop->results.temp_oid, omap); cop->omap_data.clear(); } } } else { ceph_assert(cop->omap_header.length() == 0); ceph_assert(cop->omap_data.length() == 0); } cop->temp_cursor = cop->cursor; } void PrimaryLogPG::finish_copyfrom(CopyFromCallback *cb) { OpContext *ctx = cb->ctx; dout(20) << "finish_copyfrom on " << ctx->obs->oi.soid << dendl; ObjectState& obs = ctx->new_obs; if (obs.exists) { dout(20) << __func__ << ": exists, removing" << dendl; ctx->op_t->remove(obs.oi.soid); } else { ctx->delta_stats.num_objects++; obs.exists = true; } if (cb->is_temp_obj_used()) { ctx->discard_temp_oid = cb->results->temp_oid; } cb->results->fill_in_final_tx(ctx->op_t.get()); // CopyFromCallback fills this in for us obs.oi.user_version = ctx->user_at_version; if (cb->results->is_data_digest()) { obs.oi.set_data_digest(cb->results->data_digest); } else { obs.oi.clear_data_digest(); } if (cb->results->is_omap_digest()) { obs.oi.set_omap_digest(cb->results->omap_digest); } else { obs.oi.clear_omap_digest(); } obs.oi.truncate_seq = cb->truncate_seq; obs.oi.truncate_size = cb->truncate_size; obs.oi.mtime = ceph::real_clock::to_timespec(cb->results->mtime); ctx->mtime = utime_t(); ctx->extra_reqids = cb->results->reqids; ctx->extra_reqid_return_codes = cb->results->reqid_return_codes; // cache: clear whiteout? if (obs.oi.is_whiteout()) { dout(10) << __func__ << " clearing whiteout on " << obs.oi.soid << dendl; obs.oi.clear_flag(object_info_t::FLAG_WHITEOUT); --ctx->delta_stats.num_whiteouts; } if (cb->results->has_omap) { dout(10) << __func__ << " setting omap flag on " << obs.oi.soid << dendl; obs.oi.set_flag(object_info_t::FLAG_OMAP); ctx->clean_regions.mark_omap_dirty(); } else { dout(10) << __func__ << " clearing omap flag on " << obs.oi.soid << dendl; obs.oi.clear_flag(object_info_t::FLAG_OMAP); } interval_set ch; if (obs.oi.size > 0) ch.insert(0, obs.oi.size); ctx->modified_ranges.union_of(ch); ctx->clean_regions.mark_data_region_dirty(0, std::max(obs.oi.size, cb->get_data_size())); if (cb->get_data_size() != obs.oi.size) { ctx->delta_stats.num_bytes -= obs.oi.size; obs.oi.size = cb->get_data_size(); ctx->delta_stats.num_bytes += obs.oi.size; } ctx->delta_stats.num_wr++; ctx->delta_stats.num_wr_kb += shift_round_up(obs.oi.size, 10); osd->logger->inc(l_osd_copyfrom); } void PrimaryLogPG::finish_promote(int r, CopyResults *results, ObjectContextRef obc) { const hobject_t& soid = obc->obs.oi.soid; dout(10) << __func__ << " " << soid << " r=" << r << " uv" << results->user_version << dendl; if (r == -ECANCELED) { return; } if (r != -ENOENT && soid.is_snap()) { if (results->snaps.empty()) { // we must have read "snap" content from the head object in the // base pool. use snap_seq to construct what snaps should be // for this clone (what is was before we evicted the clean clone // from this pool, and what it will be when we flush and the // clone eventually happens in the base pool). we want to use // snaps in (results->snap_seq,soid.snap] SnapSet& snapset = obc->ssc->snapset; for (auto p = snapset.clone_snaps.rbegin(); p != snapset.clone_snaps.rend(); ++p) { for (auto snap : p->second) { if (snap > soid.snap) { continue; } if (snap <= results->snap_seq) { break; } results->snaps.push_back(snap); } } } dout(20) << __func__ << " snaps " << results->snaps << dendl; filter_snapc(results->snaps); dout(20) << __func__ << " filtered snaps " << results->snaps << dendl; if (results->snaps.empty()) { dout(20) << __func__ << " snaps are empty, clone is invalid," << " setting r to ENOENT" << dendl; r = -ENOENT; } } if (r < 0 && results->started_temp_obj) { dout(10) << __func__ << " abort; will clean up partial work" << dendl; ObjectContextRef tempobc = get_object_context(results->temp_oid, false); ceph_assert(tempobc); OpContextUPtr ctx = simple_opc_create(tempobc); ctx->op_t->remove(results->temp_oid); simple_opc_submit(std::move(ctx)); results->started_temp_obj = false; } if (r == -ENOENT && soid.is_snap()) { dout(10) << __func__ << ": enoent while trying to promote clone, " << soid << " must have been trimmed, removing from snapset" << dendl; hobject_t head(soid.get_head()); ObjectContextRef obc = get_object_context(head, false); ceph_assert(obc); OpContextUPtr tctx = simple_opc_create(obc); tctx->at_version = get_next_version(); if (get_osdmap()->require_osd_release < ceph_release_t::octopus) { filter_snapc(tctx->new_snapset.snaps); } else { tctx->new_snapset.snaps.clear(); } vector new_clones; map> new_clone_snaps; for (vector::iterator i = tctx->new_snapset.clones.begin(); i != tctx->new_snapset.clones.end(); ++i) { if (*i != soid.snap) { new_clones.push_back(*i); auto p = tctx->new_snapset.clone_snaps.find(*i); if (p != tctx->new_snapset.clone_snaps.end()) { new_clone_snaps[*i] = p->second; } } } tctx->new_snapset.clones.swap(new_clones); tctx->new_snapset.clone_overlap.erase(soid.snap); tctx->new_snapset.clone_size.erase(soid.snap); tctx->new_snapset.clone_snaps.swap(new_clone_snaps); // take RWWRITE lock for duration of our local write. ignore starvation. if (!tctx->lock_manager.take_write_lock( head, obc)) { ceph_abort_msg("problem!"); } dout(20) << __func__ << " took lock on obc, " << obc->rwstate << dendl; finish_ctx(tctx.get(), pg_log_entry_t::PROMOTE); simple_opc_submit(std::move(tctx)); return; } bool whiteout = false; if (r == -ENOENT) { ceph_assert(soid.snap == CEPH_NOSNAP); // snap case is above dout(10) << __func__ << " whiteout " << soid << dendl; whiteout = true; } if (r < 0 && !whiteout) { derr << __func__ << " unexpected promote error " << cpp_strerror(r) << dendl; // pass error to everyone blocked on this object // FIXME: this is pretty sloppy, but at this point we got // something unexpected and don't have many other options. map>::iterator blocked_iter = waiting_for_blocked_object.find(soid); if (blocked_iter != waiting_for_blocked_object.end()) { while (!blocked_iter->second.empty()) { osd->reply_op_error(blocked_iter->second.front(), r); blocked_iter->second.pop_front(); } waiting_for_blocked_object.erase(blocked_iter); } return; } osd->promote_finish(results->object_size); OpContextUPtr tctx = simple_opc_create(obc); tctx->at_version = get_next_version(); if (!obc->obs.oi.has_manifest()) { ++tctx->delta_stats.num_objects; } if (soid.snap < CEPH_NOSNAP) ++tctx->delta_stats.num_object_clones; tctx->new_obs.exists = true; tctx->extra_reqids = results->reqids; tctx->extra_reqid_return_codes = results->reqid_return_codes; if (obc->obs.oi.has_manifest() && obc->obs.oi.manifest.is_redirect()) { tctx->new_obs.oi.manifest.type = object_manifest_t::TYPE_NONE; tctx->new_obs.oi.clear_flag(object_info_t::FLAG_REDIRECT_HAS_REFERENCE); tctx->new_obs.oi.clear_flag(object_info_t::FLAG_MANIFEST); tctx->new_obs.oi.manifest.redirect_target = hobject_t(); tctx->delta_stats.num_objects_manifest--; if (obc->obs.oi.test_flag(object_info_t::FLAG_REDIRECT_HAS_REFERENCE)) { dec_all_refcount_manifest(obc->obs.oi, tctx.get()); } } if (whiteout) { // create a whiteout tctx->op_t->create(soid); tctx->new_obs.oi.set_flag(object_info_t::FLAG_WHITEOUT); ++tctx->delta_stats.num_whiteouts; dout(20) << __func__ << " creating whiteout on " << soid << dendl; osd->logger->inc(l_osd_tier_whiteout); } else { if (results->has_omap) { dout(10) << __func__ << " setting omap flag on " << soid << dendl; tctx->new_obs.oi.set_flag(object_info_t::FLAG_OMAP); ++tctx->delta_stats.num_objects_omap; } results->fill_in_final_tx(tctx->op_t.get()); if (results->started_temp_obj) { tctx->discard_temp_oid = results->temp_oid; } tctx->new_obs.oi.size = results->object_size; tctx->new_obs.oi.user_version = results->user_version; tctx->new_obs.oi.mtime = ceph::real_clock::to_timespec(results->mtime); tctx->mtime = utime_t(); if (results->is_data_digest()) { tctx->new_obs.oi.set_data_digest(results->data_digest); } else { tctx->new_obs.oi.clear_data_digest(); } if (results->object_size) tctx->clean_regions.mark_data_region_dirty(0, results->object_size); if (results->is_omap_digest()) { tctx->new_obs.oi.set_omap_digest(results->omap_digest); } else { tctx->new_obs.oi.clear_omap_digest(); } if (results->has_omap) tctx->clean_regions.mark_omap_dirty(); tctx->new_obs.oi.truncate_seq = results->truncate_seq; tctx->new_obs.oi.truncate_size = results->truncate_size; if (soid.snap != CEPH_NOSNAP) { ceph_assert(obc->ssc->snapset.clone_snaps.count(soid.snap)); ceph_assert(obc->ssc->snapset.clone_size.count(soid.snap)); ceph_assert(obc->ssc->snapset.clone_size[soid.snap] == results->object_size); ceph_assert(obc->ssc->snapset.clone_overlap.count(soid.snap)); tctx->delta_stats.num_bytes += obc->ssc->snapset.get_clone_bytes(soid.snap); } else { tctx->delta_stats.num_bytes += results->object_size; } } if (results->mirror_snapset) { ceph_assert(tctx->new_obs.oi.soid.snap == CEPH_NOSNAP); tctx->new_snapset.from_snap_set( results->snapset, get_osdmap()->require_osd_release < ceph_release_t::luminous); } dout(20) << __func__ << " new_snapset " << tctx->new_snapset << dendl; // take RWWRITE lock for duration of our local write. ignore starvation. if (!tctx->lock_manager.take_write_lock( obc->obs.oi.soid, obc)) { ceph_abort_msg("problem!"); } dout(20) << __func__ << " took lock on obc, " << obc->rwstate << dendl; finish_ctx(tctx.get(), pg_log_entry_t::PROMOTE); simple_opc_submit(std::move(tctx)); osd->logger->inc(l_osd_tier_promote); if (agent_state && agent_state->is_idle()) agent_choose_mode(); } void PrimaryLogPG::finish_promote_manifest(int r, CopyResults *results, ObjectContextRef obc) { const hobject_t& soid = obc->obs.oi.soid; dout(10) << __func__ << " " << soid << " r=" << r << " uv" << results->user_version << dendl; if (r == -ECANCELED || r == -EAGAIN) { return; } if (r < 0) { derr << __func__ << " unexpected promote error " << cpp_strerror(r) << dendl; // pass error to everyone blocked on this object // FIXME: this is pretty sloppy, but at this point we got // something unexpected and don't have many other options. map>::iterator blocked_iter = waiting_for_blocked_object.find(soid); if (blocked_iter != waiting_for_blocked_object.end()) { while (!blocked_iter->second.empty()) { osd->reply_op_error(blocked_iter->second.front(), r); blocked_iter->second.pop_front(); } waiting_for_blocked_object.erase(blocked_iter); } return; } osd->promote_finish(results->object_size); osd->logger->inc(l_osd_tier_promote); if (agent_state && agent_state->is_idle()) agent_choose_mode(); } void PrimaryLogPG::cancel_copy(CopyOpRef cop, bool requeue, vector *tids) { dout(10) << __func__ << " " << cop->obc->obs.oi.soid << " from " << cop->src << " " << cop->oloc << " v" << cop->results.user_version << dendl; // cancel objecter op, if we can if (cop->objecter_tid) { tids->push_back(cop->objecter_tid); cop->objecter_tid = 0; if (cop->objecter_tid2) { tids->push_back(cop->objecter_tid2); cop->objecter_tid2 = 0; } } copy_ops.erase(cop->obc->obs.oi.soid); cop->obc->stop_block(); kick_object_context_blocked(cop->obc); cop->results.should_requeue = requeue; CopyCallbackResults result(-ECANCELED, &cop->results); cop->cb->complete(result); // There may still be an objecter callback referencing this copy op. // That callback will not need the obc since it's been canceled, and // we need the obc reference to go away prior to flush. cop->obc = ObjectContextRef(); } void PrimaryLogPG::cancel_copy_ops(bool requeue, vector *tids) { dout(10) << __func__ << dendl; map::iterator p = copy_ops.begin(); while (p != copy_ops.end()) { // requeue this op? can I queue up all of them? cancel_copy((p++)->second, requeue, tids); } } // ======================================================================== // flush // // Flush a dirty object in the cache tier by writing it back to the // base tier. The sequence looks like: // // * send a copy-from operation to the base tier to copy the current // version of the object // * base tier will pull the object via (perhaps multiple) copy-get(s) // * on completion, we check if the object has been modified. if so, // just reply with -EAGAIN. // * try to take a write lock so we can clear the dirty flag. if this // fails, wait and retry // * start a repop that clears the bit. // // If we have to wait, we will retry by coming back through the // start_flush method. We check if a flush is already in progress // and, if so, try to finish it by rechecking the version and trying // to clear the dirty bit. // // In order for the cache-flush (a write op) to not block the copy-get // from reading the object, the client *must* set the SKIPRWLOCKS // flag. // // NOTE: normally writes are strictly ordered for the client, but // flushes are special in that they can be reordered with respect to // other writes. In particular, we can't have a flush request block // an update to the cache pool object! struct C_Flush : public Context { PrimaryLogPGRef pg; hobject_t oid; epoch_t last_peering_reset; ceph_tid_t tid; utime_t start; C_Flush(PrimaryLogPG *p, hobject_t o, epoch_t lpr) : pg(p), oid(o), last_peering_reset(lpr), tid(0), start(ceph_clock_now()) {} void finish(int r) override { if (r == -ECANCELED) return; std::scoped_lock locker{*pg}; if (last_peering_reset == pg->get_last_peering_reset()) { pg->finish_flush(oid, tid, r); pg->osd->logger->tinc(l_osd_tier_flush_lat, ceph_clock_now() - start); } } }; int PrimaryLogPG::start_dedup(OpRequestRef op, ObjectContextRef obc) { const object_info_t& oi = obc->obs.oi; const hobject_t& soid = oi.soid; ceph_assert(obc->is_blocked()); if (oi.size == 0) { // evicted return 0; } if (pool.info.get_fingerprint_type() == pg_pool_t::TYPE_FINGERPRINT_NONE) { dout(0) << " fingerprint algorithm is not set " << dendl; return -EINVAL; } if (pool.info.get_dedup_tier() <= 0) { dout(10) << " dedup tier is not set " << dendl; return -EINVAL; } /* * The operations to make dedup chunks are tracked by a ManifestOp. * This op will be finished if all the operations are completed. */ ManifestOpRef mop(std::make_shared(nullptr)); // cdc std::map chunks; int r = do_cdc(oi, mop->new_manifest.chunk_map, chunks); if (r < 0) { return r; } if (!chunks.size()) { return 0; } // chunks issued here are different with chunk_map newly generated // because the same chunks in previous snap will not be issued // So, we need two data structures; the first is the issued chunk list to track // issued operations, and the second is the new chunk_map to update chunk_map after // all operations are finished object_ref_delta_t refs; ObjectContextRef obc_l, obc_g; get_adjacent_clones(obc, obc_l, obc_g); // skip if the same content exits in prev snap at same offset mop->new_manifest.calc_refs_to_inc_on_set( obc_l ? &(obc_l->obs.oi.manifest) : nullptr, obc_g ? &(obc_g->obs.oi.manifest) : nullptr, refs); for (auto p : chunks) { hobject_t target = mop->new_manifest.chunk_map[p.first].oid; if (refs.find(target) == refs.end()) { continue; } C_SetDedupChunks *fin = new C_SetDedupChunks(this, soid, get_last_peering_reset(), p.first); ceph_tid_t tid = refcount_manifest(soid, target, refcount_t::CREATE_OR_GET_REF, fin, move(chunks[p.first])); mop->chunks[target] = make_pair(p.first, p.second.length()); mop->num_chunks++; mop->tids[p.first] = tid; fin->tid = tid; dout(10) << __func__ << " oid: " << soid << " tid: " << tid << " target: " << target << " offset: " << p.first << " length: " << p.second.length() << dendl; } if (mop->tids.size()) { manifest_ops[soid] = mop; manifest_ops[soid]->op = op; } else { // size == 0 return 0; } return -EINPROGRESS; } int PrimaryLogPG::do_cdc(const object_info_t& oi, std::map& chunk_map, std::map& chunks) { string chunk_algo = pool.info.get_dedup_chunk_algorithm_name(); int64_t chunk_size = pool.info.get_dedup_cdc_chunk_size(); uint64_t total_length = 0; std::unique_ptr cdc = CDC::create(chunk_algo, cbits(chunk_size)-1); if (!cdc) { dout(0) << __func__ << " unrecognized chunk-algorithm " << dendl; return -EINVAL; } bufferlist bl; /** * We disable EC pool as a base tier of distributed dedup. * The reason why we disallow erasure code pool here is that the EC pool does not support objects_read_sync(). * Therefore, we should change the current implementation totally to make EC pool compatible. * As s result, we leave this as a future work. */ int r = pgbackend->objects_read_sync( oi.soid, 0, oi.size, 0, &bl); if (r < 0) { dout(0) << __func__ << " read fail " << oi.soid << " len: " << oi.size << " r: " << r << dendl; return r; } if (bl.length() != oi.size) { dout(0) << __func__ << " bl.length: " << bl.length() << " != oi.size: " << oi.size << " during chunking " << dendl; return -EIO; } dout(10) << __func__ << " oid: " << oi.soid << " len: " << bl.length() << " oi.size: " << oi.size << " chunk_size: " << chunk_size << dendl; vector> cdc_chunks; cdc->calc_chunks(bl, &cdc_chunks); // get fingerprint for (auto p : cdc_chunks) { bufferlist chunk; chunk.substr_of(bl, p.first, p.second); auto [ret, target] = get_fpoid_from_chunk(oi.soid, chunk); if (ret < 0) { return ret; } chunks[p.first] = std::move(chunk); chunk_map[p.first] = chunk_info_t(0, p.second, target); total_length += p.second; } return total_length; } std::pair PrimaryLogPG::get_fpoid_from_chunk( const hobject_t soid, bufferlist& chunk) { pg_pool_t::fingerprint_t fp_algo = pool.info.get_fingerprint_type(); if (fp_algo == pg_pool_t::TYPE_FINGERPRINT_NONE) { return make_pair(-EINVAL, hobject_t()); } object_t fp_oid = [&fp_algo, &chunk]() -> string { switch (fp_algo) { case pg_pool_t::TYPE_FINGERPRINT_SHA1: return ceph::crypto::digest(chunk).to_str(); case pg_pool_t::TYPE_FINGERPRINT_SHA256: return ceph::crypto::digest(chunk).to_str(); case pg_pool_t::TYPE_FINGERPRINT_SHA512: return ceph::crypto::digest(chunk).to_str(); default: assert(0 == "unrecognized fingerprint type"); return {}; } }(); pg_t raw_pg; object_locator_t oloc(soid); oloc.pool = pool.info.get_dedup_tier(); // check if dedup_tier isn't set ceph_assert(oloc.pool > 0); int ret = get_osdmap()->object_locator_to_pg(fp_oid, oloc, raw_pg); if (ret < 0) { return make_pair(ret, hobject_t()); } hobject_t target(fp_oid, oloc.key, snapid_t(), raw_pg.ps(), raw_pg.pool(), oloc.nspace); return make_pair(0, target); } int PrimaryLogPG::finish_set_dedup(hobject_t oid, int r, ceph_tid_t tid, uint64_t offset) { dout(10) << __func__ << " " << oid << " tid " << tid << " " << cpp_strerror(r) << dendl; map::iterator p = manifest_ops.find(oid); if (p == manifest_ops.end()) { dout(10) << __func__ << " no manifest_op found" << dendl; return -EINVAL; } ManifestOpRef mop = p->second; mop->results[offset] = r; if (r < 0) { // if any failure occurs, put a mark on the results to recognize the failure mop->results[0] = r; } if (mop->num_chunks != mop->results.size()) { // there are on-going works return -EINPROGRESS; } ObjectContextRef obc = get_object_context(oid, false); if (!obc) { if (mop->op) osd->reply_op_error(mop->op, -EINVAL); return -EINVAL; } ceph_assert(obc->is_blocked()); obc->stop_block(); kick_object_context_blocked(obc); if (mop->results[0] < 0) { // check if the previous op returns fail ceph_assert(mop->num_chunks == mop->results.size()); manifest_ops.erase(oid); osd->reply_op_error(mop->op, mop->results[0]); return -EIO; } if (mop->chunks.size()) { OpContextUPtr ctx = simple_opc_create(obc); ceph_assert(ctx); if (ctx->lock_manager.get_lock_type( RWState::RWWRITE, oid, obc, mop->op)) { dout(20) << __func__ << " took write lock" << dendl; } else if (mop->op) { dout(10) << __func__ << " waiting on write lock " << mop->op << dendl; close_op_ctx(ctx.release()); return -EAGAIN; } ctx->at_version = get_next_version(); ctx->new_obs = obc->obs; ctx->new_obs.oi.clear_flag(object_info_t::FLAG_DIRTY); /* * Let's assume that there is a manifest snapshotted object, and we issue tier_flush() to head. * head: [0, 2) aaa <-- tier_flush() * 20: [0, 2) ddd, [6, 2) bbb, [8, 2) ccc * * In this case, if the new chunk_map is as follows, * new_chunk_map : [0, 2) ddd, [6, 2) bbb, [8, 2) ccc * we should drop aaa from head by using calc_refs_to_drop_on_removal(). * So, the precedure is * 1. calc_refs_to_drop_on_removal() * 2. register old references to drop after tier_flush() is committed * 3. update new chunk_map */ ObjectCleanRegions c_regions = ctx->clean_regions; ObjectContextRef cobc = get_prev_clone_obc(obc); c_regions.mark_fully_dirty(); // CDC was done on entire range of manifest object, // so the first thing we should do here is to drop the reference to old chunks ObjectContextRef obc_l, obc_g; get_adjacent_clones(obc, obc_l, obc_g); // clear all old references object_ref_delta_t refs; ctx->obs->oi.manifest.calc_refs_to_drop_on_removal( obc_l ? &(obc_l->obs.oi.manifest) : nullptr, obc_g ? &(obc_g->obs.oi.manifest) : nullptr, refs); if (!refs.is_empty()) { ctx->register_on_commit( [oid, this, refs](){ dec_refcount(oid, refs); }); } // set new references ctx->new_obs.oi.manifest.chunk_map = mop->new_manifest.chunk_map; finish_ctx(ctx.get(), pg_log_entry_t::CLEAN); simple_opc_submit(std::move(ctx)); } if (mop->op) osd->reply_op_error(mop->op, r); manifest_ops.erase(oid); return 0; } int PrimaryLogPG::start_flush( OpRequestRef op, ObjectContextRef obc, bool blocking, hobject_t *pmissing, std::optional> &&on_flush) { const object_info_t& oi = obc->obs.oi; const hobject_t& soid = oi.soid; dout(10) << __func__ << " " << soid << " v" << oi.version << " uv" << oi.user_version << " " << (blocking ? "blocking" : "non-blocking/best-effort") << dendl; bool preoctopus_compat = get_osdmap()->require_osd_release < ceph_release_t::octopus; SnapSet snapset; if (preoctopus_compat) { // for pre-octopus compatibility, filter SnapSet::snaps. not // certain we need this, but let's be conservative. snapset = obc->ssc->snapset.get_filtered(pool.info); } else { // NOTE: change this to a const ref when we remove this compat code snapset = obc->ssc->snapset; } if (obc->obs.oi.has_manifest() && obc->obs.oi.manifest.is_chunked()) { // current dedup tier only supports blocking operation if (!blocking) { return -EOPNOTSUPP; } } // verify there are no (older) check for dirty clones { dout(20) << " snapset " << snapset << dendl; vector::reverse_iterator p = snapset.clones.rbegin(); while (p != snapset.clones.rend() && *p >= soid.snap) ++p; if (p != snapset.clones.rend()) { hobject_t next = soid; next.snap = *p; ceph_assert(next.snap < soid.snap); if (recovery_state.get_pg_log().get_missing().is_missing(next)) { dout(10) << __func__ << " missing clone is " << next << dendl; if (pmissing) *pmissing = next; return -ENOENT; } ObjectContextRef older_obc = get_object_context(next, false); if (older_obc) { dout(20) << __func__ << " next oldest clone is " << older_obc->obs.oi << dendl; if (older_obc->obs.oi.is_dirty()) { dout(10) << __func__ << " next oldest clone is dirty: " << older_obc->obs.oi << dendl; return -EBUSY; } } else { dout(20) << __func__ << " next oldest clone " << next << " is not present; implicitly clean" << dendl; } } else { dout(20) << __func__ << " no older clones" << dendl; } } if (blocking) obc->start_block(); map::iterator p = flush_ops.find(soid); if (p != flush_ops.end()) { FlushOpRef fop = p->second; if (fop->op == op) { // we couldn't take the write lock on a cache-try-flush before; // now we are trying again for the lock. return try_flush_mark_clean(fop); } if (fop->flushed_version == obc->obs.oi.user_version && (fop->blocking || !blocking)) { // nonblocking can join anything // blocking can only join a blocking flush dout(20) << __func__ << " piggybacking on existing flush " << dendl; if (op) fop->dup_ops.push_back(op); return -EAGAIN; // clean up this ctx; op will retry later } // cancel current flush since it will fail anyway, or because we // are blocking and the existing flush is nonblocking. dout(20) << __func__ << " canceling previous flush; it will fail" << dendl; if (fop->op) osd->reply_op_error(fop->op, -EBUSY); while (!fop->dup_ops.empty()) { osd->reply_op_error(fop->dup_ops.front(), -EBUSY); fop->dup_ops.pop_front(); } vector tids; cancel_flush(fop, false, &tids); osd->objecter->op_cancel(tids, -ECANCELED); } if (obc->obs.oi.has_manifest() && obc->obs.oi.manifest.is_chunked()) { int r = start_dedup(op, obc); if (r != -EINPROGRESS) { if (blocking) obc->stop_block(); } return r; } /** * In general, we need to send a delete and a copyfrom. * Consider snapc 10:[10, 9, 8, 4, 3, 2]:[10(10, 9), 4(4,3,2)] * where 4 is marked as clean. To flush 10, we have to: * 1) delete 4:[4,3,2] -- Logically, the object does not exist after 4 * 2) copyfrom 8:[8,4,3,2] -- flush object after snap 8 * * There is a complicating case. Supposed there had been a clone 7 * for snaps [7, 6] which has been trimmed since they no longer exist. * In the base pool, we'd have 5:[4,3,2]:[4(4,3,2)]+head. When we submit * the delete, the snap will be promoted to 5, and the head will become * a whiteout. When the copy-from goes through, we'll end up with * 8:[8,4,3,2]:[4(4,3,2)]+head. * * Another complication is the case where there is an interval change * after doing the delete and the flush but before marking the object * clean. We'll happily delete head and then recreate it at the same * sequence number, which works out ok. */ SnapContext snapc, dsnapc; if (snapset.seq != 0) { if (soid.snap == CEPH_NOSNAP) { snapc = snapset.get_ssc_as_of(snapset.seq); } else { snapid_t min_included_snap; auto p = snapset.clone_snaps.find(soid.snap); ceph_assert(p != snapset.clone_snaps.end()); min_included_snap = p->second.back(); snapc = snapset.get_ssc_as_of(min_included_snap - 1); } snapid_t prev_snapc = 0; for (vector::reverse_iterator citer = snapset.clones.rbegin(); citer != snapset.clones.rend(); ++citer) { if (*citer < soid.snap) { prev_snapc = *citer; break; } } dsnapc = snapset.get_ssc_as_of(prev_snapc); } object_locator_t base_oloc(soid); base_oloc.pool = pool.info.tier_of; if (dsnapc.seq < snapc.seq) { ObjectOperation o; o.remove(); osd->objecter->mutate( soid.oid, base_oloc, o, dsnapc, ceph::real_clock::from_ceph_timespec(oi.mtime), (CEPH_OSD_FLAG_IGNORE_OVERLAY | CEPH_OSD_FLAG_ENFORCE_SNAPC), NULL /* no callback, we'll rely on the ordering w.r.t the next op */); } FlushOpRef fop(std::make_shared()); fop->obc = obc; fop->flushed_version = oi.user_version; fop->blocking = blocking; fop->on_flush = std::move(on_flush); fop->op = op; ObjectOperation o; if (oi.is_whiteout()) { fop->removal = true; o.remove(); } else { object_locator_t oloc(soid); o.copy_from(soid.oid.name, soid.snap, oloc, oi.user_version, CEPH_OSD_COPY_FROM_FLAG_FLUSH | CEPH_OSD_COPY_FROM_FLAG_IGNORE_OVERLAY | CEPH_OSD_COPY_FROM_FLAG_IGNORE_CACHE | CEPH_OSD_COPY_FROM_FLAG_MAP_SNAP_CLONE, LIBRADOS_OP_FLAG_FADVISE_SEQUENTIAL|LIBRADOS_OP_FLAG_FADVISE_NOCACHE); //mean the base tier don't cache data after this if (agent_state && agent_state->evict_mode != TierAgentState::EVICT_MODE_FULL) o.set_last_op_flags(LIBRADOS_OP_FLAG_FADVISE_DONTNEED); } C_Flush *fin = new C_Flush(this, soid, get_last_peering_reset()); ceph_tid_t tid = osd->objecter->mutate( soid.oid, base_oloc, o, snapc, ceph::real_clock::from_ceph_timespec(oi.mtime), CEPH_OSD_FLAG_IGNORE_OVERLAY | CEPH_OSD_FLAG_ENFORCE_SNAPC, new C_OnFinisher(fin, osd->get_objecter_finisher(get_pg_shard()))); /* we're under the pg lock and fin->finish() is grabbing that */ fin->tid = tid; fop->objecter_tid = tid; flush_ops[soid] = fop; recovery_state.update_stats( [&oi](auto &history, auto &stats) { stats.stats.sum.num_flush++; stats.stats.sum.num_flush_kb += shift_round_up(oi.size, 10); return false; }); return -EINPROGRESS; } void PrimaryLogPG::finish_flush(hobject_t oid, ceph_tid_t tid, int r) { dout(10) << __func__ << " " << oid << " tid " << tid << " " << cpp_strerror(r) << dendl; map::iterator p = flush_ops.find(oid); if (p == flush_ops.end()) { dout(10) << __func__ << " no flush_op found" << dendl; return; } FlushOpRef fop = p->second; if (tid != fop->objecter_tid && !fop->obc->obs.oi.has_manifest()) { dout(10) << __func__ << " tid " << tid << " != fop " << fop << " tid " << fop->objecter_tid << dendl; return; } ObjectContextRef obc = fop->obc; fop->objecter_tid = 0; if (r < 0 && !(r == -ENOENT && fop->removal)) { if (fop->op) osd->reply_op_error(fop->op, -EBUSY); if (fop->blocking) { obc->stop_block(); kick_object_context_blocked(obc); } if (!fop->dup_ops.empty()) { dout(20) << __func__ << " requeueing dups" << dendl; requeue_ops(fop->dup_ops); } if (fop->on_flush) { (*(fop->on_flush))(); fop->on_flush = std::nullopt; } flush_ops.erase(oid); return; } r = try_flush_mark_clean(fop); if (r == -EBUSY && fop->op) { osd->reply_op_error(fop->op, r); } } int PrimaryLogPG::try_flush_mark_clean(FlushOpRef fop) { ObjectContextRef obc = fop->obc; const hobject_t& oid = obc->obs.oi.soid; if (fop->blocking) { obc->stop_block(); kick_object_context_blocked(obc); } if (fop->flushed_version != obc->obs.oi.user_version || !obc->obs.exists) { if (obc->obs.exists) dout(10) << __func__ << " flushed_version " << fop->flushed_version << " != current " << obc->obs.oi.user_version << dendl; else dout(10) << __func__ << " object no longer exists" << dendl; if (!fop->dup_ops.empty()) { dout(20) << __func__ << " requeueing dups" << dendl; requeue_ops(fop->dup_ops); } if (fop->on_flush) { (*(fop->on_flush))(); fop->on_flush = std::nullopt; } flush_ops.erase(oid); if (fop->blocking) osd->logger->inc(l_osd_tier_flush_fail); else osd->logger->inc(l_osd_tier_try_flush_fail); return -EBUSY; } if (!fop->blocking && m_scrubber->write_blocked_by_scrub(oid)) { if (fop->op) { dout(10) << __func__ << " blocked by scrub" << dendl; requeue_op(fop->op); requeue_ops(fop->dup_ops); return -EAGAIN; // will retry } else { osd->logger->inc(l_osd_tier_try_flush_fail); vector tids; cancel_flush(fop, false, &tids); osd->objecter->op_cancel(tids, -ECANCELED); return -ECANCELED; } } // successfully flushed, can we evict this object? if (!obc->obs.oi.has_manifest() && !fop->op && agent_state && agent_state->evict_mode != TierAgentState::EVICT_MODE_IDLE && agent_maybe_evict(obc, true)) { osd->logger->inc(l_osd_tier_clean); if (fop->on_flush) { (*(fop->on_flush))(); fop->on_flush = std::nullopt; } flush_ops.erase(oid); return 0; } dout(10) << __func__ << " clearing DIRTY flag for " << oid << dendl; OpContextUPtr ctx = simple_opc_create(fop->obc); // successfully flushed; can we clear the dirty bit? // try to take the lock manually, since we don't // have a ctx yet. if (ctx->lock_manager.get_lock_type( RWState::RWWRITE, oid, obc, fop->op)) { dout(20) << __func__ << " took write lock" << dendl; } else if (fop->op) { dout(10) << __func__ << " waiting on write lock " << fop->op << " " << fop->dup_ops << dendl; // fop->op is now waiting on the lock; get fop->dup_ops to wait too. for (auto op : fop->dup_ops) { bool locked = ctx->lock_manager.get_lock_type( RWState::RWWRITE, oid, obc, op); ceph_assert(!locked); } close_op_ctx(ctx.release()); return -EAGAIN; // will retry } else { dout(10) << __func__ << " failed write lock, no op; failing" << dendl; close_op_ctx(ctx.release()); osd->logger->inc(l_osd_tier_try_flush_fail); vector tids; cancel_flush(fop, false, &tids); osd->objecter->op_cancel(tids, -ECANCELED); return -ECANCELED; } if (fop->on_flush) { ctx->register_on_finish(*(fop->on_flush)); fop->on_flush = std::nullopt; } ctx->at_version = get_next_version(); ctx->new_obs = obc->obs; ctx->new_obs.oi.clear_flag(object_info_t::FLAG_DIRTY); --ctx->delta_stats.num_objects_dirty; if (fop->obc->obs.oi.has_manifest()) { ceph_assert(obc->obs.oi.manifest.is_chunked()); PGTransaction* t = ctx->op_t.get(); uint64_t chunks_size = 0; for (auto &p : ctx->new_obs.oi.manifest.chunk_map) { chunks_size += p.second.length; } if (ctx->new_obs.oi.is_omap() && pool.info.supports_omap()) { t->omap_clear(oid); ctx->new_obs.oi.clear_omap_digest(); ctx->new_obs.oi.clear_flag(object_info_t::FLAG_OMAP); ctx->clean_regions.mark_omap_dirty(); } if (obc->obs.oi.size == chunks_size) { t->truncate(oid, 0); interval_set trim; trim.insert(0, ctx->new_obs.oi.size); ctx->modified_ranges.union_of(trim); truncate_update_size_and_usage(ctx->delta_stats, ctx->new_obs.oi, 0); ctx->clean_regions.mark_data_region_dirty(0, ctx->new_obs.oi.size); ctx->new_obs.oi.new_object(); for (auto &p : ctx->new_obs.oi.manifest.chunk_map) { p.second.set_flag(chunk_info_t::FLAG_MISSING); } } else { for (auto &p : ctx->new_obs.oi.manifest.chunk_map) { dout(20) << __func__ << " offset: " << p.second.offset << " length: " << p.second.length << dendl; p.second.clear_flag(chunk_info_t::FLAG_MISSING); // CLEAN } } } finish_ctx(ctx.get(), pg_log_entry_t::CLEAN); osd->logger->inc(l_osd_tier_clean); if (!fop->dup_ops.empty() || fop->op) { dout(20) << __func__ << " requeueing for " << ctx->at_version << dendl; list ls; if (fop->op) ls.push_back(fop->op); ls.splice(ls.end(), fop->dup_ops); requeue_ops(ls); } simple_opc_submit(std::move(ctx)); flush_ops.erase(oid); if (fop->blocking) osd->logger->inc(l_osd_tier_flush); else osd->logger->inc(l_osd_tier_try_flush); return -EINPROGRESS; } void PrimaryLogPG::cancel_flush(FlushOpRef fop, bool requeue, vector *tids) { dout(10) << __func__ << " " << fop->obc->obs.oi.soid << " tid " << fop->objecter_tid << dendl; if (fop->objecter_tid) { tids->push_back(fop->objecter_tid); fop->objecter_tid = 0; } if (fop->io_tids.size()) { for (auto &p : fop->io_tids) { tids->push_back(p.second); p.second = 0; } } if (fop->blocking && fop->obc->is_blocked()) { fop->obc->stop_block(); kick_object_context_blocked(fop->obc); } if (requeue) { if (fop->op) requeue_op(fop->op); requeue_ops(fop->dup_ops); } if (fop->on_flush) { (*(fop->on_flush))(); fop->on_flush = std::nullopt; } flush_ops.erase(fop->obc->obs.oi.soid); } void PrimaryLogPG::cancel_flush_ops(bool requeue, vector *tids) { dout(10) << __func__ << dendl; map::iterator p = flush_ops.begin(); while (p != flush_ops.end()) { cancel_flush((p++)->second, requeue, tids); } } bool PrimaryLogPG::is_present_clone(hobject_t coid) { if (!pool.info.allow_incomplete_clones()) return true; if (is_missing_object(coid)) return true; ObjectContextRef obc = get_object_context(coid, false); return obc && obc->obs.exists; } // ======================================================================== // rep op gather class C_OSD_RepopCommit : public Context { PrimaryLogPGRef pg; boost::intrusive_ptr repop; public: C_OSD_RepopCommit(PrimaryLogPG *pg, PrimaryLogPG::RepGather *repop) : pg(pg), repop(repop) {} void finish(int) override { pg->repop_all_committed(repop.get()); } }; void PrimaryLogPG::repop_all_committed(RepGather *repop) { dout(10) << __func__ << ": repop tid " << repop->rep_tid << " all committed " << dendl; repop->all_committed = true; if (!repop->rep_aborted) { if (repop->v != eversion_t()) { recovery_state.complete_write(repop->v, repop->pg_local_last_complete); } eval_repop(repop); } } void PrimaryLogPG::op_applied(const eversion_t &applied_version) { dout(10) << "op_applied version " << applied_version << dendl; ceph_assert(applied_version != eversion_t()); ceph_assert(applied_version <= info.last_update); recovery_state.local_write_applied(applied_version); if (is_primary() && m_scrubber) { // if there's a scrub operation waiting for the selected chunk to be fully updated - // allow it to continue m_scrubber->on_applied_when_primary(recovery_state.get_last_update_applied()); } } void PrimaryLogPG::eval_repop(RepGather *repop) { #ifdef HAVE_JAEGER if (repop->op->osd_parent_span) { auto eval_span = jaeger_tracing::child_span(__func__, repop->op->osd_parent_span); } #endif dout(10) << "eval_repop " << *repop << (repop->op && repop->op->get_req() ? "" : " (no op)") << dendl; // ondisk? if (repop->all_committed) { dout(10) << " commit: " << *repop << dendl; for (auto p = repop->on_committed.begin(); p != repop->on_committed.end(); repop->on_committed.erase(p++)) { (*p)(); } // send dup commits, in order auto it = waiting_for_ondisk.find(repop->v); if (it != waiting_for_ondisk.end()) { ceph_assert(waiting_for_ondisk.begin()->first == repop->v); for (auto& i : it->second) { int return_code = repop->r; if (return_code >= 0) { return_code = std::get<2>(i); } osd->reply_op_error(std::get<0>(i), return_code, repop->v, std::get<1>(i), std::get<3>(i)); } waiting_for_ondisk.erase(it); } publish_stats_to_osd(); dout(10) << " removing " << *repop << dendl; ceph_assert(!repop_queue.empty()); dout(20) << " q front is " << *repop_queue.front() << dendl; if (repop_queue.front() == repop) { RepGather *to_remove = nullptr; while (!repop_queue.empty() && (to_remove = repop_queue.front())->all_committed) { repop_queue.pop_front(); for (auto p = to_remove->on_success.begin(); p != to_remove->on_success.end(); to_remove->on_success.erase(p++)) { (*p)(); } remove_repop(to_remove); } } } } void PrimaryLogPG::issue_repop(RepGather *repop, OpContext *ctx) { FUNCTRACE(cct); const hobject_t& soid = ctx->obs->oi.soid; dout(7) << "issue_repop rep_tid " << repop->rep_tid << " o " << soid << dendl; #ifdef HAVE_JAEGER if (ctx->op->osd_parent_span) { auto issue_repop_span = jaeger_tracing::child_span(__func__, ctx->op->osd_parent_span); } #endif repop->v = ctx->at_version; ctx->op_t->add_obc(ctx->obc); if (ctx->clone_obc) { ctx->op_t->add_obc(ctx->clone_obc); } if (ctx->head_obc) { ctx->op_t->add_obc(ctx->head_obc); } Context *on_all_commit = new C_OSD_RepopCommit(this, repop); if (!(ctx->log.empty())) { ceph_assert(ctx->at_version >= projected_last_update); projected_last_update = ctx->at_version; } for (auto &&entry: ctx->log) { projected_log.add(entry); } recovery_state.pre_submit_op( soid, ctx->log, ctx->at_version); pgbackend->submit_transaction( soid, ctx->delta_stats, ctx->at_version, std::move(ctx->op_t), recovery_state.get_pg_trim_to(), recovery_state.get_min_last_complete_ondisk(), std::move(ctx->log), ctx->updated_hset_history, on_all_commit, repop->rep_tid, ctx->reqid, ctx->op); } PrimaryLogPG::RepGather *PrimaryLogPG::new_repop( OpContext *ctx, ObjectContextRef obc, ceph_tid_t rep_tid) { if (ctx->op) dout(10) << "new_repop rep_tid " << rep_tid << " on " << *ctx->op->get_req() << dendl; else dout(10) << "new_repop rep_tid " << rep_tid << " (no op)" << dendl; RepGather *repop = new RepGather( ctx, rep_tid, info.last_complete); repop->start = ceph_clock_now(); repop_queue.push_back(&repop->queue_item); repop->get(); osd->logger->inc(l_osd_op_wip); dout(10) << __func__ << ": " << *repop << dendl; return repop; } boost::intrusive_ptr PrimaryLogPG::new_repop( eversion_t version, int r, ObcLockManager &&manager, OpRequestRef &&op, std::optional > &&on_complete) { RepGather *repop = new RepGather( std::move(manager), std::move(op), std::move(on_complete), osd->get_tid(), info.last_complete, r); repop->v = version; repop->start = ceph_clock_now(); repop_queue.push_back(&repop->queue_item); osd->logger->inc(l_osd_op_wip); dout(10) << __func__ << ": " << *repop << dendl; return boost::intrusive_ptr(repop); } void PrimaryLogPG::remove_repop(RepGather *repop) { dout(20) << __func__ << " " << *repop << dendl; for (auto p = repop->on_finish.begin(); p != repop->on_finish.end(); repop->on_finish.erase(p++)) { (*p)(); } release_object_locks( repop->lock_manager); repop->put(); osd->logger->dec(l_osd_op_wip); } PrimaryLogPG::OpContextUPtr PrimaryLogPG::simple_opc_create(ObjectContextRef obc) { dout(20) << __func__ << " " << obc->obs.oi.soid << dendl; ceph_tid_t rep_tid = osd->get_tid(); osd_reqid_t reqid(osd->get_cluster_msgr_name(), 0, rep_tid); OpContextUPtr ctx(new OpContext(OpRequestRef(), reqid, nullptr, obc, this)); ctx->op_t.reset(new PGTransaction()); ctx->mtime = ceph_clock_now(); return ctx; } void PrimaryLogPG::simple_opc_submit(OpContextUPtr ctx) { RepGather *repop = new_repop(ctx.get(), ctx->obc, ctx->reqid.tid); dout(20) << __func__ << " " << repop << dendl; issue_repop(repop, ctx.get()); eval_repop(repop); recovery_state.update_trim_to(); repop->put(); } void PrimaryLogPG::submit_log_entries( const mempool::osd_pglog::list &entries, ObcLockManager &&manager, std::optional > &&_on_complete, OpRequestRef op, int r) { dout(10) << __func__ << " " << entries << dendl; ceph_assert(is_primary()); eversion_t version; if (!entries.empty()) { ceph_assert(entries.rbegin()->version >= projected_last_update); version = projected_last_update = entries.rbegin()->version; } boost::intrusive_ptr repop; std::optional > on_complete; if (get_osdmap()->require_osd_release >= ceph_release_t::jewel) { repop = new_repop( version, r, std::move(manager), std::move(op), std::move(_on_complete)); } else { on_complete = std::move(_on_complete); } pgbackend->call_write_ordered( [this, entries, repop, on_complete]() { ObjectStore::Transaction t; eversion_t old_last_update = info.last_update; recovery_state.merge_new_log_entries( entries, t, recovery_state.get_pg_trim_to(), recovery_state.get_min_last_complete_ondisk()); set waiting_on; for (set::const_iterator i = get_acting_recovery_backfill().begin(); i != get_acting_recovery_backfill().end(); ++i) { pg_shard_t peer(*i); if (peer == pg_whoami) continue; ceph_assert(recovery_state.get_peer_missing().count(peer)); ceph_assert(recovery_state.has_peer_info(peer)); if (get_osdmap()->require_osd_release >= ceph_release_t::jewel) { ceph_assert(repop); MOSDPGUpdateLogMissing *m = new MOSDPGUpdateLogMissing( entries, spg_t(info.pgid.pgid, i->shard), pg_whoami.shard, get_osdmap_epoch(), get_last_peering_reset(), repop->rep_tid, recovery_state.get_pg_trim_to(), recovery_state.get_min_last_complete_ondisk()); osd->send_message_osd_cluster( peer.osd, m, get_osdmap_epoch()); waiting_on.insert(peer); } else { MOSDPGLog *m = new MOSDPGLog( peer.shard, pg_whoami.shard, info.last_update.epoch, info, get_last_peering_reset()); m->log.log = entries; m->log.tail = old_last_update; m->log.head = info.last_update; osd->send_message_osd_cluster( peer.osd, m, get_osdmap_epoch()); } } ceph_tid_t rep_tid = repop->rep_tid; waiting_on.insert(pg_whoami); log_entry_update_waiting_on.insert( make_pair( rep_tid, LogUpdateCtx{std::move(repop), std::move(waiting_on)} )); struct OnComplete : public Context { PrimaryLogPGRef pg; ceph_tid_t rep_tid; epoch_t epoch; OnComplete( PrimaryLogPGRef pg, ceph_tid_t rep_tid, epoch_t epoch) : pg(pg), rep_tid(rep_tid), epoch(epoch) {} void finish(int) override { std::scoped_lock l{*pg}; if (!pg->pg_has_reset_since(epoch)) { auto it = pg->log_entry_update_waiting_on.find(rep_tid); ceph_assert(it != pg->log_entry_update_waiting_on.end()); auto it2 = it->second.waiting_on.find(pg->pg_whoami); ceph_assert(it2 != it->second.waiting_on.end()); it->second.waiting_on.erase(it2); if (it->second.waiting_on.empty()) { pg->repop_all_committed(it->second.repop.get()); pg->log_entry_update_waiting_on.erase(it); } } } }; t.register_on_commit( new OnComplete{this, rep_tid, get_osdmap_epoch()}); int r = osd->store->queue_transaction(ch, std::move(t), NULL); ceph_assert(r == 0); op_applied(info.last_update); }); recovery_state.update_trim_to(); } void PrimaryLogPG::cancel_log_updates() { // get rid of all the LogUpdateCtx so their references to repops are // dropped log_entry_update_waiting_on.clear(); } // ------------------------------------------------------- void PrimaryLogPG::get_watchers(list *ls) { std::scoped_lock l{*this}; pair i; while (object_contexts.get_next(i.first, &i)) { ObjectContextRef obc(i.second); get_obc_watchers(obc, *ls); } } void PrimaryLogPG::get_obc_watchers(ObjectContextRef obc, list &pg_watchers) { for (map, WatchRef>::iterator j = obc->watchers.begin(); j != obc->watchers.end(); ++j) { obj_watch_item_t owi; owi.obj = obc->obs.oi.soid; owi.wi.addr = j->second->get_peer_addr(); owi.wi.name = j->second->get_entity(); owi.wi.cookie = j->second->get_cookie(); owi.wi.timeout_seconds = j->second->get_timeout(); dout(30) << "watch: Found oid=" << owi.obj << " addr=" << owi.wi.addr << " name=" << owi.wi.name << " cookie=" << owi.wi.cookie << dendl; pg_watchers.push_back(owi); } } void PrimaryLogPG::check_blocklisted_watchers() { dout(20) << "PrimaryLogPG::check_blocklisted_watchers for pg " << get_pgid() << dendl; pair i; while (object_contexts.get_next(i.first, &i)) check_blocklisted_obc_watchers(i.second); } void PrimaryLogPG::check_blocklisted_obc_watchers(ObjectContextRef obc) { dout(20) << "PrimaryLogPG::check_blocklisted_obc_watchers for obc " << obc->obs.oi.soid << dendl; for (map, WatchRef>::iterator k = obc->watchers.begin(); k != obc->watchers.end(); ) { //Advance iterator now so handle_watch_timeout() can erase element map, WatchRef>::iterator j = k++; dout(30) << "watch: Found " << j->second->get_entity() << " cookie " << j->second->get_cookie() << dendl; entity_addr_t ea = j->second->get_peer_addr(); dout(30) << "watch: Check entity_addr_t " << ea << dendl; if (get_osdmap()->is_blocklisted(ea)) { dout(10) << "watch: Found blocklisted watcher for " << ea << dendl; ceph_assert(j->second->get_pg() == this); j->second->unregister_cb(); handle_watch_timeout(j->second); } } } void PrimaryLogPG::populate_obc_watchers(ObjectContextRef obc) { ceph_assert(is_primary() && is_active()); auto it_objects = recovery_state.get_pg_log().get_log().objects.find(obc->obs.oi.soid); ceph_assert((recovering.count(obc->obs.oi.soid) || !is_missing_object(obc->obs.oi.soid)) || (it_objects != recovery_state.get_pg_log().get_log().objects.end() && // or this is a revert... see recover_primary() it_objects->second->op == pg_log_entry_t::LOST_REVERT && it_objects->second->reverting_to == obc->obs.oi.version)); dout(10) << "populate_obc_watchers " << obc->obs.oi.soid << dendl; ceph_assert(obc->watchers.empty()); // populate unconnected_watchers for (map, watch_info_t>::iterator p = obc->obs.oi.watchers.begin(); p != obc->obs.oi.watchers.end(); ++p) { utime_t expire = info.stats.last_became_active; expire += p->second.timeout_seconds; dout(10) << " unconnected watcher " << p->first << " will expire " << expire << dendl; WatchRef watch( Watch::makeWatchRef( this, osd, obc, p->second.timeout_seconds, p->first.first, p->first.second, p->second.addr)); watch->disconnect(); obc->watchers.insert( make_pair( make_pair(p->first.first, p->first.second), watch)); } // Look for watchers from blocklisted clients and drop check_blocklisted_obc_watchers(obc); } void PrimaryLogPG::handle_watch_timeout(WatchRef watch) { ObjectContextRef obc = watch->get_obc(); // handle_watch_timeout owns this ref dout(10) << "handle_watch_timeout obc " << obc << dendl; if (!is_active()) { dout(10) << "handle_watch_timeout not active, no-op" << dendl; return; } if (!obc->obs.exists) { dout(10) << __func__ << " object " << obc->obs.oi.soid << " dne" << dendl; return; } if (is_degraded_or_backfilling_object(obc->obs.oi.soid)) { callbacks_for_degraded_object[obc->obs.oi.soid].push_back( watch->get_delayed_cb() ); dout(10) << "handle_watch_timeout waiting for degraded on obj " << obc->obs.oi.soid << dendl; return; } if (m_scrubber->write_blocked_by_scrub(obc->obs.oi.soid)) { dout(10) << "handle_watch_timeout waiting for scrub on obj " << obc->obs.oi.soid << dendl; m_scrubber->add_callback( watch->get_delayed_cb() // This callback! ); return; } OpContextUPtr ctx = simple_opc_create(obc); ctx->at_version = get_next_version(); object_info_t& oi = ctx->new_obs.oi; oi.watchers.erase(make_pair(watch->get_cookie(), watch->get_entity())); list watch_disconnects = { watch_disconnect_t(watch->get_cookie(), watch->get_entity(), true) }; ctx->register_on_success( [this, obc, watch_disconnects]() { complete_disconnect_watches(obc, watch_disconnects); }); PGTransaction *t = ctx->op_t.get(); ctx->log.push_back(pg_log_entry_t(pg_log_entry_t::MODIFY, obc->obs.oi.soid, ctx->at_version, oi.version, 0, osd_reqid_t(), ctx->mtime, 0)); oi.prior_version = obc->obs.oi.version; oi.version = ctx->at_version; bufferlist bl; encode(oi, bl, get_osdmap()->get_features(CEPH_ENTITY_TYPE_OSD, nullptr)); t->setattr(obc->obs.oi.soid, OI_ATTR, bl); // apply new object state. ctx->obc->obs = ctx->new_obs; // no ctx->delta_stats simple_opc_submit(std::move(ctx)); } ObjectContextRef PrimaryLogPG::create_object_context(const object_info_t& oi, SnapSetContext *ssc) { ObjectContextRef obc(object_contexts.lookup_or_create(oi.soid)); ceph_assert(obc->destructor_callback == NULL); obc->destructor_callback = new C_PG_ObjectContext(this, obc.get()); obc->obs.oi = oi; obc->obs.exists = false; obc->ssc = ssc; if (ssc) register_snapset_context(ssc); dout(10) << "create_object_context " << (void*)obc.get() << " " << oi.soid << " " << dendl; if (is_active()) populate_obc_watchers(obc); return obc; } ObjectContextRef PrimaryLogPG::get_object_context( const hobject_t& soid, bool can_create, const map *attrs) { auto it_objects = recovery_state.get_pg_log().get_log().objects.find(soid); ceph_assert( attrs || !recovery_state.get_pg_log().get_missing().is_missing(soid) || // or this is a revert... see recover_primary() (it_objects != recovery_state.get_pg_log().get_log().objects.end() && it_objects->second->op == pg_log_entry_t::LOST_REVERT)); ObjectContextRef obc = object_contexts.lookup(soid); osd->logger->inc(l_osd_object_ctx_cache_total); if (obc) { osd->logger->inc(l_osd_object_ctx_cache_hit); dout(10) << __func__ << ": found obc in cache: " << obc << dendl; } else { dout(10) << __func__ << ": obc NOT found in cache: " << soid << dendl; // check disk bufferlist bv; if (attrs) { auto it_oi = attrs->find(OI_ATTR); ceph_assert(it_oi != attrs->end()); bv = it_oi->second; } else { int r = pgbackend->objects_get_attr(soid, OI_ATTR, &bv); if (r < 0) { if (!can_create) { dout(10) << __func__ << ": no obc for soid " << soid << " and !can_create" << dendl; return ObjectContextRef(); // -ENOENT! } dout(10) << __func__ << ": no obc for soid " << soid << " but can_create" << dendl; // new object. object_info_t oi(soid); SnapSetContext *ssc = get_snapset_context( soid, true, 0, false); ceph_assert(ssc); obc = create_object_context(oi, ssc); dout(10) << __func__ << ": " << obc << " " << soid << " " << obc->rwstate << " oi: " << obc->obs.oi << " ssc: " << obc->ssc << " snapset: " << obc->ssc->snapset << dendl; return obc; } } object_info_t oi; try { bufferlist::const_iterator bliter = bv.begin(); decode(oi, bliter); } catch (...) { dout(0) << __func__ << ": obc corrupt: " << soid << dendl; return ObjectContextRef(); // -ENOENT! } ceph_assert(oi.soid.pool == (int64_t)info.pgid.pool()); obc = object_contexts.lookup_or_create(oi.soid); obc->destructor_callback = new C_PG_ObjectContext(this, obc.get()); obc->obs.oi = oi; obc->obs.exists = true; obc->ssc = get_snapset_context( soid, true, soid.has_snapset() ? attrs : 0); if (is_primary() && is_active()) populate_obc_watchers(obc); if (pool.info.is_erasure()) { if (attrs) { obc->attr_cache = *attrs; } else { int r = pgbackend->objects_get_attrs( soid, &obc->attr_cache); ceph_assert(r == 0); } } dout(10) << __func__ << ": creating obc from disk: " << obc << dendl; } // XXX: Caller doesn't expect this if (obc->ssc == NULL) { derr << __func__ << ": obc->ssc not available, not returning context" << dendl; return ObjectContextRef(); // -ENOENT! } dout(10) << __func__ << ": " << obc << " " << soid << " " << obc->rwstate << " oi: " << obc->obs.oi << " exists: " << (int)obc->obs.exists << " ssc: " << obc->ssc << " snapset: " << obc->ssc->snapset << dendl; return obc; } void PrimaryLogPG::context_registry_on_change() { pair i; while (object_contexts.get_next(i.first, &i)) { ObjectContextRef obc(i.second); if (obc) { for (map, WatchRef>::iterator j = obc->watchers.begin(); j != obc->watchers.end(); obc->watchers.erase(j++)) { j->second->discard(); } } } } /* * If we return an error, and set *pmissing, then promoting that * object may help. * * If we return -EAGAIN, we will always set *pmissing to the missing * object to wait for. * * If we return an error but do not set *pmissing, then we know the * object does not exist. */ int PrimaryLogPG::find_object_context(const hobject_t& oid, ObjectContextRef *pobc, bool can_create, bool map_snapid_to_clone, hobject_t *pmissing) { FUNCTRACE(cct); ceph_assert(oid.pool == static_cast(info.pgid.pool())); // want the head? if (oid.snap == CEPH_NOSNAP) { ObjectContextRef obc = get_object_context(oid, can_create); if (!obc) { if (pmissing) *pmissing = oid; return -ENOENT; } dout(10) << __func__ << " " << oid << " @" << oid.snap << " oi=" << obc->obs.oi << dendl; *pobc = obc; return 0; } // we want a snap hobject_t head = oid.get_head(); SnapSetContext *ssc = get_snapset_context(oid, can_create); if (!ssc || !(ssc->exists || can_create)) { dout(20) << __func__ << " " << oid << " no snapset" << dendl; if (pmissing) *pmissing = head; // start by getting the head if (ssc) put_snapset_context(ssc); return -ENOENT; } if (map_snapid_to_clone) { dout(10) << __func__ << " " << oid << " @" << oid.snap << " snapset " << ssc->snapset << " map_snapid_to_clone=true" << dendl; if (oid.snap > ssc->snapset.seq) { // already must be readable ObjectContextRef obc = get_object_context(head, false); dout(10) << __func__ << " " << oid << " @" << oid.snap << " snapset " << ssc->snapset << " maps to head" << dendl; *pobc = obc; put_snapset_context(ssc); return (obc && obc->obs.exists) ? 0 : -ENOENT; } else { vector::const_iterator citer = std::find( ssc->snapset.clones.begin(), ssc->snapset.clones.end(), oid.snap); if (citer == ssc->snapset.clones.end()) { dout(10) << __func__ << " " << oid << " @" << oid.snap << " snapset " << ssc->snapset << " maps to nothing" << dendl; put_snapset_context(ssc); return -ENOENT; } dout(10) << __func__ << " " << oid << " @" << oid.snap << " snapset " << ssc->snapset << " maps to " << oid << dendl; if (recovery_state.get_pg_log().get_missing().is_missing(oid)) { dout(10) << __func__ << " " << oid << " @" << oid.snap << " snapset " << ssc->snapset << " " << oid << " is missing" << dendl; if (pmissing) *pmissing = oid; put_snapset_context(ssc); return -EAGAIN; } ObjectContextRef obc = get_object_context(oid, false); if (!obc || !obc->obs.exists) { dout(10) << __func__ << " " << oid << " @" << oid.snap << " snapset " << ssc->snapset << " " << oid << " is not present" << dendl; if (pmissing) *pmissing = oid; put_snapset_context(ssc); return -ENOENT; } dout(10) << __func__ << " " << oid << " @" << oid.snap << " snapset " << ssc->snapset << " " << oid << " HIT" << dendl; *pobc = obc; put_snapset_context(ssc); return 0; } ceph_abort(); //unreachable } dout(10) << __func__ << " " << oid << " @" << oid.snap << " snapset " << ssc->snapset << dendl; // head? if (oid.snap > ssc->snapset.seq) { ObjectContextRef obc = get_object_context(head, false); dout(10) << __func__ << " " << head << " want " << oid.snap << " > snapset seq " << ssc->snapset.seq << " -- HIT " << obc->obs << dendl; if (!obc->ssc) obc->ssc = ssc; else { ceph_assert(ssc == obc->ssc); put_snapset_context(ssc); } *pobc = obc; return 0; } // which clone would it be? unsigned k = 0; while (k < ssc->snapset.clones.size() && ssc->snapset.clones[k] < oid.snap) k++; if (k == ssc->snapset.clones.size()) { dout(10) << __func__ << " no clones with last >= oid.snap " << oid.snap << " -- DNE" << dendl; put_snapset_context(ssc); return -ENOENT; } hobject_t soid(oid.oid, oid.get_key(), ssc->snapset.clones[k], oid.get_hash(), info.pgid.pool(), oid.get_namespace()); if (recovery_state.get_pg_log().get_missing().is_missing(soid)) { dout(20) << __func__ << " " << soid << " missing, try again later" << dendl; if (pmissing) *pmissing = soid; put_snapset_context(ssc); return -EAGAIN; } ObjectContextRef obc = get_object_context(soid, false); if (!obc || !obc->obs.exists) { if (pmissing) *pmissing = soid; put_snapset_context(ssc); if (is_primary()) { if (is_degraded_or_backfilling_object(soid)) { dout(20) << __func__ << " clone is degraded or backfilling " << soid << dendl; return -EAGAIN; } else if (is_degraded_on_async_recovery_target(soid)) { dout(20) << __func__ << " clone is recovering " << soid << dendl; return -EAGAIN; } else { dout(20) << __func__ << " missing clone " << soid << dendl; return -ENOENT; } } else { dout(20) << __func__ << " replica missing clone" << soid << dendl; return -ENOENT; } } if (!obc->ssc) { obc->ssc = ssc; } else { ceph_assert(obc->ssc == ssc); put_snapset_context(ssc); } ssc = 0; // clone dout(20) << __func__ << " " << soid << " snapset " << obc->ssc->snapset << dendl; snapid_t first, last; auto p = obc->ssc->snapset.clone_snaps.find(soid.snap); ceph_assert(p != obc->ssc->snapset.clone_snaps.end()); if (p->second.empty()) { dout(1) << __func__ << " " << soid << " empty snapset -- DNE" << dendl; ceph_assert(!cct->_conf->osd_debug_verify_snaps); return -ENOENT; } if (std::find(p->second.begin(), p->second.end(), oid.snap) == p->second.end()) { dout(20) << __func__ << " " << soid << " clone_snaps " << p->second << " does not contain " << oid.snap << " -- DNE" << dendl; return -ENOENT; } if (get_osdmap()->in_removed_snaps_queue(info.pgid.pgid.pool(), oid.snap)) { dout(20) << __func__ << " " << soid << " snap " << oid.snap << " in removed_snaps_queue" << " -- DNE" << dendl; return -ENOENT; } dout(20) << __func__ << " " << soid << " clone_snaps " << p->second << " contains " << oid.snap << " -- HIT " << obc->obs << dendl; *pobc = obc; return 0; } void PrimaryLogPG::object_context_destructor_callback(ObjectContext *obc) { if (obc->ssc) put_snapset_context(obc->ssc); } void PrimaryLogPG::add_object_context_to_pg_stat(ObjectContextRef obc, pg_stat_t *pgstat) { object_info_t& oi = obc->obs.oi; dout(10) << __func__ << " " << oi.soid << dendl; ceph_assert(!oi.soid.is_snapdir()); object_stat_sum_t stat; stat.num_objects++; if (oi.is_dirty()) stat.num_objects_dirty++; if (oi.is_whiteout()) stat.num_whiteouts++; if (oi.is_omap()) stat.num_objects_omap++; if (oi.is_cache_pinned()) stat.num_objects_pinned++; if (oi.has_manifest()) stat.num_objects_manifest++; if (oi.soid.is_snap()) { stat.num_object_clones++; if (!obc->ssc) obc->ssc = get_snapset_context(oi.soid, false); ceph_assert(obc->ssc); stat.num_bytes += obc->ssc->snapset.get_clone_bytes(oi.soid.snap); } else { stat.num_bytes += oi.size; } // add it in pgstat->stats.sum.add(stat); } void PrimaryLogPG::kick_object_context_blocked(ObjectContextRef obc) { const hobject_t& soid = obc->obs.oi.soid; if (obc->is_blocked()) { dout(10) << __func__ << " " << soid << " still blocked" << dendl; return; } map>::iterator p = waiting_for_blocked_object.find(soid); if (p != waiting_for_blocked_object.end()) { list& ls = p->second; dout(10) << __func__ << " " << soid << " requeuing " << ls.size() << " requests" << dendl; requeue_ops(ls); waiting_for_blocked_object.erase(p); } map::iterator i = objects_blocked_on_snap_promotion.find(obc->obs.oi.soid.get_head()); if (i != objects_blocked_on_snap_promotion.end()) { ceph_assert(i->second == obc); objects_blocked_on_snap_promotion.erase(i); } if (obc->requeue_scrub_on_unblock) { obc->requeue_scrub_on_unblock = false; dout(20) << __func__ << " requeuing if still active: " << (is_active() ? "yes" : "no") << dendl; // only requeue if we are still active: we may be unblocking // because we are resetting for a new peering interval if (is_active()) { osd->queue_scrub_unblocking(this, is_scrub_blocking_ops()); } } } SnapSetContext *PrimaryLogPG::get_snapset_context( const hobject_t& oid, bool can_create, const map *attrs, bool oid_existed) { std::lock_guard l(snapset_contexts_lock); SnapSetContext *ssc; map::iterator p = snapset_contexts.find( oid.get_snapdir()); if (p != snapset_contexts.end()) { if (can_create || p->second->exists) { ssc = p->second; } else { return NULL; } } else { bufferlist bv; if (!attrs) { int r = -ENOENT; if (!(oid.is_head() && !oid_existed)) { r = pgbackend->objects_get_attr(oid.get_head(), SS_ATTR, &bv); } if (r < 0 && !can_create) return NULL; } else { auto it_ss = attrs->find(SS_ATTR); ceph_assert(it_ss != attrs->end()); bv = it_ss->second; } ssc = new SnapSetContext(oid.get_snapdir()); _register_snapset_context(ssc); if (bv.length()) { bufferlist::const_iterator bvp = bv.begin(); try { ssc->snapset.decode(bvp); } catch (const ceph::buffer::error& e) { dout(0) << __func__ << " Can't decode snapset: " << e.what() << dendl; return NULL; } ssc->exists = true; } else { ssc->exists = false; } } ceph_assert(ssc); ssc->ref++; return ssc; } void PrimaryLogPG::put_snapset_context(SnapSetContext *ssc) { std::lock_guard l(snapset_contexts_lock); --ssc->ref; if (ssc->ref == 0) { if (ssc->registered) snapset_contexts.erase(ssc->oid); delete ssc; } } /* * Return values: * NONE - didn't pull anything * YES - pulled what the caller wanted * HEAD - needed to pull head first */ enum { PULL_NONE, PULL_HEAD, PULL_YES }; int PrimaryLogPG::recover_missing( const hobject_t &soid, eversion_t v, int priority, PGBackend::RecoveryHandle *h) { if (recovery_state.get_missing_loc().is_unfound(soid)) { dout(7) << __func__ << " " << soid << " v " << v << " but it is unfound" << dendl; return PULL_NONE; } if (recovery_state.get_missing_loc().is_deleted(soid)) { start_recovery_op(soid); ceph_assert(!recovering.count(soid)); recovering.insert(make_pair(soid, ObjectContextRef())); epoch_t cur_epoch = get_osdmap_epoch(); remove_missing_object(soid, v, new LambdaContext( [=](int) { std::scoped_lock locker{*this}; if (!pg_has_reset_since(cur_epoch)) { bool object_missing = false; for (const auto& shard : get_acting_recovery_backfill()) { if (shard == pg_whoami) continue; if (recovery_state.get_peer_missing(shard).is_missing(soid)) { dout(20) << __func__ << ": soid " << soid << " needs to be deleted from replica " << shard << dendl; object_missing = true; break; } } if (!object_missing) { object_stat_sum_t stat_diff; stat_diff.num_objects_recovered = 1; if (scrub_after_recovery) stat_diff.num_objects_repaired = 1; on_global_recover(soid, stat_diff, true); } else { auto recovery_handle = pgbackend->open_recovery_op(); pgbackend->recover_delete_object(soid, v, recovery_handle); pgbackend->run_recovery_op(recovery_handle, priority); } } })); return PULL_YES; } // is this a snapped object? if so, consult the snapset.. we may not need the entire object! ObjectContextRef obc; ObjectContextRef head_obc; if (soid.snap && soid.snap < CEPH_NOSNAP) { // do we have the head? hobject_t head = soid.get_head(); if (recovery_state.get_pg_log().get_missing().is_missing(head)) { if (recovering.count(head)) { dout(10) << " missing but already recovering head " << head << dendl; return PULL_NONE; } else { int r = recover_missing( head, recovery_state.get_pg_log().get_missing().get_items().find(head)->second.need, priority, h); if (r != PULL_NONE) return PULL_HEAD; return PULL_NONE; } } head_obc = get_object_context( head, false, 0); ceph_assert(head_obc); } start_recovery_op(soid); ceph_assert(!recovering.count(soid)); recovering.insert(make_pair(soid, obc)); int r = pgbackend->recover_object( soid, v, head_obc, obc, h); // This is only a pull which shouldn't return an error ceph_assert(r >= 0); return PULL_YES; } void PrimaryLogPG::remove_missing_object(const hobject_t &soid, eversion_t v, Context *on_complete) { dout(20) << __func__ << " " << soid << " " << v << dendl; ceph_assert(on_complete != nullptr); // delete locally ObjectStore::Transaction t; remove_snap_mapped_object(t, soid); ObjectRecoveryInfo recovery_info; recovery_info.soid = soid; recovery_info.version = v; epoch_t cur_epoch = get_osdmap_epoch(); t.register_on_complete(new LambdaContext( [=](int) { std::unique_lock locker{*this}; if (!pg_has_reset_since(cur_epoch)) { ObjectStore::Transaction t2; on_local_recover(soid, recovery_info, ObjectContextRef(), true, &t2); t2.register_on_complete(on_complete); int r = osd->store->queue_transaction(ch, std::move(t2), nullptr); ceph_assert(r == 0); locker.unlock(); } else { locker.unlock(); on_complete->complete(-EAGAIN); } })); int r = osd->store->queue_transaction(ch, std::move(t), nullptr); ceph_assert(r == 0); } void PrimaryLogPG::finish_degraded_object(const hobject_t oid) { dout(10) << __func__ << " " << oid << dendl; if (callbacks_for_degraded_object.count(oid)) { list contexts; contexts.swap(callbacks_for_degraded_object[oid]); callbacks_for_degraded_object.erase(oid); for (list::iterator i = contexts.begin(); i != contexts.end(); ++i) { (*i)->complete(0); } } map::iterator i = objects_blocked_on_degraded_snap.find( oid.get_head()); if (i != objects_blocked_on_degraded_snap.end() && i->second == oid.snap) objects_blocked_on_degraded_snap.erase(i); } void PrimaryLogPG::_committed_pushed_object( epoch_t epoch, eversion_t last_complete) { std::scoped_lock locker{*this}; if (!pg_has_reset_since(epoch)) { recovery_state.recovery_committed_to(last_complete); } else { dout(10) << __func__ << " pg has changed, not touching last_complete_ondisk" << dendl; } } void PrimaryLogPG::_applied_recovered_object(ObjectContextRef obc) { dout(20) << __func__ << dendl; if (obc) { dout(20) << "obc = " << *obc << dendl; } ceph_assert(active_pushes >= 1); --active_pushes; // requeue an active chunky scrub waiting on recovery ops if (!recovery_state.is_deleting() && active_pushes == 0 && m_scrubber->is_scrub_active()) { osd->queue_scrub_pushes_update(this, is_scrub_blocking_ops()); } } void PrimaryLogPG::_applied_recovered_object_replica() { dout(20) << __func__ << dendl; ceph_assert(active_pushes >= 1); --active_pushes; // requeue an active scrub waiting on recovery ops if (!recovery_state.is_deleting() && active_pushes == 0 && m_scrubber->is_scrub_active()) { osd->queue_scrub_replica_pushes(this, m_scrubber->replica_op_priority()); } } void PrimaryLogPG::on_failed_pull( const set &from, const hobject_t &soid, const eversion_t &v) { dout(20) << __func__ << ": " << soid << dendl; ceph_assert(recovering.count(soid)); auto obc = recovering[soid]; if (obc) { list blocked_ops; obc->drop_recovery_read(&blocked_ops); requeue_ops(blocked_ops); } recovering.erase(soid); for (auto&& i : from) { if (i != pg_whoami) { // we'll get it below in primary_error recovery_state.force_object_missing(i, soid, v); } } dout(0) << __func__ << " " << soid << " from shard " << from << ", reps on " << recovery_state.get_missing_loc().get_locations(soid) << " unfound? " << recovery_state.get_missing_loc().is_unfound(soid) << dendl; finish_recovery_op(soid); // close out this attempt, finish_degraded_object(soid); if (from.count(pg_whoami)) { dout(0) << " primary missing oid " << soid << " version " << v << dendl; primary_error(soid, v); backfills_in_flight.erase(soid); } } eversion_t PrimaryLogPG::pick_newest_available(const hobject_t& oid) { eversion_t v; pg_missing_item pmi; bool is_missing = recovery_state.get_pg_log().get_missing().is_missing(oid, &pmi); ceph_assert(is_missing); v = pmi.have; dout(10) << "pick_newest_available " << oid << " " << v << " on osd." << osd->whoami << " (local)" << dendl; ceph_assert(!get_acting_recovery_backfill().empty()); for (set::iterator i = get_acting_recovery_backfill().begin(); i != get_acting_recovery_backfill().end(); ++i) { if (*i == get_primary()) continue; pg_shard_t peer = *i; if (!recovery_state.get_peer_missing(peer).is_missing(oid)) { continue; } eversion_t h = recovery_state.get_peer_missing(peer).get_items().at(oid).have; dout(10) << "pick_newest_available " << oid << " " << h << " on osd." << peer << dendl; if (h > v) v = h; } dout(10) << "pick_newest_available " << oid << " " << v << " (newest)" << dendl; return v; } void PrimaryLogPG::do_update_log_missing(OpRequestRef &op) { const MOSDPGUpdateLogMissing *m = static_cast( op->get_req()); ceph_assert(m->get_type() == MSG_OSD_PG_UPDATE_LOG_MISSING); ObjectStore::Transaction t; std::optional op_trim_to, op_roll_forward_to; if (m->pg_trim_to != eversion_t()) op_trim_to = m->pg_trim_to; if (m->pg_roll_forward_to != eversion_t()) op_roll_forward_to = m->pg_roll_forward_to; dout(20) << __func__ << " op_trim_to = " << op_trim_to << " op_roll_forward_to = " << op_roll_forward_to << dendl; recovery_state.append_log_entries_update_missing( m->entries, t, op_trim_to, op_roll_forward_to); eversion_t new_lcod = info.last_complete; Context *complete = new LambdaContext( [=](int) { const MOSDPGUpdateLogMissing *msg = static_cast( op->get_req()); std::scoped_lock locker{*this}; if (!pg_has_reset_since(msg->get_epoch())) { update_last_complete_ondisk(new_lcod); MOSDPGUpdateLogMissingReply *reply = new MOSDPGUpdateLogMissingReply( spg_t(info.pgid.pgid, primary_shard().shard), pg_whoami.shard, msg->get_epoch(), msg->min_epoch, msg->get_tid(), new_lcod); reply->set_priority(CEPH_MSG_PRIO_HIGH); msg->get_connection()->send_message(reply); } }); if (get_osdmap()->require_osd_release >= ceph_release_t::kraken) { t.register_on_commit(complete); } else { /* Hack to work around the fact that ReplicatedBackend sends * ack+commit if commit happens first * * This behavior is no longer necessary, but we preserve it so old * primaries can keep their repops in order */ if (pool.info.is_erasure()) { t.register_on_complete(complete); } else { t.register_on_commit(complete); } } int tr = osd->store->queue_transaction( ch, std::move(t), nullptr); ceph_assert(tr == 0); op_applied(info.last_update); } void PrimaryLogPG::do_update_log_missing_reply(OpRequestRef &op) { const MOSDPGUpdateLogMissingReply *m = static_cast( op->get_req()); dout(20) << __func__ << " got reply from " << m->get_from() << dendl; auto it = log_entry_update_waiting_on.find(m->get_tid()); if (it != log_entry_update_waiting_on.end()) { if (it->second.waiting_on.count(m->get_from())) { it->second.waiting_on.erase(m->get_from()); if (m->last_complete_ondisk != eversion_t()) { update_peer_last_complete_ondisk(m->get_from(), m->last_complete_ondisk); } } else { osd->clog->error() << info.pgid << " got reply " << *m << " from shard we are not waiting for " << m->get_from(); } if (it->second.waiting_on.empty()) { repop_all_committed(it->second.repop.get()); log_entry_update_waiting_on.erase(it); } } else { osd->clog->error() << info.pgid << " got reply " << *m << " on unknown tid " << m->get_tid(); } } /* Mark all unfound objects as lost. */ void PrimaryLogPG::mark_all_unfound_lost( int what, std::function on_finish) { dout(3) << __func__ << " " << pg_log_entry_t::get_op_name(what) << dendl; list oids; dout(30) << __func__ << ": log before:\n"; recovery_state.get_pg_log().get_log().print(*_dout); *_dout << dendl; mempool::osd_pglog::list log_entries; utime_t mtime = ceph_clock_now(); map::const_iterator m = recovery_state.get_missing_loc().get_needs_recovery().begin(); map::const_iterator mend = recovery_state.get_missing_loc().get_needs_recovery().end(); ObcLockManager manager; eversion_t v = get_next_version(); v.epoch = get_osdmap_epoch(); uint64_t num_unfound = recovery_state.get_missing_loc().num_unfound(); while (m != mend) { const hobject_t &oid(m->first); if (!recovery_state.get_missing_loc().is_unfound(oid)) { // We only care about unfound objects ++m; continue; } ObjectContextRef obc; eversion_t prev; switch (what) { case pg_log_entry_t::LOST_MARK: ceph_abort_msg("actually, not implemented yet!"); break; case pg_log_entry_t::LOST_REVERT: prev = pick_newest_available(oid); if (prev > eversion_t()) { // log it pg_log_entry_t e( pg_log_entry_t::LOST_REVERT, oid, v, m->second.need, 0, osd_reqid_t(), mtime, 0); e.reverting_to = prev; e.mark_unrollbackable(); log_entries.push_back(e); dout(10) << e << dendl; // we are now missing the new version; recovery code will sort it out. ++v.version; ++m; break; } case pg_log_entry_t::LOST_DELETE: { pg_log_entry_t e(pg_log_entry_t::LOST_DELETE, oid, v, m->second.need, 0, osd_reqid_t(), mtime, 0); if (get_osdmap()->require_osd_release >= ceph_release_t::jewel) { if (pool.info.require_rollback()) { e.mod_desc.try_rmobject(v.version); } else { e.mark_unrollbackable(); } } // otherwise, just do what we used to do dout(10) << e << dendl; log_entries.push_back(e); oids.push_back(oid); // If context found mark object as deleted in case // of racing with new creation. This can happen if // object lost and EIO at primary. obc = object_contexts.lookup(oid); if (obc) obc->obs.exists = false; ++v.version; ++m; } break; default: ceph_abort(); } } recovery_state.update_stats( [](auto &history, auto &stats) { stats.stats_invalid = true; return false; }); submit_log_entries( log_entries, std::move(manager), std::optional >( [this, oids, num_unfound, on_finish]() { if (recovery_state.perform_deletes_during_peering()) { for (auto oid : oids) { // clear old locations - merge_new_log_entries will have // handled rebuilding missing_loc for each of these // objects if we have the RECOVERY_DELETES flag recovery_state.object_recovered(oid, object_stat_sum_t()); } } if (is_recovery_unfound()) { queue_peering_event( PGPeeringEventRef( std::make_shared( get_osdmap_epoch(), get_osdmap_epoch(), PeeringState::DoRecovery()))); } else if (is_backfill_unfound()) { queue_peering_event( PGPeeringEventRef( std::make_shared( get_osdmap_epoch(), get_osdmap_epoch(), PeeringState::RequestBackfill()))); } else { queue_recovery(); } stringstream ss; ss << "pg has " << num_unfound << " objects unfound and apparently lost marking"; string rs = ss.str(); dout(0) << "do_command r=" << 0 << " " << rs << dendl; osd->clog->info() << rs; bufferlist empty; on_finish(0, rs, empty); }), OpRequestRef()); } void PrimaryLogPG::_split_into(pg_t child_pgid, PG *child, unsigned split_bits) { ceph_assert(repop_queue.empty()); } /* * pg status change notification */ void PrimaryLogPG::apply_and_flush_repops(bool requeue) { list rq; // apply all repops while (!repop_queue.empty()) { RepGather *repop = repop_queue.front(); repop_queue.pop_front(); dout(10) << " canceling repop tid " << repop->rep_tid << dendl; repop->rep_aborted = true; repop->on_committed.clear(); repop->on_success.clear(); if (requeue) { if (repop->op) { dout(10) << " requeuing " << *repop->op->get_req() << dendl; rq.push_back(repop->op); repop->op = OpRequestRef(); } // also requeue any dups, interleaved into position auto p = waiting_for_ondisk.find(repop->v); if (p != waiting_for_ondisk.end()) { dout(10) << " also requeuing ondisk waiters " << p->second << dendl; for (auto& i : p->second) { rq.push_back(std::get<0>(i)); } waiting_for_ondisk.erase(p); } } remove_repop(repop); } ceph_assert(repop_queue.empty()); if (requeue) { requeue_ops(rq); if (!waiting_for_ondisk.empty()) { for (auto& i : waiting_for_ondisk) { for (auto& j : i.second) { derr << __func__ << ": op " << *(std::get<0>(j)->get_req()) << " waiting on " << i.first << dendl; } } ceph_assert(waiting_for_ondisk.empty()); } } waiting_for_ondisk.clear(); } void PrimaryLogPG::on_flushed() { requeue_ops(waiting_for_flush); if (!is_peered() || !is_primary()) { pair i; while (object_contexts.get_next(i.first, &i)) { derr << __func__ << ": object " << i.first << " obc still alive" << dendl; } ceph_assert(object_contexts.empty()); } } void PrimaryLogPG::on_removal(ObjectStore::Transaction &t) { dout(10) << __func__ << dendl; on_shutdown(); t.register_on_commit(new C_DeleteMore(this, get_osdmap_epoch())); } void PrimaryLogPG::clear_async_reads() { dout(10) << __func__ << dendl; for(auto& i : in_progress_async_reads) { dout(10) << "clear ctx: " << "OpRequestRef " << i.first << " OpContext " << i.second << dendl; close_op_ctx(i.second); } } void PrimaryLogPG::clear_cache() { object_contexts.clear(); } void PrimaryLogPG::on_shutdown() { dout(10) << __func__ << dendl; if (recovery_queued) { recovery_queued = false; osd->clear_queued_recovery(this); } m_scrubber->scrub_clear_state(); m_scrubber->unreg_next_scrub(); vector tids; cancel_copy_ops(false, &tids); cancel_flush_ops(false, &tids); cancel_proxy_ops(false, &tids); cancel_manifest_ops(false, &tids); osd->objecter->op_cancel(tids, -ECANCELED); apply_and_flush_repops(false); cancel_log_updates(); // we must remove PGRefs, so do this this prior to release_backoffs() callers clear_backoffs(); // clean up snap trim references snap_trimmer_machine.process_event(Reset()); pgbackend->on_change(); context_registry_on_change(); object_contexts.clear(); clear_async_reads(); osd->remote_reserver.cancel_reservation(info.pgid); osd->local_reserver.cancel_reservation(info.pgid); clear_primary_state(); cancel_recovery(); if (is_primary()) { osd->clear_ready_to_merge(this); } } void PrimaryLogPG::on_activate_complete() { check_local(); // waiters if (!recovery_state.needs_flush()) { requeue_ops(waiting_for_peered); } else if (!waiting_for_peered.empty()) { dout(10) << __func__ << " flushes in progress, moving " << waiting_for_peered.size() << " items to waiting_for_flush" << dendl; ceph_assert(waiting_for_flush.empty()); waiting_for_flush.swap(waiting_for_peered); } // all clean? if (needs_recovery()) { dout(10) << "activate not all replicas are up-to-date, queueing recovery" << dendl; queue_peering_event( PGPeeringEventRef( std::make_shared( get_osdmap_epoch(), get_osdmap_epoch(), PeeringState::DoRecovery()))); } else if (needs_backfill()) { dout(10) << "activate queueing backfill" << dendl; queue_peering_event( PGPeeringEventRef( std::make_shared( get_osdmap_epoch(), get_osdmap_epoch(), PeeringState::RequestBackfill()))); } else { dout(10) << "activate all replicas clean, no recovery" << dendl; queue_peering_event( PGPeeringEventRef( std::make_shared( get_osdmap_epoch(), get_osdmap_epoch(), PeeringState::AllReplicasRecovered()))); } publish_stats_to_osd(); if (get_backfill_targets().size()) { last_backfill_started = recovery_state.earliest_backfill(); new_backfill = true; ceph_assert(!last_backfill_started.is_max()); dout(5) << __func__ << ": bft=" << get_backfill_targets() << " from " << last_backfill_started << dendl; for (set::const_iterator i = get_backfill_targets().begin(); i != get_backfill_targets().end(); ++i) { dout(5) << "target shard " << *i << " from " << recovery_state.get_peer_info(*i).last_backfill << dendl; } } hit_set_setup(); agent_setup(); } void PrimaryLogPG::on_change(ObjectStore::Transaction &t) { dout(10) << __func__ << dendl; if (hit_set && hit_set->insert_count() == 0) { dout(20) << " discarding empty hit_set" << dendl; hit_set_clear(); } if (recovery_queued) { recovery_queued = false; osd->clear_queued_recovery(this); } // requeue everything in the reverse order they should be // reexamined. requeue_ops(waiting_for_peered); requeue_ops(waiting_for_flush); requeue_ops(waiting_for_active); requeue_ops(waiting_for_readable); vector tids; cancel_copy_ops(is_primary(), &tids); cancel_flush_ops(is_primary(), &tids); cancel_proxy_ops(is_primary(), &tids); cancel_manifest_ops(is_primary(), &tids); osd->objecter->op_cancel(tids, -ECANCELED); // requeue object waiters for (auto& p : waiting_for_unreadable_object) { release_backoffs(p.first); } if (is_primary()) { requeue_object_waiters(waiting_for_unreadable_object); } else { waiting_for_unreadable_object.clear(); } for (map>::iterator p = waiting_for_degraded_object.begin(); p != waiting_for_degraded_object.end(); waiting_for_degraded_object.erase(p++)) { release_backoffs(p->first); if (is_primary()) requeue_ops(p->second); else p->second.clear(); finish_degraded_object(p->first); } // requeues waiting_for_scrub m_scrubber->scrub_clear_state(); for (auto p = waiting_for_blocked_object.begin(); p != waiting_for_blocked_object.end(); waiting_for_blocked_object.erase(p++)) { if (is_primary()) requeue_ops(p->second); else p->second.clear(); } for (auto i = callbacks_for_degraded_object.begin(); i != callbacks_for_degraded_object.end(); ) { finish_degraded_object((i++)->first); } ceph_assert(callbacks_for_degraded_object.empty()); if (is_primary()) { requeue_ops(waiting_for_cache_not_full); } else { waiting_for_cache_not_full.clear(); } objects_blocked_on_cache_full.clear(); for (list >::iterator i = in_progress_async_reads.begin(); i != in_progress_async_reads.end(); in_progress_async_reads.erase(i++)) { close_op_ctx(i->second); if (is_primary()) requeue_op(i->first); } // this will requeue ops we were working on but didn't finish, and // any dups apply_and_flush_repops(is_primary()); cancel_log_updates(); // do this *after* apply_and_flush_repops so that we catch any newly // registered watches. context_registry_on_change(); pgbackend->on_change_cleanup(&t); m_scrubber->cleanup_store(&t); pgbackend->on_change(); // clear snap_trimmer state snap_trimmer_machine.process_event(Reset()); debug_op_order.clear(); unstable_stats.clear(); // we don't want to cache object_contexts through the interval change // NOTE: we actually assert that all currently live references are dead // by the time the flush for the next interval completes. object_contexts.clear(); // should have been cleared above by finishing all of the degraded objects ceph_assert(objects_blocked_on_degraded_snap.empty()); } void PrimaryLogPG::plpg_on_role_change() { dout(10) << __func__ << dendl; if (get_role() != 0 && hit_set) { dout(10) << " clearing hit set" << dendl; hit_set_clear(); } } void PrimaryLogPG::plpg_on_pool_change() { dout(10) << __func__ << dendl; // requeue cache full waiters just in case the cache_mode is // changing away from writeback mode. note that if we are not // active the normal requeuing machinery is sufficient (and properly // ordered). if (is_active() && pool.info.cache_mode != pg_pool_t::CACHEMODE_WRITEBACK && !waiting_for_cache_not_full.empty()) { dout(10) << __func__ << " requeuing full waiters (not in writeback) " << dendl; requeue_ops(waiting_for_cache_not_full); objects_blocked_on_cache_full.clear(); } hit_set_setup(); agent_setup(); } // clear state. called on recovery completion AND cancellation. void PrimaryLogPG::_clear_recovery_state() { #ifdef DEBUG_RECOVERY_OIDS recovering_oids.clear(); #endif dout(15) << __func__ << " flags: " << m_planned_scrub << dendl; last_backfill_started = hobject_t(); set::iterator i = backfills_in_flight.begin(); while (i != backfills_in_flight.end()) { backfills_in_flight.erase(i++); } list blocked_ops; for (map::iterator i = recovering.begin(); i != recovering.end(); recovering.erase(i++)) { if (i->second) { i->second->drop_recovery_read(&blocked_ops); requeue_ops(blocked_ops); } } ceph_assert(backfills_in_flight.empty()); pending_backfill_updates.clear(); ceph_assert(recovering.empty()); pgbackend->clear_recovery_state(); } void PrimaryLogPG::cancel_pull(const hobject_t &soid) { dout(20) << __func__ << ": " << soid << dendl; ceph_assert(recovering.count(soid)); ObjectContextRef obc = recovering[soid]; if (obc) { list blocked_ops; obc->drop_recovery_read(&blocked_ops); requeue_ops(blocked_ops); } recovering.erase(soid); finish_recovery_op(soid); release_backoffs(soid); if (waiting_for_degraded_object.count(soid)) { dout(20) << " kicking degraded waiters on " << soid << dendl; requeue_ops(waiting_for_degraded_object[soid]); waiting_for_degraded_object.erase(soid); } if (waiting_for_unreadable_object.count(soid)) { dout(20) << " kicking unreadable waiters on " << soid << dendl; requeue_ops(waiting_for_unreadable_object[soid]); waiting_for_unreadable_object.erase(soid); } if (is_missing_object(soid)) recovery_state.set_last_requested(0); finish_degraded_object(soid); } void PrimaryLogPG::check_recovery_sources(const OSDMapRef& osdmap) { pgbackend->check_recovery_sources(osdmap); } bool PrimaryLogPG::start_recovery_ops( uint64_t max, ThreadPool::TPHandle &handle, uint64_t *ops_started) { uint64_t& started = *ops_started; started = 0; bool work_in_progress = false; bool recovery_started = false; ceph_assert(is_primary()); ceph_assert(is_peered()); ceph_assert(!recovery_state.is_deleting()); ceph_assert(recovery_queued); recovery_queued = false; if (!state_test(PG_STATE_RECOVERING) && !state_test(PG_STATE_BACKFILLING)) { /* TODO: I think this case is broken and will make do_recovery() * unhappy since we're returning false */ dout(10) << "recovery raced and were queued twice, ignoring!" << dendl; return have_unfound(); } const auto &missing = recovery_state.get_pg_log().get_missing(); uint64_t num_unfound = get_num_unfound(); if (!recovery_state.have_missing()) { recovery_state.local_recovery_complete(); } if (!missing.have_missing() || // Primary does not have missing // or all of the missing objects are unfound. recovery_state.all_missing_unfound()) { // Recover the replicas. started = recover_replicas(max, handle, &recovery_started); } if (!started) { // We still have missing objects that we should grab from replicas. started += recover_primary(max, handle); } if (!started && num_unfound != get_num_unfound()) { // second chance to recovery replicas started = recover_replicas(max, handle, &recovery_started); } if (started || recovery_started) work_in_progress = true; bool deferred_backfill = false; if (recovering.empty() && state_test(PG_STATE_BACKFILLING) && !get_backfill_targets().empty() && started < max && missing.num_missing() == 0 && waiting_on_backfill.empty()) { if (get_osdmap()->test_flag(CEPH_OSDMAP_NOBACKFILL)) { dout(10) << "deferring backfill due to NOBACKFILL" << dendl; deferred_backfill = true; } else if (get_osdmap()->test_flag(CEPH_OSDMAP_NOREBALANCE) && !is_degraded()) { dout(10) << "deferring backfill due to NOREBALANCE" << dendl; deferred_backfill = true; } else if (!recovery_state.is_backfill_reserved()) { /* DNMNOTE I think this branch is dead */ dout(10) << "deferring backfill due to !backfill_reserved" << dendl; if (!backfill_reserving) { dout(10) << "queueing RequestBackfill" << dendl; backfill_reserving = true; queue_peering_event( PGPeeringEventRef( std::make_shared( get_osdmap_epoch(), get_osdmap_epoch(), PeeringState::RequestBackfill()))); } deferred_backfill = true; } else { started += recover_backfill(max - started, handle, &work_in_progress); } } dout(10) << " started " << started << dendl; osd->logger->inc(l_osd_rop, started); if (!recovering.empty() || work_in_progress || recovery_ops_active > 0 || deferred_backfill) return !work_in_progress && have_unfound(); ceph_assert(recovering.empty()); ceph_assert(recovery_ops_active == 0); dout(10) << __func__ << " needs_recovery: " << recovery_state.get_missing_loc().get_needs_recovery() << dendl; dout(10) << __func__ << " missing_loc: " << recovery_state.get_missing_loc().get_missing_locs() << dendl; int unfound = get_num_unfound(); if (unfound) { dout(10) << " still have " << unfound << " unfound" << dendl; return true; } if (missing.num_missing() > 0) { // this shouldn't happen! osd->clog->error() << info.pgid << " Unexpected Error: recovery ending with " << missing.num_missing() << ": " << missing.get_items(); return false; } if (needs_recovery()) { // this shouldn't happen! // We already checked num_missing() so we must have missing replicas osd->clog->error() << info.pgid << " Unexpected Error: recovery ending with missing replicas"; return false; } if (state_test(PG_STATE_RECOVERING)) { state_clear(PG_STATE_RECOVERING); state_clear(PG_STATE_FORCED_RECOVERY); if (needs_backfill()) { dout(10) << "recovery done, queuing backfill" << dendl; queue_peering_event( PGPeeringEventRef( std::make_shared( get_osdmap_epoch(), get_osdmap_epoch(), PeeringState::RequestBackfill()))); } else { dout(10) << "recovery done, no backfill" << dendl; state_clear(PG_STATE_FORCED_BACKFILL); queue_peering_event( PGPeeringEventRef( std::make_shared( get_osdmap_epoch(), get_osdmap_epoch(), PeeringState::AllReplicasRecovered()))); } } else { // backfilling state_clear(PG_STATE_BACKFILLING); state_clear(PG_STATE_FORCED_BACKFILL); state_clear(PG_STATE_FORCED_RECOVERY); dout(10) << "recovery done, backfill done" << dendl; queue_peering_event( PGPeeringEventRef( std::make_shared( get_osdmap_epoch(), get_osdmap_epoch(), PeeringState::Backfilled()))); } return false; } /** * do one recovery op. * return true if done, false if nothing left to do. */ uint64_t PrimaryLogPG::recover_primary(uint64_t max, ThreadPool::TPHandle &handle) { ceph_assert(is_primary()); const auto &missing = recovery_state.get_pg_log().get_missing(); dout(10) << __func__ << " recovering " << recovering.size() << " in pg," << " missing " << missing << dendl; dout(25) << __func__ << " " << missing.get_items() << dendl; // look at log! pg_log_entry_t *latest = 0; unsigned started = 0; int skipped = 0; PGBackend::RecoveryHandle *h = pgbackend->open_recovery_op(); map::const_iterator p = missing.get_rmissing().lower_bound(recovery_state.get_pg_log().get_log().last_requested); while (p != missing.get_rmissing().end()) { handle.reset_tp_timeout(); hobject_t soid; version_t v = p->first; auto it_objects = recovery_state.get_pg_log().get_log().objects.find(p->second); if (it_objects != recovery_state.get_pg_log().get_log().objects.end()) { latest = it_objects->second; ceph_assert(latest->is_update() || latest->is_delete()); soid = latest->soid; } else { latest = 0; soid = p->second; } const pg_missing_item& item = missing.get_items().find(p->second)->second; ++p; hobject_t head = soid.get_head(); eversion_t need = item.need; dout(10) << __func__ << " " << soid << " " << item.need << (missing.is_missing(soid) ? " (missing)":"") << (missing.is_missing(head) ? " (missing head)":"") << (recovering.count(soid) ? " (recovering)":"") << (recovering.count(head) ? " (recovering head)":"") << dendl; if (latest) { switch (latest->op) { case pg_log_entry_t::CLONE: /* * Handling for this special case removed for now, until we * can correctly construct an accurate SnapSet from the old * one. */ break; case pg_log_entry_t::LOST_REVERT: { if (item.have == latest->reverting_to) { ObjectContextRef obc = get_object_context(soid, true); if (obc->obs.oi.version == latest->version) { // I'm already reverting dout(10) << " already reverting " << soid << dendl; } else { dout(10) << " reverting " << soid << " to " << latest->prior_version << dendl; obc->obs.oi.version = latest->version; ObjectStore::Transaction t; bufferlist b2; obc->obs.oi.encode( b2, get_osdmap()->get_features(CEPH_ENTITY_TYPE_OSD, nullptr)); ceph_assert(!pool.info.require_rollback()); t.setattr(coll, ghobject_t(soid), OI_ATTR, b2); recovery_state.recover_got( soid, latest->version, false, t); ++active_pushes; t.register_on_applied(new C_OSD_AppliedRecoveredObject(this, obc)); t.register_on_commit(new C_OSD_CommittedPushedObject( this, get_osdmap_epoch(), info.last_complete)); osd->store->queue_transaction(ch, std::move(t)); continue; } } else { /* * Pull the old version of the object. Update missing_loc here to have the location * of the version we want. * * This doesn't use the usual missing_loc paths, but that's okay: * - if we have it locally, we hit the case above, and go from there. * - if we don't, we always pass through this case during recovery and set up the location * properly. * - this way we don't need to mangle the missing code to be general about needing an old * version... */ eversion_t alternate_need = latest->reverting_to; dout(10) << " need to pull prior_version " << alternate_need << " for revert " << item << dendl; set good_peers; for (auto p = recovery_state.get_peer_missing().begin(); p != recovery_state.get_peer_missing().end(); ++p) { if (p->second.is_missing(soid, need) && p->second.get_items().at(soid).have == alternate_need) { good_peers.insert(p->first); } } recovery_state.set_revert_with_targets( soid, good_peers); dout(10) << " will pull " << alternate_need << " or " << need << " from one of " << recovery_state.get_missing_loc().get_locations(soid) << dendl; } } break; } } if (!recovering.count(soid)) { if (recovering.count(head)) { ++skipped; } else { int r = recover_missing( soid, need, get_recovery_op_priority(), h); switch (r) { case PULL_YES: ++started; break; case PULL_HEAD: ++started; case PULL_NONE: ++skipped; break; default: ceph_abort(); } if (started >= max) break; } } // only advance last_requested if we haven't skipped anything if (!skipped) recovery_state.set_last_requested(v); } pgbackend->run_recovery_op(h, get_recovery_op_priority()); return started; } bool PrimaryLogPG::primary_error( const hobject_t& soid, eversion_t v) { recovery_state.force_object_missing(pg_whoami, soid, v); bool uhoh = recovery_state.get_missing_loc().is_unfound(soid); if (uhoh) osd->clog->error() << info.pgid << " missing primary copy of " << soid << ", unfound"; else osd->clog->error() << info.pgid << " missing primary copy of " << soid << ", will try copies on " << recovery_state.get_missing_loc().get_locations(soid); return uhoh; } int PrimaryLogPG::prep_object_replica_deletes( const hobject_t& soid, eversion_t v, PGBackend::RecoveryHandle *h, bool *work_started) { ceph_assert(is_primary()); dout(10) << __func__ << ": on " << soid << dendl; ObjectContextRef obc = get_object_context(soid, false); if (obc) { if (!obc->get_recovery_read()) { dout(20) << "replica delete delayed on " << soid << "; could not get rw_manager lock" << dendl; *work_started = true; return 0; } else { dout(20) << "replica delete got recovery read lock on " << soid << dendl; } } start_recovery_op(soid); ceph_assert(!recovering.count(soid)); if (!obc) recovering.insert(make_pair(soid, ObjectContextRef())); else recovering.insert(make_pair(soid, obc)); pgbackend->recover_delete_object(soid, v, h); return 1; } int PrimaryLogPG::prep_object_replica_pushes( const hobject_t& soid, eversion_t v, PGBackend::RecoveryHandle *h, bool *work_started) { ceph_assert(is_primary()); dout(10) << __func__ << ": on " << soid << dendl; if (soid.snap && soid.snap < CEPH_NOSNAP) { // do we have the head and/or snapdir? hobject_t head = soid.get_head(); if (recovery_state.get_pg_log().get_missing().is_missing(head)) { if (recovering.count(head)) { dout(10) << " missing but already recovering head " << head << dendl; return 0; } else { int r = recover_missing( head, recovery_state.get_pg_log().get_missing().get_items().find(head)->second.need, get_recovery_op_priority(), h); if (r != PULL_NONE) return 1; return 0; } } } // NOTE: we know we will get a valid oloc off of disk here. ObjectContextRef obc = get_object_context(soid, false); if (!obc) { primary_error(soid, v); return 0; } if (!obc->get_recovery_read()) { dout(20) << "recovery delayed on " << soid << "; could not get rw_manager lock" << dendl; *work_started = true; return 0; } else { dout(20) << "recovery got recovery read lock on " << soid << dendl; } start_recovery_op(soid); ceph_assert(!recovering.count(soid)); recovering.insert(make_pair(soid, obc)); int r = pgbackend->recover_object( soid, v, ObjectContextRef(), obc, // has snapset context h); if (r < 0) { dout(0) << __func__ << " Error " << r << " on oid " << soid << dendl; on_failed_pull({ pg_whoami }, soid, v); return 0; } return 1; } uint64_t PrimaryLogPG::recover_replicas(uint64_t max, ThreadPool::TPHandle &handle, bool *work_started) { dout(10) << __func__ << "(" << max << ")" << dendl; uint64_t started = 0; PGBackend::RecoveryHandle *h = pgbackend->open_recovery_op(); // this is FAR from an optimal recovery order. pretty lame, really. ceph_assert(!get_acting_recovery_backfill().empty()); // choose replicas to recover, replica has the shortest missing list first // so we can bring it back to normal ASAP std::vector> replicas_by_num_missing, async_by_num_missing; replicas_by_num_missing.reserve(get_acting_recovery_backfill().size() - 1); for (auto &p: get_acting_recovery_backfill()) { if (p == get_primary()) { continue; } auto pm = recovery_state.get_peer_missing().find(p); ceph_assert(pm != recovery_state.get_peer_missing().end()); auto nm = pm->second.num_missing(); if (nm != 0) { if (is_async_recovery_target(p)) { async_by_num_missing.push_back(make_pair(nm, p)); } else { replicas_by_num_missing.push_back(make_pair(nm, p)); } } } // sort by number of missing objects, in ascending order. auto func = [](const std::pair &lhs, const std::pair &rhs) { return lhs.first < rhs.first; }; // acting goes first std::sort(replicas_by_num_missing.begin(), replicas_by_num_missing.end(), func); // then async_recovery_targets std::sort(async_by_num_missing.begin(), async_by_num_missing.end(), func); replicas_by_num_missing.insert(replicas_by_num_missing.end(), async_by_num_missing.begin(), async_by_num_missing.end()); for (auto &replica: replicas_by_num_missing) { pg_shard_t &peer = replica.second; ceph_assert(peer != get_primary()); auto pm = recovery_state.get_peer_missing().find(peer); ceph_assert(pm != recovery_state.get_peer_missing().end()); size_t m_sz = pm->second.num_missing(); dout(10) << " peer osd." << peer << " missing " << m_sz << " objects." << dendl; dout(20) << " peer osd." << peer << " missing " << pm->second.get_items() << dendl; // oldest first! const pg_missing_t &m(pm->second); for (map::const_iterator p = m.get_rmissing().begin(); p != m.get_rmissing().end() && started < max; ++p) { handle.reset_tp_timeout(); const hobject_t soid(p->second); if (recovery_state.get_missing_loc().is_unfound(soid)) { dout(10) << __func__ << ": " << soid << " still unfound" << dendl; continue; } const pg_info_t &pi = recovery_state.get_peer_info(peer); if (soid > pi.last_backfill) { if (!recovering.count(soid)) { derr << __func__ << ": object " << soid << " last_backfill " << pi.last_backfill << dendl; derr << __func__ << ": object added to missing set for backfill, but " << "is not in recovering, error!" << dendl; ceph_abort(); } continue; } if (recovering.count(soid)) { dout(10) << __func__ << ": already recovering " << soid << dendl; continue; } if (recovery_state.get_missing_loc().is_deleted(soid)) { dout(10) << __func__ << ": " << soid << " is a delete, removing" << dendl; map::const_iterator r = m.get_items().find(soid); started += prep_object_replica_deletes(soid, r->second.need, h, work_started); continue; } if (soid.is_snap() && recovery_state.get_pg_log().get_missing().is_missing( soid.get_head())) { dout(10) << __func__ << ": " << soid.get_head() << " still missing on primary" << dendl; continue; } if (recovery_state.get_pg_log().get_missing().is_missing(soid)) { dout(10) << __func__ << ": " << soid << " still missing on primary" << dendl; continue; } dout(10) << __func__ << ": recover_object_replicas(" << soid << ")" << dendl; map::const_iterator r = m.get_items().find(soid); started += prep_object_replica_pushes(soid, r->second.need, h, work_started); } } pgbackend->run_recovery_op(h, get_recovery_op_priority()); return started; } hobject_t PrimaryLogPG::earliest_peer_backfill() const { hobject_t e = hobject_t::get_max(); for (const pg_shard_t& peer : get_backfill_targets()) { const auto iter = peer_backfill_info.find(peer); ceph_assert(iter != peer_backfill_info.end()); e = std::min(e, iter->second.begin); } return e; } bool PrimaryLogPG::all_peer_done() const { // Primary hasn't got any more objects ceph_assert(backfill_info.empty()); for (const pg_shard_t& bt : get_backfill_targets()) { const auto piter = peer_backfill_info.find(bt); ceph_assert(piter != peer_backfill_info.end()); const BackfillInterval& pbi = piter->second; // See if peer has more to process if (!pbi.extends_to_end() || !pbi.empty()) return false; } return true; } /** * recover_backfill * * Invariants: * * backfilled: fully pushed to replica or present in replica's missing set (both * our copy and theirs). * * All objects on a backfill_target in * [MIN,peer_backfill_info[backfill_target].begin) are valid; logically-removed * objects have been actually deleted and all logically-valid objects are replicated. * There may be PG objects in this interval yet to be backfilled. * * All objects in PG in [MIN,backfill_info.begin) have been backfilled to all * backfill_targets. There may be objects on backfill_target(s) yet to be deleted. * * For a backfill target, all objects < std::min(peer_backfill_info[target].begin, * backfill_info.begin) in PG are backfilled. No deleted objects in this * interval remain on the backfill target. * * For a backfill target, all objects <= peer_info[target].last_backfill * have been backfilled to target * * There *MAY* be missing/outdated objects between last_backfill_started and * std::min(peer_backfill_info[*].begin, backfill_info.begin) in the event that client * io created objects since the last scan. For this reason, we call * update_range() again before continuing backfill. */ uint64_t PrimaryLogPG::recover_backfill( uint64_t max, ThreadPool::TPHandle &handle, bool *work_started) { dout(10) << __func__ << " (" << max << ")" << " bft=" << get_backfill_targets() << " last_backfill_started " << last_backfill_started << (new_backfill ? " new_backfill":"") << dendl; ceph_assert(!get_backfill_targets().empty()); // Initialize from prior backfill state if (new_backfill) { // on_activate() was called prior to getting here ceph_assert(last_backfill_started == recovery_state.earliest_backfill()); new_backfill = false; // initialize BackfillIntervals for (set::const_iterator i = get_backfill_targets().begin(); i != get_backfill_targets().end(); ++i) { peer_backfill_info[*i].reset( recovery_state.get_peer_info(*i).last_backfill); } backfill_info.reset(last_backfill_started); backfills_in_flight.clear(); pending_backfill_updates.clear(); } for (set::const_iterator i = get_backfill_targets().begin(); i != get_backfill_targets().end(); ++i) { dout(10) << "peer osd." << *i << " info " << recovery_state.get_peer_info(*i) << " interval " << peer_backfill_info[*i].begin << "-" << peer_backfill_info[*i].end << " " << peer_backfill_info[*i].objects.size() << " objects" << dendl; } // update our local interval to cope with recent changes backfill_info.begin = last_backfill_started; update_range(&backfill_info, handle); unsigned ops = 0; vector > to_remove; set add_to_stat; for (set::const_iterator i = get_backfill_targets().begin(); i != get_backfill_targets().end(); ++i) { peer_backfill_info[*i].trim_to( std::max( recovery_state.get_peer_info(*i).last_backfill, last_backfill_started)); } backfill_info.trim_to(last_backfill_started); PGBackend::RecoveryHandle *h = pgbackend->open_recovery_op(); while (ops < max) { if (backfill_info.begin <= earliest_peer_backfill() && !backfill_info.extends_to_end() && backfill_info.empty()) { hobject_t next = backfill_info.end; backfill_info.reset(next); backfill_info.end = hobject_t::get_max(); update_range(&backfill_info, handle); backfill_info.trim(); } dout(20) << " my backfill interval " << backfill_info << dendl; bool sent_scan = false; for (set::const_iterator i = get_backfill_targets().begin(); i != get_backfill_targets().end(); ++i) { pg_shard_t bt = *i; BackfillInterval& pbi = peer_backfill_info[bt]; dout(20) << " peer shard " << bt << " backfill " << pbi << dendl; if (pbi.begin <= backfill_info.begin && !pbi.extends_to_end() && pbi.empty()) { dout(10) << " scanning peer osd." << bt << " from " << pbi.end << dendl; epoch_t e = get_osdmap_epoch(); MOSDPGScan *m = new MOSDPGScan( MOSDPGScan::OP_SCAN_GET_DIGEST, pg_whoami, e, get_last_peering_reset(), spg_t(info.pgid.pgid, bt.shard), pbi.end, hobject_t()); osd->send_message_osd_cluster(bt.osd, m, get_osdmap_epoch()); ceph_assert(waiting_on_backfill.find(bt) == waiting_on_backfill.end()); waiting_on_backfill.insert(bt); sent_scan = true; } } // Count simultaneous scans as a single op and let those complete if (sent_scan) { ops++; start_recovery_op(hobject_t::get_max()); // XXX: was pbi.end break; } if (backfill_info.empty() && all_peer_done()) { dout(10) << " reached end for both local and all peers" << dendl; break; } // Get object within set of peers to operate on and // the set of targets for which that object applies. hobject_t check = earliest_peer_backfill(); if (check < backfill_info.begin) { set check_targets; for (set::const_iterator i = get_backfill_targets().begin(); i != get_backfill_targets().end(); ++i) { pg_shard_t bt = *i; BackfillInterval& pbi = peer_backfill_info[bt]; if (pbi.begin == check) check_targets.insert(bt); } ceph_assert(!check_targets.empty()); dout(20) << " BACKFILL removing " << check << " from peers " << check_targets << dendl; for (set::iterator i = check_targets.begin(); i != check_targets.end(); ++i) { pg_shard_t bt = *i; BackfillInterval& pbi = peer_backfill_info[bt]; ceph_assert(pbi.begin == check); to_remove.push_back(boost::make_tuple(check, pbi.objects.begin()->second, bt)); pbi.pop_front(); } last_backfill_started = check; // Don't increment ops here because deletions // are cheap and not replied to unlike real recovery_ops, // and we can't increment ops without requeueing ourself // for recovery. } else { eversion_t& obj_v = backfill_info.objects.begin()->second; vector need_ver_targs, missing_targs, keep_ver_targs, skip_targs; for (set::const_iterator i = get_backfill_targets().begin(); i != get_backfill_targets().end(); ++i) { pg_shard_t bt = *i; BackfillInterval& pbi = peer_backfill_info[bt]; // Find all check peers that have the wrong version if (check == backfill_info.begin && check == pbi.begin) { if (pbi.objects.begin()->second != obj_v) { need_ver_targs.push_back(bt); } else { keep_ver_targs.push_back(bt); } } else { const pg_info_t& pinfo = recovery_state.get_peer_info(bt); // Only include peers that we've caught up to their backfill line // otherwise, they only appear to be missing this object // because their pbi.begin > backfill_info.begin. if (backfill_info.begin > pinfo.last_backfill) missing_targs.push_back(bt); else skip_targs.push_back(bt); } } if (!keep_ver_targs.empty()) { // These peers have version obj_v dout(20) << " BACKFILL keeping " << check << " with ver " << obj_v << " on peers " << keep_ver_targs << dendl; //assert(!waiting_for_degraded_object.count(check)); } if (!need_ver_targs.empty() || !missing_targs.empty()) { ObjectContextRef obc = get_object_context(backfill_info.begin, false); ceph_assert(obc); if (obc->get_recovery_read()) { if (!need_ver_targs.empty()) { dout(20) << " BACKFILL replacing " << check << " with ver " << obj_v << " to peers " << need_ver_targs << dendl; } if (!missing_targs.empty()) { dout(20) << " BACKFILL pushing " << backfill_info.begin << " with ver " << obj_v << " to peers " << missing_targs << dendl; } vector all_push = need_ver_targs; all_push.insert(all_push.end(), missing_targs.begin(), missing_targs.end()); handle.reset_tp_timeout(); int r = prep_backfill_object_push(backfill_info.begin, obj_v, obc, all_push, h); if (r < 0) { *work_started = true; dout(0) << __func__ << " Error " << r << " trying to backfill " << backfill_info.begin << dendl; break; } ops++; } else { *work_started = true; dout(20) << "backfill blocking on " << backfill_info.begin << "; could not get rw_manager lock" << dendl; break; } } dout(20) << "need_ver_targs=" << need_ver_targs << " keep_ver_targs=" << keep_ver_targs << dendl; dout(20) << "backfill_targets=" << get_backfill_targets() << " missing_targs=" << missing_targs << " skip_targs=" << skip_targs << dendl; last_backfill_started = backfill_info.begin; add_to_stat.insert(backfill_info.begin); // XXX: Only one for all pushes? backfill_info.pop_front(); vector check_targets = need_ver_targs; check_targets.insert(check_targets.end(), keep_ver_targs.begin(), keep_ver_targs.end()); for (vector::iterator i = check_targets.begin(); i != check_targets.end(); ++i) { pg_shard_t bt = *i; BackfillInterval& pbi = peer_backfill_info[bt]; pbi.pop_front(); } } } for (set::iterator i = add_to_stat.begin(); i != add_to_stat.end(); ++i) { ObjectContextRef obc = get_object_context(*i, false); ceph_assert(obc); pg_stat_t stat; add_object_context_to_pg_stat(obc, &stat); pending_backfill_updates[*i] = stat; } map reqs; for (unsigned i = 0; i < to_remove.size(); ++i) { handle.reset_tp_timeout(); const hobject_t& oid = to_remove[i].get<0>(); eversion_t v = to_remove[i].get<1>(); pg_shard_t peer = to_remove[i].get<2>(); MOSDPGBackfillRemove *m; auto it = reqs.find(peer); if (it != reqs.end()) { m = it->second; } else { m = reqs[peer] = new MOSDPGBackfillRemove( spg_t(info.pgid.pgid, peer.shard), get_osdmap_epoch()); } m->ls.push_back(make_pair(oid, v)); if (oid <= last_backfill_started) pending_backfill_updates[oid]; // add empty stat! } for (auto p : reqs) { osd->send_message_osd_cluster(p.first.osd, p.second, get_osdmap_epoch()); } pgbackend->run_recovery_op(h, get_recovery_op_priority()); hobject_t backfill_pos = std::min(backfill_info.begin, earliest_peer_backfill()); dout(5) << "backfill_pos is " << backfill_pos << dendl; for (set::iterator i = backfills_in_flight.begin(); i != backfills_in_flight.end(); ++i) { dout(20) << *i << " is still in flight" << dendl; } hobject_t next_backfill_to_complete = backfills_in_flight.empty() ? backfill_pos : *(backfills_in_flight.begin()); hobject_t new_last_backfill = recovery_state.earliest_backfill(); dout(10) << "starting new_last_backfill at " << new_last_backfill << dendl; for (map::iterator i = pending_backfill_updates.begin(); i != pending_backfill_updates.end() && i->first < next_backfill_to_complete; pending_backfill_updates.erase(i++)) { dout(20) << " pending_backfill_update " << i->first << dendl; ceph_assert(i->first > new_last_backfill); // carried from a previous round – if we are here, then we had to // be requeued (by e.g. on_global_recover()) and those operations // are done. recovery_state.update_complete_backfill_object_stats( i->first, i->second); new_last_backfill = i->first; } dout(10) << "possible new_last_backfill at " << new_last_backfill << dendl; ceph_assert(!pending_backfill_updates.empty() || new_last_backfill == last_backfill_started); if (pending_backfill_updates.empty() && backfill_pos.is_max()) { ceph_assert(backfills_in_flight.empty()); new_last_backfill = backfill_pos; last_backfill_started = backfill_pos; } dout(10) << "final new_last_backfill at " << new_last_backfill << dendl; // If new_last_backfill == MAX, then we will send OP_BACKFILL_FINISH to // all the backfill targets. Otherwise, we will move last_backfill up on // those targets need it and send OP_BACKFILL_PROGRESS to them. for (set::const_iterator i = get_backfill_targets().begin(); i != get_backfill_targets().end(); ++i) { pg_shard_t bt = *i; const pg_info_t& pinfo = recovery_state.get_peer_info(bt); if (new_last_backfill > pinfo.last_backfill) { recovery_state.update_peer_last_backfill(bt, new_last_backfill); epoch_t e = get_osdmap_epoch(); MOSDPGBackfill *m = NULL; if (pinfo.last_backfill.is_max()) { m = new MOSDPGBackfill( MOSDPGBackfill::OP_BACKFILL_FINISH, e, get_last_peering_reset(), spg_t(info.pgid.pgid, bt.shard)); // Use default priority here, must match sub_op priority start_recovery_op(hobject_t::get_max()); } else { m = new MOSDPGBackfill( MOSDPGBackfill::OP_BACKFILL_PROGRESS, e, get_last_peering_reset(), spg_t(info.pgid.pgid, bt.shard)); // Use default priority here, must match sub_op priority } m->last_backfill = pinfo.last_backfill; m->stats = pinfo.stats; osd->send_message_osd_cluster(bt.osd, m, get_osdmap_epoch()); dout(10) << " peer " << bt << " num_objects now " << pinfo.stats.stats.sum.num_objects << " / " << info.stats.stats.sum.num_objects << dendl; } } if (ops) *work_started = true; return ops; } int PrimaryLogPG::prep_backfill_object_push( hobject_t oid, eversion_t v, ObjectContextRef obc, vector peers, PGBackend::RecoveryHandle *h) { dout(10) << __func__ << " " << oid << " v " << v << " to peers " << peers << dendl; ceph_assert(!peers.empty()); backfills_in_flight.insert(oid); recovery_state.prepare_backfill_for_missing(oid, v, peers); ceph_assert(!recovering.count(oid)); start_recovery_op(oid); recovering.insert(make_pair(oid, obc)); int r = pgbackend->recover_object( oid, v, ObjectContextRef(), obc, h); if (r < 0) { dout(0) << __func__ << " Error " << r << " on oid " << oid << dendl; on_failed_pull({ pg_whoami }, oid, v); } return r; } void PrimaryLogPG::update_range( BackfillInterval *bi, ThreadPool::TPHandle &handle) { int local_min = cct->_conf->osd_backfill_scan_min; int local_max = cct->_conf->osd_backfill_scan_max; if (bi->version < info.log_tail) { dout(10) << __func__<< ": bi is old, rescanning local backfill_info" << dendl; bi->version = info.last_update; scan_range(local_min, local_max, bi, handle); } if (bi->version >= projected_last_update) { dout(10) << __func__<< ": bi is current " << dendl; ceph_assert(bi->version == projected_last_update); } else if (bi->version >= info.log_tail) { if (recovery_state.get_pg_log().get_log().empty() && projected_log.empty()) { /* Because we don't move log_tail on split, the log might be * empty even if log_tail != last_update. However, the only * way to get here with an empty log is if log_tail is actually * eversion_t(), because otherwise the entry which changed * last_update since the last scan would have to be present. */ ceph_assert(bi->version == eversion_t()); return; } dout(10) << __func__<< ": bi is old, (" << bi->version << ") can be updated with log to projected_last_update " << projected_last_update << dendl; auto func = [&](const pg_log_entry_t &e) { dout(10) << __func__ << ": updating from version " << e.version << dendl; const hobject_t &soid = e.soid; if (soid >= bi->begin && soid < bi->end) { if (e.is_update()) { dout(10) << __func__ << ": " << e.soid << " updated to version " << e.version << dendl; bi->objects.erase(e.soid); bi->objects.insert( make_pair( e.soid, e.version)); } else if (e.is_delete()) { dout(10) << __func__ << ": " << e.soid << " removed" << dendl; bi->objects.erase(e.soid); } } }; dout(10) << "scanning pg log first" << dendl; recovery_state.get_pg_log().get_log().scan_log_after(bi->version, func); dout(10) << "scanning projected log" << dendl; projected_log.scan_log_after(bi->version, func); bi->version = projected_last_update; } else { ceph_abort_msg("scan_range should have raised bi->version past log_tail"); } } void PrimaryLogPG::scan_range( int min, int max, BackfillInterval *bi, ThreadPool::TPHandle &handle) { ceph_assert(is_locked()); dout(10) << "scan_range from " << bi->begin << dendl; bi->clear_objects(); vector ls; ls.reserve(max); int r = pgbackend->objects_list_partial(bi->begin, min, max, &ls, &bi->end); ceph_assert(r >= 0); dout(10) << " got " << ls.size() << " items, next " << bi->end << dendl; dout(20) << ls << dendl; for (vector::iterator p = ls.begin(); p != ls.end(); ++p) { handle.reset_tp_timeout(); ObjectContextRef obc; if (is_primary()) obc = object_contexts.lookup(*p); if (obc) { if (!obc->obs.exists) { /* If the object does not exist here, it must have been removed * between the collection_list_partial and here. This can happen * for the first item in the range, which is usually last_backfill. */ continue; } bi->objects[*p] = obc->obs.oi.version; dout(20) << " " << *p << " " << obc->obs.oi.version << dendl; } else { bufferlist bl; int r = pgbackend->objects_get_attr(*p, OI_ATTR, &bl); /* If the object does not exist here, it must have been removed * between the collection_list_partial and here. This can happen * for the first item in the range, which is usually last_backfill. */ if (r == -ENOENT) continue; ceph_assert(r >= 0); object_info_t oi(bl); bi->objects[*p] = oi.version; dout(20) << " " << *p << " " << oi.version << dendl; } } } /** check_local * * verifies that stray objects have been deleted */ void PrimaryLogPG::check_local() { dout(10) << __func__ << dendl; ceph_assert( info.last_update >= recovery_state.get_pg_log().get_tail()); // otherwise we need some help! if (!cct->_conf->osd_debug_verify_stray_on_activate) return; // just scan the log. set did; for (list::const_reverse_iterator p = recovery_state.get_pg_log().get_log().log.rbegin(); p != recovery_state.get_pg_log().get_log().log.rend(); ++p) { if (did.count(p->soid)) continue; did.insert(p->soid); if (p->is_delete() && !is_missing_object(p->soid)) { dout(10) << " checking " << p->soid << " at " << p->version << dendl; struct stat st; int r = osd->store->stat( ch, ghobject_t(p->soid, ghobject_t::NO_GEN, pg_whoami.shard), &st); if (r != -ENOENT) { derr << __func__ << " " << p->soid << " exists, but should have been " << "deleted" << dendl; ceph_abort_msg("erroneously present object"); } } else { // ignore old(+missing) objects } } } // =========================== // hit sets hobject_t PrimaryLogPG::get_hit_set_current_object(utime_t stamp) { ostringstream ss; ss << "hit_set_" << info.pgid.pgid << "_current_" << stamp; hobject_t hoid(sobject_t(ss.str(), CEPH_NOSNAP), "", info.pgid.ps(), info.pgid.pool(), cct->_conf->osd_hit_set_namespace); dout(20) << __func__ << " " << hoid << dendl; return hoid; } hobject_t PrimaryLogPG::get_hit_set_archive_object(utime_t start, utime_t end, bool using_gmt) { ostringstream ss; ss << "hit_set_" << info.pgid.pgid << "_archive_"; if (using_gmt) { start.gmtime(ss, true /* legacy pre-octopus form */) << "_"; end.gmtime(ss, true /* legacy pre-octopus form */); } else { start.localtime(ss, true /* legacy pre-octopus form */) << "_"; end.localtime(ss, true /* legacy pre-octopus form */); } hobject_t hoid(sobject_t(ss.str(), CEPH_NOSNAP), "", info.pgid.ps(), info.pgid.pool(), cct->_conf->osd_hit_set_namespace); dout(20) << __func__ << " " << hoid << dendl; return hoid; } void PrimaryLogPG::hit_set_clear() { dout(20) << __func__ << dendl; hit_set.reset(); hit_set_start_stamp = utime_t(); } void PrimaryLogPG::hit_set_setup() { if (!is_active() || !is_primary()) { hit_set_clear(); return; } if (is_active() && is_primary() && (!pool.info.hit_set_count || !pool.info.hit_set_period || pool.info.hit_set_params.get_type() == HitSet::TYPE_NONE)) { hit_set_clear(); // only primary is allowed to remove all the hit set objects hit_set_remove_all(); return; } // FIXME: discard any previous data for now hit_set_create(); // include any writes we know about from the pg log. this doesn't // capture reads, but it is better than nothing! hit_set_apply_log(); } void PrimaryLogPG::hit_set_remove_all() { // If any archives are degraded we skip this for (auto p = info.hit_set.history.begin(); p != info.hit_set.history.end(); ++p) { hobject_t aoid = get_hit_set_archive_object(p->begin, p->end, p->using_gmt); // Once we hit a degraded object just skip if (is_degraded_or_backfilling_object(aoid)) return; if (m_scrubber->write_blocked_by_scrub(aoid)) return; } if (!info.hit_set.history.empty()) { auto p = info.hit_set.history.rbegin(); ceph_assert(p != info.hit_set.history.rend()); hobject_t oid = get_hit_set_archive_object(p->begin, p->end, p->using_gmt); ceph_assert(!is_degraded_or_backfilling_object(oid)); ObjectContextRef obc = get_object_context(oid, false); ceph_assert(obc); OpContextUPtr ctx = simple_opc_create(obc); ctx->at_version = get_next_version(); ctx->updated_hset_history = info.hit_set; utime_t now = ceph_clock_now(); ctx->mtime = now; hit_set_trim(ctx, 0); simple_opc_submit(std::move(ctx)); } recovery_state.update_hset(pg_hit_set_history_t()); if (agent_state) { agent_state->discard_hit_sets(); } } void PrimaryLogPG::hit_set_create() { utime_t now = ceph_clock_now(); // make a copy of the params to modify HitSet::Params params(pool.info.hit_set_params); dout(20) << __func__ << " " << params << dendl; if (pool.info.hit_set_params.get_type() == HitSet::TYPE_BLOOM) { BloomHitSet::Params *p = static_cast(params.impl.get()); // convert false positive rate so it holds up across the full period p->set_fpp(p->get_fpp() / pool.info.hit_set_count); if (p->get_fpp() <= 0.0) p->set_fpp(.01); // fpp cannot be zero! // if we don't have specified size, estimate target size based on the // previous bin! if (p->target_size == 0 && hit_set) { utime_t dur = now - hit_set_start_stamp; unsigned unique = hit_set->approx_unique_insert_count(); dout(20) << __func__ << " previous set had approx " << unique << " unique items over " << dur << " seconds" << dendl; p->target_size = (double)unique * (double)pool.info.hit_set_period / (double)dur; } if (p->target_size < static_cast(cct->_conf->osd_hit_set_min_size)) p->target_size = cct->_conf->osd_hit_set_min_size; if (p->target_size > static_cast(cct->_conf->osd_hit_set_max_size)) p->target_size = cct->_conf->osd_hit_set_max_size; p->seed = now.sec(); dout(10) << __func__ << " target_size " << p->target_size << " fpp " << p->get_fpp() << dendl; } hit_set.reset(new HitSet(params)); hit_set_start_stamp = now; } /** * apply log entries to set * * this would only happen after peering, to at least capture writes * during an interval that was potentially lost. */ bool PrimaryLogPG::hit_set_apply_log() { if (!hit_set) return false; eversion_t to = info.last_update; eversion_t from = info.hit_set.current_last_update; if (to <= from) { dout(20) << __func__ << " no update" << dendl; return false; } dout(20) << __func__ << " " << to << " .. " << info.last_update << dendl; list::const_reverse_iterator p = recovery_state.get_pg_log().get_log().log.rbegin(); while (p != recovery_state.get_pg_log().get_log().log.rend() && p->version > to) ++p; while (p != recovery_state.get_pg_log().get_log().log.rend() && p->version > from) { hit_set->insert(p->soid); ++p; } return true; } void PrimaryLogPG::hit_set_persist() { dout(10) << __func__ << dendl; bufferlist bl; unsigned max = pool.info.hit_set_count; utime_t now = ceph_clock_now(); hobject_t oid; // If any archives are degraded we skip this persist request // account for the additional entry being added below for (auto p = info.hit_set.history.begin(); p != info.hit_set.history.end(); ++p) { hobject_t aoid = get_hit_set_archive_object(p->begin, p->end, p->using_gmt); // Once we hit a degraded object just skip further trim if (is_degraded_or_backfilling_object(aoid)) return; if (m_scrubber->write_blocked_by_scrub(aoid)) return; } // If backfill is in progress and we could possibly overlap with the // hit_set_* objects, back off. Since these all have // hobject_t::hash set to pgid.ps(), and those sort first, we can // look just at that. This is necessary because our transactions // may include a modify of the new hit_set *and* a delete of the // old one, and this may span the backfill boundary. for (set::const_iterator p = get_backfill_targets().begin(); p != get_backfill_targets().end(); ++p) { const pg_info_t& pi = recovery_state.get_peer_info(*p); if (pi.last_backfill == hobject_t() || pi.last_backfill.get_hash() == info.pgid.ps()) { dout(10) << __func__ << " backfill target osd." << *p << " last_backfill has not progressed past pgid ps" << dendl; return; } } pg_hit_set_info_t new_hset = pg_hit_set_info_t(pool.info.use_gmt_hitset); new_hset.begin = hit_set_start_stamp; new_hset.end = now; oid = get_hit_set_archive_object( new_hset.begin, new_hset.end, new_hset.using_gmt); // If the current object is degraded we skip this persist request if (m_scrubber->write_blocked_by_scrub(oid)) return; hit_set->seal(); encode(*hit_set, bl); dout(20) << __func__ << " archive " << oid << dendl; if (agent_state) { agent_state->add_hit_set(new_hset.begin, hit_set); uint32_t size = agent_state->hit_set_map.size(); if (size >= pool.info.hit_set_count) { size = pool.info.hit_set_count > 0 ? pool.info.hit_set_count - 1: 0; } hit_set_in_memory_trim(size); } ObjectContextRef obc = get_object_context(oid, true); OpContextUPtr ctx = simple_opc_create(obc); ctx->at_version = get_next_version(); ctx->updated_hset_history = info.hit_set; pg_hit_set_history_t &updated_hit_set_hist = *(ctx->updated_hset_history); updated_hit_set_hist.current_last_update = info.last_update; new_hset.version = ctx->at_version; updated_hit_set_hist.history.push_back(new_hset); hit_set_create(); // fabricate an object_info_t and SnapSet obc->obs.oi.version = ctx->at_version; obc->obs.oi.mtime = now; obc->obs.oi.size = bl.length(); obc->obs.exists = true; obc->obs.oi.set_data_digest(bl.crc32c(-1)); ctx->new_obs = obc->obs; ctx->new_snapset = obc->ssc->snapset; ctx->delta_stats.num_objects++; ctx->delta_stats.num_objects_hit_set_archive++; ctx->delta_stats.num_bytes += bl.length(); ctx->delta_stats.num_bytes_hit_set_archive += bl.length(); bufferlist bss; encode(ctx->new_snapset, bss); bufferlist boi(sizeof(ctx->new_obs.oi)); encode(ctx->new_obs.oi, boi, get_osdmap()->get_features(CEPH_ENTITY_TYPE_OSD, nullptr)); ctx->op_t->create(oid); if (bl.length()) { ctx->op_t->write(oid, 0, bl.length(), bl, 0); write_update_size_and_usage(ctx->delta_stats, obc->obs.oi, ctx->modified_ranges, 0, bl.length()); ctx->clean_regions.mark_data_region_dirty(0, bl.length()); } map attrs; attrs[OI_ATTR] = std::move(boi); attrs[SS_ATTR] = std::move(bss); setattrs_maybe_cache(ctx->obc, ctx->op_t.get(), attrs); ctx->log.push_back( pg_log_entry_t( pg_log_entry_t::MODIFY, oid, ctx->at_version, eversion_t(), 0, osd_reqid_t(), ctx->mtime, 0) ); ctx->log.back().clean_regions = ctx->clean_regions; hit_set_trim(ctx, max); simple_opc_submit(std::move(ctx)); } void PrimaryLogPG::hit_set_trim(OpContextUPtr &ctx, unsigned max) { ceph_assert(ctx->updated_hset_history); pg_hit_set_history_t &updated_hit_set_hist = *(ctx->updated_hset_history); for (unsigned num = updated_hit_set_hist.history.size(); num > max; --num) { list::iterator p = updated_hit_set_hist.history.begin(); ceph_assert(p != updated_hit_set_hist.history.end()); hobject_t oid = get_hit_set_archive_object(p->begin, p->end, p->using_gmt); ceph_assert(!is_degraded_or_backfilling_object(oid)); dout(20) << __func__ << " removing " << oid << dendl; ++ctx->at_version.version; ctx->log.push_back( pg_log_entry_t(pg_log_entry_t::DELETE, oid, ctx->at_version, p->version, 0, osd_reqid_t(), ctx->mtime, 0)); ctx->op_t->remove(oid); updated_hit_set_hist.history.pop_front(); ObjectContextRef obc = get_object_context(oid, false); ceph_assert(obc); --ctx->delta_stats.num_objects; --ctx->delta_stats.num_objects_hit_set_archive; ctx->delta_stats.num_bytes -= obc->obs.oi.size; ctx->delta_stats.num_bytes_hit_set_archive -= obc->obs.oi.size; } } void PrimaryLogPG::hit_set_in_memory_trim(uint32_t max_in_memory) { while (agent_state->hit_set_map.size() > max_in_memory) { agent_state->remove_oldest_hit_set(); } } // ======================================= // cache agent void PrimaryLogPG::agent_setup() { ceph_assert(is_locked()); if (!is_active() || !is_primary() || state_test(PG_STATE_PREMERGE) || pool.info.cache_mode == pg_pool_t::CACHEMODE_NONE || pool.info.tier_of < 0 || !get_osdmap()->have_pg_pool(pool.info.tier_of)) { agent_clear(); return; } if (!agent_state) { agent_state.reset(new TierAgentState); // choose random starting position agent_state->position = hobject_t(); agent_state->position.pool = info.pgid.pool(); agent_state->position.set_hash(pool.info.get_random_pg_position( info.pgid.pgid, rand())); agent_state->start = agent_state->position; dout(10) << __func__ << " allocated new state, position " << agent_state->position << dendl; } else { dout(10) << __func__ << " keeping existing state" << dendl; } if (info.stats.stats_invalid) { osd->clog->warn() << "pg " << info.pgid << " has invalid (post-split) stats; must scrub before tier agent can activate"; } agent_choose_mode(); } void PrimaryLogPG::agent_clear() { agent_stop(); agent_state.reset(NULL); } // Return false if no objects operated on since start of object hash space bool PrimaryLogPG::agent_work(int start_max, int agent_flush_quota) { std::scoped_lock locker{*this}; if (!agent_state) { dout(10) << __func__ << " no agent state, stopping" << dendl; return true; } ceph_assert(!recovery_state.is_deleting()); if (agent_state->is_idle()) { dout(10) << __func__ << " idle, stopping" << dendl; return true; } osd->logger->inc(l_osd_agent_wake); dout(10) << __func__ << " max " << start_max << ", flush " << agent_state->get_flush_mode_name() << ", evict " << agent_state->get_evict_mode_name() << ", pos " << agent_state->position << dendl; ceph_assert(is_primary()); ceph_assert(is_active()); agent_load_hit_sets(); const pg_pool_t *base_pool = get_osdmap()->get_pg_pool(pool.info.tier_of); ceph_assert(base_pool); int ls_min = 1; int ls_max = cct->_conf->osd_pool_default_cache_max_evict_check_size; // list some objects. this conveniently lists clones (oldest to // newest) before heads... the same order we want to flush in. // // NOTE: do not flush the Sequencer. we will assume that the // listing we get back is imprecise. vector ls; hobject_t next; int r = pgbackend->objects_list_partial(agent_state->position, ls_min, ls_max, &ls, &next); ceph_assert(r >= 0); dout(20) << __func__ << " got " << ls.size() << " objects" << dendl; int started = 0; for (vector::iterator p = ls.begin(); p != ls.end(); ++p) { if (p->nspace == cct->_conf->osd_hit_set_namespace) { dout(20) << __func__ << " skip (hit set) " << *p << dendl; osd->logger->inc(l_osd_agent_skip); continue; } if (is_degraded_or_backfilling_object(*p)) { dout(20) << __func__ << " skip (degraded) " << *p << dendl; osd->logger->inc(l_osd_agent_skip); continue; } if (is_missing_object(p->get_head())) { dout(20) << __func__ << " skip (missing head) " << *p << dendl; osd->logger->inc(l_osd_agent_skip); continue; } ObjectContextRef obc = get_object_context(*p, false, NULL); if (!obc) { // we didn't flush; we may miss something here. dout(20) << __func__ << " skip (no obc) " << *p << dendl; osd->logger->inc(l_osd_agent_skip); continue; } if (!obc->obs.exists) { dout(20) << __func__ << " skip (dne) " << obc->obs.oi.soid << dendl; osd->logger->inc(l_osd_agent_skip); continue; } if (m_scrubber->range_intersects_scrub(obc->obs.oi.soid, obc->obs.oi.soid.get_head())) { dout(20) << __func__ << " skip (scrubbing) " << obc->obs.oi << dendl; osd->logger->inc(l_osd_agent_skip); continue; } if (obc->is_blocked()) { dout(20) << __func__ << " skip (blocked) " << obc->obs.oi << dendl; osd->logger->inc(l_osd_agent_skip); continue; } if (obc->is_request_pending()) { dout(20) << __func__ << " skip (request pending) " << obc->obs.oi << dendl; osd->logger->inc(l_osd_agent_skip); continue; } // be careful flushing omap to an EC pool. if (!base_pool->supports_omap() && obc->obs.oi.is_omap()) { dout(20) << __func__ << " skip (omap to EC) " << obc->obs.oi << dendl; osd->logger->inc(l_osd_agent_skip); continue; } if (agent_state->evict_mode != TierAgentState::EVICT_MODE_IDLE && agent_maybe_evict(obc, false)) ++started; else if (agent_state->flush_mode != TierAgentState::FLUSH_MODE_IDLE && agent_flush_quota > 0 && agent_maybe_flush(obc)) { ++started; --agent_flush_quota; } if (started >= start_max) { // If finishing early, set "next" to the next object if (++p != ls.end()) next = *p; break; } } if (++agent_state->hist_age > cct->_conf->osd_agent_hist_halflife) { dout(20) << __func__ << " resetting atime and temp histograms" << dendl; agent_state->hist_age = 0; agent_state->temp_hist.decay(); } // Total objects operated on so far int total_started = agent_state->started + started; bool need_delay = false; dout(20) << __func__ << " start pos " << agent_state->position << " next start pos " << next << " started " << total_started << dendl; // See if we've made a full pass over the object hash space // This might check at most ls_max objects a second time to notice that // we've checked every objects at least once. if (agent_state->position < agent_state->start && next >= agent_state->start) { dout(20) << __func__ << " wrap around " << agent_state->start << dendl; if (total_started == 0) need_delay = true; else total_started = 0; agent_state->start = next; } agent_state->started = total_started; // See if we are starting from beginning if (next.is_max()) agent_state->position = hobject_t(); else agent_state->position = next; // Discard old in memory HitSets hit_set_in_memory_trim(pool.info.hit_set_count); if (need_delay) { ceph_assert(agent_state->delaying == false); agent_delay(); return false; } agent_choose_mode(); return true; } void PrimaryLogPG::agent_load_hit_sets() { if (agent_state->evict_mode == TierAgentState::EVICT_MODE_IDLE) { return; } if (agent_state->hit_set_map.size() < info.hit_set.history.size()) { dout(10) << __func__ << dendl; for (auto p = info.hit_set.history.begin(); p != info.hit_set.history.end(); ++p) { if (agent_state->hit_set_map.count(p->begin.sec()) == 0) { dout(10) << __func__ << " loading " << p->begin << "-" << p->end << dendl; if (!pool.info.is_replicated()) { // FIXME: EC not supported here yet derr << __func__ << " on non-replicated pool" << dendl; break; } hobject_t oid = get_hit_set_archive_object(p->begin, p->end, p->using_gmt); if (is_unreadable_object(oid)) { dout(10) << __func__ << " unreadable " << oid << ", waiting" << dendl; break; } ObjectContextRef obc = get_object_context(oid, false); if (!obc) { derr << __func__ << ": could not load hitset " << oid << dendl; break; } bufferlist bl; { int r = osd->store->read(ch, ghobject_t(oid), 0, 0, bl); ceph_assert(r >= 0); } HitSetRef hs(new HitSet); bufferlist::const_iterator pbl = bl.begin(); decode(*hs, pbl); agent_state->add_hit_set(p->begin.sec(), hs); } } } } bool PrimaryLogPG::agent_maybe_flush(ObjectContextRef& obc) { if (!obc->obs.oi.is_dirty()) { dout(20) << __func__ << " skip (clean) " << obc->obs.oi << dendl; osd->logger->inc(l_osd_agent_skip); return false; } if (obc->obs.oi.is_cache_pinned()) { dout(20) << __func__ << " skip (cache_pinned) " << obc->obs.oi << dendl; osd->logger->inc(l_osd_agent_skip); return false; } utime_t now = ceph_clock_now(); utime_t ob_local_mtime; if (obc->obs.oi.local_mtime != utime_t()) { ob_local_mtime = obc->obs.oi.local_mtime; } else { ob_local_mtime = obc->obs.oi.mtime; } bool evict_mode_full = (agent_state->evict_mode == TierAgentState::EVICT_MODE_FULL); if (!evict_mode_full && obc->obs.oi.soid.snap == CEPH_NOSNAP && // snaps immutable; don't delay (ob_local_mtime + utime_t(pool.info.cache_min_flush_age, 0) > now)) { dout(20) << __func__ << " skip (too young) " << obc->obs.oi << dendl; osd->logger->inc(l_osd_agent_skip); return false; } if (osd->agent_is_active_oid(obc->obs.oi.soid)) { dout(20) << __func__ << " skip (flushing) " << obc->obs.oi << dendl; osd->logger->inc(l_osd_agent_skip); return false; } dout(10) << __func__ << " flushing " << obc->obs.oi << dendl; // FIXME: flush anything dirty, regardless of what distribution of // ages we expect. hobject_t oid = obc->obs.oi.soid; osd->agent_start_op(oid); // no need to capture a pg ref, can't outlive fop or ctx std::function on_flush = [this, oid]() { osd->agent_finish_op(oid); }; int result = start_flush( OpRequestRef(), obc, false, NULL, on_flush); if (result != -EINPROGRESS) { on_flush(); dout(10) << __func__ << " start_flush() failed " << obc->obs.oi << " with " << result << dendl; osd->logger->inc(l_osd_agent_skip); return false; } osd->logger->inc(l_osd_agent_flush); return true; } bool PrimaryLogPG::agent_maybe_evict(ObjectContextRef& obc, bool after_flush) { const hobject_t& soid = obc->obs.oi.soid; if (!after_flush && obc->obs.oi.is_dirty()) { dout(20) << __func__ << " skip (dirty) " << obc->obs.oi << dendl; return false; } // This is already checked by agent_work() which passes after_flush = false if (after_flush && m_scrubber->range_intersects_scrub(soid, soid.get_head())) { dout(20) << __func__ << " skip (scrubbing) " << obc->obs.oi << dendl; return false; } if (!obc->obs.oi.watchers.empty()) { dout(20) << __func__ << " skip (watchers) " << obc->obs.oi << dendl; return false; } if (obc->is_blocked()) { dout(20) << __func__ << " skip (blocked) " << obc->obs.oi << dendl; return false; } if (obc->obs.oi.is_cache_pinned()) { dout(20) << __func__ << " skip (cache_pinned) " << obc->obs.oi << dendl; return false; } if (soid.snap == CEPH_NOSNAP) { int result = _verify_no_head_clones(soid, obc->ssc->snapset); if (result < 0) { dout(20) << __func__ << " skip (clones) " << obc->obs.oi << dendl; return false; } } if (agent_state->evict_mode != TierAgentState::EVICT_MODE_FULL) { // is this object old than cache_min_evict_age? utime_t now = ceph_clock_now(); utime_t ob_local_mtime; if (obc->obs.oi.local_mtime != utime_t()) { ob_local_mtime = obc->obs.oi.local_mtime; } else { ob_local_mtime = obc->obs.oi.mtime; } if (ob_local_mtime + utime_t(pool.info.cache_min_evict_age, 0) > now) { dout(20) << __func__ << " skip (too young) " << obc->obs.oi << dendl; osd->logger->inc(l_osd_agent_skip); return false; } // is this object old and/or cold enough? int temp = 0; uint64_t temp_upper = 0, temp_lower = 0; if (hit_set) agent_estimate_temp(soid, &temp); agent_state->temp_hist.add(temp); agent_state->temp_hist.get_position_micro(temp, &temp_lower, &temp_upper); dout(20) << __func__ << " temp " << temp << " pos " << temp_lower << "-" << temp_upper << ", evict_effort " << agent_state->evict_effort << dendl; dout(30) << "agent_state:\n"; Formatter *f = Formatter::create(""); f->open_object_section("agent_state"); agent_state->dump(f); f->close_section(); f->flush(*_dout); delete f; *_dout << dendl; if (1000000 - temp_upper >= agent_state->evict_effort) return false; } dout(10) << __func__ << " evicting " << obc->obs.oi << dendl; OpContextUPtr ctx = simple_opc_create(obc); auto null_op_req = OpRequestRef(); if (!ctx->lock_manager.get_lock_type( RWState::RWWRITE, obc->obs.oi.soid, obc, null_op_req)) { close_op_ctx(ctx.release()); dout(20) << __func__ << " skip (cannot get lock) " << obc->obs.oi << dendl; return false; } osd->agent_start_evict_op(); ctx->register_on_finish( [this]() { osd->agent_finish_evict_op(); }); ctx->at_version = get_next_version(); ceph_assert(ctx->new_obs.exists); int r = _delete_oid(ctx.get(), true, false); if (obc->obs.oi.is_omap()) ctx->delta_stats.num_objects_omap--; ctx->delta_stats.num_evict++; ctx->delta_stats.num_evict_kb += shift_round_up(obc->obs.oi.size, 10); if (obc->obs.oi.is_dirty()) --ctx->delta_stats.num_objects_dirty; ceph_assert(r == 0); finish_ctx(ctx.get(), pg_log_entry_t::DELETE); simple_opc_submit(std::move(ctx)); osd->logger->inc(l_osd_tier_evict); osd->logger->inc(l_osd_agent_evict); return true; } void PrimaryLogPG::agent_stop() { dout(20) << __func__ << dendl; if (agent_state && !agent_state->is_idle()) { agent_state->evict_mode = TierAgentState::EVICT_MODE_IDLE; agent_state->flush_mode = TierAgentState::FLUSH_MODE_IDLE; osd->agent_disable_pg(this, agent_state->evict_effort); } } void PrimaryLogPG::agent_delay() { dout(20) << __func__ << dendl; if (agent_state && !agent_state->is_idle()) { ceph_assert(agent_state->delaying == false); agent_state->delaying = true; osd->agent_disable_pg(this, agent_state->evict_effort); } } void PrimaryLogPG::agent_choose_mode_restart() { dout(20) << __func__ << dendl; std::scoped_lock locker{*this}; if (agent_state && agent_state->delaying) { agent_state->delaying = false; agent_choose_mode(true); } } bool PrimaryLogPG::agent_choose_mode(bool restart, OpRequestRef op) { bool requeued = false; // Let delay play out if (agent_state->delaying) { dout(20) << __func__ << " " << this << " delaying, ignored" << dendl; return requeued; } TierAgentState::flush_mode_t flush_mode = TierAgentState::FLUSH_MODE_IDLE; TierAgentState::evict_mode_t evict_mode = TierAgentState::EVICT_MODE_IDLE; unsigned evict_effort = 0; if (info.stats.stats_invalid) { // idle; stats can't be trusted until we scrub. dout(20) << __func__ << " stats invalid (post-split), idle" << dendl; goto skip_calc; } { uint64_t divisor = pool.info.get_pg_num_divisor(info.pgid.pgid); ceph_assert(divisor > 0); // adjust (effective) user objects down based on the number // of HitSet objects, which should not count toward our total since // they cannot be flushed. uint64_t unflushable = info.stats.stats.sum.num_objects_hit_set_archive; // also exclude omap objects if ec backing pool const pg_pool_t *base_pool = get_osdmap()->get_pg_pool(pool.info.tier_of); ceph_assert(base_pool); if (!base_pool->supports_omap()) unflushable += info.stats.stats.sum.num_objects_omap; uint64_t num_user_objects = info.stats.stats.sum.num_objects; if (num_user_objects > unflushable) num_user_objects -= unflushable; else num_user_objects = 0; uint64_t num_user_bytes = info.stats.stats.sum.num_bytes; uint64_t unflushable_bytes = info.stats.stats.sum.num_bytes_hit_set_archive; num_user_bytes -= unflushable_bytes; uint64_t num_overhead_bytes = osd->store->estimate_objects_overhead(num_user_objects); num_user_bytes += num_overhead_bytes; // also reduce the num_dirty by num_objects_omap int64_t num_dirty = info.stats.stats.sum.num_objects_dirty; if (!base_pool->supports_omap()) { if (num_dirty > info.stats.stats.sum.num_objects_omap) num_dirty -= info.stats.stats.sum.num_objects_omap; else num_dirty = 0; } dout(10) << __func__ << " flush_mode: " << TierAgentState::get_flush_mode_name(agent_state->flush_mode) << " evict_mode: " << TierAgentState::get_evict_mode_name(agent_state->evict_mode) << " num_objects: " << info.stats.stats.sum.num_objects << " num_bytes: " << info.stats.stats.sum.num_bytes << " num_objects_dirty: " << info.stats.stats.sum.num_objects_dirty << " num_objects_omap: " << info.stats.stats.sum.num_objects_omap << " num_dirty: " << num_dirty << " num_user_objects: " << num_user_objects << " num_user_bytes: " << num_user_bytes << " num_overhead_bytes: " << num_overhead_bytes << " pool.info.target_max_bytes: " << pool.info.target_max_bytes << " pool.info.target_max_objects: " << pool.info.target_max_objects << dendl; // get dirty, full ratios uint64_t dirty_micro = 0; uint64_t full_micro = 0; if (pool.info.target_max_bytes && num_user_objects > 0) { uint64_t avg_size = num_user_bytes / num_user_objects; dirty_micro = num_dirty * avg_size * 1000000 / std::max(pool.info.target_max_bytes / divisor, 1); full_micro = num_user_objects * avg_size * 1000000 / std::max(pool.info.target_max_bytes / divisor, 1); } if (pool.info.target_max_objects > 0) { uint64_t dirty_objects_micro = num_dirty * 1000000 / std::max(pool.info.target_max_objects / divisor, 1); if (dirty_objects_micro > dirty_micro) dirty_micro = dirty_objects_micro; uint64_t full_objects_micro = num_user_objects * 1000000 / std::max(pool.info.target_max_objects / divisor, 1); if (full_objects_micro > full_micro) full_micro = full_objects_micro; } dout(20) << __func__ << " dirty " << ((float)dirty_micro / 1000000.0) << " full " << ((float)full_micro / 1000000.0) << dendl; // flush mode uint64_t flush_target = pool.info.cache_target_dirty_ratio_micro; uint64_t flush_high_target = pool.info.cache_target_dirty_high_ratio_micro; uint64_t flush_slop = (float)flush_target * cct->_conf->osd_agent_slop; if (restart || agent_state->flush_mode == TierAgentState::FLUSH_MODE_IDLE) { flush_target += flush_slop; flush_high_target += flush_slop; } else { flush_target -= std::min(flush_target, flush_slop); flush_high_target -= std::min(flush_high_target, flush_slop); } if (dirty_micro > flush_high_target) { flush_mode = TierAgentState::FLUSH_MODE_HIGH; } else if (dirty_micro > flush_target || (!flush_target && num_dirty > 0)) { flush_mode = TierAgentState::FLUSH_MODE_LOW; } // evict mode uint64_t evict_target = pool.info.cache_target_full_ratio_micro; uint64_t evict_slop = (float)evict_target * cct->_conf->osd_agent_slop; if (restart || agent_state->evict_mode == TierAgentState::EVICT_MODE_IDLE) evict_target += evict_slop; else evict_target -= std::min(evict_target, evict_slop); if (full_micro > 1000000) { // evict anything clean evict_mode = TierAgentState::EVICT_MODE_FULL; evict_effort = 1000000; } else if (full_micro > evict_target) { // set effort in [0..1] range based on where we are between evict_mode = TierAgentState::EVICT_MODE_SOME; uint64_t over = full_micro - evict_target; uint64_t span = 1000000 - evict_target; evict_effort = std::max(over * 1000000 / span, uint64_t(1000000.0 * cct->_conf->osd_agent_min_evict_effort)); // quantize effort to avoid too much reordering in the agent_queue. uint64_t inc = cct->_conf->osd_agent_quantize_effort * 1000000; ceph_assert(inc > 0); uint64_t was = evict_effort; evict_effort -= evict_effort % inc; if (evict_effort < inc) evict_effort = inc; ceph_assert(evict_effort >= inc && evict_effort <= 1000000); dout(30) << __func__ << " evict_effort " << was << " quantized by " << inc << " to " << evict_effort << dendl; } } skip_calc: bool old_idle = agent_state->is_idle(); if (flush_mode != agent_state->flush_mode) { dout(5) << __func__ << " flush_mode " << TierAgentState::get_flush_mode_name(agent_state->flush_mode) << " -> " << TierAgentState::get_flush_mode_name(flush_mode) << dendl; recovery_state.update_stats( [=](auto &history, auto &stats) { if (flush_mode == TierAgentState::FLUSH_MODE_HIGH) { osd->agent_inc_high_count(); stats.stats.sum.num_flush_mode_high = 1; } else if (flush_mode == TierAgentState::FLUSH_MODE_LOW) { stats.stats.sum.num_flush_mode_low = 1; } if (agent_state->flush_mode == TierAgentState::FLUSH_MODE_HIGH) { osd->agent_dec_high_count(); stats.stats.sum.num_flush_mode_high = 0; } else if (agent_state->flush_mode == TierAgentState::FLUSH_MODE_LOW) { stats.stats.sum.num_flush_mode_low = 0; } return false; }); agent_state->flush_mode = flush_mode; } if (evict_mode != agent_state->evict_mode) { dout(5) << __func__ << " evict_mode " << TierAgentState::get_evict_mode_name(agent_state->evict_mode) << " -> " << TierAgentState::get_evict_mode_name(evict_mode) << dendl; if (agent_state->evict_mode == TierAgentState::EVICT_MODE_FULL && is_active()) { if (op) requeue_op(op); requeue_ops(waiting_for_flush); requeue_ops(waiting_for_active); requeue_ops(waiting_for_readable); requeue_ops(waiting_for_scrub); requeue_ops(waiting_for_cache_not_full); objects_blocked_on_cache_full.clear(); requeued = true; } recovery_state.update_stats( [=](auto &history, auto &stats) { if (evict_mode == TierAgentState::EVICT_MODE_SOME) { stats.stats.sum.num_evict_mode_some = 1; } else if (evict_mode == TierAgentState::EVICT_MODE_FULL) { stats.stats.sum.num_evict_mode_full = 1; } if (agent_state->evict_mode == TierAgentState::EVICT_MODE_SOME) { stats.stats.sum.num_evict_mode_some = 0; } else if (agent_state->evict_mode == TierAgentState::EVICT_MODE_FULL) { stats.stats.sum.num_evict_mode_full = 0; } return false; }); agent_state->evict_mode = evict_mode; } uint64_t old_effort = agent_state->evict_effort; if (evict_effort != agent_state->evict_effort) { dout(5) << __func__ << " evict_effort " << ((float)agent_state->evict_effort / 1000000.0) << " -> " << ((float)evict_effort / 1000000.0) << dendl; agent_state->evict_effort = evict_effort; } // NOTE: we are using evict_effort as a proxy for *all* agent effort // (including flush). This is probably fine (they should be // correlated) but it is not precisely correct. if (agent_state->is_idle()) { if (!restart && !old_idle) { osd->agent_disable_pg(this, old_effort); } } else { if (restart || old_idle) { osd->agent_enable_pg(this, agent_state->evict_effort); } else if (old_effort != agent_state->evict_effort) { osd->agent_adjust_pg(this, old_effort, agent_state->evict_effort); } } return requeued; } void PrimaryLogPG::agent_estimate_temp(const hobject_t& oid, int *temp) { ceph_assert(hit_set); ceph_assert(temp); *temp = 0; if (hit_set->contains(oid)) *temp = 1000000; unsigned i = 0; int last_n = pool.info.hit_set_search_last_n; for (map::reverse_iterator p = agent_state->hit_set_map.rbegin(); last_n > 0 && p != agent_state->hit_set_map.rend(); ++p, ++i) { if (p->second->contains(oid)) { *temp += pool.info.get_grade(i); --last_n; } } } // Dup op detection bool PrimaryLogPG::already_complete(eversion_t v) { dout(20) << __func__ << ": " << v << dendl; for (xlist::iterator i = repop_queue.begin(); !i.end(); ++i) { dout(20) << __func__ << ": " << **i << dendl; // skip copy from temp object ops if ((*i)->v == eversion_t()) { dout(20) << __func__ << ": " << **i << " version is empty" << dendl; continue; } if ((*i)->v > v) { dout(20) << __func__ << ": " << **i << " (*i)->v past v" << dendl; break; } if (!(*i)->all_committed) { dout(20) << __func__ << ": " << **i << " not committed, returning false" << dendl; return false; } } dout(20) << __func__ << ": returning true" << dendl; return true; } // ========================================================================================== // SCRUB void PrimaryLogPG::do_replica_scrub_map(OpRequestRef op) { dout(15) << __func__ << " is scrub active? " << m_scrubber->is_scrub_active() << dendl; op->mark_started(); if (!m_scrubber->is_scrub_active()) { dout(10) << __func__ << " scrub isn't active" << dendl; return; } m_scrubber->map_from_replica(op); } bool PrimaryLogPG::_range_available_for_scrub(const hobject_t& begin, const hobject_t& end) { pair next; next.second = object_contexts.lookup(begin); next.first = begin; bool more = true; while (more && next.first < end) { if (next.second && next.second->is_blocked()) { next.second->requeue_scrub_on_unblock = true; dout(10) << __func__ << ": scrub delayed, " << next.first << " is blocked" << dendl; return false; } more = object_contexts.get_next(next.first, &next); } return true; } int PrimaryLogPG::rep_repair_primary_object(const hobject_t& soid, OpContext *ctx) { OpRequestRef op = ctx->op; // Only supports replicated pools ceph_assert(!pool.info.is_erasure()); ceph_assert(is_primary()); dout(10) << __func__ << " " << soid << " peers osd.{" << get_acting_recovery_backfill() << "}" << dendl; if (!is_clean()) { block_for_clean(soid, op); return -EAGAIN; } ceph_assert(!recovery_state.get_pg_log().get_missing().is_missing(soid)); auto& oi = ctx->new_obs.oi; eversion_t v = oi.version; if (primary_error(soid, v)) { dout(0) << __func__ << " No other replicas available for " << soid << dendl; // XXX: If we knew that there is no down osd which could include this // object, it would be nice if we could return EIO here. // If a "never fail" flag was available, that could be used // for rbd to NOT return EIO until object marked lost. // Drop through to save this op in case an osd comes up with the object. } // Restart the op after object becomes readable again waiting_for_unreadable_object[soid].push_back(op); op->mark_delayed("waiting for missing object"); ceph_assert(is_clean()); state_set(PG_STATE_REPAIR); state_clear(PG_STATE_CLEAN); queue_peering_event( PGPeeringEventRef( std::make_shared( get_osdmap_epoch(), get_osdmap_epoch(), PeeringState::DoRecovery()))); return -EAGAIN; } /*---SnapTrimmer Logging---*/ #undef dout_prefix #define dout_prefix pg->gen_prefix(*_dout) void PrimaryLogPG::SnapTrimmer::log_enter(const char *state_name) { ldout(pg->cct, 20) << "enter " << state_name << dendl; } void PrimaryLogPG::SnapTrimmer::log_exit(const char *state_name, utime_t enter_time) { ldout(pg->cct, 20) << "exit " << state_name << dendl; } bool PrimaryLogPG::SnapTrimmer::permit_trim() { return pg->is_clean() && !pg->is_scrub_queued_or_active() && !pg->snap_trimq.empty(); } /*---SnapTrimmer states---*/ #undef dout_prefix #define dout_prefix (context< SnapTrimmer >().pg->gen_prefix(*_dout) \ << "SnapTrimmer state<" << get_state_name() << ">: ") /* NotTrimming */ PrimaryLogPG::NotTrimming::NotTrimming(my_context ctx) : my_base(ctx), NamedState(nullptr, "NotTrimming") { context< SnapTrimmer >().log_enter(state_name); } void PrimaryLogPG::NotTrimming::exit() { context< SnapTrimmer >().log_exit(state_name, enter_time); } boost::statechart::result PrimaryLogPG::NotTrimming::react(const KickTrim&) { PrimaryLogPG *pg = context< SnapTrimmer >().pg; ldout(pg->cct, 10) << "NotTrimming react KickTrim" << dendl; if (!(pg->is_primary() && pg->is_active())) { ldout(pg->cct, 10) << "NotTrimming not primary or active" << dendl; return discard_event(); } if (!pg->is_clean() || pg->snap_trimq.empty()) { ldout(pg->cct, 10) << "NotTrimming not clean or nothing to trim" << dendl; return discard_event(); } if (pg->is_scrub_queued_or_active()) { ldout(pg->cct, 10) << " scrubbing, will requeue snap_trimmer after" << dendl; return transit< WaitScrub >(); } else { return transit< Trimming >(); } } boost::statechart::result PrimaryLogPG::WaitReservation::react(const SnapTrimReserved&) { PrimaryLogPG *pg = context< SnapTrimmer >().pg; ldout(pg->cct, 10) << "WaitReservation react SnapTrimReserved" << dendl; pending = nullptr; if (!context< SnapTrimmer >().can_trim()) { post_event(KickTrim()); return transit< NotTrimming >(); } context().snap_to_trim = pg->snap_trimq.range_start(); ldout(pg->cct, 10) << "NotTrimming: trimming " << pg->snap_trimq.range_start() << dendl; return transit< AwaitAsyncWork >(); } /* AwaitAsyncWork */ PrimaryLogPG::AwaitAsyncWork::AwaitAsyncWork(my_context ctx) : my_base(ctx), NamedState(nullptr, "Trimming/AwaitAsyncWork") { auto *pg = context< SnapTrimmer >().pg; context< SnapTrimmer >().log_enter(state_name); context< SnapTrimmer >().pg->osd->queue_for_snap_trim(pg); pg->state_set(PG_STATE_SNAPTRIM); pg->state_clear(PG_STATE_SNAPTRIM_ERROR); pg->publish_stats_to_osd(); } boost::statechart::result PrimaryLogPG::AwaitAsyncWork::react(const DoSnapWork&) { PrimaryLogPGRef pg = context< SnapTrimmer >().pg; snapid_t snap_to_trim = context().snap_to_trim; auto &in_flight = context().in_flight; ceph_assert(in_flight.empty()); ceph_assert(pg->is_primary() && pg->is_active()); if (!context< SnapTrimmer >().can_trim()) { ldout(pg->cct, 10) << "something changed, reverting to NotTrimming" << dendl; post_event(KickTrim()); return transit< NotTrimming >(); } ldout(pg->cct, 10) << "AwaitAsyncWork: trimming snap " << snap_to_trim << dendl; vector to_trim; unsigned max = pg->cct->_conf->osd_pg_max_concurrent_snap_trims; // we need to look for at least 1 snaptrim, otherwise we'll misinterpret // the ENOENT below and erase snap_to_trim. ceph_assert(max > 0); to_trim.reserve(max); int r = pg->snap_mapper.get_next_objects_to_trim( snap_to_trim, max, &to_trim); if (r != 0 && r != -ENOENT) { lderr(pg->cct) << "get_next_objects_to_trim returned " << cpp_strerror(r) << dendl; ceph_abort_msg("get_next_objects_to_trim returned an invalid code"); } else if (r == -ENOENT) { // Done! ldout(pg->cct, 10) << "got ENOENT" << dendl; pg->snap_trimq.erase(snap_to_trim); if (pg->snap_trimq_repeat.count(snap_to_trim)) { ldout(pg->cct, 10) << " removing from snap_trimq_repeat" << dendl; pg->snap_trimq_repeat.erase(snap_to_trim); } else { ldout(pg->cct, 10) << "adding snap " << snap_to_trim << " to purged_snaps" << dendl; ObjectStore::Transaction t; pg->recovery_state.adjust_purged_snaps( [snap_to_trim](auto &purged_snaps) { purged_snaps.insert(snap_to_trim); }); pg->write_if_dirty(t); ldout(pg->cct, 10) << "purged_snaps now " << pg->info.purged_snaps << ", snap_trimq now " << pg->snap_trimq << dendl; int tr = pg->osd->store->queue_transaction(pg->ch, std::move(t), NULL); ceph_assert(tr == 0); pg->recovery_state.share_pg_info(); } post_event(KickTrim()); return transit< NotTrimming >(); } ceph_assert(!to_trim.empty()); for (auto &&object: to_trim) { // Get next ldout(pg->cct, 10) << "AwaitAsyncWork react trimming " << object << dendl; OpContextUPtr ctx; int error = pg->trim_object(in_flight.empty(), object, snap_to_trim, &ctx); if (error) { if (error == -ENOLCK) { ldout(pg->cct, 10) << "could not get write lock on obj " << object << dendl; } else { pg->state_set(PG_STATE_SNAPTRIM_ERROR); ldout(pg->cct, 10) << "Snaptrim error=" << error << dendl; } if (!in_flight.empty()) { ldout(pg->cct, 10) << "letting the ones we already started finish" << dendl; return transit< WaitRepops >(); } if (error == -ENOLCK) { ldout(pg->cct, 10) << "waiting for it to clear" << dendl; return transit< WaitRWLock >(); } else { return transit< NotTrimming >(); } } in_flight.insert(object); ctx->register_on_success( [pg, object, &in_flight]() { ceph_assert(in_flight.find(object) != in_flight.end()); in_flight.erase(object); if (in_flight.empty()) { if (pg->state_test(PG_STATE_SNAPTRIM_ERROR)) { pg->snap_trimmer_machine.process_event(Reset()); } else { pg->snap_trimmer_machine.process_event(RepopsComplete()); } } }); pg->simple_opc_submit(std::move(ctx)); } return transit< WaitRepops >(); } void PrimaryLogPG::setattr_maybe_cache( ObjectContextRef obc, PGTransaction *t, const string &key, bufferlist &val) { t->setattr(obc->obs.oi.soid, key, val); } void PrimaryLogPG::setattrs_maybe_cache( ObjectContextRef obc, PGTransaction *t, map &attrs) { t->setattrs(obc->obs.oi.soid, attrs); } void PrimaryLogPG::rmattr_maybe_cache( ObjectContextRef obc, PGTransaction *t, const string &key) { t->rmattr(obc->obs.oi.soid, key); } int PrimaryLogPG::getattr_maybe_cache( ObjectContextRef obc, const string &key, bufferlist *val) { if (pool.info.is_erasure()) { map::iterator i = obc->attr_cache.find(key); if (i != obc->attr_cache.end()) { if (val) *val = i->second; return 0; } else { return -ENODATA; } } return pgbackend->objects_get_attr(obc->obs.oi.soid, key, val); } int PrimaryLogPG::getattrs_maybe_cache( ObjectContextRef obc, map *out) { int r = 0; ceph_assert(out); if (pool.info.is_erasure()) { *out = obc->attr_cache; } else { r = pgbackend->objects_get_attrs(obc->obs.oi.soid, out); } map tmp; for (map::iterator i = out->begin(); i != out->end(); ++i) { if (i->first.size() > 1 && i->first[0] == '_') tmp[i->first.substr(1, i->first.size())] = std::move(i->second); } tmp.swap(*out); return r; } bool PrimaryLogPG::check_failsafe_full() { return osd->check_failsafe_full(get_dpp()); } bool PrimaryLogPG::maybe_preempt_replica_scrub(const hobject_t& oid) { return m_scrubber->write_blocked_by_scrub(oid); } void intrusive_ptr_add_ref(PrimaryLogPG *pg) { pg->get("intptr"); } void intrusive_ptr_release(PrimaryLogPG *pg) { pg->put("intptr"); } #ifdef PG_DEBUG_REFS uint64_t get_with_id(PrimaryLogPG *pg) { return pg->get_with_id(); } void put_with_id(PrimaryLogPG *pg, uint64_t id) { return pg->put_with_id(id); } #endif void intrusive_ptr_add_ref(PrimaryLogPG::RepGather *repop) { repop->get(); } void intrusive_ptr_release(PrimaryLogPG::RepGather *repop) { repop->put(); }