From: Alex Ainscow Date: Sat, 28 Feb 2026 23:58:51 +0000 (+0000) Subject: Refactor: Extract mock classes from TestPeeringState into separate header files X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=f5debfe6b244675c6f659f99f39fef34b014ba92;p=ceph.git Refactor: Extract mock classes from TestPeeringState into separate header files Move the following mock classes out of TestPeeringState.cc into their own header files. The code is identical - this is a pure refactor with no functional changes: - MockConnection -> MockConnection.h - MockLog -> MockLog.h - MockECRecPred -> MockECRecPred.h - MockECReadPred -> MockECReadPred.h - MockPGBackendListener -> MockPGBackendListener.h - MockPGBackend -> MockPGBackend.h - MockPGLogEntryHandler -> MockPGLogEntryHandler.h - MockPeeringListener -> MockPeeringListener.h TestPeeringState.cc is updated to include the new headers instead of defining the classes inline. Signed-off-by: Alex Ainscow --- diff --git a/src/test/osd/MockConnection.h b/src/test/osd/MockConnection.h new file mode 100644 index 000000000000..a0785c217ac1 --- /dev/null +++ b/src/test/osd/MockConnection.h @@ -0,0 +1,51 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:nil -*- +// vim: ts=8 sw=2 sts=2 expandtab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2026 IBM + * + * 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. + * + */ + +#pragma once + +#include "msg/Connection.h" +#include "msg/Message.h" +#include "global/global_context.h" + +//MockConnection - simple stub. Required because PeeringState needs +//to know the features of the peer OSD which sent a peering message +class MockConnection : public Connection { + public: + MockConnection() : Connection(g_ceph_context, nullptr) { + set_features(CEPH_FEATURES_ALL); + } + + bool is_connected() override { + return true; + } + + int send_message(Message *m) override { + m->put(); + return 0; + } + + void send_keepalive() override { + } + + void mark_down() override { + } + + void mark_disposable() override { + } + + entity_addr_t get_peer_socket_addr() const override { + return entity_addr_t(); + } +}; + diff --git a/src/test/osd/MockECReadPred.h b/src/test/osd/MockECReadPred.h new file mode 100644 index 000000000000..cff7b353f166 --- /dev/null +++ b/src/test/osd/MockECReadPred.h @@ -0,0 +1,30 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:nil -*- +// vim: ts=8 sw=2 sts=2 expandtab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2026 IBM + * + * 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. + * + */ + +#pragma once + +#include +#include "osd/PGBackend.h" + +// MockECReadPred - simple stub for IsPGReadablePredicate +// Warning - this always returns true. This means we cannot test scenarios +// where there are too many OSDs down and the PG should be incomplete +class MockECReadPred : public IsPGReadablePredicate { + public: + MockECReadPred() {} + bool operator()(const std::set &_have) const override { + return true; + } +}; + diff --git a/src/test/osd/MockECRecPred.h b/src/test/osd/MockECRecPred.h new file mode 100644 index 000000000000..1b603350190d --- /dev/null +++ b/src/test/osd/MockECRecPred.h @@ -0,0 +1,31 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:nil -*- +// vim: ts=8 sw=2 sts=2 expandtab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2026 IBM + * + * 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. + * + */ + +#pragma once + +#include +#include "osd/PGBackend.h" + +// MockECRecPred - simple stub for IsPGRecoverablePredicate +// Warning - this always returns true. This means we cannot test scenarios +// where there are too many OSDs down and the PG should be incomplete +class MockECRecPred : public IsPGRecoverablePredicate { + public: + MockECRecPred() {} + + bool operator()(const std::set &_have) const override { + return true; + } +}; + diff --git a/src/test/osd/MockLog.h b/src/test/osd/MockLog.h new file mode 100644 index 000000000000..e3ee9cf2b194 --- /dev/null +++ b/src/test/osd/MockLog.h @@ -0,0 +1,107 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:nil -*- +// vim: ts=8 sw=2 sts=2 expandtab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2026 IBM + * + * 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. + * + */ + +#pragma once + +#include +#include +#include +#include "common/ostream_temp.h" + +//MockLog - simple stub +class MockLog : public LoggerSinkSet { + public: + void debug(std::stringstream& s) final + { + std::cout << "\n<> " << s.str() << std::endl; + } + + void info(std::stringstream& s) final + { + std::cout << "\n<> " << s.str() << std::endl; + } + + void sec(std::stringstream& s) final + { + std::cout << "\n<> " << s.str() << std::endl; + } + + void warn(std::stringstream& s) final + { + std::cout << "\n<> " << s.str() << std::endl; + } + + void error(std::stringstream& s) final + { + err_count++; + std::cout << "\n<> " << s.str() << std::endl; + } + + OstreamTemp info() final { return OstreamTemp(CLOG_INFO, this); } + OstreamTemp warn() final { return OstreamTemp(CLOG_WARN, this); } + OstreamTemp error() final { return OstreamTemp(CLOG_ERROR, this); } + OstreamTemp sec() final { return OstreamTemp(CLOG_ERROR, this); } + OstreamTemp debug() final { return OstreamTemp(CLOG_DEBUG, this); } + + void do_log(clog_type prio, std::stringstream& ss) final + { + switch (prio) { + case CLOG_DEBUG: + debug(ss); + break; + case CLOG_INFO: + info(ss); + break; + case CLOG_SEC: + sec(ss); + break; + case CLOG_WARN: + warn(ss); + break; + case CLOG_ERROR: + default: + error(ss); + break; + } + } + + void do_log(clog_type prio, const std::string& ss) final + { + switch (prio) { + case CLOG_DEBUG: + debug() << ss; + break; + case CLOG_INFO: + info() << ss; + break; + case CLOG_SEC: + sec() << ss; + break; + case CLOG_WARN: + warn() << ss; + break; + case CLOG_ERROR: + default: + error() << ss; + break; + } + } + + virtual ~MockLog() {} + + int err_count{0}; + int expected_err_count{0}; + void set_expected_err_count(int c) { expected_err_count = c; } +}; + diff --git a/src/test/osd/MockPGBackend.h b/src/test/osd/MockPGBackend.h new file mode 100644 index 000000000000..5be8a218664a --- /dev/null +++ b/src/test/osd/MockPGBackend.h @@ -0,0 +1,159 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:nil -*- +// vim: ts=8 sw=2 sts=2 expandtab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2026 IBM + * + * 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. + * + */ + +#pragma once + +#include +#include +#include +#include +#include "osd/PGBackend.h" +#include "osd/ECUtil.h" +#include "os/ObjectStore.h" + +// MockPGBackend - simple stub for PGBackend +class MockPGBackend : public PGBackend { +public: + MockPGBackend(CephContext* cct, Listener *l, ObjectStore *store, + const coll_t &coll, ObjectStore::CollectionHandle &ch) + : PGBackend(cct, l, store, coll, ch) {} + + // Recovery operations + RecoveryHandle *open_recovery_op() override { + return nullptr; + } + + void run_recovery_op(RecoveryHandle *h, int priority) override { + } + + int recover_object( + const hobject_t &hoid, + eversion_t v, + ObjectContextRef head, + ObjectContextRef obc, + RecoveryHandle *h) override { + return 0; + } + + // Message handling + bool can_handle_while_inactive(OpRequestRef op) override { + return false; + } + + bool _handle_message(OpRequestRef op) override { + return false; + } + + void check_recovery_sources(const OSDMapRef& osdmap) override { + } + + // State management + void on_change() override { + } + + void clear_recovery_state() override { + } + + // Predicates + IsPGRecoverablePredicate *get_is_recoverable_predicate() const override { + return nullptr; + } + + IsPGReadablePredicate *get_is_readable_predicate() const override { + return nullptr; + } + + bool get_ec_supports_crc_encode_decode() const override { + return false; + } + + void dump_recovery_info(ceph::Formatter *f) const override { + } + + bool ec_can_decode(const shard_id_set &available_shards) const override { + return false; + } + + shard_id_map ec_encode_acting_set( + const bufferlist &in_bl) const override { + return {0}; + } + + shard_id_map ec_decode_acting_set( + const shard_id_map &shard_map, int chunk_size) const override { + return {0}; + } + + ECUtil::stripe_info_t ec_get_sinfo() const override { + return {0, 0, 0}; + } + + // Transaction submission + void submit_transaction( + const hobject_t &hoid, + const object_stat_sum_t &delta_stats, + const eversion_t &at_version, + PGTransactionUPtr &&t, + const eversion_t &trim_to, + const eversion_t &pg_committed_to, + std::vector&& log_entries, + std::optional &hset_history, + Context *on_all_commit, + ceph_tid_t tid, + osd_reqid_t reqid, + OpRequestRef op) override { + } + + void call_write_ordered(std::function &&cb) override { + cb(); + } + + // Object operations + int objects_read_sync( + const hobject_t &hoid, + uint64_t off, + uint64_t len, + uint32_t op_flags, + ceph::buffer::list *bl) override { + return 0; + } + + void objects_read_async( + const hobject_t &hoid, + uint64_t object_size, + const std::list>> &to_read, + Context *on_complete, bool fast_read = false) override { + } + + bool auto_repair_supported() const override { + return false; + } + + uint64_t be_get_ondisk_size(uint64_t logical_size, + shard_id_t shard_id, + bool object_is_legacy_ec) const override { + return logical_size; + } + + int be_deep_scrub( + const Scrub::ScrubCounterSet& io_counters, + const hobject_t &oid, + ScrubMap &map, + ScrubMapBuilder &pos, + ScrubMap::object &o) override { + return 0; + } +}; + diff --git a/src/test/osd/MockPGBackendListener.h b/src/test/osd/MockPGBackendListener.h new file mode 100644 index 000000000000..219e5298b616 --- /dev/null +++ b/src/test/osd/MockPGBackendListener.h @@ -0,0 +1,401 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:nil -*- +// vim: ts=8 sw=2 sts=2 expandtab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2026 IBM + * + * 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. + * + */ + +#pragma once + +#include +#include +#include +#include "osd/PGBackend.h" +#include "osd/OSDMap.h" +#include "osd/osd_types.h" +#include "osd/PGLog.h" +#include "common/intrusive_timer.h" +#include "common/ostream_temp.h" +#include "global/global_context.h" +#include "os/ObjectStore.h" + +// MockPGBackendListener - simple stub for PGBackend::Listener +class MockPGBackendListener : public PGBackend::Listener { +public: + pg_info_t info; + OSDMapRef osdmap; + const pg_pool_t pool; + PGLog log; + DoutPrefixProvider *dpp; + pg_shard_t pg_whoami; + std::set shardset; + std::map shard_info; + std::map shard_missing; + std::map> missing_loc_shards; + pg_missing_tracker_t local_missing; + + MockPGBackendListener(OSDMapRef osdmap, const pg_pool_t pi, DoutPrefixProvider *dpp, pg_shard_t pg_whoami) : + osdmap(osdmap), pool(pi), log(g_ceph_context), dpp(dpp), pg_whoami(pg_whoami) {} + + // Debugging + DoutPrefixProvider *get_dpp() override { + return dpp; + } + + // Recovery callbacks + void on_local_recover( + const hobject_t &oid, + const ObjectRecoveryInfo &recovery_info, + ObjectContextRef obc, + bool is_delete, + ObjectStore::Transaction *t) override { + } + + void on_global_recover( + const hobject_t &oid, + const object_stat_sum_t &stat_diff, + bool is_delete) override { + } + + void on_peer_recover( + pg_shard_t peer, + const hobject_t &oid, + const ObjectRecoveryInfo &recovery_info) override { + } + + void begin_peer_recover( + pg_shard_t peer, + const hobject_t oid) override { + } + + void apply_stats( + const hobject_t &soid, + const object_stat_sum_t &delta_stats) override { + } + + void on_failed_pull( + const std::set &from, + const hobject_t &soid, + const eversion_t &v) override { + } + + void cancel_pull(const hobject_t &soid) override { + } + + void remove_missing_object( + const hobject_t &oid, + eversion_t v, + Context *on_complete) override { + } + + // Locking + void pg_lock() override {} + void pg_unlock() override {} + void pg_add_ref() override {} + void pg_dec_ref() override {} + + // Context wrapping + Context *bless_context(Context *c) override { + return c; + } + + GenContext *bless_gencontext( + GenContext *c) override { + return c; + } + + GenContext *bless_unlocked_gencontext( + GenContext *c) override { + return c; + } + + // Messaging + void send_message(int to_osd, Message *m) override { + } + + void queue_transaction( + ObjectStore::Transaction&& t, + OpRequestRef op = OpRequestRef()) override { + } + + void queue_transactions( + std::vector& tls, + OpRequestRef op = OpRequestRef()) override { + } + + epoch_t get_interval_start_epoch() const override { + return 1; + } + + epoch_t get_last_peering_reset_epoch() const override { + return 1; + } + + // Shard information + const std::set &get_acting_recovery_backfill_shards() const override { + return shardset; + } + + const std::set &get_acting_shards() const override { + return shardset; + } + + const std::set &get_backfill_shards() const override { + return shardset; + } + + std::ostream& gen_dbg_prefix(std::ostream& out) const override { + return out << "MockPGBackend "; + } + + const std::map> &get_missing_loc_shards() const override { + return missing_loc_shards; + } + + const pg_missing_tracker_t &get_local_missing() const override { + return local_missing; + } + + void add_local_next_event(const pg_log_entry_t& e) override { + } + + const std::map &get_shard_missing() const override { + return shard_missing; + } + + const pg_missing_const_i &get_shard_missing(pg_shard_t peer) const override { + return local_missing; + } + + const std::map &get_shard_info() const override { + return shard_info; + } + + const PGLog &get_log() const override { + return log; + } + + bool pgb_is_primary() const override { + return true; + } + + const OSDMapRef& pgb_get_osdmap() const override { + return osdmap; + } + + epoch_t pgb_get_osdmap_epoch() const override { + return osdmap->get_epoch(); + } + + const pg_info_t &get_info() const override { + return info; + } + + const pg_pool_t &get_pool() const override { + return pool; + } + + eversion_t get_pg_committed_to() const override { + return eversion_t(); + } + + ObjectContextRef get_obc( + const hobject_t &hoid, + const std::map> &attrs) override { + return ObjectContextRef(); + } + + bool try_lock_for_read( + const hobject_t &hoid, + ObcLockManager &manager) override { + return true; + } + + void release_locks(ObcLockManager &manager) override { + } + + void op_applied(const eversion_t &applied_version) override { + } + + bool should_send_op(pg_shard_t peer, const hobject_t &hoid) override { + return true; + } + + bool pg_is_undersized() const override { + return false; + } + + bool pg_is_repair() const override { + return false; + } + +#if POOL_MIGRATION + void update_migration_watermark(const hobject_t &watermark) override { + } +#endif + +#if POOL_MIGRATION + std::optional consider_updating_migration_watermark( + std::set &deleted) override { + return std::nullopt; + } +#endif + + void log_operation( + std::vector&& logv, + const std::optional &hset_history, + const eversion_t &trim_to, + const eversion_t &roll_forward_to, + const eversion_t &pg_committed_to, + bool transaction_applied, + ObjectStore::Transaction &t, + bool async = false) override { + } + + void pgb_set_object_snap_mapping( + const hobject_t &soid, + const std::set &snaps, + ObjectStore::Transaction *t) override { + } + + void pgb_clear_object_snap_mapping( + const hobject_t &soid, + ObjectStore::Transaction *t) override { + } + + void update_peer_last_complete_ondisk( + pg_shard_t fromosd, + eversion_t lcod) override { + } + + void update_last_complete_ondisk(eversion_t lcod) override { + } + + void update_pct(eversion_t pct) override { + } + + void update_stats(const pg_stat_t &stat) override { + } + + void schedule_recovery_work( + GenContext *c, + uint64_t cost) override { + } + + common::intrusive_timer &get_pg_timer() override { + ceph_abort("Not supported"); + } + + pg_shard_t whoami_shard() const override { + return pg_whoami; + } + + spg_t primary_spg_t() const override { + return spg_t(); + } + + pg_shard_t primary_shard() const override { + return pg_shard_t(); + } + + uint64_t min_peer_features() const override { + return CEPH_FEATURES_ALL; + } + + uint64_t min_upacting_features() const override { + return CEPH_FEATURES_ALL; + } + + pg_feature_vec_t get_pg_acting_features() const override { + return pg_feature_vec_t(); + } + + hobject_t get_temp_recovery_object( + const hobject_t& target, + eversion_t version) override { + return hobject_t(); + } + + void send_message_osd_cluster( + int peer, Message *m, epoch_t from_epoch) override { + } + + void send_message_osd_cluster( + std::vector>& messages, epoch_t from_epoch) override { + } + + void send_message_osd_cluster(MessageRef, Connection *con) override { + } + + void send_message_osd_cluster(Message *m, const ConnectionRef& con) override { + } + + void start_mon_command( + std::vector&& cmd, bufferlist&& inbl, + bufferlist *outbl, std::string *outs, + Context *onfinish) override { + } + + ConnectionRef get_con_osd_cluster(int peer, epoch_t from_epoch) override { + return nullptr; + } + + entity_name_t get_cluster_msgr_name() override { + return entity_name_t(); + } + + PerfCounters *get_logger() override { + return nullptr; + } + + ceph_tid_t get_tid() override { + return 0; + } + + OstreamTemp clog_error() override { + return OstreamTemp(CLOG_ERROR, nullptr); + } + + OstreamTemp clog_warn() override { + return OstreamTemp(CLOG_WARN, nullptr); + } + + bool check_failsafe_full() override { + return false; + } + + void inc_osd_stat_repaired() override { + } + + bool pg_is_remote_backfilling() override { + return false; + } + + void pg_add_local_num_bytes(int64_t num_bytes) override { + } + + void pg_sub_local_num_bytes(int64_t num_bytes) override { + } + + void pg_add_num_bytes(int64_t num_bytes) override { + } + + void pg_sub_num_bytes(int64_t num_bytes) override { + } + + bool maybe_preempt_replica_scrub(const hobject_t& oid) override { + return false; + } + + struct ECListener *get_eclistener() override { + return nullptr; + } +}; + diff --git a/src/test/osd/MockPGLogEntryHandler.h b/src/test/osd/MockPGLogEntryHandler.h new file mode 100644 index 000000000000..629d335de61b --- /dev/null +++ b/src/test/osd/MockPGLogEntryHandler.h @@ -0,0 +1,71 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:nil -*- +// vim: ts=8 sw=2 sts=2 expandtab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2026 IBM + * + * 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. + * + */ + +#pragma once + +#include "osd/PGLog.h" +#include "os/ObjectStore.h" +#include "test/osd/MockPGBackend.h" + +// dout using global context and OSD subsystem +#define dout_context g_ceph_context +#define dout_subsys ceph_subsys_osd + +// MockPGLogEntryHandler +// +// This is a fully functional implementation of the PGLog::LogEntryHandler +// interface. It calls code in PGBackend to perform the requested operations +// although some of the stubs in MockPGBackend return questionable information +// about the object size so the generated ObjectStore::Transaction is probably +// not correct. The main purpose is to use partial_write to update the PWLC +// information when appending entries to the log +class MockPGLogEntryHandler : public PGLog::LogEntryHandler { + public: + MockPGBackend *backend; + ObjectStore::Transaction *t; + MockPGLogEntryHandler(MockPGBackend *backend, ObjectStore::Transaction *t) : backend(backend), t(t) {} + + // LogEntryHandler + void remove(const hobject_t &hoid) override { + dout(0) << "MockPGLogEntryHandler::remove " << hoid << dendl; + backend->remove(hoid, t); + } + void try_stash(const hobject_t &hoid, version_t v) override { + dout(0) << "MockPGLogEntryHandler::try_stash " << hoid << " " << v << dendl; + backend->try_stash(hoid, v, t); + } + void rollback(const pg_log_entry_t &entry) override { + dout(0) << "MockPGLogEntryHandler::rollback " << entry << dendl; + ceph_assert(entry.can_rollback()); + backend->rollback(entry, t); + } + void rollforward(const pg_log_entry_t &entry) override { + dout(0) << "MockPGLogEntryHandler::rollforward " << entry << dendl; + backend->rollforward(entry, t); + } + void trim(const pg_log_entry_t &entry) override { + dout(0) << "MockPGLogEntryHandler::trim " << entry << dendl; + backend->trim(entry, t); + } + void partial_write(pg_info_t *info, eversion_t previous_version, + const pg_log_entry_t &entry + ) override { + dout(0) << "MockPGLogEntryHandler::partial_write " << entry << dendl; + backend->partial_write(info, previous_version, entry); + } +}; + +#undef dout_context +#undef dout_subsys + diff --git a/src/test/osd/MockPeeringListener.h b/src/test/osd/MockPeeringListener.h new file mode 100644 index 000000000000..d2e9140c2399 --- /dev/null +++ b/src/test/osd/MockPeeringListener.h @@ -0,0 +1,572 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:nil -*- +// vim: ts=8 sw=2 sts=2 expandtab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2026 IBM + * + * 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. + * + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include "osd/PeeringState.h" +#include "osd/osd_perf_counters.h" +#include "common/perf_counters_collection.h" +#include "global/global_context.h" +#include "os/ObjectStore.h" +#include "test/osd/MockLog.h" +#include "test/osd/MockPGBackend.h" +#include "test/osd/MockPGBackendListener.h" +#include "test/osd/MockPGLogEntryHandler.h" + +// dout using global context and OSD subsystem +#define dout_context g_ceph_context +#define dout_subsys ceph_subsys_osd + +using namespace std; + +// Mock PeeringListener - stub of PeeringState::PeeringListener +// to help with testing of PeeringState. Keep track of calls +// from PeeringState and emulate some of PrimaryLogPG/PG +// functionality for testing purposes. +// +// There are some inject_* variables that can be used to help +// tests create race hazards or test failure paths +class MockPeeringListener : public PeeringState::PeeringListener { + public: + pg_shard_t pg_whoami; + MockLog logger; + PeeringState *ps; + unique_ptr backend_listener; + coll_t coll; + ObjectStore::CollectionHandle ch; + unique_ptr backend; + PerfCounters* recoverystate_perf; + PerfCounters* logger_perf; + std::vector next_acting; + +#ifdef WITH_CRIMSON + // Per OSD state + std::map> messages; +#else + // Per OSD state + std::map> messages; +#endif + std::vector hb_stamps; + std::list events; + std::list stalled_events; + + // By default MockPeeringListener will add events to the event queue immediately + // simulating the responses that PrimaryLogPG normally generates. These inject + // booleans can change the behavior to test other code paths + + // If inject_event_stall is true then events are added to the stalled_events list + // and the test case must manually dispatch the event + bool inject_event_stall = false; + + // If inject_keep_preempt is true then the preempt event for a local/remote + // reservation is added to the stalled_events list so the test case can later + // dispatch this event to test a preempted reservation + bool inject_keep_preempt = false; + + // If inject_fail_reserve_recovery_space is true then reject backfill/pool + // migration requests with too full + bool inject_fail_reserve_recovery_space = false; + + MockPeeringListener(OSDMapRef osdmap, + const pg_pool_t pi, + DoutPrefixProvider *dpp, + pg_shard_t pg_whoami) : pg_whoami(pg_whoami) { + backend_listener = make_unique(osdmap, pi, dpp, pg_whoami); + backend = make_unique(g_ceph_context, backend_listener.get(), nullptr, coll, ch); + recoverystate_perf = build_recoverystate_perf(g_ceph_context); + g_ceph_context->get_perfcounters_collection()->add(recoverystate_perf); + logger_perf = build_osd_logger(g_ceph_context); + g_ceph_context->get_perfcounters_collection()->add(logger_perf); + } + + // EpochSource interface + epoch_t get_osdmap_epoch() const override { + return current_epoch; + } + + // PeeringListener interface + void prepare_write( + pg_info_t &info, + pg_info_t &last_written_info, + PastIntervals &past_intervals, + PGLog &pglog, + bool dirty_info, + bool dirty_big_info, + bool need_write_epoch, + ObjectStore::Transaction &t) override { + prepare_write_called = true; + } + + void scrub_requested(scrub_level_t scrub_level, scrub_type_t scrub_type) override { + scrub_requested_called = true; + } + + uint64_t get_snap_trimq_size() const override { + return snap_trimq_size; + } + +#ifdef WITH_CRIMSON + void send_cluster_message( + int osd, MessageURef m, epoch_t epoch, bool share_map_update=false) override { + dout(0) << "send_cluster_message to " << osd << " " << m << dendl; + messages[osd].push_back(m); + messages_sent++; + } +#else + void send_cluster_message( + int osd, MessageRef m, epoch_t epoch, bool share_map_update=false) override { + dout(0) << "send_cluster_message to " << osd << " " << m << dendl; + messages[osd].push_back(m); + messages_sent++; + } +#endif + + void send_pg_created(pg_t pgid) override { + pg_created_sent = true; + } + ceph::signedspan get_mnow() const override { + return ceph::signedspan::zero(); + } + + HeartbeatStampsRef get_hb_stamps(int peer) override { + if (peer >= (int)hb_stamps.size()) { + hb_stamps.resize(peer + 1); + } + if (!hb_stamps[peer]) { + hb_stamps[peer] = ceph::make_ref(peer); + } + return hb_stamps[peer]; + } + + void schedule_renew_lease(epoch_t plr, ceph::timespan delay) override { + renew_lease_scheduled = true; + } + + void queue_check_readable(epoch_t lpr, ceph::timespan delay) override { + check_readable_queued = true; + } + + void recheck_readable() override { + readable_rechecked = true; + } + + unsigned get_target_pg_log_entries() const override { + return target_pg_log_entries; + } + + + bool try_flush_or_schedule_async() override { + return true; + } + + void start_flush_on_transaction(ObjectStore::Transaction &t) override { + flush_started = true; + } + + void on_flushed() override { + flushed = true; + } + + void schedule_event_after( + PGPeeringEventRef event, + float delay) override { + stalled_events.push_back(std::move(event)); + events_scheduled++; + } + + void request_local_background_io_reservation( + unsigned priority, + PGPeeringEventURef on_grant, + PGPeeringEventURef on_preempt) override { + if (inject_event_stall) { + stalled_events.push_back(std::move(on_grant)); + } else { + events.push_back(std::move(on_grant)); + } + if (inject_keep_preempt) { + stalled_events.push_back(std::move(on_preempt)); + } + io_reservations_requested++; + } + + void update_local_background_io_priority( + unsigned priority) override { + io_priority_updated = true; + } + + void cancel_local_background_io_reservation() override { + io_reservation_cancelled = true; + } + + void request_remote_recovery_reservation( + unsigned priority, + PGPeeringEventURef on_grant, + PGPeeringEventURef on_preempt) override { + if (inject_event_stall) { + stalled_events.push_back(std::move(on_grant)); + } else { + events.push_back(std::move(on_grant)); + } + if (inject_keep_preempt) { + stalled_events.push_back(std::move(on_preempt)); + } + remote_recovery_reservations_requested++; + } + + void cancel_remote_recovery_reservation() override { + remote_recovery_reservation_cancelled = true; + } + + void schedule_event_on_commit( + ObjectStore::Transaction &t, + PGPeeringEventRef on_commit) override { + if (inject_event_stall) { + stalled_events.push_back(std::move(on_commit)); + } else { + events.push_back(std::move(on_commit)); + } + events_on_commit_scheduled++; + } + + void update_heartbeat_peers(std::set peers) override { + heartbeat_peers_updated = true; + } + + void set_probe_targets(const std::set &probe_set) override { + probe_targets_set = true; + } + + void clear_probe_targets() override { + probe_targets_cleared = true; + } + + void queue_want_pg_temp(const std::vector &wanted) override { + pg_temp_wanted = true; + next_acting = wanted; + } + + void clear_want_pg_temp() override { + pg_temp_cleared = true; + } + +#if POOL_MIGRATION + void send_pg_migrated_pool() override { + pg_migrated_pool_sent = true; + } +#endif + + void publish_stats_to_osd() override { + stats_published = true; + } + + void clear_publish_stats() override { + stats_cleared = true; + } + + void check_recovery_sources(const OSDMapRef& newmap) override { + recovery_sources_checked = true; + } + + void check_blocklisted_watchers() override { + blocklisted_watchers_checked = true; + } + + void clear_primary_state() override { + primary_state_cleared = true; + } + + void on_active_exit() override { + active_exited = true; + } + + void on_active_actmap() override { + active_actmap_called = true; + } + + void on_active_advmap(const OSDMapRef &osdmap) override { + active_advmap_called = true; + } + + void on_backfill_reserved() override { + backfill_reserved = true; + } + + void on_recovery_reserved() override { + recovery_reserved = true; + } + + Context *on_clean() override { + clean_called = true; + return nullptr; + } + + void on_activate(interval_set snaps) override { + activate_called = true; + } + + void on_change(ObjectStore::Transaction &t) override { + first_write_in_interval = true; + change_called = true; + } + + std::pair do_delete_work( + ObjectStore::Transaction &t, ghobject_t _next) override { + delete_work_done = true; + return std::make_pair(ghobject_t(), true); + } + + void clear_ready_to_merge() override { + ready_to_merge_cleared = true; + } + + void set_not_ready_to_merge_target(pg_t pgid, pg_t src) override { + not_ready_to_merge_target_set = true; + } + + void set_not_ready_to_merge_source(pg_t pgid) override { + not_ready_to_merge_source_set = true; + } + + void set_ready_to_merge_target(eversion_t lu, epoch_t les, epoch_t lec) override { + ready_to_merge_target_set = true; + } + + void set_ready_to_merge_source(eversion_t lu) override { + ready_to_merge_source_set = true; + } + + epoch_t cluster_osdmap_trim_lower_bound() override { + return 1; + } + + void on_backfill_suspended() override { + backfill_suspended = true; + } + + void on_recovery_cancelled() override { + recovery_cancelled = true; + } + +#if POOL_MIGRATION + void on_pool_migration_reserved() override { + pool_migration_reserved = true; + } +#endif + +#if POOL_MIGRATION + void on_pool_migration_suspended() override { + pool_migration_suspended = true; + } +#endif + + bool try_reserve_recovery_space( + int64_t primary_num_bytes, + int64_t local_num_bytes) override { + recovery_space_reserved = true; + if (inject_fail_reserve_recovery_space) { + return false; + } + return true; + } + + void unreserve_recovery_space() override { + recovery_space_unreserved = true; + } + + PGLog::LogEntryHandlerRef get_log_handler( + ObjectStore::Transaction &t) override { + return std::make_unique(backend.get(), &t); + } + + void rebuild_missing_set_with_deletes(PGLog &pglog) override { + missing_set_rebuilt = true; + } + + PerfCounters &get_peering_perf() override { + return *recoverystate_perf; + } + + PerfCounters &get_perf_logger() override { + return *logger_perf; + } + + void log_state_enter(const char *state) override { + last_state_entered = string(state); + state_entered = true; + } + + void log_state_exit( + const char *state_name, utime_t enter_time, + uint64_t events, utime_t event_dur) override { + last_state_exited = string(state_name); + state_exited = true; + } + + void dump_recovery_info(ceph::Formatter *f) const override { + recovery_info_dumped = true; + } + + OstreamTemp get_clog_info() override { + return logger.info(); + } + + OstreamTemp get_clog_error() override { + return logger.error(); + } + + OstreamTemp get_clog_debug() override { + return logger.debug(); + } + + void on_activate_complete() override { + dout(0) << __func__ << dendl; + std::list *event_queue; + if (inject_event_stall) { + event_queue = &stalled_events; + } else { + event_queue = &events; + } + + if (ps->needs_recovery()) { + dout(10) << "activate not all replicas are up-to-date, queueing recovery" << dendl; + event_queue->push_back( + std::make_shared( + get_osdmap_epoch(), + get_osdmap_epoch(), + PeeringState::DoRecovery())); + } else if (ps->needs_backfill()) { + dout(10) << "activate queueing backfill" << dendl; + event_queue->push_back( + std::make_shared( + get_osdmap_epoch(), + get_osdmap_epoch(), + PeeringState::RequestBackfill())); +#if POOL_MIGRATION + } else if (ps->needs_pool_migration()) { + dout(10) << "activate queueing pool migration" << dendl; + event_queue->push_back( + std::make_shared( + get_osdmap_epoch(), + get_osdmap_epoch(), + PeeringState::DoPoolMigration())); +#endif + } else { + dout(10) << "activate all replicas clean, no recovery" << dendl; + event_queue->push_back( + std::make_shared( + get_osdmap_epoch(), + get_osdmap_epoch(), + PeeringState::AllReplicasRecovered())); + } + activate_complete_called = true; + } + + void on_activate_committed() override { + activate_committed_called = true; + } + + void on_new_interval() override { + new_interval_called = true; + } + + void on_pool_change() override { + pool_changed = true; + } + + void on_role_change() override { + role_changed = true; + } + + void on_removal(ObjectStore::Transaction &t) override { + removal_called = true; + } + + // Test state tracking + unsigned target_pg_log_entries = 100; + bool renew_lease_scheduled = false; + bool check_readable_queued = false; + bool readable_rechecked = false; + bool heartbeat_peers_updated = false; + bool probe_targets_set = false; + bool probe_targets_cleared = false; + bool pg_temp_wanted = false; + bool pg_temp_cleared = false; + bool pg_migrated_pool_sent = false; + bool stats_published = false; + bool stats_cleared = false; + bool recovery_sources_checked = false; + bool blocklisted_watchers_checked = false; + bool primary_state_cleared = false; + bool delete_work_done = false; + bool ready_to_merge_cleared = false; + bool not_ready_to_merge_target_set = false; + bool not_ready_to_merge_source_set = false; + bool ready_to_merge_target_set = false; + bool ready_to_merge_source_set = false; + bool backfill_suspended = false; + bool recovery_cancelled = false; + bool pool_migration_reserved = false; + bool pool_migration_suspended = false; + bool recovery_space_reserved = false; + bool recovery_space_unreserved = false; + bool missing_set_rebuilt = false; + string last_state_entered; + bool state_entered = false; + string last_state_exited; + bool state_exited = false; + mutable bool recovery_info_dumped = false; + epoch_t current_epoch = 1; + uint64_t snap_trimq_size = 0; + bool prepare_write_called = false; + bool scrub_requested_called = false; + bool pg_created_sent = false; + bool flush_started = false; + bool flushed = false; + bool io_priority_updated = false; + bool io_reservation_cancelled = false; + bool remote_recovery_reservation_cancelled = false; + bool active_exited = false; + bool active_actmap_called = false; + bool active_advmap_called = false; + bool backfill_reserved = false; + bool backfill_cancelled = false; + bool recovery_reserved = false; + bool clean_called = false; + bool activate_called = false; + bool activate_complete_called = false; + bool change_called = false; + bool activate_committed_called = false; + bool new_interval_called = false; + bool primary_status_changed = false; + bool pool_changed = false; + bool role_changed = false; + bool removal_called = false; + bool shutdown_called = false; + int messages_sent = 0; + int events_scheduled = 0; + int io_reservations_requested = 0; + int remote_recovery_reservations_requested = 0; + int events_on_commit_scheduled = 0; + bool first_write_in_interval = false; +}; + +#undef dout_context +#undef dout_subsys + diff --git a/src/test/osd/TestPeeringState.cc b/src/test/osd/TestPeeringState.cc index 7552304842b2..1013cc30e3b6 100644 --- a/src/test/osd/TestPeeringState.cc +++ b/src/test/osd/TestPeeringState.cc @@ -31,19 +31,10 @@ #include #include -#include "osd/PeeringState.h" -#include "osd/PGBackend.h" -#include "osd/ReplicatedBackend.h" -#include "osd/ECBackend.h" -#include "osd/OSDMap.h" -#include "osd/osd_types.h" -#include "osd/osd_perf_counters.h" -#include "common/ceph_context.h" -#include "common/ostream_temp.h" -#include "common/perf_counters_collection.h" -#include "common/WorkQueue.h" -#include "common/intrusive_timer.h" -#include "global/global_context.h" +#include "test/osd/MockConnection.h" +#include "test/osd/MockECRecPred.h" +#include "test/osd/MockECReadPred.h" +#include "test/osd/MockPeeringListener.h" #include "global/global_init.h" #include "messages/MOSDPeeringOp.h" #include "msg/Connection.h" @@ -56,1236 +47,15 @@ using namespace std; -//MockConnection - simple stub. Required because PeeringState needs -//to know the features of the peer OSD which sent a peering message -class MockConnection : public Connection { - public: - MockConnection() : Connection(g_ceph_context, nullptr) { - set_features(CEPH_FEATURES_ALL); - } - - bool is_connected() override { - return true; - } - - int send_message(Message *m) override { - m->put(); - return 0; - } - - void send_keepalive() override { - } - - void mark_down() override { - } - - void mark_disposable() override { - } - - entity_addr_t get_peer_socket_addr() const override { - return entity_addr_t(); - } -}; - -//MockLog - simple stub -class MockLog : public LoggerSinkSet { - public: - void debug(std::stringstream& s) final - { - std::cout << "\n<> " << s.str() << std::endl; - } - - void info(std::stringstream& s) final - { - std::cout << "\n<> " << s.str() << std::endl; - } - - void sec(std::stringstream& s) final - { - std::cout << "\n<> " << s.str() << std::endl; - } - - void warn(std::stringstream& s) final - { - std::cout << "\n<> " << s.str() << std::endl; - } - - void error(std::stringstream& s) final - { - err_count++; - std::cout << "\n<> " << s.str() << std::endl; - } - - OstreamTemp info() final { return OstreamTemp(CLOG_INFO, this); } - OstreamTemp warn() final { return OstreamTemp(CLOG_WARN, this); } - OstreamTemp error() final { return OstreamTemp(CLOG_ERROR, this); } - OstreamTemp sec() final { return OstreamTemp(CLOG_ERROR, this); } - OstreamTemp debug() final { return OstreamTemp(CLOG_DEBUG, this); } - - void do_log(clog_type prio, std::stringstream& ss) final - { - switch (prio) { - case CLOG_DEBUG: - debug(ss); - break; - case CLOG_INFO: - info(ss); - break; - case CLOG_SEC: - sec(ss); - break; - case CLOG_WARN: - warn(ss); - break; - case CLOG_ERROR: - default: - error(ss); - break; - } - } - - void do_log(clog_type prio, const std::string& ss) final - { - switch (prio) { - case CLOG_DEBUG: - debug() << ss; - break; - case CLOG_INFO: - info() << ss; - break; - case CLOG_SEC: - sec() << ss; - break; - case CLOG_WARN: - warn() << ss; - break; - case CLOG_ERROR: - default: - error() << ss; - break; - } - } - - virtual ~MockLog() {} - - int err_count{0}; - int expected_err_count{0}; - void set_expected_err_count(int c) { expected_err_count = c; } -}; - -// MockECRecPred - simple stub for IsPGRecoverablePredicate -// Warning - this always returns true. This means we cannot test scenarios -// where there are too many OSDs down and the PG should be incomplete -class MockECRecPred : public IsPGRecoverablePredicate { - public: - MockECRecPred() {} - - bool operator()(const std::set &_have) const override { - return true; - } -}; IsPGRecoverablePredicate *get_is_recoverable_predicate() { return new MockECRecPred(); } -// MockECReadPred - simple stub for IsPGReadablePredicate -// Warning - this always returns true. This means we cannot test scenarios -// where there are too many OSDs down and the PG should be incomplete -class MockECReadPred : public IsPGReadablePredicate { - public: - MockECReadPred() {} - bool operator()(const std::set &_have) const override { - return true; - } -}; - IsPGReadablePredicate *get_is_readable_predicate() { return new MockECReadPred(); } -// MockPGBackendListener - simple stub for PGBackend::Listener -class MockPGBackendListener : public PGBackend::Listener { -public: - pg_info_t info; - OSDMapRef osdmap; - const pg_pool_t pool; - PGLog log; - DoutPrefixProvider *dpp; - pg_shard_t pg_whoami; - std::set shardset; - std::map shard_info; - std::map shard_missing; - std::map> missing_loc_shards; - pg_missing_tracker_t local_missing; - - MockPGBackendListener(OSDMapRef osdmap, const pg_pool_t pi, DoutPrefixProvider *dpp, pg_shard_t pg_whoami) : - osdmap(osdmap), pool(pi), log(g_ceph_context), dpp(dpp), pg_whoami(pg_whoami) {} - - // Debugging - DoutPrefixProvider *get_dpp() override { - return dpp; - } - - // Recovery callbacks - void on_local_recover( - const hobject_t &oid, - const ObjectRecoveryInfo &recovery_info, - ObjectContextRef obc, - bool is_delete, - ObjectStore::Transaction *t) override { - } - - void on_global_recover( - const hobject_t &oid, - const object_stat_sum_t &stat_diff, - bool is_delete) override { - } - - void on_peer_recover( - pg_shard_t peer, - const hobject_t &oid, - const ObjectRecoveryInfo &recovery_info) override { - } - - void begin_peer_recover( - pg_shard_t peer, - const hobject_t oid) override { - } - - void apply_stats( - const hobject_t &soid, - const object_stat_sum_t &delta_stats) override { - } - - void on_failed_pull( - const std::set &from, - const hobject_t &soid, - const eversion_t &v) override { - } - - void cancel_pull(const hobject_t &soid) override { - } - - void remove_missing_object( - const hobject_t &oid, - eversion_t v, - Context *on_complete) override { - } - - // Locking - void pg_lock() override {} - void pg_unlock() override {} - void pg_add_ref() override {} - void pg_dec_ref() override {} - - // Context wrapping - Context *bless_context(Context *c) override { - return c; - } - - GenContext *bless_gencontext( - GenContext *c) override { - return c; - } - - GenContext *bless_unlocked_gencontext( - GenContext *c) override { - return c; - } - - // Messaging - void send_message(int to_osd, Message *m) override { - } - - void queue_transaction( - ObjectStore::Transaction&& t, - OpRequestRef op = OpRequestRef()) override { - } - - void queue_transactions( - std::vector& tls, - OpRequestRef op = OpRequestRef()) override { - } - - epoch_t get_interval_start_epoch() const override { - return 1; - } - - epoch_t get_last_peering_reset_epoch() const override { - return 1; - } - - // Shard information - const std::set &get_acting_recovery_backfill_shards() const override { - return shardset; - } - - const std::set &get_acting_shards() const override { - return shardset; - } - - const std::set &get_backfill_shards() const override { - return shardset; - } - - std::ostream& gen_dbg_prefix(std::ostream& out) const override { - return out << "MockPGBackend "; - } - - const std::map> &get_missing_loc_shards() const override { - return missing_loc_shards; - } - - const pg_missing_tracker_t &get_local_missing() const override { - return local_missing; - } - - void add_local_next_event(const pg_log_entry_t& e) override { - } - - const std::map &get_shard_missing() const override { - return shard_missing; - } - - const pg_missing_const_i &get_shard_missing(pg_shard_t peer) const override { - return local_missing; - } - - const std::map &get_shard_info() const override { - return shard_info; - } - - const PGLog &get_log() const override { - return log; - } - - bool pgb_is_primary() const override { - return true; - } - - const OSDMapRef& pgb_get_osdmap() const override { - return osdmap; - } - - epoch_t pgb_get_osdmap_epoch() const override { - return osdmap->get_epoch(); - } - - const pg_info_t &get_info() const override { - return info; - } - - const pg_pool_t &get_pool() const override { - return pool; - } - - eversion_t get_pg_committed_to() const override { - return eversion_t(); - } - - ObjectContextRef get_obc( - const hobject_t &hoid, - const std::map> &attrs) override { - return ObjectContextRef(); - } - - bool try_lock_for_read( - const hobject_t &hoid, - ObcLockManager &manager) override { - return true; - } - - void release_locks(ObcLockManager &manager) override { - } - - void op_applied(const eversion_t &applied_version) override { - } - - bool should_send_op(pg_shard_t peer, const hobject_t &hoid) override { - return true; - } - - bool pg_is_undersized() const override { - return false; - } - - bool pg_is_repair() const override { - return false; - } - -#if POOL_MIGRATION - void update_migration_watermark(const hobject_t &watermark) override { - } -#endif - -#if POOL_MIGRATION - std::optional consider_updating_migration_watermark( - std::set &deleted) override { - return std::nullopt; - } -#endif - - void log_operation( - std::vector&& logv, - const std::optional &hset_history, - const eversion_t &trim_to, - const eversion_t &roll_forward_to, - const eversion_t &pg_committed_to, - bool transaction_applied, - ObjectStore::Transaction &t, - bool async = false) override { - } - - void pgb_set_object_snap_mapping( - const hobject_t &soid, - const std::set &snaps, - ObjectStore::Transaction *t) override { - } - - void pgb_clear_object_snap_mapping( - const hobject_t &soid, - ObjectStore::Transaction *t) override { - } - - void update_peer_last_complete_ondisk( - pg_shard_t fromosd, - eversion_t lcod) override { - } - - void update_last_complete_ondisk(eversion_t lcod) override { - } - - void update_pct(eversion_t pct) override { - } - - void update_stats(const pg_stat_t &stat) override { - } - - void schedule_recovery_work( - GenContext *c, - uint64_t cost) override { - } - - common::intrusive_timer &get_pg_timer() override { - ceph_abort("Not supported"); - } - - pg_shard_t whoami_shard() const override { - return pg_whoami; - } - - spg_t primary_spg_t() const override { - return spg_t(); - } - - pg_shard_t primary_shard() const override { - return pg_shard_t(); - } - - uint64_t min_peer_features() const override { - return CEPH_FEATURES_ALL; - } - - uint64_t min_upacting_features() const override { - return CEPH_FEATURES_ALL; - } - - pg_feature_vec_t get_pg_acting_features() const override { - return pg_feature_vec_t(); - } - - hobject_t get_temp_recovery_object( - const hobject_t& target, - eversion_t version) override { - return hobject_t(); - } - - void send_message_osd_cluster( - int peer, Message *m, epoch_t from_epoch) override { - } - - void send_message_osd_cluster( - std::vector>& messages, epoch_t from_epoch) override { - } - - void send_message_osd_cluster(MessageRef, Connection *con) override { - } - - void send_message_osd_cluster(Message *m, const ConnectionRef& con) override { - } - - void start_mon_command( - std::vector&& cmd, bufferlist&& inbl, - bufferlist *outbl, std::string *outs, - Context *onfinish) override { - } - - ConnectionRef get_con_osd_cluster(int peer, epoch_t from_epoch) override { - return nullptr; - } - - entity_name_t get_cluster_msgr_name() override { - return entity_name_t(); - } - - PerfCounters *get_logger() override { - return nullptr; - } - - ceph_tid_t get_tid() override { - return 0; - } - - OstreamTemp clog_error() override { - return OstreamTemp(CLOG_ERROR, nullptr); - } - - OstreamTemp clog_warn() override { - return OstreamTemp(CLOG_WARN, nullptr); - } - - bool check_failsafe_full() override { - return false; - } - - void inc_osd_stat_repaired() override { - } - - bool pg_is_remote_backfilling() override { - return false; - } - - void pg_add_local_num_bytes(int64_t num_bytes) override { - } - - void pg_sub_local_num_bytes(int64_t num_bytes) override { - } - - void pg_add_num_bytes(int64_t num_bytes) override { - } - - void pg_sub_num_bytes(int64_t num_bytes) override { - } - - bool maybe_preempt_replica_scrub(const hobject_t& oid) override { - return false; - } - - struct ECListener *get_eclistener() override { - return nullptr; - } -}; - -// MockPGBackend - simple stub for PGBackend -class MockPGBackend : public PGBackend { -public: - MockPGBackend(CephContext* cct, Listener *l, ObjectStore *store, - const coll_t &coll, ObjectStore::CollectionHandle &ch) - : PGBackend(cct, l, store, coll, ch) {} - - // Recovery operations - RecoveryHandle *open_recovery_op() override { - return nullptr; - } - - void run_recovery_op(RecoveryHandle *h, int priority) override { - } - - int recover_object( - const hobject_t &hoid, - eversion_t v, - ObjectContextRef head, - ObjectContextRef obc, - RecoveryHandle *h) override { - return 0; - } - - // Message handling - bool can_handle_while_inactive(OpRequestRef op) override { - return false; - } - - bool _handle_message(OpRequestRef op) override { - return false; - } - - void check_recovery_sources(const OSDMapRef& osdmap) override { - } - - // State management - void on_change() override { - } - - void clear_recovery_state() override { - } - - // Predicates - IsPGRecoverablePredicate *get_is_recoverable_predicate() const override { - return nullptr; - } - - IsPGReadablePredicate *get_is_readable_predicate() const override { - return nullptr; - } - - bool get_ec_supports_crc_encode_decode() const override { - return false; - } - - void dump_recovery_info(ceph::Formatter *f) const override { - } - - bool ec_can_decode(const shard_id_set &available_shards) const override { - return false; - } - - shard_id_map ec_encode_acting_set( - const bufferlist &in_bl) const override { - return {0}; - } - - shard_id_map ec_decode_acting_set( - const shard_id_map &shard_map, int chunk_size) const override { - return {0}; - } - - ECUtil::stripe_info_t ec_get_sinfo() const override { - return {0, 0, 0}; - } - - // Transaction submission - void submit_transaction( - const hobject_t &hoid, - const object_stat_sum_t &delta_stats, - const eversion_t &at_version, - PGTransactionUPtr &&t, - const eversion_t &trim_to, - const eversion_t &pg_committed_to, - std::vector&& log_entries, - std::optional &hset_history, - Context *on_all_commit, - ceph_tid_t tid, - osd_reqid_t reqid, - OpRequestRef op) override { - } - - void call_write_ordered(std::function &&cb) override { - cb(); - } - - // Object operations - int objects_read_sync( - const hobject_t &hoid, - uint64_t off, - uint64_t len, - uint32_t op_flags, - ceph::buffer::list *bl) override { - return 0; - } - - void objects_read_async( - const hobject_t &hoid, - uint64_t object_size, - const std::list>> &to_read, - Context *on_complete, bool fast_read = false) override { - } - - bool auto_repair_supported() const override { - return false; - } - - uint64_t be_get_ondisk_size(uint64_t logical_size, - shard_id_t shard_id, - bool object_is_legacy_ec) const override { - return logical_size; - } - - int be_deep_scrub( - const Scrub::ScrubCounterSet& io_counters, - const hobject_t &oid, - ScrubMap &map, - ScrubMapBuilder &pos, - ScrubMap::object &o) override { - return 0; - } -}; - -// MockPGLogEntryHandler -// -// This is a fully functional implementation of the PGLog::LogEntryHandler -// interface. It calls code in PGBackend to perform the requested operations -// although some of the stubs in MockPGBackend return questionable information -// about the object size so the generated ObjectStore::Transaction is probably -// not correct. The main purpose is to use partial_write to update the PWLC -// information when appending entries to the log -class MockPGLogEntryHandler : public PGLog::LogEntryHandler { - public: - MockPGBackend *backend; - ObjectStore::Transaction *t; - MockPGLogEntryHandler(MockPGBackend *backend, ObjectStore::Transaction *t) : backend(backend), t(t) {} - - // LogEntryHandler - void remove(const hobject_t &hoid) override { - dout(0) << "MockPGLogEntryHandler::remove " << hoid << dendl; - backend->remove(hoid, t); - } - void try_stash(const hobject_t &hoid, version_t v) override { - dout(0) << "MockPGLogEntryHandler::try_stash " << hoid << " " << v << dendl; - backend->try_stash(hoid, v, t); - } - void rollback(const pg_log_entry_t &entry) override { - dout(0) << "MockPGLogEntryHandler::rollback " << entry << dendl; - ceph_assert(entry.can_rollback()); - backend->rollback(entry, t); - } - void rollforward(const pg_log_entry_t &entry) override { - dout(0) << "MockPGLogEntryHandler::rollforward " << entry << dendl; - backend->rollforward(entry, t); - } - void trim(const pg_log_entry_t &entry) override { - dout(0) << "MockPGLogEntryHandler::trim " << entry << dendl; - backend->trim(entry, t); - } - void partial_write(pg_info_t *info, eversion_t previous_version, - const pg_log_entry_t &entry - ) override { - dout(0) << "MockPGLogEntryHandler::partial_write " << entry << dendl; - backend->partial_write(info, previous_version, entry); - } -}; - -// Mock PeeringListener - stub of PeeringState::PeeringListener -// to help with testing of PeeringState. Keep track of calls -// from PeeringState and emulate some of PrimaryLogPG/PG -// functionality for testing purposes. -// -// There are some inject_* variables that can be used to help -// tests create race hazards or test failure paths -class MockPeeringListener : public PeeringState::PeeringListener { - public: - pg_shard_t pg_whoami; - MockLog logger; - PeeringState *ps; - unique_ptr backend_listener; - coll_t coll; - ObjectStore::CollectionHandle ch; - unique_ptr backend; - PerfCounters* recoverystate_perf; - PerfCounters* logger_perf; - std::vector next_acting; - -#ifdef WITH_CRIMSON - // Per OSD state - std::map> messages; -#else - // Per OSD state - std::map> messages; -#endif - std::vector hb_stamps; - std::list events; - std::list stalled_events; - - // By default MockPeeringListener will add events to the event queue immediately - // simulating the responses that PrimaryLogPG normally generates. These inject - // booleans can change the behavior to test other code paths - - // If inject_event_stall is true then events are added to the stalled_events list - // and the test case must manually dispatch the event - bool inject_event_stall = false; - - // If inject_keep_preempt is true then the preempt event for a local/remote - // reservation is added to the stalled_events list so the test case can later - // dispatch this event to test a preempted reservation - bool inject_keep_preempt = false; - - // If inject_fail_reserve_recovery_space is true then reject backfill/pool - // migration requests with too full - bool inject_fail_reserve_recovery_space = false; - - MockPeeringListener(OSDMapRef osdmap, - const pg_pool_t pi, - DoutPrefixProvider *dpp, - pg_shard_t pg_whoami) : pg_whoami(pg_whoami) { - backend_listener = make_unique(osdmap, pi, dpp, pg_whoami); - backend = make_unique(g_ceph_context, backend_listener.get(), nullptr, coll, ch); - recoverystate_perf = build_recoverystate_perf(g_ceph_context); - g_ceph_context->get_perfcounters_collection()->add(recoverystate_perf); - logger_perf = build_osd_logger(g_ceph_context); - g_ceph_context->get_perfcounters_collection()->add(logger_perf); - } - - // EpochSource interface - epoch_t get_osdmap_epoch() const override { - return current_epoch; - } - - // PeeringListener interface - void prepare_write( - pg_info_t &info, - pg_info_t &last_written_info, - PastIntervals &past_intervals, - PGLog &pglog, - bool dirty_info, - bool dirty_big_info, - bool need_write_epoch, - ObjectStore::Transaction &t) override { - prepare_write_called = true; - } - - void scrub_requested(scrub_level_t scrub_level, scrub_type_t scrub_type) override { - scrub_requested_called = true; - } - - uint64_t get_snap_trimq_size() const override { - return snap_trimq_size; - } - -#ifdef WITH_CRIMSON - void send_cluster_message( - int osd, MessageURef m, epoch_t epoch, bool share_map_update=false) override { - dout(0) << "send_cluster_message to " << osd << " " << m << dendl; - messages[osd].push_back(m); - messages_sent++; - } -#else - void send_cluster_message( - int osd, MessageRef m, epoch_t epoch, bool share_map_update=false) override { - dout(0) << "send_cluster_message to " << osd << " " << m << dendl; - messages[osd].push_back(m); - messages_sent++; - } -#endif - - void send_pg_created(pg_t pgid) override { - pg_created_sent = true; - } - ceph::signedspan get_mnow() const override { - return ceph::signedspan::zero(); - } - - HeartbeatStampsRef get_hb_stamps(int peer) override { - if (peer >= (int)hb_stamps.size()) { - hb_stamps.resize(peer + 1); - } - if (!hb_stamps[peer]) { - hb_stamps[peer] = ceph::make_ref(peer); - } - return hb_stamps[peer]; - } - - void schedule_renew_lease(epoch_t plr, ceph::timespan delay) override { - renew_lease_scheduled = true; - } - - void queue_check_readable(epoch_t lpr, ceph::timespan delay) override { - check_readable_queued = true; - } - - void recheck_readable() override { - readable_rechecked = true; - } - - unsigned get_target_pg_log_entries() const override { - return target_pg_log_entries; - } - - - bool try_flush_or_schedule_async() override { - return true; - } - - void start_flush_on_transaction(ObjectStore::Transaction &t) override { - flush_started = true; - } - - void on_flushed() override { - flushed = true; - } - - void schedule_event_after( - PGPeeringEventRef event, - float delay) override { - stalled_events.push_back(std::move(event)); - events_scheduled++; - } - - void request_local_background_io_reservation( - unsigned priority, - PGPeeringEventURef on_grant, - PGPeeringEventURef on_preempt) override { - if (inject_event_stall) { - stalled_events.push_back(std::move(on_grant)); - } else { - events.push_back(std::move(on_grant)); - } - if (inject_keep_preempt) { - stalled_events.push_back(std::move(on_preempt)); - } - io_reservations_requested++; - } - - void update_local_background_io_priority( - unsigned priority) override { - io_priority_updated = true; - } - - void cancel_local_background_io_reservation() override { - io_reservation_cancelled = true; - } - - void request_remote_recovery_reservation( - unsigned priority, - PGPeeringEventURef on_grant, - PGPeeringEventURef on_preempt) override { - if (inject_event_stall) { - stalled_events.push_back(std::move(on_grant)); - } else { - events.push_back(std::move(on_grant)); - } - if (inject_keep_preempt) { - stalled_events.push_back(std::move(on_preempt)); - } - remote_recovery_reservations_requested++; - } - - void cancel_remote_recovery_reservation() override { - remote_recovery_reservation_cancelled = true; - } - - void schedule_event_on_commit( - ObjectStore::Transaction &t, - PGPeeringEventRef on_commit) override { - if (inject_event_stall) { - stalled_events.push_back(std::move(on_commit)); - } else { - events.push_back(std::move(on_commit)); - } - events_on_commit_scheduled++; - } - - void update_heartbeat_peers(std::set peers) override { - heartbeat_peers_updated = true; - } - - void set_probe_targets(const std::set &probe_set) override { - probe_targets_set = true; - } - - void clear_probe_targets() override { - probe_targets_cleared = true; - } - - void queue_want_pg_temp(const std::vector &wanted) override { - pg_temp_wanted = true; - next_acting = wanted; - } - - void clear_want_pg_temp() override { - pg_temp_cleared = true; - } - -#if POOL_MIGRATION - void send_pg_migrated_pool() override { - pg_migrated_pool_sent = true; - } -#endif - - void publish_stats_to_osd() override { - stats_published = true; - } - - void clear_publish_stats() override { - stats_cleared = true; - } - - void check_recovery_sources(const OSDMapRef& newmap) override { - recovery_sources_checked = true; - } - - void check_blocklisted_watchers() override { - blocklisted_watchers_checked = true; - } - - void clear_primary_state() override { - primary_state_cleared = true; - } - - void on_active_exit() override { - active_exited = true; - } - - void on_active_actmap() override { - active_actmap_called = true; - } - - void on_active_advmap(const OSDMapRef &osdmap) override { - active_advmap_called = true; - } - - void on_backfill_reserved() override { - backfill_reserved = true; - } - - void on_recovery_reserved() override { - recovery_reserved = true; - } - - Context *on_clean() override { - clean_called = true; - return nullptr; - } - - void on_activate(interval_set snaps) override { - activate_called = true; - } - - void on_change(ObjectStore::Transaction &t) override { - first_write_in_interval = true; - change_called = true; - } - - std::pair do_delete_work( - ObjectStore::Transaction &t, ghobject_t _next) override { - delete_work_done = true; - return std::make_pair(ghobject_t(), true); - } - - void clear_ready_to_merge() override { - ready_to_merge_cleared = true; - } - - void set_not_ready_to_merge_target(pg_t pgid, pg_t src) override { - not_ready_to_merge_target_set = true; - } - - void set_not_ready_to_merge_source(pg_t pgid) override { - not_ready_to_merge_source_set = true; - } - - void set_ready_to_merge_target(eversion_t lu, epoch_t les, epoch_t lec) override { - ready_to_merge_target_set = true; - } - - void set_ready_to_merge_source(eversion_t lu) override { - ready_to_merge_source_set = true; - } - - epoch_t cluster_osdmap_trim_lower_bound() override { - return 1; - } - - void on_backfill_suspended() override { - backfill_suspended = true; - } - - void on_recovery_cancelled() override { - recovery_cancelled = true; - } - -#if POOL_MIGRATION - void on_pool_migration_reserved() override { - pool_migration_reserved = true; - } -#endif - -#if POOL_MIGRATION - void on_pool_migration_suspended() override { - pool_migration_suspended = true; - } -#endif - - bool try_reserve_recovery_space( - int64_t primary_num_bytes, - int64_t local_num_bytes) override { - recovery_space_reserved = true; - if (inject_fail_reserve_recovery_space) { - return false; - } - return true; - } - - void unreserve_recovery_space() override { - recovery_space_unreserved = true; - } - - PGLog::LogEntryHandlerRef get_log_handler( - ObjectStore::Transaction &t) override { - return std::make_unique(backend.get(), &t); - } - - void rebuild_missing_set_with_deletes(PGLog &pglog) override { - missing_set_rebuilt = true; - } - - PerfCounters &get_peering_perf() override { - return *recoverystate_perf; - } - - PerfCounters &get_perf_logger() override { - return *logger_perf; - } - - void log_state_enter(const char *state) override { - last_state_entered = string(state); - state_entered = true; - } - - void log_state_exit( - const char *state_name, utime_t enter_time, - uint64_t events, utime_t event_dur) override { - last_state_exited = string(state_name); - state_exited = true; - } - - void dump_recovery_info(ceph::Formatter *f) const override { - recovery_info_dumped = true; - } - - OstreamTemp get_clog_info() override { - return logger.info(); - } - - OstreamTemp get_clog_error() override { - return logger.error(); - } - - OstreamTemp get_clog_debug() override { - return logger.debug(); - } - - void on_activate_complete() override { - dout(0) << __func__ << dendl; - std::list *event_queue; - if (inject_event_stall) { - event_queue = &stalled_events; - } else { - event_queue = &events; - } - - if (ps->needs_recovery()) { - dout(10) << "activate not all replicas are up-to-date, queueing recovery" << dendl; - event_queue->push_back( - std::make_shared( - get_osdmap_epoch(), - get_osdmap_epoch(), - PeeringState::DoRecovery())); - } else if (ps->needs_backfill()) { - dout(10) << "activate queueing backfill" << dendl; - event_queue->push_back( - std::make_shared( - get_osdmap_epoch(), - get_osdmap_epoch(), - PeeringState::RequestBackfill())); -#if POOL_MIGRATION - } else if (ps->needs_pool_migration()) { - dout(10) << "activate queueing pool migration" << dendl; - event_queue->push_back( - std::make_shared( - get_osdmap_epoch(), - get_osdmap_epoch(), - PeeringState::DoPoolMigration())); -#endif - } else { - dout(10) << "activate all replicas clean, no recovery" << dendl; - event_queue->push_back( - std::make_shared( - get_osdmap_epoch(), - get_osdmap_epoch(), - PeeringState::AllReplicasRecovered())); - } - activate_complete_called = true; - } - - void on_activate_committed() override { - activate_committed_called = true; - } - - void on_new_interval() override { - new_interval_called = true; - } - - void on_pool_change() override { - pool_changed = true; - } - - void on_role_change() override { - role_changed = true; - } - - void on_removal(ObjectStore::Transaction &t) override { - removal_called = true; - } - - // Test state tracking - unsigned target_pg_log_entries = 100; - bool renew_lease_scheduled = false; - bool check_readable_queued = false; - bool readable_rechecked = false; - bool heartbeat_peers_updated = false; - bool probe_targets_set = false; - bool probe_targets_cleared = false; - bool pg_temp_wanted = false; - bool pg_temp_cleared = false; - bool pg_migrated_pool_sent = false; - bool stats_published = false; - bool stats_cleared = false; - bool recovery_sources_checked = false; - bool blocklisted_watchers_checked = false; - bool primary_state_cleared = false; - bool delete_work_done = false; - bool ready_to_merge_cleared = false; - bool not_ready_to_merge_target_set = false; - bool not_ready_to_merge_source_set = false; - bool ready_to_merge_target_set = false; - bool ready_to_merge_source_set = false; - bool backfill_suspended = false; - bool recovery_cancelled = false; - bool pool_migration_reserved = false; - bool pool_migration_suspended = false; - bool recovery_space_reserved = false; - bool recovery_space_unreserved = false; - bool missing_set_rebuilt = false; - string last_state_entered; - bool state_entered = false; - string last_state_exited; - bool state_exited = false; - mutable bool recovery_info_dumped = false; - epoch_t current_epoch = 1; - uint64_t snap_trimq_size = 0; - bool prepare_write_called = false; - bool scrub_requested_called = false; - bool pg_created_sent = false; - bool flush_started = false; - bool flushed = false; - bool io_priority_updated = false; - bool io_reservation_cancelled = false; - bool remote_recovery_reservation_cancelled = false; - bool active_exited = false; - bool active_actmap_called = false; - bool active_advmap_called = false; - bool backfill_reserved = false; - bool backfill_cancelled = false; - bool recovery_reserved = false; - bool clean_called = false; - bool activate_called = false; - bool activate_complete_called = false; - bool change_called = false; - bool activate_committed_called = false; - bool new_interval_called = false; - bool primary_status_changed = false; - bool pool_changed = false; - bool role_changed = false; - bool removal_called = false; - bool shutdown_called = false; - int messages_sent = 0; - int events_scheduled = 0; - int io_reservations_requested = 0; - int remote_recovery_reservations_requested = 0; - int events_on_commit_scheduled = 0; - bool first_write_in_interval = false; -}; // Test fixture for PeeringState tests class PeeringStateTest : public ::testing::Test {