summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/common/ceph_strings.cc63
-rw-r--r--src/include/rados.h3
-rw-r--r--src/messages/MMonPaxos.h8
-rw-r--r--src/mon/MonCommands.h5
-rw-r--r--src/mon/MonOpRequest.h26
-rw-r--r--src/mon/Monitor.cc30
-rw-r--r--src/mon/Monitor.h7
-rw-r--r--src/mon/OSDMonitor.cc39
-rw-r--r--src/mon/Paxos.cc8
-rw-r--r--src/mon/Session.h19
-rw-r--r--src/mon/mon_types.h89
-rw-r--r--src/test/CMakeLists.txt7
-rw-r--r--src/test/test_features.cc44
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();
+}