mirror of
https://github.com/ceph/ceph
synced 2025-02-22 02:27:29 +00:00
Merge pull request #2560 from ceph/wip-9418
mon: add new profiles & audit cap checks Reviewed-by: Sage Weil <sage@redhat.com>
This commit is contained in:
commit
2ac2a96898
@ -262,7 +262,6 @@ function test_tiering()
|
||||
ceph osd pool delete basepoolA basepoolA --yes-i-really-really-mean-it
|
||||
}
|
||||
|
||||
|
||||
function test_auth()
|
||||
{
|
||||
ceph auth add client.xx mon allow osd "allow *"
|
||||
@ -287,6 +286,67 @@ function test_auth()
|
||||
ceph auth del client.xx
|
||||
}
|
||||
|
||||
function test_auth_profiles()
|
||||
{
|
||||
ceph auth add client.xx-profile-ro mon 'allow profile read-only'
|
||||
ceph auth add client.xx-profile-rw mon 'allow profile read-write'
|
||||
ceph auth add client.xx-profile-rd mon 'allow profile role-definer'
|
||||
|
||||
ceph auth export > client.xx.keyring
|
||||
|
||||
# read-only is allowed all read-only commands (auth excluded)
|
||||
ceph -n client.xx-profile-ro -k client.xx.keyring status
|
||||
ceph -n client.xx-profile-ro -k client.xx.keyring osd dump
|
||||
ceph -n client.xx-profile-ro -k client.xx.keyring pg dump
|
||||
ceph -n client.xx-profile-ro -k client.xx.keyring mon dump
|
||||
ceph -n client.xx-profile-ro -k client.xx.keyring mds dump
|
||||
# read-only gets access denied for rw commands or auth commands
|
||||
ceph -n client.xx-profile-ro -k client.xx.keyring log foo >& $TMPFILE || true
|
||||
check_response "EACCES: access denied"
|
||||
ceph -n client.xx-profile-ro -k client.xx.keyring osd set noout >& $TMPFILE || true
|
||||
check_response "EACCES: access denied"
|
||||
ceph -n client.xx-profile-ro -k client.xx.keyring auth list >& $TMPFILE || true
|
||||
check_response "EACCES: access denied"
|
||||
|
||||
# read-write is allowed for all read-write commands (except auth)
|
||||
ceph -n client.xx-profile-rw -k client.xx.keyring status
|
||||
ceph -n client.xx-profile-rw -k client.xx.keyring osd dump
|
||||
ceph -n client.xx-profile-rw -k client.xx.keyring pg dump
|
||||
ceph -n client.xx-profile-rw -k client.xx.keyring mon dump
|
||||
ceph -n client.xx-profile-rw -k client.xx.keyring mds dump
|
||||
ceph -n client.xx-profile-rw -k client.xx.keyring log foo
|
||||
ceph -n client.xx-profile-rw -k client.xx.keyring osd set noout
|
||||
ceph -n client.xx-profile-rw -k client.xx.keyring osd unset noout
|
||||
# read-write gets access denied for auth commands
|
||||
ceph -n client.xx-profile-rw -k client.xx.keyring auth list >& $TMPFILE || true
|
||||
check_response "EACCES: access denied"
|
||||
|
||||
# role-definer is allowed RWX 'auth' commands and read-only 'mon' commands
|
||||
ceph -n client.xx-profile-rd -k client.xx.keyring auth list
|
||||
ceph -n client.xx-profile-rd -k client.xx.keyring auth export
|
||||
ceph -n client.xx-profile-rd -k client.xx.keyring auth add client.xx-profile-foo
|
||||
ceph -n client.xx-profile-rd -k client.xx.keyring status
|
||||
ceph -n client.xx-profile-rd -k client.xx.keyring osd dump >& $TMPFILE || true
|
||||
check_response "EACCES: access denied"
|
||||
ceph -n client.xx-profile-rd -k client.xx.keyring pg dump >& $TMPFILE || true
|
||||
check_response "EACCES: access denied"
|
||||
# read-only 'mon' subsystem commands are allowed
|
||||
ceph -n client.xx-profile-rd -k client.xx.keyring mon dump
|
||||
# but read-write 'mon' commands are not
|
||||
ceph -n client.xx-profile-rd -k client.xx.keyring mon add foo 1.1.1.1 >& $TMPFILE || true
|
||||
check_response "EACCES: access denied"
|
||||
ceph -n client.xx-profile-rd -k client.xx.keyring mds dump >& $TMPFILE || true
|
||||
check_response "EACCES: access denied"
|
||||
ceph -n client.xx-profile-rd -k client.xx.keyring log foo >& $TMPFILE || true
|
||||
check_response "EACCES: access denied"
|
||||
ceph -n client.xx-profile-rd -k client.xx.keyring osd set noout >& $TMPFILE || true
|
||||
check_response "EACCES: access denied"
|
||||
|
||||
ceph -n client.xx-profile-rd -k client.xx.keyring auth del client.xx-profile-ro
|
||||
ceph -n client.xx-profile-rd -k client.xx.keyring auth del client.xx-profile-rw
|
||||
ceph -n client.xx-profile-rd -k client.xx.keyring auth del client.xx-profile-rd
|
||||
rm -f client.xx.keyring
|
||||
}
|
||||
|
||||
function test_mon_misc()
|
||||
{
|
||||
@ -1030,6 +1090,7 @@ TESTS=(
|
||||
mon_injectargs_SI
|
||||
tiering
|
||||
auth
|
||||
auth_profiles
|
||||
mon_misc
|
||||
mon_mds
|
||||
mon_mon
|
||||
|
@ -13,7 +13,8 @@ for i in ${combinations}; do
|
||||
done
|
||||
|
||||
# add special caps
|
||||
keymap["blank"]=`ceph auth get-or-create-key client.blank mon 'allow'` || exit 1
|
||||
# force blank cap with '--force'
|
||||
keymap["blank"]=`ceph auth get-or-create-key client.blank mon 'allow' --force` || exit 1
|
||||
keymap["all"]=`ceph auth get-or-create-key client.all mon 'allow *'` || exit 1
|
||||
|
||||
tmp=`mktemp`
|
||||
@ -24,7 +25,10 @@ trap "rm $tmp" INT ERR EXIT QUIT 0
|
||||
expect() {
|
||||
|
||||
set +e
|
||||
expected_ret=$1
|
||||
|
||||
local expected_ret=$1
|
||||
local ret
|
||||
|
||||
shift
|
||||
cmd=$@
|
||||
|
||||
@ -42,10 +46,11 @@ expect() {
|
||||
}
|
||||
|
||||
read_ops() {
|
||||
local caps=$1
|
||||
local has_read=1 has_exec=1
|
||||
local ret
|
||||
local args
|
||||
|
||||
caps=$1
|
||||
has_read=1
|
||||
has_exec=1
|
||||
( echo $caps | grep 'r' ) || has_read=0
|
||||
( echo $caps | grep 'x' ) || has_exec=0
|
||||
|
||||
@ -72,10 +77,12 @@ read_ops() {
|
||||
|
||||
write_ops() {
|
||||
|
||||
caps=$1
|
||||
has_read=1
|
||||
has_write=1
|
||||
has_exec=1
|
||||
local caps=$1
|
||||
local has_read=1 has_write=1 has_exec=1
|
||||
local ret
|
||||
local err
|
||||
local args
|
||||
|
||||
( echo $caps | grep 'r' ) || has_read=0
|
||||
( echo $caps | grep 'w' ) || has_write=0
|
||||
( echo $caps | grep 'x' ) || has_exec=0
|
||||
@ -96,9 +103,16 @@ write_ops() {
|
||||
expect $ret ceph auth add client.foo $args
|
||||
expect $ret "ceph auth caps client.foo mon 'allow *' $args"
|
||||
expect $ret ceph auth get-or-create client.admin $args
|
||||
expect $ret "ceph auth get-or-create client.bar mon 'allow' $args"
|
||||
echo "wtf -- before: err=$err ret=$ret"
|
||||
err=$ret
|
||||
[[ $ret -eq 0 ]] && err=22 # EINVAL
|
||||
expect $err "ceph auth get-or-create client.bar mon 'allow' $args"
|
||||
echo "wtf -- after: err=$err ret=$ret"
|
||||
expect $ret "ceph auth get-or-create client.bar mon 'allow' --force $args"
|
||||
expect $ret ceph auth get-or-create-key client.admin $args
|
||||
expect $ret ceph auth get-or-create-key client.baz $args
|
||||
expect $ret ceph auth del client.bar $args
|
||||
expect $ret ceph auth del client.baz $args
|
||||
expect $ret ceph auth del client.foo $args
|
||||
expect $ret ceph auth import -i $tmp $args
|
||||
}
|
||||
@ -118,4 +132,9 @@ for i in ${!keymap[@]}; do
|
||||
fi
|
||||
done
|
||||
|
||||
# cleanup
|
||||
for i in ${combinations} blank all; do
|
||||
ceph auth del client.$i || exit 1
|
||||
done
|
||||
|
||||
echo "OK"
|
||||
|
@ -859,6 +859,11 @@ bool AuthMonitor::prepare_command(MMonCommand *m)
|
||||
!entity_name.empty()) {
|
||||
// auth get-or-create <name> [mon osdcapa osd osdcapb ...]
|
||||
|
||||
if (!valid_caps(caps_vec, &ss)) {
|
||||
err = -EINVAL;
|
||||
goto done;
|
||||
}
|
||||
|
||||
// do we have it?
|
||||
EntityAuth entity_auth;
|
||||
if (mon->key_server.get_auth(entity, entity_auth)) {
|
||||
@ -952,6 +957,11 @@ bool AuthMonitor::prepare_command(MMonCommand *m)
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (!valid_caps(caps_vec, &ss)) {
|
||||
err = -EINVAL;
|
||||
goto done;
|
||||
}
|
||||
|
||||
map<string,bufferlist> newcaps;
|
||||
for (vector<string>::iterator it = caps_vec.begin();
|
||||
it != caps_vec.end(); it += 2)
|
||||
|
@ -124,6 +124,20 @@ private:
|
||||
pending_auth.push_back(inc);
|
||||
}
|
||||
|
||||
/* validate mon caps ; don't care about caps for other services as
|
||||
* we don't know how to validate them */
|
||||
bool valid_caps(const vector<string>& caps, ostream *out) {
|
||||
for (vector<string>::const_iterator p = caps.begin();
|
||||
p != caps.end(); p += 2) {
|
||||
if (!p->empty() && *p != "mon")
|
||||
continue;
|
||||
MonCap tmp;
|
||||
if (!tmp.parse(*(p+1), out))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void on_active();
|
||||
bool should_propose(double& delay);
|
||||
void create_initial();
|
||||
|
@ -174,6 +174,25 @@ void MonCapGrant::expand_profile(entity_name_t name) const
|
||||
profile_grants.push_back(MonCapGrant("osd", MON_CAP_R));
|
||||
profile_grants.push_back(MonCapGrant("pg", MON_CAP_R));
|
||||
}
|
||||
|
||||
if (profile == "read-only") {
|
||||
// grants READ-ONLY caps monitor-wide
|
||||
// 'auth' requires MON_CAP_X even for RO, which we do not grant here.
|
||||
profile_grants.push_back(mon_rwxa_t(MON_CAP_R));
|
||||
}
|
||||
|
||||
if (profile == "read-write") {
|
||||
// grants READ-WRITE caps monitor-wide
|
||||
// 'auth' requires MON_CAP_X for all operations, which we do not grant.
|
||||
profile_grants.push_back(mon_rwxa_t(MON_CAP_R | MON_CAP_W));
|
||||
}
|
||||
|
||||
if (profile == "role-definer") {
|
||||
// grants ALL caps to the auth subsystem, read-only on the
|
||||
// monitor subsystem and nothing else.
|
||||
profile_grants.push_back(MonCapGrant("mon", MON_CAP_R));
|
||||
profile_grants.push_back(MonCapGrant("auth", MON_CAP_ALL));
|
||||
}
|
||||
}
|
||||
|
||||
mon_rwxa_t MonCapGrant::get_allowed(CephContext *cct,
|
||||
|
@ -167,7 +167,8 @@ COMMAND("auth import", "auth import: read keyring file from -i <file>", \
|
||||
COMMAND("auth add " \
|
||||
"name=entity,type=CephString " \
|
||||
"name=caps,type=CephString,n=N,req=false", \
|
||||
"add auth info for <entity> from input file, or random key if no input given, and/or any caps specified in the command",
|
||||
"add auth info for <entity> from input file, or random key if no " \
|
||||
"input is given, and/or any caps specified in the command",
|
||||
"auth", "rwx", "cli,rest")
|
||||
COMMAND("auth get-or-create-key " \
|
||||
"name=entity,type=CephString " \
|
||||
|
@ -2449,6 +2449,13 @@ void Monitor::handle_command(MMonCommand *m)
|
||||
forward_request_leader(m);
|
||||
return;
|
||||
}
|
||||
|
||||
/* what we perceive as being the service the command falls under */
|
||||
string service(mon_cmd->module);
|
||||
|
||||
dout(25) << __func__ << " prefix='" << prefix
|
||||
<< "' module='" << module
|
||||
<< "' service='" << service << "'" << dendl;
|
||||
|
||||
bool cmd_is_rw =
|
||||
(mon_cmd->requires_perm('w') || mon_cmd->requires_perm('x'));
|
||||
@ -2456,7 +2463,7 @@ void Monitor::handle_command(MMonCommand *m)
|
||||
// validate user's permissions for requested command
|
||||
map<string,string> param_str_map;
|
||||
_generate_command_map(cmdmap, param_str_map);
|
||||
if (!_allowed_command(session, module, prefix, cmdmap,
|
||||
if (!_allowed_command(session, service, prefix, cmdmap,
|
||||
param_str_map, mon_cmd)) {
|
||||
dout(1) << __func__ << " access denied" << dendl;
|
||||
(cmd_is_rw ? audit_clog->info() : audit_clog->debug())
|
||||
@ -3161,41 +3168,30 @@ void Monitor::dispatch(MonSession *s, Message *m, const bool src_is_mon)
|
||||
{
|
||||
assert(m != NULL);
|
||||
|
||||
/* deal with all messages that do not necessarily need caps */
|
||||
bool dealt_with = true;
|
||||
switch (m->get_type()) {
|
||||
|
||||
case MSG_ROUTE:
|
||||
handle_route(static_cast<MRoute*>(m));
|
||||
// auth
|
||||
case MSG_MON_GLOBAL_ID:
|
||||
case CEPH_MSG_AUTH:
|
||||
/* no need to check caps here */
|
||||
paxos_service[PAXOS_AUTH]->dispatch((PaxosServiceMessage*)m);
|
||||
break;
|
||||
|
||||
// misc
|
||||
case CEPH_MSG_MON_GET_MAP:
|
||||
handle_mon_get_map(static_cast<MMonGetMap*>(m));
|
||||
case CEPH_MSG_PING:
|
||||
handle_ping(static_cast<MPing*>(m));
|
||||
break;
|
||||
|
||||
case CEPH_MSG_MON_GET_VERSION:
|
||||
handle_get_version(static_cast<MMonGetVersion*>(m));
|
||||
default:
|
||||
dealt_with = false;
|
||||
break;
|
||||
}
|
||||
if (dealt_with)
|
||||
return;
|
||||
|
||||
case MSG_MON_COMMAND:
|
||||
handle_command(static_cast<MMonCommand*>(m));
|
||||
break;
|
||||
|
||||
case CEPH_MSG_MON_SUBSCRIBE:
|
||||
/* FIXME: check what's being subscribed, filter accordingly */
|
||||
handle_subscribe(static_cast<MMonSubscribe*>(m));
|
||||
break;
|
||||
|
||||
case MSG_MON_PROBE:
|
||||
handle_probe(static_cast<MMonProbe*>(m));
|
||||
break;
|
||||
|
||||
// Sync (i.e., the new slurp, but on steroids)
|
||||
case MSG_MON_SYNC:
|
||||
handle_sync(static_cast<MMonSync*>(m));
|
||||
break;
|
||||
case MSG_MON_SCRUB:
|
||||
handle_scrub(static_cast<MMonScrub*>(m));
|
||||
break;
|
||||
/* deal with all messages which caps should be checked somewhere else */
|
||||
dealt_with = true;
|
||||
switch (m->get_type()) {
|
||||
|
||||
// OSDs
|
||||
case MSG_OSD_MARK_ME_DOWN:
|
||||
@ -3203,9 +3199,6 @@ void Monitor::dispatch(MonSession *s, Message *m, const bool src_is_mon)
|
||||
case MSG_OSD_BOOT:
|
||||
case MSG_OSD_ALIVE:
|
||||
case MSG_OSD_PGTEMP:
|
||||
paxos_service[PAXOS_OSDMAP]->dispatch((PaxosServiceMessage*)m);
|
||||
break;
|
||||
|
||||
case MSG_REMOVE_SNAPS:
|
||||
paxos_service[PAXOS_OSDMAP]->dispatch((PaxosServiceMessage*)m);
|
||||
break;
|
||||
@ -3216,12 +3209,6 @@ void Monitor::dispatch(MonSession *s, Message *m, const bool src_is_mon)
|
||||
paxos_service[PAXOS_MDSMAP]->dispatch((PaxosServiceMessage*)m);
|
||||
break;
|
||||
|
||||
// auth
|
||||
case MSG_MON_GLOBAL_ID:
|
||||
case CEPH_MSG_AUTH:
|
||||
/* no need to check caps here */
|
||||
paxos_service[PAXOS_AUTH]->dispatch((PaxosServiceMessage*)m);
|
||||
break;
|
||||
|
||||
// pg
|
||||
case CEPH_MSG_STATFS:
|
||||
@ -3239,6 +3226,81 @@ void Monitor::dispatch(MonSession *s, Message *m, const bool src_is_mon)
|
||||
paxos_service[PAXOS_LOG]->dispatch((PaxosServiceMessage*)m);
|
||||
break;
|
||||
|
||||
// handle_command() does its own caps checking
|
||||
case MSG_MON_COMMAND:
|
||||
handle_command(static_cast<MMonCommand*>(m));
|
||||
break;
|
||||
|
||||
default:
|
||||
dealt_with = false;
|
||||
break;
|
||||
}
|
||||
if (dealt_with)
|
||||
return;
|
||||
|
||||
/* messages we, the Monitor class, need to deal with
|
||||
* but may be sent by clients. */
|
||||
|
||||
if (!s->is_capable("mon", MON_CAP_R)) {
|
||||
dout(5) << __func__ << " " << m->get_source_inst()
|
||||
<< " not enough caps for " << *m << " -- dropping"
|
||||
<< dendl;
|
||||
goto drop;
|
||||
}
|
||||
|
||||
dealt_with = true;
|
||||
switch (m->get_type()) {
|
||||
|
||||
// misc
|
||||
case CEPH_MSG_MON_GET_MAP:
|
||||
handle_mon_get_map(static_cast<MMonGetMap*>(m));
|
||||
break;
|
||||
|
||||
case CEPH_MSG_MON_GET_VERSION:
|
||||
handle_get_version(static_cast<MMonGetVersion*>(m));
|
||||
break;
|
||||
|
||||
case CEPH_MSG_MON_SUBSCRIBE:
|
||||
/* FIXME: check what's being subscribed, filter accordingly */
|
||||
handle_subscribe(static_cast<MMonSubscribe*>(m));
|
||||
break;
|
||||
|
||||
default:
|
||||
dealt_with = false;
|
||||
break;
|
||||
}
|
||||
if (dealt_with)
|
||||
return;
|
||||
|
||||
if (!src_is_mon) {
|
||||
dout(1) << __func__ << " unexpected monitor message from"
|
||||
<< " non-monitor entity " << m->get_source_inst()
|
||||
<< " " << *m << " -- dropping" << dendl;
|
||||
goto drop;
|
||||
}
|
||||
|
||||
/* messages that should only be sent by another monitor */
|
||||
dealt_with = true;
|
||||
switch (m->get_type()) {
|
||||
|
||||
case MSG_ROUTE:
|
||||
handle_route(static_cast<MRoute*>(m));
|
||||
break;
|
||||
|
||||
case MSG_MON_PROBE:
|
||||
handle_probe(static_cast<MMonProbe*>(m));
|
||||
break;
|
||||
|
||||
// Sync (i.e., the new slurp, but on steroids)
|
||||
case MSG_MON_SYNC:
|
||||
handle_sync(static_cast<MMonSync*>(m));
|
||||
break;
|
||||
case MSG_MON_SCRUB:
|
||||
handle_scrub(static_cast<MMonScrub*>(m));
|
||||
break;
|
||||
|
||||
/* log acks are sent from a monitor we sent the MLog to, and are
|
||||
never sent by clients to us. */
|
||||
case MSG_LOGACK:
|
||||
log_client.handle_log_ack((MLogAck*)m);
|
||||
m->put();
|
||||
@ -3313,15 +3375,18 @@ void Monitor::dispatch(MonSession *s, Message *m, const bool src_is_mon)
|
||||
health_monitor->dispatch(static_cast<MMonHealth *>(m));
|
||||
break;
|
||||
|
||||
case CEPH_MSG_PING:
|
||||
handle_ping(static_cast<MPing*>(m));
|
||||
break;
|
||||
|
||||
default:
|
||||
dout(1) << "dropping unexpected " << *m << dendl;
|
||||
m->put();
|
||||
dealt_with = false;
|
||||
break;
|
||||
}
|
||||
if (!dealt_with) {
|
||||
dout(1) << "dropping unexpected " << *m << dendl;
|
||||
goto drop;
|
||||
}
|
||||
return;
|
||||
|
||||
drop:
|
||||
m->put();
|
||||
}
|
||||
|
||||
void Monitor::handle_ping(MPing *m)
|
||||
|
@ -728,6 +728,11 @@ ceph_test_mon_workloadgen_SOURCES = test/mon/test_mon_workloadgen.cc
|
||||
ceph_test_mon_workloadgen_LDADD = $(LIBOS) $(LIBOSDC) $(CEPH_GLOBAL)
|
||||
bin_DEBUGPROGRAMS += ceph_test_mon_workloadgen
|
||||
|
||||
ceph_test_mon_msg_SOURCES = test/mon/test-mon-msg.cc
|
||||
ceph_test_mon_msg_LDADD = $(LIBOS) $(LIBOSDC) $(CEPH_GLOBAL) $(UNITTEST_LDADD)
|
||||
ceph_test_mon_msg_CXXFLAGS = $(UNITTEST_CXXFLAGS)
|
||||
bin_DEBUGPROGRAMS += ceph_test_mon_msg
|
||||
|
||||
ceph_test_rados_api_cmd_SOURCES = test/librados/cmd.cc
|
||||
ceph_test_rados_api_cmd_LDADD = $(LIBRADOS) $(UNITTEST_LDADD) $(RADOS_TEST_LDADD)
|
||||
ceph_test_rados_api_cmd_CXXFLAGS = $(UNITTEST_CXXFLAGS)
|
||||
|
337
src/test/mon/test-mon-msg.cc
Normal file
337
src/test/mon/test-mon-msg.cc
Normal file
@ -0,0 +1,337 @@
|
||||
// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
|
||||
// vim: ts=8 sw=2 smarttab
|
||||
/*
|
||||
* Ceph - scalable distributed file system
|
||||
*
|
||||
* Copyright (C) 2014 Red Hat
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <time.h>
|
||||
#include <stdlib.h>
|
||||
#include <map>
|
||||
|
||||
#include "global/global_init.h"
|
||||
#include "global/global_context.h"
|
||||
#include "common/ceph_argparse.h"
|
||||
#include "common/dout.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/Cond.h"
|
||||
#include "common/Mutex.h"
|
||||
#include "common/Timer.h"
|
||||
#include "common/errno.h"
|
||||
#include "mon/MonClient.h"
|
||||
#include "msg/Dispatcher.h"
|
||||
#include "include/err.h"
|
||||
#include <boost/scoped_ptr.hpp>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include "common/config.h"
|
||||
#include "include/assert.h"
|
||||
|
||||
#include "messages/MMonProbe.h"
|
||||
#include "messages/MRoute.h"
|
||||
#include "messages/MGenericMessage.h"
|
||||
#include "messages/MMonJoin.h"
|
||||
|
||||
#define dout_subsys ceph_subsys_
|
||||
#undef dout_prefix
|
||||
#define dout_prefix *_dout << "test-mon-msg "
|
||||
|
||||
class MonClientHelper : public Dispatcher
|
||||
{
|
||||
protected:
|
||||
CephContext *cct;
|
||||
Messenger *msg;
|
||||
MonClient monc;
|
||||
|
||||
Mutex lock;
|
||||
|
||||
set<int> wanted;
|
||||
|
||||
public:
|
||||
|
||||
MonClientHelper(CephContext *cct_)
|
||||
: Dispatcher(cct_),
|
||||
cct(cct_),
|
||||
monc(cct_),
|
||||
lock("mon-msg-test::lock")
|
||||
{ }
|
||||
|
||||
|
||||
int post_init() {
|
||||
dout(1) << __func__ << dendl;
|
||||
if (!msg)
|
||||
return -EINVAL;
|
||||
msg->add_dispatcher_tail(this);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int init_messenger() {
|
||||
dout(1) << __func__ << dendl;
|
||||
|
||||
msg = Messenger::create(cct, entity_name_t::CLIENT(-1),
|
||||
"test-mon-msg", 0);
|
||||
assert(msg != NULL);
|
||||
msg->set_default_policy(Messenger::Policy::lossy_client(0,0));
|
||||
dout(0) << __func__ << " starting messenger at "
|
||||
<< msg->get_myaddr() << dendl;
|
||||
msg->start();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int init_monc() {
|
||||
dout(1) << __func__ << dendl;
|
||||
assert(msg != NULL);
|
||||
int err = monc.build_initial_monmap();
|
||||
if (err < 0) {
|
||||
derr << __func__ << " error building monmap: "
|
||||
<< cpp_strerror(err) << dendl;
|
||||
return err;
|
||||
}
|
||||
|
||||
monc.set_messenger(msg);
|
||||
msg->add_dispatcher_head(&monc);
|
||||
|
||||
monc.set_want_keys(CEPH_ENTITY_TYPE_MON);
|
||||
err = monc.init();
|
||||
if (err < 0) {
|
||||
derr << __func__ << " monc init failed: "
|
||||
<< cpp_strerror(err) << dendl;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
err = monc.authenticate();
|
||||
if (err < 0) {
|
||||
derr << __func__ << " monc auth failed: "
|
||||
<< cpp_strerror(err) << dendl;
|
||||
goto fail_monc;
|
||||
}
|
||||
monc.wait_auth_rotating(30.0);
|
||||
monc.renew_subs();
|
||||
dout(0) << __func__ << " finished" << dendl;
|
||||
return 0;
|
||||
|
||||
fail_monc:
|
||||
derr << __func__ << " failing monc" << dendl;
|
||||
monc.shutdown();
|
||||
fail:
|
||||
return err;
|
||||
}
|
||||
|
||||
void shutdown_messenger() {
|
||||
dout(0) << __func__ << dendl;
|
||||
msg->shutdown();
|
||||
msg->wait();
|
||||
}
|
||||
|
||||
void shutdown_monc() {
|
||||
dout(0) << __func__ << dendl;
|
||||
monc.shutdown();
|
||||
}
|
||||
|
||||
void shutdown() {
|
||||
dout(0) << __func__ << dendl;
|
||||
shutdown_monc();
|
||||
shutdown_messenger();
|
||||
}
|
||||
|
||||
MonMap *get_monmap() {
|
||||
return &monc.monmap;
|
||||
}
|
||||
|
||||
int init() {
|
||||
int err = init_messenger();
|
||||
if (err < 0)
|
||||
goto fail;
|
||||
err = init_monc();
|
||||
if (err < 0)
|
||||
goto fail_msgr;
|
||||
err = post_init();
|
||||
if (err < 0)
|
||||
goto fail_monc;
|
||||
return 0;
|
||||
fail_monc:
|
||||
shutdown_monc();
|
||||
fail_msgr:
|
||||
shutdown_messenger();
|
||||
fail:
|
||||
return err;
|
||||
}
|
||||
|
||||
virtual void handle_wanted(Message *m) { }
|
||||
|
||||
bool handle_message(Message *m) {
|
||||
dout(1) << __func__ << " " << *m << dendl;
|
||||
if (!is_wanted(m)) {
|
||||
dout(10) << __func__ << " not wanted" << dendl;
|
||||
return false;
|
||||
}
|
||||
handle_wanted(m);
|
||||
m->put();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ms_dispatch(Message *m) {
|
||||
return handle_message(m);
|
||||
}
|
||||
void ms_handle_connect(Connection *con) { }
|
||||
void ms_handle_remote_reset(Connection *con) { }
|
||||
bool ms_handle_reset(Connection *con) { return false; }
|
||||
|
||||
bool is_wanted(Message *m) {
|
||||
dout(20) << __func__ << " " << *m << " type " << m->get_type() << dendl;
|
||||
return (wanted.find(m->get_type()) != wanted.end());
|
||||
}
|
||||
|
||||
void add_wanted(int t) {
|
||||
dout(20) << __func__ << " type " << t << dendl;
|
||||
wanted.insert(t);
|
||||
}
|
||||
|
||||
void rm_wanted(int t) {
|
||||
dout(20) << __func__ << " type " << t << dendl;
|
||||
wanted.erase(t);
|
||||
}
|
||||
|
||||
void send_message(Message *m) {
|
||||
dout(15) << __func__ << " " << *m << dendl;
|
||||
monc.send_mon_message(m);
|
||||
}
|
||||
|
||||
void wait() { msg->wait(); }
|
||||
};
|
||||
|
||||
class MonMsgTest : public MonClientHelper,
|
||||
public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
int reply_type;
|
||||
Message *reply_msg;
|
||||
Mutex lock;
|
||||
Cond cond;
|
||||
|
||||
MonMsgTest() :
|
||||
MonClientHelper(g_ceph_context),
|
||||
lock("lock") { }
|
||||
|
||||
public:
|
||||
virtual void SetUp() {
|
||||
reply_type = -1;
|
||||
if (reply_msg) {
|
||||
reply_msg->put();
|
||||
reply_msg = NULL;
|
||||
}
|
||||
ASSERT_EQ(init(), 0);
|
||||
}
|
||||
|
||||
virtual void TearDown() {
|
||||
shutdown();
|
||||
if (reply_msg) {
|
||||
reply_msg->put();
|
||||
reply_msg = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void handle_wanted(Message *m) {
|
||||
lock.Lock();
|
||||
// caller will put() after they call us, so hold on to a ref
|
||||
m->get();
|
||||
reply_msg = m;
|
||||
cond.Signal();
|
||||
lock.Unlock();
|
||||
}
|
||||
|
||||
Message *send_wait_reply(Message *m, int t, double timeout=30.0) {
|
||||
lock.Lock();
|
||||
reply_type = t;
|
||||
add_wanted(t);
|
||||
send_message(m);
|
||||
|
||||
int err = 0;
|
||||
if (timeout > 0) {
|
||||
utime_t cond_timeout;
|
||||
cond_timeout.set_from_double(timeout);
|
||||
utime_t s = ceph_clock_now(g_ceph_context);
|
||||
err = cond.WaitInterval(g_ceph_context, lock, cond_timeout);
|
||||
utime_t e = ceph_clock_now(g_ceph_context);
|
||||
dout(20) << __func__ << " took " << (e-s) << " seconds" << dendl;
|
||||
} else {
|
||||
err = cond.Wait(lock);
|
||||
}
|
||||
rm_wanted(t);
|
||||
lock.Unlock();
|
||||
if (err > 0) {
|
||||
dout(20) << __func__ << " error: " << cpp_strerror(err) << dendl;
|
||||
return (Message*)((long)-err);
|
||||
}
|
||||
|
||||
if (!reply_msg)
|
||||
dout(20) << __func__ << " reply_msg is NULL" << dendl;
|
||||
else
|
||||
dout(20) << __func__ << " reply_msg " << *reply_msg << dendl;
|
||||
return reply_msg;
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(MonMsgTest, MMonProbeTest)
|
||||
{
|
||||
Message *m = new MMonProbe(get_monmap()->fsid,
|
||||
MMonProbe::OP_PROBE, "b", false);
|
||||
Message *r = send_wait_reply(m, MSG_MON_PROBE);
|
||||
ASSERT_NE(IS_ERR(r), 0);
|
||||
ASSERT_EQ(PTR_ERR(r), -ETIMEDOUT);
|
||||
}
|
||||
|
||||
TEST_F(MonMsgTest, MRouteTest)
|
||||
{
|
||||
Message *payload = new MGenericMessage(CEPH_MSG_SHUTDOWN);
|
||||
MRoute *m = new MRoute;
|
||||
m->msg = payload;
|
||||
m->dest = msg->get_myinst();
|
||||
Message *r = send_wait_reply(m, CEPH_MSG_SHUTDOWN);
|
||||
// we want an error
|
||||
ASSERT_NE(IS_ERR(r), 0);
|
||||
ASSERT_EQ(PTR_ERR(r), -ETIMEDOUT);
|
||||
}
|
||||
|
||||
/* MMonScrub and MMonSync have other safeguards in place that prevent
|
||||
* us from actually receiving a reply even if the message is handled
|
||||
* by the monitor due to lack of cap checking.
|
||||
*/
|
||||
TEST_F(MonMsgTest, MMonJoin)
|
||||
{
|
||||
Message *m = new MMonJoin(get_monmap()->fsid, string("client"),
|
||||
msg->get_myaddr());
|
||||
send_wait_reply(m, MSG_MON_PAXOS, 10.0);
|
||||
|
||||
int r = monc.get_monmap();
|
||||
ASSERT_EQ(r, 0);
|
||||
ASSERT_FALSE(monc.monmap.contains("client"));
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
vector<const char*> def_args;
|
||||
vector<const char*> args;
|
||||
argv_to_vec(argc, (const char **)argv, args);
|
||||
|
||||
global_init(&def_args, args,
|
||||
CEPH_ENTITY_TYPE_CLIENT, CODE_ENVIRONMENT_UTILITY,
|
||||
0);
|
||||
common_init_finish(g_ceph_context);
|
||||
g_ceph_context->_conf->apply_changes(NULL);
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ function vstart_setup()
|
||||
export LC_ALL=C # some tests are vulnerable to i18n
|
||||
MON=1 OSD=3 ./vstart.sh \
|
||||
-o 'paxos propose interval = 0.01' \
|
||||
-n -X -l mon osd || return 1
|
||||
-n -l mon osd || return 1
|
||||
export PATH=.:$PATH
|
||||
export CEPH_CONF=ceph.conf
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user