]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mds: implement filtered "session ls" tell command
authorJohn Spray <john.spray@redhat.com>
Fri, 2 Oct 2015 10:19:11 +0000 (11:19 +0100)
committerJohn Spray <john.spray@redhat.com>
Tue, 6 Oct 2015 13:27:40 +0000 (14:27 +0100)
So that one can efficiently query client session
state remotely.

Signed-off-by: John Spray <john.spray@redhat.com>
src/mds/MDSDaemon.cc
src/mds/MDSRank.cc
src/mds/MDSRank.h
src/mds/SessionMap.cc
src/mds/SessionMap.h

index bb09dad8f84ea5bbeac8ebc9801056cd0fda064e..c82f6ba988be7379188963980b04472175f48b8d 100644 (file)
@@ -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)", \
index ae9aef3c0f932bc86373df30541a753648eb57ef..8cc273ade5add307607d7578f482b13383b08b1c 100644 (file)
@@ -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<entity_name_t, Session*> session_map = sessionmap.get_sessions();
-    for (ceph::unordered_map<entity_name_t,Session*>::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<string, string>::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<entity_name_t> MDSRankDispatcher::evict_sessions(
+    const SessionFilter &filter)
+{
+  std::list<Session*> 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<entity_name_t> 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<entity_name_t, Session*> session_map = sessionmap.get_sessions();
+  for (ceph::unordered_map<entity_name_t,Session*>::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<string, string>::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<std::string> 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<std::string> 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;
+  }
+}
+
index b999657a0ad7473e1baec15227e8e0e7a3bc3231..96b0688f050c398b043171719ea658ff17204423 100644 (file)
@@ -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<std::string> 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<entity_name_t> evict_sessions(
+      const SessionFilter &filter);
+
   // Call into me from MDS::ms_dispatch
   bool ms_dispatch(Message *m);
 
index 3593867de76039a28ef38dfd8bf32e343b610b69..a4ec18ae1cd701f39ad875ec32b435d51254a019 100644 (file)
@@ -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<std::string> &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<bool(client_t)> 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;
+}
+
index 5dc459a9d6b5ee3b7dd61f10b229559f9eea899c..b6a3792707542bf1096593ee9c5752dca0ad07f2 100644 (file)
@@ -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<version_t> 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<bool, bool> reconnecting;
+
+public:
+  std::map<std::string, std::string> metadata;
+  std::string auth_name;
+  std::string state;
+  int64_t id;
+
+  SessionFilter()
+    : reconnecting(false, false), id(0)
+  {}
+
+  bool match(
+      const Session &session,
+      std::function<bool(client_t)> is_reconnecting) const;
+  int parse(const std::vector<std::string> &args, std::stringstream *ss);
+  void set_reconnecting(bool v)
+  {
+    reconnecting.first = true;
+    reconnecting.second = v;
+  }
+};
+
 /*
  * session map
  */