From: John Spray Date: Fri, 2 Oct 2015 10:19:11 +0000 (+0100) Subject: mds: implement filtered "session ls" tell command X-Git-Tag: v10.0.0~105^2~1 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=be3c4a81fbcf27903b53729c5645bcb535908a42;p=ceph.git mds: implement filtered "session ls" tell command So that one can efficiently query client session state remotely. Signed-off-by: John Spray --- diff --git a/src/mds/MDSDaemon.cc b/src/mds/MDSDaemon.cc index bb09dad8f84e..c82f6ba988be 100644 --- a/src/mds/MDSDaemon.cc +++ b/src/mds/MDSDaemon.cc @@ -603,6 +603,9 @@ COMMAND("cpu_profiler " \ COMMAND("session ls " \ "name=filters,type=CephString,n=N,req=false", "List client sessions", "mds", "r", "cli,rest") +COMMAND("session evict " \ + "name=filters,type=CephString,n=N,req=false", + "Evict client session(s)", "mds", "rw", "cli,rest") COMMAND("heap " \ "name=heapcmd,type=CephChoices,strings=dump|start_profiler|stop_profiler|release|stats", \ "show heap usage info (available only if compiled with tcmalloc)", \ diff --git a/src/mds/MDSRank.cc b/src/mds/MDSRank.cc index ae9aef3c0f93..8cc273ade5ad 100644 --- a/src/mds/MDSRank.cc +++ b/src/mds/MDSRank.cc @@ -1687,38 +1687,7 @@ bool MDSRankDispatcher::handle_asok_command( heartbeat_reset(); - // Dump sessions, decorated with recovery/replay status - f->open_array_section("sessions"); - const ceph::unordered_map session_map = sessionmap.get_sessions(); - for (ceph::unordered_map::const_iterator p = session_map.begin(); - p != session_map.end(); - ++p) { - if (!p->first.is_client()) { - continue; - } - - Session *s = p->second; - - f->open_object_section("session"); - f->dump_int("id", p->first.num()); - - f->dump_int("num_leases", s->leases.size()); - f->dump_int("num_caps", s->caps.size()); - - f->dump_string("state", s->get_state_name()); - f->dump_int("replay_requests", is_clientreplay() ? s->get_request_count() : 0); - f->dump_unsigned("completed_requests", s->get_num_completed_requests()); - f->dump_bool("reconnecting", server->waiting_for_reconnect(p->first.num())); - f->dump_stream("inst") << s->info.inst; - f->open_object_section("client_metadata"); - for (map::const_iterator i = s->info.client_metadata.begin(); - i != s->info.client_metadata.end(); ++i) { - f->dump_string(i->first.c_str(), i->second); - } - f->close_section(); // client_metadata - f->close_section(); //session - } - f->close_section(); //sessions + dump_sessions(SessionFilter(), f); mds_lock.Unlock(); } else if (command == "session evict") { @@ -1792,7 +1761,84 @@ bool MDSRankDispatcher::handle_asok_command( return true; } +/** + * This function drops the mds_lock, so don't do anything with + * MDSRank after calling it (we could have gone into shutdown): just + * send your result back to the calling client and finish. + */ +std::vector MDSRankDispatcher::evict_sessions( + const SessionFilter &filter) +{ + std::list victims; + + const auto sessions = sessionmap.get_sessions(); + for (const auto p : sessions) { + if (!p.first.is_client()) { + continue; + } + + Session *s = p.second; + + if (filter.match(*s, std::bind(&Server::waiting_for_reconnect, server, std::placeholders::_1))) { + victims.push_back(s); + } + } + + std::vector result; + + C_SaferCond on_safe; + C_GatherBuilder gather(g_ceph_context, &on_safe); + for (const auto s : victims) { + server->kill_session(s, gather.new_sub()); + result.push_back(s->info.inst.name); + } + gather.activate(); + mds_lock.Unlock(); + on_safe.wait(); + mds_lock.Lock(); + + return result; +} + +void MDSRankDispatcher::dump_sessions(const SessionFilter &filter, Formatter *f) const +{ + // Dump sessions, decorated with recovery/replay status + f->open_array_section("sessions"); + const ceph::unordered_map session_map = sessionmap.get_sessions(); + for (ceph::unordered_map::const_iterator p = session_map.begin(); + p != session_map.end(); + ++p) { + if (!p->first.is_client()) { + continue; + } + + Session *s = p->second; + if (!filter.match(*s, std::bind(&Server::waiting_for_reconnect, server, std::placeholders::_1))) { + continue; + } + + f->open_object_section("session"); + f->dump_int("id", p->first.num()); + + f->dump_int("num_leases", s->leases.size()); + f->dump_int("num_caps", s->caps.size()); + + f->dump_string("state", s->get_state_name()); + f->dump_int("replay_requests", is_clientreplay() ? s->get_request_count() : 0); + f->dump_unsigned("completed_requests", s->get_num_completed_requests()); + f->dump_bool("reconnecting", server->waiting_for_reconnect(p->first.num())); + f->dump_stream("inst") << s->info.inst; + f->open_object_section("client_metadata"); + for (map::const_iterator i = s->info.client_metadata.begin(); + i != s->info.client_metadata.end(); ++i) { + f->dump_string(i->first.c_str(), i->second); + } + f->close_section(); // client_metadata + f->close_section(); //session + } + f->close_section(); //sessions +} void MDSRank::command_scrub_path(Formatter *f, const string& path) { @@ -2403,3 +2449,50 @@ MDSRankDispatcher::MDSRankDispatcher( msgr, monc_, objecter_, respawn_hook_, suicide_hook_) {} +bool MDSRankDispatcher::handle_command( + const cmdmap_t &cmdmap, + bufferlist const &inbl, + int *r, + std::stringstream *ds, + std::stringstream *ss) +{ + assert(r != nullptr); + assert(ds != nullptr); + assert(ss != nullptr); + + std::string prefix; + cmd_getval(g_ceph_context, cmdmap, "prefix", prefix); + + if (prefix == "session ls") { + std::vector filter_args; + cmd_getval(g_ceph_context, cmdmap, "filters", filter_args); + + SessionFilter filter; + *r = filter.parse(filter_args, ss); + if (*r != 0) { + return true; + } + + Formatter *f = new JSONFormatter(); + dump_sessions(filter, f); + f->flush(*ds); + delete f; + return true; + } else if (prefix == "session evict") { + std::vector filter_args; + cmd_getval(g_ceph_context, cmdmap, "filters", filter_args); + + SessionFilter filter; + *r = filter.parse(filter_args, ss); + if (*r != 0) { + return true; + } + + evict_sessions(filter); + + return true; + } else { + return false; + } +} + diff --git a/src/mds/MDSRank.h b/src/mds/MDSRank.h index b999657a0ad7..96b0688f050c 100644 --- a/src/mds/MDSRank.h +++ b/src/mds/MDSRank.h @@ -176,21 +176,21 @@ class MDSRank { MDSMap::DaemonState get_state() const { return state; } MDSMap::DaemonState get_want_state() const { return beacon.get_want_state(); } - bool is_creating() { return state == MDSMap::STATE_CREATING; } - bool is_starting() { return state == MDSMap::STATE_STARTING; } - bool is_standby() { return state == MDSMap::STATE_STANDBY; } - bool is_replay() { return state == MDSMap::STATE_REPLAY; } - bool is_standby_replay() { return state == MDSMap::STATE_STANDBY_REPLAY; } - bool is_resolve() { return state == MDSMap::STATE_RESOLVE; } - bool is_reconnect() { return state == MDSMap::STATE_RECONNECT; } - bool is_rejoin() { return state == MDSMap::STATE_REJOIN; } - bool is_clientreplay() { return state == MDSMap::STATE_CLIENTREPLAY; } - bool is_active() { return state == MDSMap::STATE_ACTIVE; } - bool is_stopping() { return state == MDSMap::STATE_STOPPING; } - bool is_oneshot_replay() { return state == MDSMap::STATE_ONESHOT_REPLAY; } - bool is_any_replay() { return (is_replay() || is_standby_replay() || + bool is_creating() const { return state == MDSMap::STATE_CREATING; } + bool is_starting() const { return state == MDSMap::STATE_STARTING; } + bool is_standby() const { return state == MDSMap::STATE_STANDBY; } + bool is_replay() const { return state == MDSMap::STATE_REPLAY; } + bool is_standby_replay() const { return state == MDSMap::STATE_STANDBY_REPLAY; } + bool is_resolve() const { return state == MDSMap::STATE_RESOLVE; } + bool is_reconnect() const { return state == MDSMap::STATE_RECONNECT; } + bool is_rejoin() const { return state == MDSMap::STATE_REJOIN; } + bool is_clientreplay() const { return state == MDSMap::STATE_CLIENTREPLAY; } + bool is_active() const { return state == MDSMap::STATE_ACTIVE; } + bool is_stopping() const { return state == MDSMap::STATE_STOPPING; } + bool is_oneshot_replay() const { return state == MDSMap::STATE_ONESHOT_REPLAY; } + bool is_any_replay() const { return (is_replay() || is_standby_replay() || is_oneshot_replay()); } - bool is_stopped() { return mdsmap->is_stopped(whoami); } + bool is_stopped() const { return mdsmap->is_stopped(whoami); } void handle_write_error(int err); @@ -490,6 +490,18 @@ public: void update_log_config(); bool handle_command_legacy(std::vector args); + bool handle_command( + const cmdmap_t &cmdmap, + bufferlist const &inbl, + int *r, + std::stringstream *ds, + std::stringstream *ss); + + void dump_sessions( + const SessionFilter &filter, Formatter *f) const; + std::vector evict_sessions( + const SessionFilter &filter); + // Call into me from MDS::ms_dispatch bool ms_dispatch(Message *m); diff --git a/src/mds/SessionMap.cc b/src/mds/SessionMap.cc index 3593867de760..a4ec18ae1cd7 100644 --- a/src/mds/SessionMap.cc +++ b/src/mds/SessionMap.cc @@ -813,13 +813,15 @@ void Session::_update_human_name() if (info.client_metadata.count("hostname")) { // Happy path, refer to clients by hostname human_name = info.client_metadata["hostname"]; - if (info.client_metadata.count("entity_id")) { - EntityName entity; - entity.set_id(info.client_metadata["entity_id"]); - if (!entity.has_default_id()) { - // When a non-default entity ID is set by the user, assume they - // would like to see it in references to the client - human_name += std::string(":") + entity.get_id(); + if (!info.auth_name.has_default_id()) { + // When a non-default entity ID is set by the user, assume they + // would like to see it in references to the client, if it's + // reasonable short. Limit the length because we don't want + // to put e.g. uuid-generated names into a "human readable" + // rendering. + const int arbitrarily_short = 16; + if (info.auth_name.get_id().size() < arbitrarily_short) { + human_name += std::string(":") + info.auth_name.get_id(); } } } else { @@ -859,3 +861,120 @@ bool Session::check_access(CInode *in, unsigned mask, return false; } +int SessionFilter::parse( + const std::vector &args, + std::stringstream *ss) +{ + assert(ss != NULL); + + for (const auto &s : args) { + dout(20) << __func__ << " parsing filter '" << s << "'" << dendl; + + auto eq = s.find("="); + if (eq == std::string::npos || eq == s.size()) { + *ss << "Invalid filter '" << s << "'"; + return -EINVAL; + } + + // Keys that start with this are to be taken as referring + // to freeform client metadata fields. + const std::string metadata_prefix("client_metadata."); + + auto k = s.substr(0, eq); + auto v = s.substr(eq + 1); + + dout(20) << __func__ << " parsed k='" << k << "', v='" << v << "'" << dendl; + + if (k.compare(0, metadata_prefix.size(), metadata_prefix) == 0 + && k.size() > metadata_prefix.size()) { + // Filter on arbitrary metadata key (no fixed schema for this, + // so anything after the dot is a valid field to filter on) + auto metadata_key = k.substr(metadata_prefix.size()); + metadata.insert(std::make_pair(metadata_key, v)); + } else if (k == "auth_name") { + // Filter on client entity name + auth_name = v; + } else if (k == "state") { + state = v; + } else if (k == "id") { + std::string err; + id = strict_strtoll(v.c_str(), 10, &err); + if (!err.empty()) { + *ss << err; + return -EINVAL; + } + } else if (k == "reconnecting") { + + /** + * Strict boolean parser. Allow true/false/0/1. + * Anything else is -EINVAL. + */ + auto is_true = [](const std::string &bstr, bool *out) -> bool + { + assert(out != nullptr); + + if (bstr == "true" || bstr == "1") { + *out = true; + return 0; + } else if (bstr == "false" || bstr == "0") { + *out = false; + return 0; + } else { + return -EINVAL; + } + }; + + bool bval; + int r = is_true(v, &bval); + if (r == 0) { + set_reconnecting(bval); + } else { + *ss << "Invalid boolean value '" << v << "'"; + return -EINVAL; + } + } else { + *ss << "Invalid filter key '" << k << "'"; + return -EINVAL; + } + } + + return 0; +} + +bool SessionFilter::match( + const Session &session, + std::function is_reconnecting) const +{ + for (auto m : metadata) { + auto k = m.first; + auto v = m.second; + if (session.info.client_metadata.count(k) == 0) { + return false; + } + if (session.info.client_metadata.at(k) != v) { + return false; + } + } + + if (!auth_name.empty() && auth_name != session.info.auth_name.get_id()) { + return false; + } + + if (!state.empty() && state != session.get_state_name()) { + return false; + } + + if (id != 0 && id != session.info.inst.name.num()) { + return false; + } + + if (reconnecting.first) { + const bool am_reconnecting = is_reconnecting(session.info.inst.name.num()); + if (reconnecting.second != am_reconnecting) { + return false; + } + } + + return true; +} + diff --git a/src/mds/SessionMap.h b/src/mds/SessionMap.h index 5dc459a9d6b5..b6a379270754 100644 --- a/src/mds/SessionMap.h +++ b/src/mds/SessionMap.h @@ -64,7 +64,7 @@ public: STATE_KILLING = 5 }; - const char *get_state_name(int s) { + const char *get_state_name(int s) const { switch (s) { case STATE_CLOSED: return "closed"; case STATE_OPENING: return "opening"; @@ -90,6 +90,8 @@ private: // that appropriate mark_dirty calls follow. std::deque projected; + + public: void push_pv(version_t pv) @@ -171,7 +173,7 @@ public: } int get_state() { return state; } - const char *get_state_name() { return get_state_name(state); } + const char *get_state_name() const { return get_state_name(state); } uint64_t get_state_seq() { return state_seq; } bool is_closed() const { return state == STATE_CLOSED; } bool is_opening() const { return state == STATE_OPENING; } @@ -334,6 +336,33 @@ public: } }; +class SessionFilter +{ +protected: + // First is whether to filter, second is filter value + std::pair reconnecting; + +public: + std::map metadata; + std::string auth_name; + std::string state; + int64_t id; + + SessionFilter() + : reconnecting(false, false), id(0) + {} + + bool match( + const Session &session, + std::function is_reconnecting) const; + int parse(const std::vector &args, std::stringstream *ss); + void set_reconnecting(bool v) + { + reconnecting.first = true; + reconnecting.second = v; + } +}; + /* * session map */