diff options
-rw-r--r-- | src/common/ceph_strings.cc | 63 | ||||
-rw-r--r-- | src/include/rados.h | 3 | ||||
-rw-r--r-- | src/messages/MMonPaxos.h | 8 | ||||
-rw-r--r-- | src/mon/MonCommands.h | 5 | ||||
-rw-r--r-- | src/mon/MonOpRequest.h | 26 | ||||
-rw-r--r-- | src/mon/Monitor.cc | 30 | ||||
-rw-r--r-- | src/mon/Monitor.h | 7 | ||||
-rw-r--r-- | src/mon/OSDMonitor.cc | 39 | ||||
-rw-r--r-- | src/mon/Paxos.cc | 8 | ||||
-rw-r--r-- | src/mon/Session.h | 19 | ||||
-rw-r--r-- | src/mon/mon_types.h | 89 | ||||
-rw-r--r-- | src/test/CMakeLists.txt | 7 | ||||
-rw-r--r-- | src/test/test_features.cc | 44 |
13 files changed, 317 insertions, 31 deletions
diff --git a/src/common/ceph_strings.cc b/src/common/ceph_strings.cc index ef3aa802761..2b7e716e177 100644 --- a/src/common/ceph_strings.cc +++ b/src/common/ceph_strings.cc @@ -2,6 +2,7 @@ * Ceph string constants */ #include "include/types.h" +#include "include/ceph_features.h" const char *ceph_entity_type_name(int type) { @@ -90,6 +91,9 @@ int ceph_release_from_name(const char *s) if (!s) { return -1; } + if (strcmp(s, "mimic") == 0) { + return CEPH_RELEASE_MIMIC; + } if (strcmp(s, "luminous") == 0) { return CEPH_RELEASE_LUMINOUS; } @@ -129,6 +133,65 @@ int ceph_release_from_name(const char *s) return -1; } +uint64_t ceph_release_features(int r) +{ + uint64_t req = 0; + + req |= CEPH_FEATURE_CRUSH_TUNABLES; + if (r <= CEPH_RELEASE_CUTTLEFISH) + return req; + + req |= CEPH_FEATURE_CRUSH_TUNABLES2 | + CEPH_FEATURE_OSDHASHPSPOOL; + if (r <= CEPH_RELEASE_EMPEROR) + return req; + + req |= CEPH_FEATURE_CRUSH_TUNABLES3 | + CEPH_FEATURE_OSD_PRIMARY_AFFINITY | + CEPH_FEATURE_OSD_CACHEPOOL; + if (r <= CEPH_RELEASE_GIANT) + return req; + + req |= CEPH_FEATURE_CRUSH_V4; + if (r <= CEPH_RELEASE_INFERNALIS) + return req; + + req |= CEPH_FEATURE_CRUSH_TUNABLES5; + if (r <= CEPH_RELEASE_JEWEL) + return req; + + req |= CEPH_FEATURE_MSG_ADDR2; + if (r <= CEPH_RELEASE_KRAKEN) + return req; + + req |= CEPH_FEATUREMASK_CRUSH_CHOOSE_ARGS; // and overlaps + if (r <= CEPH_RELEASE_LUMINOUS) + return req; + + return req; +} + +/* return oldest/first release that supports these features */ +int ceph_release_from_features(uint64_t features) +{ + int r = 1; + while (true) { + uint64_t need = ceph_release_features(r); + if ((need & features) != need || + r == CEPH_RELEASE_MAX) { + r--; + need = ceph_release_features(r); + /* we want the first release that looks like this */ + while (r > 1 && ceph_release_features(r - 1) == need) { + r--; + } + break; + } + ++r; + } + return r; +} + const char *ceph_osd_watch_op_name(int o) { switch (o) { diff --git a/src/include/rados.h b/src/include/rados.h index b2fa55b18e1..2f32eb0387f 100644 --- a/src/include/rados.h +++ b/src/include/rados.h @@ -177,9 +177,12 @@ extern const char *ceph_osd_state_name(int s); #define CEPH_RELEASE_KRAKEN 11 #define CEPH_RELEASE_LUMINOUS 12 #define CEPH_RELEASE_MIMIC 13 +#define CEPH_RELEASE_MAX 14 /* highest + 1 */ extern const char *ceph_release_name(int r); extern int ceph_release_from_name(const char *s); +extern uint64_t ceph_release_features(int r); +extern int ceph_release_from_features(uint64_t features); /* * The error code to return when an OSD can't handle a write diff --git a/src/messages/MMonPaxos.h b/src/messages/MMonPaxos.h index 8c709ddb8e6..4b21ee38a0b 100644 --- a/src/messages/MMonPaxos.h +++ b/src/messages/MMonPaxos.h @@ -22,7 +22,7 @@ class MMonPaxos : public Message { - static const int HEAD_VERSION = 3; + static const int HEAD_VERSION = 4; static const int COMPAT_VERSION = 3; public: @@ -63,6 +63,8 @@ class MMonPaxos : public Message { map<version_t,bufferlist> values; + bufferlist feature_map; + MMonPaxos() : Message(MSG_MON_PAXOS, HEAD_VERSION, COMPAT_VERSION) { } MMonPaxos(epoch_t e, int o, utime_t now) : Message(MSG_MON_PAXOS, HEAD_VERSION, COMPAT_VERSION), @@ -103,6 +105,7 @@ public: ::encode(latest_version, payload); ::encode(latest_value, payload); ::encode(values, payload); + ::encode(feature_map, payload); } void decode_payload() override { bufferlist::iterator p = payload.begin(); @@ -118,6 +121,9 @@ public: ::decode(latest_version, p); ::decode(latest_value, p); ::decode(values, p); + if (header.version >= 4) { + ::decode(feature_map, p); + } } }; diff --git a/src/mon/MonCommands.h b/src/mon/MonCommands.h index 165ffa41f98..bec3fb205d4 100644 --- a/src/mon/MonCommands.h +++ b/src/mon/MonCommands.h @@ -212,6 +212,8 @@ COMMAND("df name=detail,type=CephChoices,strings=detail,req=false", \ COMMAND("report name=tags,type=CephString,n=N,req=false", \ "report full status of cluster, optional title tag strings", \ "mon", "r", "cli,rest") +COMMAND("features", "report of connected features", \ + "mon", "r", "cli,rest") COMMAND("quorum_status", "report status of monitor quorum", \ "mon", "r", "cli,rest") @@ -608,7 +610,8 @@ COMMAND("osd set-nearfull-ratio " \ "set usage ratio at which OSDs are marked near-full", "osd", "rw", "cli,rest") COMMAND("osd set-require-min-compat-client " \ - "name=version,type=CephString", + "name=version,type=CephString " \ + "name=sure,type=CephChoices,strings=--yes-i-really-mean-it,req=false", \ "set the minimum client version we will maintain compatibility with", "osd", "rw", "cli,rest") COMMAND("osd pause", "pause osd", "osd", "rw", "cli,rest") diff --git a/src/mon/MonOpRequest.h b/src/mon/MonOpRequest.h index bd85f11f683..a5000efe44e 100644 --- a/src/mon/MonOpRequest.h +++ b/src/mon/MonOpRequest.h @@ -218,4 +218,30 @@ public: typedef MonOpRequest::Ref MonOpRequestRef; +struct C_MonOp : public Context +{ + MonOpRequestRef op; + + explicit C_MonOp(MonOpRequestRef o) : + op(o) { } + + void finish(int r) override { + if (op && r == -ECANCELED) { + op->mark_event("callback canceled"); + } else if (op && r == -EAGAIN) { + op->mark_event("callback retry"); + } else if (op && r == 0) { + op->mark_event("callback finished"); + } + _finish(r); + } + + void mark_op_event(const string &event) { + if (op) + op->mark_event_string(event); + } + + virtual void _finish(int r) = 0; +}; + #endif /* MON_OPREQUEST_H_ */ diff --git a/src/mon/Monitor.cc b/src/mon/Monitor.cc index 804bd1e6472..de1651393c8 100644 --- a/src/mon/Monitor.cc +++ b/src/mon/Monitor.cc @@ -1074,6 +1074,7 @@ void Monitor::_reset() } quorum.clear(); outside_quorum.clear(); + quorum_feature_map.clear(); scrub_reset(); @@ -2094,6 +2095,16 @@ void Monitor::calc_quorum_requirements() dout(10) << __func__ << " required_features " << required_features << dendl; } +void Monitor::get_combined_feature_map(FeatureMap *fm) +{ + *fm += session_map.feature_map; + for (auto id : quorum) { + if (id != rank) { + *fm += quorum_feature_map[id]; + } + } +} + void Monitor::sync_force(Formatter *f, ostream& ss) { bool free_formatter = false; @@ -2225,6 +2236,7 @@ void Monitor::get_mon_status(Formatter *f, ostream& ss) monmap->dump(f); f->close_section(); + f->dump_object("feature_map", session_map.feature_map); f->close_section(); // mon_status if (free_formatter) { @@ -3158,6 +3170,24 @@ void Monitor::handle_command(MonOpRequestRef op) rdata.append(ds); rs = ""; r = 0; + } else if (prefix == "features") { + if (!is_leader() && !is_peon()) { + dout(10) << " waiting for quorum" << dendl; + waitfor_quorum.push_back(new C_RetryMessage(this, op)); + return; + } + if (!is_leader()) { + forward_request_leader(op); + return; + } + if (!f) + f.reset(Formatter::create("json-pretty")); + FeatureMap fm; + get_combined_feature_map(&fm); + f->dump_object("features", fm); + f->flush(rdata); + rs = ""; + r = 0; } else if (prefix == "mon metadata") { if (!f) f.reset(Formatter::create("json-pretty")); diff --git a/src/mon/Monitor.h b/src/mon/Monitor.h index 0d3a9080e81..ff3350cdf4c 100644 --- a/src/mon/Monitor.h +++ b/src/mon/Monitor.h @@ -233,6 +233,11 @@ private: set<int> quorum; // current active set of monitors (if !starting) utime_t leader_since; // when this monitor became the leader, if it is the leader utime_t exited_quorum; // time detected as not in quorum; 0 if in + + // map of counts of connected clients, by type and features, for + // each quorum mon + map<int,FeatureMap> quorum_feature_map; + /** * Intersection of quorum member's connection feature bits. */ @@ -574,6 +579,8 @@ public: void apply_monmap_to_compatset_features(); void calc_quorum_requirements(); + void get_combined_feature_map(FeatureMap *fm); + private: void _reset(); ///< called from bootstrap, start_, or join_election void wait_for_paxos_write(); diff --git a/src/mon/OSDMonitor.cc b/src/mon/OSDMonitor.cc index 7dc58af8780..1f9cfbbd33d 100644 --- a/src/mon/OSDMonitor.cc +++ b/src/mon/OSDMonitor.cc @@ -8174,6 +8174,45 @@ bool OSDMonitor::prepare_command_impl(MonOpRequestRef op, err = -EPERM; goto reply; } + string sure; + cmd_getval(g_ceph_context, cmdmap, "sure", sure); + if (sure != "--yes-i-really-mean-it") { + FeatureMap m; + mon->get_combined_feature_map(&m); + uint64_t features = ceph_release_features(vno); + bool first = true; + bool ok = true; + for (int type : { + CEPH_ENTITY_TYPE_CLIENT, + CEPH_ENTITY_TYPE_MDS, + CEPH_ENTITY_TYPE_MGR }) { + auto p = m.m.find(type); + if (p == m.m.end()) { + continue; + } + for (auto& q : p->second) { + uint64_t missing = ~q.first & features; + if (missing) { + if (first) { + ss << "cannot set require_min_compat_client to " << v << ": "; + } else { + ss << "; "; + } + first = false; + ss << q.second << " connected " << ceph_entity_type_name(type) + << "(s) look like " << ceph_release_name( + ceph_release_from_features(q.first)) + << " (missing 0x" << std::hex << missing << std::dec << ")"; + ok = false; + } + } + } + if (!ok) { + ss << "; add --yes-i-really-mean-it to do it anyway"; + err = -EPERM; + goto reply; + } + } ss << "set require_min_compat_client to " << ceph_release_name(vno); pending_inc.new_require_min_compat_client = vno; getline(ss, rs); diff --git a/src/mon/Paxos.cc b/src/mon/Paxos.cc index 08741b17170..b1680d25aeb 100644 --- a/src/mon/Paxos.cc +++ b/src/mon/Paxos.cc @@ -17,6 +17,7 @@ #include "Monitor.h" #include "messages/MMonPaxos.h" +#include "mon/mon_types.h" #include "common/config.h" #include "include/assert.h" #include "include/stringify.h" @@ -1113,6 +1114,7 @@ void Paxos::handle_lease(MonOpRequestRef op) ack->last_committed = last_committed; ack->first_committed = first_committed; ack->lease_timestamp = ceph_clock_now(); + ::encode(mon->session_map.feature_map, ack->feature_map); lease->get_connection()->send_message(ack); // (re)set timeout event. @@ -1136,7 +1138,11 @@ void Paxos::handle_lease_ack(MonOpRequestRef op) } else if (acked_lease.count(from) == 0) { acked_lease.insert(from); - + if (ack->feature_map.length()) { + auto p = ack->feature_map.begin(); + FeatureMap& t = mon->quorum_feature_map[from]; + ::decode(t, p); + } if (acked_lease == mon->get_quorum()) { // yay! dout(10) << "handle_lease_ack from " << ack->get_source() diff --git a/src/mon/Session.h b/src/mon/Session.h index 75051bf6312..cb594644983 100644 --- a/src/mon/Session.h +++ b/src/mon/Session.h @@ -17,6 +17,7 @@ #include "include/xlist.h" #include "msg/msg_types.h" +#include "mon/mon_types.h" #include "auth/AuthServiceHandler.h" #include "osd/OSDMap.h" @@ -39,6 +40,8 @@ struct Subscription { struct MonSession : public RefCountedObject { ConnectionRef con; + int con_type = 0; + uint64_t con_features = 0; // zero if AnonConnection entity_inst_t inst; utime_t session_timeout; utime_t time_established; @@ -60,13 +63,20 @@ struct MonSession : public RefCountedObject { MonSession(const entity_inst_t& i, Connection *c) : RefCountedObject(g_ceph_context), - con(c), inst(i), closed(false), item(this), + con(c), + con_type(c->get_peer_type()), + con_features(0), + inst(i), closed(false), item(this), auid(0), global_id(0), osd_epoch(0), auth_handler(NULL), proxy_con(NULL), proxy_tid(0) { time_established = ceph_clock_now(); + if (c->get_messenger()) { + // only fill in features if this is a non-anonymous connection + con_features = c->get_features(); + } } ~MonSession() override { //generic_dout(0) << "~MonSession " << this << dendl; @@ -92,6 +102,7 @@ struct MonSessionMap { xlist<MonSession*> sessions; map<string, xlist<Subscription*>* > subs; multimap<int, MonSession*> by_osd; + FeatureMap feature_map; // type -> features -> count MonSessionMap() {} ~MonSessionMap() { @@ -123,6 +134,9 @@ struct MonSessionMap { break; } } + if (s->con_features) { + feature_map.rm(s->con_type, s->con_features); + } s->closed = true; s->put(); } @@ -133,6 +147,9 @@ struct MonSessionMap { sessions.push_back(&s->item); if (i.name.is_osd()) by_osd.insert(pair<int,MonSession*>(i.name.num(), s)); + if (s->con_features) { + feature_map.add(s->con_type, s->con_features); + } s->get(); // caller gets a ref return s; } diff --git a/src/mon/mon_types.h b/src/mon/mon_types.h index 204c7e703ec..883f4669e2b 100644 --- a/src/mon/mon_types.h +++ b/src/mon/mon_types.h @@ -15,12 +15,13 @@ #ifndef CEPH_MON_TYPES_H #define CEPH_MON_TYPES_H +#include <map> + #include "include/utime.h" #include "include/util.h" #include "common/Formatter.h" #include "common/bit_str.h" #include "include/Context.h" -#include "mon/MonOpRequest.h" #define PAXOS_PGMAP 0 // before osd, for pg kick to behave #define PAXOS_MDSMAP 1 @@ -48,6 +49,66 @@ inline const char *get_paxos_name(int p) { #define CEPH_MON_ONDISK_MAGIC "ceph mon volume v012" +// map of entity_type -> features -> count +struct FeatureMap { + std::map<uint32_t,std::map<uint64_t,uint64_t>> m; + + void add(uint32_t type, uint64_t features) { + m[type][features]++; + } + + void rm(uint32_t type, uint64_t features) { + auto p = m.find(type); + assert(p != m.end()); + auto q = p->second.find(features); + assert(q != p->second.end()); + if (--q->second == 0) { + p->second.erase(q); + if (p->second.empty()) { + m.erase(p); + } + } + } + + FeatureMap& operator+=(const FeatureMap& o) { + for (auto& p : o.m) { + auto &v = m[p.first]; + for (auto& q : p.second) { + v[q.first] += q.second; + } + } + return *this; + } + + void encode(bufferlist& bl) const { + ENCODE_START(1, 1, bl); + ::encode(m, bl); + ENCODE_FINISH(bl); + } + + void decode(bufferlist::iterator& p) { + DECODE_START(1, p); + ::decode(m, p); + DECODE_FINISH(p); + } + + void dump(Formatter *f) const { + for (auto& p : m) { + f->open_object_section(ceph_entity_type_name(p.first)); + for (auto& q : p.second) { + f->open_object_section("group"); + f->dump_unsigned("features", q.first); + f->dump_string("release", ceph_release_name( + ceph_release_from_features(q.first))); + f->dump_unsigned("num", q.second); + f->close_section(); + } + f->close_section(); + } + } +}; +WRITE_CLASS_ENCODER(FeatureMap) + /** * leveldb store stats * @@ -213,32 +274,6 @@ static inline ostream& operator<<(ostream& out, const ScrubResult& r) { /// for information like os, kernel, hostname, memory info, cpu model. typedef map<string, string> Metadata; -struct C_MonOp : public Context -{ - MonOpRequestRef op; - - explicit C_MonOp(MonOpRequestRef o) : - op(o) { } - - void finish(int r) override { - if (op && r == -ECANCELED) { - op->mark_event("callback canceled"); - } else if (op && r == -EAGAIN) { - op->mark_event("callback retry"); - } else if (op && r == 0) { - op->mark_event("callback finished"); - } - _finish(r); - } - - void mark_op_event(const string &event) { - if (op) - op->mark_event_string(event); - } - - virtual void _finish(int r) = 0; -}; - namespace ceph { namespace features { namespace mon { diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index 9b62b29ce2f..9473ad79f82 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -728,6 +728,13 @@ add_executable(unittest_mempool add_ceph_unittest(unittest_mempool ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/unittest_mempool) target_link_libraries(unittest_mempool global) +# unittest_features +add_executable(unittest_features + test_features.cc + ) +add_ceph_unittest(unittest_features ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/unittest_features) +target_link_libraries(unittest_features global) + # unittest_crypto add_executable(unittest_crypto crypto.cc diff --git a/src/test/test_features.cc b/src/test/test_features.cc new file mode 100644 index 00000000000..eca067cb753 --- /dev/null +++ b/src/test/test_features.cc @@ -0,0 +1,44 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +#include <stdio.h> + +#include "global/global_init.h" +#include "common/ceph_argparse.h" +#include "global/global_context.h" +#include "gtest/gtest.h" +#include "include/ceph_features.h" +#include "include/rados.h" + + +TEST(features, release_features) +{ + for (int r = 1; r < CEPH_RELEASE_MAX; ++r) { + const char *name = ceph_release_name(r); + ASSERT_NE(string("unknown"), name); + ASSERT_EQ(r, ceph_release_from_name(name)); + uint64_t features = ceph_release_features(r); + int rr = ceph_release_from_features(features); + cout << r << " " << name << " features 0x" << std::hex << features + << std::dec << " looks like " << ceph_release_name(rr) << std::endl; + EXPECT_LE(rr, r); + } +} + +TEST(features, release_from_features) { + ASSERT_EQ(CEPH_RELEASE_JEWEL, ceph_release_from_features(575862587619852283)); + ASSERT_EQ(CEPH_RELEASE_LUMINOUS, + ceph_release_from_features(1152323339925389307)); +} + +int main(int argc, char **argv) +{ + vector<const char*> args; + argv_to_vec(argc, (const char **)argv, args); + + auto cct = global_init(NULL, args, CEPH_ENTITY_TYPE_CLIENT, + CODE_ENVIRONMENT_UTILITY, 0); + common_init_finish(g_ceph_context); + + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} |