diff options
author | Robert Godfrey <rgodfrey@apache.org> | 2013-03-27 21:58:51 +0000 |
---|---|---|
committer | Robert Godfrey <rgodfrey@apache.org> | 2013-03-27 21:58:51 +0000 |
commit | 32972ee3c75877f960c42401ddab86e0f1f02bef (patch) | |
tree | 851e925c187741f10e3e1700a1d26104c0582efc | |
parent | f576e5bc80863ac9a2f89bef42d1414d7f9a4605 (diff) | |
download | qpid-python-32972ee3c75877f960c42401ddab86e0f1f02bef.tar.gz |
NO-JIRA : merging from trunk to keep branch up to date
git-svn-id: https://svn.apache.org/repos/asf/qpid/branches/QPID-4659@1461870 13f79535-47bb-0310-9956-ffa450edef68
147 files changed, 4166 insertions, 2853 deletions
diff --git a/qpid/QPID_VERSION.txt b/qpid/QPID_VERSION.txt index 5320adc1c9..39010d220e 100644 --- a/qpid/QPID_VERSION.txt +++ b/qpid/QPID_VERSION.txt @@ -1 +1 @@ -0.21 +0.23 diff --git a/qpid/cpp/examples/messaging/client.cpp b/qpid/cpp/examples/messaging/client.cpp index f0ecd96206..983f0a8878 100644 --- a/qpid/cpp/examples/messaging/client.cpp +++ b/qpid/cpp/examples/messaging/client.cpp @@ -48,8 +48,8 @@ int main(int argc, char** argv) { Sender sender = session.createSender("service_queue"); //create temp queue & receiver... - Address responseQueue("#response-queue; {create:always, delete:always}"); - Receiver receiver = session.createReceiver(responseQueue); + Receiver receiver = session.createReceiver("#"); + Address responseQueue = receiver.getAddress(); // Now send some messages ... string s[] = { diff --git a/qpid/cpp/include/qpid/messaging/Address.h b/qpid/cpp/include/qpid/messaging/Address.h index 63dce0c49d..224a70b193 100644 --- a/qpid/cpp/include/qpid/messaging/Address.h +++ b/qpid/cpp/include/qpid/messaging/Address.h @@ -153,6 +153,7 @@ class QPID_MESSAGING_CLASS_EXTERN Address QPID_MESSAGING_EXTERN bool operator !() const; private: AddressImpl* impl; + friend class AddressImpl; }; #ifndef SWIG diff --git a/qpid/cpp/include/qpid/messaging/Receiver.h b/qpid/cpp/include/qpid/messaging/Receiver.h index 13317dfcbd..6c6fdaa7cb 100644 --- a/qpid/cpp/include/qpid/messaging/Receiver.h +++ b/qpid/cpp/include/qpid/messaging/Receiver.h @@ -34,6 +34,7 @@ namespace messaging { template <class> class PrivateImplRef; #endif +class Address; class Message; class ReceiverImpl; class Session; @@ -134,6 +135,11 @@ class QPID_MESSAGING_CLASS_EXTERN Receiver : public qpid::messaging::Handle<Rece */ QPID_MESSAGING_EXTERN Session getSession() const; + /** + * Returns an address for this receiver. + */ + QPID_MESSAGING_EXTERN Address getAddress() const; + #ifndef SWIG private: friend class qpid::messaging::PrivateImplRef<Receiver>; diff --git a/qpid/cpp/include/qpid/messaging/Sender.h b/qpid/cpp/include/qpid/messaging/Sender.h index 8e1c5846e9..9c7bca1905 100644 --- a/qpid/cpp/include/qpid/messaging/Sender.h +++ b/qpid/cpp/include/qpid/messaging/Sender.h @@ -34,6 +34,7 @@ namespace messaging { #ifndef SWIG template <class> class PrivateImplRef; #endif +class Address; class Message; class SenderImpl; class Session; @@ -89,6 +90,11 @@ class QPID_MESSAGING_CLASS_EXTERN Sender : public qpid::messaging::Handle<Sender * Returns a handle to the session associated with this sender. */ QPID_MESSAGING_EXTERN Session getSession() const; + + /** + * Returns an address for this sender. + */ + QPID_MESSAGING_EXTERN Address getAddress() const; #ifndef SWIG private: friend class qpid::messaging::PrivateImplRef<Sender>; diff --git a/qpid/cpp/src/CMakeLists.txt b/qpid/cpp/src/CMakeLists.txt index ef68d79fb2..bb00046857 100644 --- a/qpid/cpp/src/CMakeLists.txt +++ b/qpid/cpp/src/CMakeLists.txt @@ -1063,6 +1063,7 @@ install_pdb (qpidclient ${QPID_COMPONENT_CLIENT}) set (qpidmessaging_SOURCES_hidden qpid/messaging/AddressParser.h + qpid/messaging/AddressImpl.h qpid/messaging/ConnectionImpl.h qpid/messaging/ReceiverImpl.h qpid/messaging/SessionImpl.h diff --git a/qpid/cpp/src/Makefile.am b/qpid/cpp/src/Makefile.am index 42d5c60475..cad41bca86 100644 --- a/qpid/cpp/src/Makefile.am +++ b/qpid/cpp/src/Makefile.am @@ -935,6 +935,7 @@ libqpidtypes_la_LDFLAGS = -version-info $(QPIDTYPES_VERSION_INFO) libqpidmessaging_la_LIBADD = libqpidclient.la libqpidtypes.la libqpidmessaging_la_SOURCES = \ qpid/messaging/Address.cpp \ + qpid/messaging/AddressImpl.h \ qpid/messaging/AddressParser.h \ qpid/messaging/AddressParser.cpp \ qpid/messaging/Connection.cpp \ diff --git a/qpid/cpp/src/qpid/amqp/descriptors.h b/qpid/cpp/src/qpid/amqp/descriptors.h index 0a3e71fe6d..11bb7df6bb 100644 --- a/qpid/cpp/src/qpid/amqp/descriptors.h +++ b/qpid/cpp/src/qpid/amqp/descriptors.h @@ -78,10 +78,11 @@ const Descriptor SASL_OUTCOME(SASL_OUTCOME_CODE); namespace filters { const std::string LEGACY_DIRECT_FILTER_SYMBOL("apache.org:legacy-amqp-direct-binding:string"); const std::string LEGACY_TOPIC_FILTER_SYMBOL("apache.org:legacy-amqp-topic-binding:string"); -const std::string QPID_SELECTOR_FILTER_SYMBOL("qpid.apache.org:selector-filter:string"); +const std::string SELECTOR_FILTER_SYMBOL("apache.org:selector-filter:string"); const uint64_t LEGACY_DIRECT_FILTER_CODE(0x0000468C00000000ULL); const uint64_t LEGACY_TOPIC_FILTER_CODE(0x0000468C00000001ULL); +const uint64_t SELECTOR_FILTER_CODE(0x0000468C00000004ULL); } }} // namespace qpid::amqp diff --git a/qpid/cpp/src/qpid/broker/Selector.cpp b/qpid/cpp/src/qpid/broker/Selector.cpp index a6378f1910..3e7e044d5d 100644 --- a/qpid/cpp/src/qpid/broker/Selector.cpp +++ b/qpid/cpp/src/qpid/broker/Selector.cpp @@ -147,7 +147,8 @@ const Value& MessageSelectorEnv::value(const string& identifier) const return returnedValues[identifier]; } -Selector::Selector(const string& e) : +Selector::Selector(const string& e) +try : parse(TopExpression::parse(e)), expression(e) { @@ -159,6 +160,10 @@ Selector::Selector(const string& e) : QPID_LOG(debug, "Selector parsed[" << e << "] into: " << ss.str()); } } +catch (std::range_error& ex) { + QPID_LOG(debug, "Selector failed[" << e << "] -> " << ex.what()); + throw; +} Selector::~Selector() { diff --git a/qpid/cpp/src/qpid/broker/SelectorExpression.cpp b/qpid/cpp/src/qpid/broker/SelectorExpression.cpp index 2eaffd7ccb..e64fd327d5 100644 --- a/qpid/cpp/src/qpid/broker/SelectorExpression.cpp +++ b/qpid/cpp/src/qpid/broker/SelectorExpression.cpp @@ -132,12 +132,11 @@ public: virtual BoolOrNone eval(Expression&, Expression&, const SelectorEnv&) const = 0; }; -template <typename T> class UnaryBooleanOperator { public: virtual ~UnaryBooleanOperator() {} virtual void repr(ostream&) const = 0; - virtual BoolOrNone eval(T&, const SelectorEnv&) const = 0; + virtual BoolOrNone eval(Expression&, const SelectorEnv&) const = 0; }; class ArithmeticOperator { @@ -170,8 +169,7 @@ ostream& operator<<(ostream& os, const ComparisonOperator& e) return os; } -template <typename T> -ostream& operator<<(ostream& os, const UnaryBooleanOperator<T>& e) +ostream& operator<<(ostream& os, const UnaryBooleanOperator& e) { e.repr(os); return os; @@ -260,13 +258,12 @@ public: } }; -template <typename T> class UnaryBooleanExpression : public BoolExpression { - UnaryBooleanOperator<T>* op; - boost::scoped_ptr<T> e1; + UnaryBooleanOperator* op; + boost::scoped_ptr<Expression> e1; public: - UnaryBooleanExpression(UnaryBooleanOperator<T>* o, T* e) : + UnaryBooleanExpression(UnaryBooleanOperator* o, Expression* e) : op(o), e1(e) {} @@ -287,7 +284,7 @@ class LikeExpression : public BoolExpression { static string toRegex(const string& s, const string& escape) { string regex("^"); - if (escape.size()>1) throw std::range_error("Illegal escape char"); + if (escape.size()>1) throw std::logic_error("Internal error"); char e = 0; if (escape.size()==1) { e = escape[0]; @@ -594,7 +591,7 @@ class Greq : public ComparisonOperator { }; // "IS NULL" -class IsNull : public UnaryBooleanOperator<Expression> { +class IsNull : public UnaryBooleanOperator { void repr(ostream& os) const { os << "IsNull"; } @@ -605,7 +602,7 @@ class IsNull : public UnaryBooleanOperator<Expression> { }; // "IS NOT NULL" -class IsNonNull : public UnaryBooleanOperator<Expression> { +class IsNonNull : public UnaryBooleanOperator { void repr(ostream& os) const { os << "IsNonNull"; } @@ -616,7 +613,7 @@ class IsNonNull : public UnaryBooleanOperator<Expression> { }; // "NOT" -class Not : public UnaryBooleanOperator<Expression> { +class Not : public UnaryBooleanOperator { void repr(ostream& os) const { os << "NOT"; } @@ -696,14 +693,6 @@ Div div; //////////////////////////////////////////////////// -Expression* parseOrExpression(Tokeniser&); -Expression* parseAndExpression(Tokeniser&); -Expression* parseComparisonExpression(Tokeniser&); -Expression* parseAddExpression(Tokeniser&); -Expression* parseMultiplyExpression(Tokeniser&); -Expression* parseUnaryArithExpression(Tokeniser&); -Expression* parsePrimaryExpression(Tokeniser&); - // Top level parser class TopBoolExpression : public TopExpression { boost::scoped_ptr<Expression> expression; @@ -724,24 +713,28 @@ public: {} }; -TopExpression* TopExpression::parse(const string& exp) -{ - string::const_iterator s = exp.begin(); - string::const_iterator e = exp.end(); - Tokeniser tokeniser(s,e); - std::auto_ptr<Expression> b(parseOrExpression(tokeniser)); - if (!b.get()) throw std::range_error("Illegal selector: couldn't parse"); - if (tokeniser.nextToken().type != T_EOS) throw std::range_error("Illegal selector: too much input"); - return new TopBoolExpression(b.release()); +void throwParseError(Tokeniser& tokeniser, const string& msg) { + tokeniser.returnTokens(); + string error("Illegal selector: '"); + error += tokeniser.nextToken().val; + error += "': "; + error += msg; + throw std::range_error(error); } -Expression* parseOrExpression(Tokeniser& tokeniser) +class Parse { + +friend TopExpression* TopExpression::parse(const string&); + +string error; + +Expression* orExpression(Tokeniser& tokeniser) { - std::auto_ptr<Expression> e(parseAndExpression(tokeniser)); + std::auto_ptr<Expression> e(andExpression(tokeniser)); if (!e.get()) return 0; while ( tokeniser.nextToken().type==T_OR ) { std::auto_ptr<Expression> e1(e); - std::auto_ptr<Expression> e2(parseAndExpression(tokeniser)); + std::auto_ptr<Expression> e2(andExpression(tokeniser)); if (!e2.get()) return 0; e.reset(new OrExpression(e1.release(), e2.release())); } @@ -749,13 +742,13 @@ Expression* parseOrExpression(Tokeniser& tokeniser) return e.release(); } -Expression* parseAndExpression(Tokeniser& tokeniser) +Expression* andExpression(Tokeniser& tokeniser) { - std::auto_ptr<Expression> e(parseComparisonExpression(tokeniser)); + std::auto_ptr<Expression> e(comparisonExpression(tokeniser)); if (!e.get()) return 0; while ( tokeniser.nextToken().type==T_AND ) { std::auto_ptr<Expression> e1(e); - std::auto_ptr<Expression> e2(parseComparisonExpression(tokeniser)); + std::auto_ptr<Expression> e2(comparisonExpression(tokeniser)); if (!e2.get()) return 0; e.reset(new AndExpression(e1.release(), e2.release())); } @@ -763,15 +756,24 @@ Expression* parseAndExpression(Tokeniser& tokeniser) return e.release(); } -BoolExpression* parseSpecialComparisons(Tokeniser& tokeniser, std::auto_ptr<Expression> e1) { +BoolExpression* specialComparisons(Tokeniser& tokeniser, std::auto_ptr<Expression> e1) { switch (tokeniser.nextToken().type) { case T_LIKE: { const Token t = tokeniser.nextToken(); - if ( t.type!=T_STRING ) return 0; + if ( t.type!=T_STRING ) { + error = "expected string after LIKE"; + return 0; + } // Check for "ESCAPE" if ( tokeniser.nextToken().type==T_ESCAPE ) { const Token e = tokeniser.nextToken(); - if ( e.type!=T_STRING ) return 0; + if ( e.type!=T_STRING ) { + error = "expected string after ESCAPE"; + return 0; + } + if (e.val.size()>1) { + throwParseError(tokeniser, "single character string required after ESCAPE"); + } return new LikeExpression(e1.release(), t.val, e.val); } else { tokeniser.returnTokens(); @@ -779,41 +781,51 @@ BoolExpression* parseSpecialComparisons(Tokeniser& tokeniser, std::auto_ptr<Expr } } case T_BETWEEN: { - std::auto_ptr<Expression> lower(parseAddExpression(tokeniser)); + std::auto_ptr<Expression> lower(addExpression(tokeniser)); if ( !lower.get() ) return 0; - if ( tokeniser.nextToken().type!=T_AND ) return 0; - std::auto_ptr<Expression> upper(parseAddExpression(tokeniser)); + if ( tokeniser.nextToken().type!=T_AND ) { + error = "expected AND after BETWEEN"; + return 0; + } + std::auto_ptr<Expression> upper(addExpression(tokeniser)); if ( !upper.get() ) return 0; return new BetweenExpression(e1.release(), lower.release(), upper.release()); } case T_IN: { - if ( tokeniser.nextToken().type!=T_LPAREN ) return 0; + if ( tokeniser.nextToken().type!=T_LPAREN ) { + error = "missing '(' after IN"; + return 0; + } boost::ptr_vector<Expression> list; do { - std::auto_ptr<Expression> e(parseAddExpression(tokeniser)); + std::auto_ptr<Expression> e(addExpression(tokeniser)); if (!e.get()) return 0; list.push_back(e.release()); } while (tokeniser.nextToken().type==T_COMMA); tokeniser.returnTokens(); - if ( tokeniser.nextToken().type!=T_RPAREN ) return 0; + if ( tokeniser.nextToken().type!=T_RPAREN ) { + error = "missing ',' or ')' after IN"; + return 0; + } return new InExpression(e1.release(), list); } default: + error = "expected LIKE, IN or BETWEEN"; return 0; } } -Expression* parseComparisonExpression(Tokeniser& tokeniser) +Expression* comparisonExpression(Tokeniser& tokeniser) { const Token t = tokeniser.nextToken(); if ( t.type==T_NOT ) { - std::auto_ptr<Expression> e(parseComparisonExpression(tokeniser)); + std::auto_ptr<Expression> e(comparisonExpression(tokeniser)); if (!e.get()) return 0; - return new UnaryBooleanExpression<Expression>(¬Op, e.release()); + return new UnaryBooleanExpression(¬Op, e.release()); } tokeniser.returnTokens(); - std::auto_ptr<Expression> e1(parseAddExpression(tokeniser)); + std::auto_ptr<Expression> e1(addExpression(tokeniser)); if (!e1.get()) return 0; switch (tokeniser.nextToken().type) { @@ -822,23 +834,24 @@ Expression* parseComparisonExpression(Tokeniser& tokeniser) // The rest must be T_NULL or T_NOT, T_NULL switch (tokeniser.nextToken().type) { case T_NULL: - return new UnaryBooleanExpression<Expression>(&isNullOp, e1.release()); + return new UnaryBooleanExpression(&isNullOp, e1.release()); case T_NOT: if ( tokeniser.nextToken().type == T_NULL) - return new UnaryBooleanExpression<Expression>(&isNonNullOp, e1.release()); + return new UnaryBooleanExpression(&isNonNullOp, e1.release()); default: + error = "expected NULL or NOT NULL after IS"; return 0; } case T_NOT: { - std::auto_ptr<BoolExpression> e(parseSpecialComparisons(tokeniser, e1)); + std::auto_ptr<BoolExpression> e(specialComparisons(tokeniser, e1)); if (!e.get()) return 0; - return new UnaryBooleanExpression<Expression>(¬Op, e.release()); + return new UnaryBooleanExpression(¬Op, e.release()); } case T_BETWEEN: case T_LIKE: case T_IN: { tokeniser.returnTokens(); - return parseSpecialComparisons(tokeniser, e1); + return specialComparisons(tokeniser, e1); } default: break; @@ -858,15 +871,15 @@ Expression* parseComparisonExpression(Tokeniser& tokeniser) return e1.release(); } - std::auto_ptr<Expression> e2(parseAddExpression(tokeniser)); + std::auto_ptr<Expression> e2(addExpression(tokeniser)); if (!e2.get()) return 0; return new ComparisonExpression(op, e1.release(), e2.release()); } -Expression* parseAddExpression(Tokeniser& tokeniser) +Expression* addExpression(Tokeniser& tokeniser) { - std::auto_ptr<Expression> e(parseMultiplyExpression(tokeniser)); + std::auto_ptr<Expression> e(multiplyExpression(tokeniser)); if (!e.get()) return 0; Token t = tokeniser.nextToken(); @@ -876,10 +889,11 @@ Expression* parseAddExpression(Tokeniser& tokeniser) case T_PLUS: op = &add; break; case T_MINUS: op = ⊂ break; default: + error = "internal error processing binary + or -"; return 0; } std::auto_ptr<Expression> e1(e); - std::auto_ptr<Expression> e2(parseMultiplyExpression(tokeniser)); + std::auto_ptr<Expression> e2(multiplyExpression(tokeniser)); if (!e2.get()) return 0; e.reset(new ArithmeticExpression(op, e1.release(), e2.release())); t = tokeniser.nextToken(); @@ -889,9 +903,9 @@ Expression* parseAddExpression(Tokeniser& tokeniser) return e.release(); } -Expression* parseMultiplyExpression(Tokeniser& tokeniser) +Expression* multiplyExpression(Tokeniser& tokeniser) { - std::auto_ptr<Expression> e(parseUnaryArithExpression(tokeniser)); + std::auto_ptr<Expression> e(unaryArithExpression(tokeniser)); if (!e.get()) return 0; Token t = tokeniser.nextToken(); @@ -901,10 +915,11 @@ Expression* parseMultiplyExpression(Tokeniser& tokeniser) case T_MULT: op = &mult; break; case T_DIV: op = ÷ break; default: + error = "internal error processing * or /"; return 0; } std::auto_ptr<Expression> e1(e); - std::auto_ptr<Expression> e2(parseMultiplyExpression(tokeniser)); + std::auto_ptr<Expression> e2(unaryArithExpression(tokeniser)); if (!e2.get()) return 0; e.reset(new ArithmeticExpression(op, e1.release(), e2.release())); t = tokeniser.nextToken(); @@ -914,20 +929,23 @@ Expression* parseMultiplyExpression(Tokeniser& tokeniser) return e.release(); } -Expression* parseUnaryArithExpression(Tokeniser& tokeniser) +Expression* unaryArithExpression(Tokeniser& tokeniser) { const Token t = tokeniser.nextToken(); switch (t.type) { case T_LPAREN: { - std::auto_ptr<Expression> e(parseOrExpression(tokeniser)); + std::auto_ptr<Expression> e(orExpression(tokeniser)); if (!e.get()) return 0; - if ( tokeniser.nextToken().type!=T_RPAREN ) return 0; + if ( tokeniser.nextToken().type!=T_RPAREN ) { + error = "missing ')' after '('"; + return 0; + } return e.release(); } case T_PLUS: break; // Unary + is no op case T_MINUS: { - std::auto_ptr<Expression> e(parseUnaryArithExpression(tokeniser)); + std::auto_ptr<Expression> e(unaryArithExpression(tokeniser)); if (!e.get()) return 0; return new UnaryArithExpression(&negate, e.release()); } @@ -936,11 +954,11 @@ Expression* parseUnaryArithExpression(Tokeniser& tokeniser) } tokeniser.returnTokens(); - std::auto_ptr<Expression> e(parsePrimaryExpression(tokeniser)); + std::auto_ptr<Expression> e(primaryExpression(tokeniser)); return e.release(); } -Expression* parsePrimaryExpression(Tokeniser& tokeniser) +Expression* primaryExpression(Tokeniser& tokeniser) { const Token& t = tokeniser.nextToken(); switch (t.type) { @@ -957,8 +975,27 @@ Expression* parsePrimaryExpression(Tokeniser& tokeniser) case T_NUMERIC_APPROX: return new Literal(boost::lexical_cast<double>(t.val)); default: + error = "expected literal or identifier"; return 0; } } +}; + +TopExpression* TopExpression::parse(const string& exp) +{ + string::const_iterator s = exp.begin(); + string::const_iterator e = exp.end(); + Tokeniser tokeniser(s,e); + Parse parse; + std::auto_ptr<Expression> b(parse.orExpression(tokeniser)); + if (!b.get()) { + throwParseError(tokeniser, parse.error); + } + if (tokeniser.nextToken().type != T_EOS) { + throwParseError(tokeniser, "extra input"); + } + return new TopBoolExpression(b.release()); +} + }} diff --git a/qpid/cpp/src/qpid/broker/SelectorToken.cpp b/qpid/cpp/src/qpid/broker/SelectorToken.cpp index 1e84834e18..e745bc3889 100644 --- a/qpid/cpp/src/qpid/broker/SelectorToken.cpp +++ b/qpid/cpp/src/qpid/broker/SelectorToken.cpp @@ -132,8 +132,8 @@ bool processString(std::string::const_iterator& s, std::string::const_iterator& ++q; } + tok = Token(T_STRING, s, content); s = q; - tok = Token(T_STRING, content); return true; } @@ -171,7 +171,7 @@ bool tokenise(std::string::const_iterator& s, std::string::const_iterator& e, To while (true) switch (state) { case START: - if (t==e) {tok = Token(T_EOS, ""); return true;} + if (t==e) {tok = Token(T_EOS, s, "<END>"); return true;} else switch (*t) { case '(': tokType = T_LPAREN; state = ACCEPT_INC; continue; case ')': tokType = T_RPAREN; state = ACCEPT_INC; continue; @@ -259,6 +259,7 @@ bool tokenise(std::string::const_iterator& s, std::string::const_iterator& e, To Tokeniser::Tokeniser(const std::string::const_iterator& s, const std::string::const_iterator& e) : tokp(0), + inStart(s), inp(s), inEnd(e) { @@ -294,5 +295,11 @@ void Tokeniser::returnTokens(unsigned int n) tokp-=n; } +std::string Tokeniser::remaining() +{ + Token& currentTok = tokens[tokp]; + return std::string(currentTok.tokenStart, inEnd); +} + }} diff --git a/qpid/cpp/src/qpid/broker/SelectorToken.h b/qpid/cpp/src/qpid/broker/SelectorToken.h index 91fb1d5c36..bd60b69dce 100644 --- a/qpid/cpp/src/qpid/broker/SelectorToken.h +++ b/qpid/cpp/src/qpid/broker/SelectorToken.h @@ -67,6 +67,7 @@ typedef enum { struct Token { TokenType type; std::string val; + std::string::const_iterator tokenStart; Token() {} @@ -76,14 +77,23 @@ struct Token { val(v) {} + Token(TokenType t, const std::string::const_iterator& s, const std::string& v) : + type(t), + val(v), + tokenStart(s) + {} + Token(TokenType t, const std::string::const_iterator& s, const std::string::const_iterator& e) : type(t), - val(std::string(s,e)) + val(std::string(s,e)), + tokenStart(s) {} bool operator==(const Token& r) const { - return type == r.type && val == r.val; + return + (type == T_EOS && r.type == T_EOS) || + (type == r.type && val == r.val); } }; @@ -100,6 +110,7 @@ class Tokeniser { std::vector<Token> tokens; unsigned int tokp; + std::string::const_iterator inStart; std::string::const_iterator inp; std::string::const_iterator inEnd; @@ -107,6 +118,7 @@ public: QPID_BROKER_EXTERN Tokeniser(const std::string::const_iterator& s, const std::string::const_iterator& e); QPID_BROKER_EXTERN void returnTokens(unsigned int n = 1); QPID_BROKER_EXTERN const Token& nextToken(); + QPID_BROKER_EXTERN std::string remaining(); }; }} diff --git a/qpid/cpp/src/qpid/broker/SelectorValue.cpp b/qpid/cpp/src/qpid/broker/SelectorValue.cpp index de097b8969..0855fa91d0 100644 --- a/qpid/cpp/src/qpid/broker/SelectorValue.cpp +++ b/qpid/cpp/src/qpid/broker/SelectorValue.cpp @@ -100,6 +100,8 @@ NumericPairBase* promoteNumeric(const Value& v1, const Value& v2) assert(false); } } + // Can never get here - but this stops a warning + return 0; } bool operator==(const Value& v1, const Value& v2) diff --git a/qpid/cpp/src/qpid/broker/SemanticState.cpp b/qpid/cpp/src/qpid/broker/SemanticState.cpp index 60c2d6e555..275886c6e3 100644 --- a/qpid/cpp/src/qpid/broker/SemanticState.cpp +++ b/qpid/cpp/src/qpid/broker/SemanticState.cpp @@ -286,7 +286,7 @@ void SemanticState::record(const DeliveryRecord& delivery) } const std::string QPID_SYNC_FREQUENCY("qpid.sync_frequency"); -const std::string QPID_SELECTOR("qpid.selector"); +const std::string APACHE_SELECTOR("x-apache-selector"); SemanticStateConsumerImpl::SemanticStateConsumerImpl(SemanticState* _parent, const string& _name, @@ -309,7 +309,7 @@ Consumer(_name, type), exclusive(_exclusive), resumeId(_resumeId), tag(_tag), - selector(returnSelector(_arguments.getAsString(QPID_SELECTOR))), + selector(returnSelector(_arguments.getAsString(APACHE_SELECTOR))), resumeTtl(_resumeTtl), arguments(_arguments), notifyEnabled(true), diff --git a/qpid/cpp/src/qpid/broker/amqp/Connection.cpp b/qpid/cpp/src/qpid/broker/amqp/Connection.cpp index a83034eb6e..be98c048b6 100644 --- a/qpid/cpp/src/qpid/broker/amqp/Connection.cpp +++ b/qpid/cpp/src/qpid/broker/amqp/Connection.cpp @@ -37,11 +37,11 @@ namespace qpid { namespace broker { namespace amqp { -Connection::Connection(qpid::sys::OutputControl& o, const std::string& i, qpid::broker::Broker& b, Interconnects& interconnects_, bool saslInUse) +Connection::Connection(qpid::sys::OutputControl& o, const std::string& i, qpid::broker::Broker& b, Interconnects& interconnects_, bool saslInUse, const std::string& d) : ManagedConnection(b, i), connection(pn_connection()), transport(pn_transport()), - out(o), id(i), broker(b), haveOutput(true), interconnects(interconnects_) + out(o), id(i), broker(b), haveOutput(true), interconnects(interconnects_), domain(d) { if (pn_transport_bind(transport, connection)) { //error @@ -265,4 +265,8 @@ std::string Connection::getError() return text.str(); } +std::string Connection::getDomain() const +{ + return domain; +} }}} // namespace qpid::broker::amqp diff --git a/qpid/cpp/src/qpid/broker/amqp/Connection.h b/qpid/cpp/src/qpid/broker/amqp/Connection.h index 28cf86f123..d61db82e60 100644 --- a/qpid/cpp/src/qpid/broker/amqp/Connection.h +++ b/qpid/cpp/src/qpid/broker/amqp/Connection.h @@ -45,7 +45,7 @@ class Session; class Connection : public sys::ConnectionCodec, public ManagedConnection { public: - Connection(qpid::sys::OutputControl& out, const std::string& id, qpid::broker::Broker& broker, Interconnects&, bool saslInUse); + Connection(qpid::sys::OutputControl& out, const std::string& id, qpid::broker::Broker& broker, Interconnects&, bool saslInUse, const std::string& domain); virtual ~Connection(); size_t decode(const char* buffer, size_t size); virtual size_t encode(char* buffer, size_t size); @@ -57,6 +57,7 @@ class Connection : public sys::ConnectionCodec, public ManagedConnection framing::ProtocolVersion getVersion() const; pn_transport_t* getTransport(); Interconnects& getInterconnects(); + std::string getDomain() const; protected: typedef std::map<pn_session_t*, boost::shared_ptr<Session> > Sessions; pn_connection_t* connection; @@ -67,6 +68,7 @@ class Connection : public sys::ConnectionCodec, public ManagedConnection bool haveOutput; Sessions sessions; Interconnects& interconnects; + std::string domain; virtual void process(); std::string getError(); diff --git a/qpid/cpp/src/qpid/broker/amqp/Filter.cpp b/qpid/cpp/src/qpid/broker/amqp/Filter.cpp index 2984c2923a..bbe3330939 100644 --- a/qpid/cpp/src/qpid/broker/amqp/Filter.cpp +++ b/qpid/cpp/src/qpid/broker/amqp/Filter.cpp @@ -61,7 +61,7 @@ void Filter::onStringValue(const qpid::amqp::CharSequence& key, const qpid::amqp if (descriptor->match(qpid::amqp::filters::LEGACY_TOPIC_FILTER_SYMBOL, qpid::amqp::filters::LEGACY_TOPIC_FILTER_CODE) || descriptor->match(qpid::amqp::filters::LEGACY_DIRECT_FILTER_SYMBOL, qpid::amqp::filters::LEGACY_DIRECT_FILTER_CODE)) { setSubjectFilter(filter); - } else if (descriptor->match(qpid::amqp::filters::QPID_SELECTOR_FILTER_SYMBOL, 0)) { + } else if (descriptor->match(qpid::amqp::filters::SELECTOR_FILTER_SYMBOL, qpid::amqp::filters::SELECTOR_FILTER_CODE)) { setSelectorFilter(filter); } else { QPID_LOG(notice, "Skipping unrecognised string filter with key " << filter.key << " and descriptor " << filter.descriptor); diff --git a/qpid/cpp/src/qpid/broker/amqp/Interconnect.cpp b/qpid/cpp/src/qpid/broker/amqp/Interconnect.cpp index 082715b1b2..92a6f75f1e 100644 --- a/qpid/cpp/src/qpid/broker/amqp/Interconnect.cpp +++ b/qpid/cpp/src/qpid/broker/amqp/Interconnect.cpp @@ -41,7 +41,7 @@ namespace amqp { Interconnect::Interconnect(qpid::sys::OutputControl& out, const std::string& id, qpid::broker::Broker& broker, bool saslInUse, bool i, const std::string& n, const std::string& s, const std::string& t, Domain& d, Interconnects& r) - : Connection(out, id, broker, r, saslInUse), incoming(i), name(n), source(s), target(t), domain(d), registry(r), headerDiscarded(false), + : Connection(out, id, broker, r, saslInUse, std::string()), incoming(i), name(n), source(s), target(t), domain(d), registry(r), headerDiscarded(false), closeRequested(false), isTransportDeleted(false) {} diff --git a/qpid/cpp/src/qpid/broker/amqp/ProtocolPlugin.cpp b/qpid/cpp/src/qpid/broker/amqp/ProtocolPlugin.cpp index 0e622f8d20..16f0cdc39f 100644 --- a/qpid/cpp/src/qpid/broker/amqp/ProtocolPlugin.cpp +++ b/qpid/cpp/src/qpid/broker/amqp/ProtocolPlugin.cpp @@ -41,10 +41,19 @@ namespace qpid { namespace broker { namespace amqp { +struct Options : public qpid::Options { + std::string domain; + + Options() : qpid::Options("AMQP 1.0 Options") { + addOptions() + ("domain", optValue(domain, "DOMAIN"), "Domain of this broker"); + } +}; + class ProtocolImpl : public Protocol { public: - ProtocolImpl(Interconnects* i, Broker& b) : interconnects(i), broker(b) + ProtocolImpl(Interconnects* i, Broker& b, const std::string& d) : interconnects(i), broker(b), domain(d) { broker.getObjectFactoryRegistry().add(interconnects);//registry deletes on shutdown } @@ -54,16 +63,20 @@ class ProtocolImpl : public Protocol private: Interconnects* interconnects; Broker& broker; + std::string domain; }; struct ProtocolPlugin : public Plugin { + Options options; + Options* getOptions() { return &options; } + void earlyInitialize(Plugin::Target& target) { //need to register protocol before recovery from store broker::Broker* broker = dynamic_cast<qpid::broker::Broker*>(&target); if (broker) { - ProtocolImpl* impl = new ProtocolImpl(new Interconnects(), *broker); + ProtocolImpl* impl = new ProtocolImpl(new Interconnects(), *broker, options.domain); broker->getProtocolRegistry().add("AMQP 1.0", impl);//registry deletes on shutdown } } @@ -79,18 +92,20 @@ qpid::sys::ConnectionCodec* ProtocolImpl::create(const qpid::framing::ProtocolVe if (v.getProtocol() == qpid::framing::ProtocolVersion::SASL) { if (broker.getOptions().auth) { QPID_LOG(info, "Using AMQP 1.0 (with SASL layer)"); - return new qpid::broker::amqp::Sasl(out, id, broker, *interconnects, qpid::SaslFactory::getInstance().createServer(broker.getOptions().realm, broker.getOptions().requireEncrypted, external)); + return new qpid::broker::amqp::Sasl(out, id, broker, *interconnects, + qpid::SaslFactory::getInstance().createServer(broker.getOptions().realm,broker.getOptions().requireEncrypted, external), + domain); } else { std::auto_ptr<SaslServer> authenticator(new qpid::NullSaslServer(broker.getOptions().realm)); QPID_LOG(info, "Using AMQP 1.0 (with dummy SASL layer)"); - return new qpid::broker::amqp::Sasl(out, id, broker, *interconnects, authenticator); + return new qpid::broker::amqp::Sasl(out, id, broker, *interconnects, authenticator, domain); } } else { if (broker.getOptions().auth) { throw qpid::Exception("SASL layer required!"); } else { QPID_LOG(info, "Using AMQP 1.0 (no SASL layer)"); - return new qpid::broker::amqp::Connection(out, id, broker, *interconnects, false); + return new qpid::broker::amqp::Connection(out, id, broker, *interconnects, false, domain); } } } diff --git a/qpid/cpp/src/qpid/broker/amqp/Sasl.cpp b/qpid/cpp/src/qpid/broker/amqp/Sasl.cpp index d8e12fcfdd..820aaf87d4 100644 --- a/qpid/cpp/src/qpid/broker/amqp/Sasl.cpp +++ b/qpid/cpp/src/qpid/broker/amqp/Sasl.cpp @@ -31,8 +31,8 @@ namespace qpid { namespace broker { namespace amqp { -Sasl::Sasl(qpid::sys::OutputControl& o, const std::string& id, qpid::broker::Broker& broker, Interconnects& i, std::auto_ptr<qpid::SaslServer> auth) - : qpid::amqp::SaslServer(id), out(o), connection(out, id, broker, i, true), +Sasl::Sasl(qpid::sys::OutputControl& o, const std::string& id, qpid::broker::Broker& broker, Interconnects& i, std::auto_ptr<qpid::SaslServer> auth, const std::string& domain) + : qpid::amqp::SaslServer(id), out(o), connection(out, id, broker, i, true, domain), authenticator(auth), state(INCOMPLETE), writeHeader(true), haveOutput(true) { diff --git a/qpid/cpp/src/qpid/broker/amqp/Sasl.h b/qpid/cpp/src/qpid/broker/amqp/Sasl.h index 7718b4c43a..194ab0a0d5 100644 --- a/qpid/cpp/src/qpid/broker/amqp/Sasl.h +++ b/qpid/cpp/src/qpid/broker/amqp/Sasl.h @@ -39,7 +39,7 @@ namespace amqp { class Sasl : public sys::ConnectionCodec, qpid::amqp::SaslServer { public: - Sasl(qpid::sys::OutputControl& out, const std::string& id, qpid::broker::Broker& broker, Interconnects&, std::auto_ptr<qpid::SaslServer> authenticator); + Sasl(qpid::sys::OutputControl& out, const std::string& id, qpid::broker::Broker& broker, Interconnects&, std::auto_ptr<qpid::SaslServer> authenticator, const std::string& domain); ~Sasl(); size_t decode(const char* buffer, size_t size); diff --git a/qpid/cpp/src/qpid/broker/amqp/Session.cpp b/qpid/cpp/src/qpid/broker/amqp/Session.cpp index 544616b897..62011f6372 100644 --- a/qpid/cpp/src/qpid/broker/amqp/Session.cpp +++ b/qpid/cpp/src/qpid/broker/amqp/Session.cpp @@ -53,6 +53,20 @@ namespace qpid { namespace broker { namespace amqp { +namespace { +bool is_capability_requested(const std::string& name, pn_data_t* capabilities) +{ + while (pn_data_next(capabilities)) { + pn_bytes_t c = pn_data_get_symbol(capabilities); + std::string s(c.start, c.size); + if (s == name) return true; + } + return false; +} + +const std::string CREATE_ON_DEMAND("create-on-demand"); +} + class IncomingToQueue : public DecodingIncoming { public: @@ -81,7 +95,7 @@ Session::ResolvedNode Session::resolve(const std::string name, pn_terminus_t* te node.exchange = broker.getExchanges().find(name); node.queue = broker.getQueues().find(name); if (!node.queue && !node.exchange) { - if (pn_terminus_is_dynamic(terminus)) { + if (pn_terminus_is_dynamic(terminus) || is_capability_requested(CREATE_ON_DEMAND, pn_terminus_capabilities(terminus))) { //is it a queue or an exchange? NodeProperties properties; properties.read(pn_terminus_properties(terminus)); @@ -117,25 +131,43 @@ Session::ResolvedNode Session::resolve(const std::string name, pn_terminus_t* te return node; } +std::string Session::generateName(pn_link_t* link) +{ + std::stringstream s; + s << qpid::types::Uuid(true) << "::" << pn_link_name(link); + if (!connection.getDomain().empty()) { + s << "@" << connection.getDomain(); + } + return s.str(); +} + void Session::attach(pn_link_t* link) { if (pn_link_is_sender(link)) { pn_terminus_t* source = pn_link_remote_source(link); //i.e a subscription + std::string name; if (pn_terminus_get_type(source) == PN_UNSPECIFIED) { throw qpid::Exception("No source specified!");/*invalid-field?*/ + } else if (pn_terminus_is_dynamic(source)) { + name = generateName(link); + } else { + name = pn_terminus_get_address(source); } - std::string name = pn_terminus_get_address(source); QPID_LOG(debug, "Received attach request for outgoing link from " << name); pn_terminus_set_address(pn_link_source(link), name.c_str()); setupOutgoing(link, source, name); } else { pn_terminus_t* target = pn_link_remote_target(link); + std::string name; if (pn_terminus_get_type(target) == PN_UNSPECIFIED) { throw qpid::Exception("No target specified!");/*invalid field?*/ + } else if (pn_terminus_is_dynamic(target)) { + name = generateName(link); + } else { + name = pn_terminus_get_address(target); } - std::string name = pn_terminus_get_address(target); QPID_LOG(debug, "Received attach request for incoming link to " << name); pn_terminus_set_address(pn_link_target(link), name.c_str()); diff --git a/qpid/cpp/src/qpid/broker/amqp/Session.h b/qpid/cpp/src/qpid/broker/amqp/Session.h index 74f50a9eda..b142224cfd 100644 --- a/qpid/cpp/src/qpid/broker/amqp/Session.h +++ b/qpid/cpp/src/qpid/broker/amqp/Session.h @@ -95,6 +95,7 @@ class Session : public ManagedSession, public boost::enable_shared_from_this<Ses ResolvedNode resolve(const std::string name, pn_terminus_t* terminus, bool incoming); void setupOutgoing(pn_link_t* link, pn_terminus_t* source, const std::string& name); void setupIncoming(pn_link_t* link, pn_terminus_t* target, const std::string& name); + std::string generateName(pn_link_t*); }; }}} // namespace qpid::broker::amqp diff --git a/qpid/cpp/src/qpid/client/amqp0_10/AddressResolution.cpp b/qpid/cpp/src/qpid/client/amqp0_10/AddressResolution.cpp index de6ee0cd2d..a9f2758605 100644 --- a/qpid/cpp/src/qpid/client/amqp0_10/AddressResolution.cpp +++ b/qpid/cpp/src/qpid/client/amqp0_10/AddressResolution.cpp @@ -93,7 +93,8 @@ const std::string DURABLE("durable"); const std::string X_DECLARE("x-declare"); const std::string X_SUBSCRIBE("x-subscribe"); const std::string X_BINDINGS("x-bindings"); -const std::string QPID_SELECTOR("qpid.selector"); +const std::string SELECTOR("selector"); +const std::string APACHE_SELECTOR("x-apache-selector"); const std::string EXCHANGE("exchange"); const std::string QUEUE("queue"); const std::string KEY("key"); @@ -477,8 +478,8 @@ QueueSource::QueueSource(const Address& address) : //options) exclusive = Opt(address)/LINK/X_SUBSCRIBE/EXCLUSIVE; (Opt(address)/LINK/X_SUBSCRIBE/ARGUMENTS).collect(options); - std::string selector = Opt(address)/LINK/QPID_SELECTOR; - if (!selector.empty()) options.setString(QPID_SELECTOR, selector); + std::string selector = Opt(address)/LINK/SELECTOR; + if (!selector.empty()) options.setString(APACHE_SELECTOR, selector); } void QueueSource::subscribe(qpid::client::AsyncSession& session, const std::string& destination) @@ -990,7 +991,7 @@ Verifier::Verifier() link[X_SUBSCRIBE] = true; link[X_DECLARE] = true; link[X_BINDINGS] = true; - link[QPID_SELECTOR] = true; + link[SELECTOR] = true; defined[LINK] = link; } void Verifier::verify(const Address& address) const diff --git a/qpid/cpp/src/qpid/client/amqp0_10/ReceiverImpl.cpp b/qpid/cpp/src/qpid/client/amqp0_10/ReceiverImpl.cpp index 40b09b0fc0..11f9475cad 100644 --- a/qpid/cpp/src/qpid/client/amqp0_10/ReceiverImpl.cpp +++ b/qpid/cpp/src/qpid/client/amqp0_10/ReceiverImpl.cpp @@ -143,6 +143,11 @@ uint32_t ReceiverImpl::getUnsettled() return parent->getUnsettledAcks(destination); } +qpid::messaging::Address ReceiverImpl::getAddress() const +{ + return address; +} + ReceiverImpl::ReceiverImpl(SessionImpl& p, const std::string& name, const qpid::messaging::Address& a) : diff --git a/qpid/cpp/src/qpid/client/amqp0_10/ReceiverImpl.h b/qpid/cpp/src/qpid/client/amqp0_10/ReceiverImpl.h index 76da4f31a9..4dba76c8d9 100644 --- a/qpid/cpp/src/qpid/client/amqp0_10/ReceiverImpl.h +++ b/qpid/cpp/src/qpid/client/amqp0_10/ReceiverImpl.h @@ -66,6 +66,7 @@ class ReceiverImpl : public qpid::messaging::ReceiverImpl void received(qpid::messaging::Message& message); qpid::messaging::Session getSession() const; bool isClosed() const; + qpid::messaging::Address getAddress() const; private: mutable sys::Mutex lock; diff --git a/qpid/cpp/src/qpid/client/amqp0_10/SenderImpl.cpp b/qpid/cpp/src/qpid/client/amqp0_10/SenderImpl.cpp index b275db38d7..7001acaf99 100644 --- a/qpid/cpp/src/qpid/client/amqp0_10/SenderImpl.cpp +++ b/qpid/cpp/src/qpid/client/amqp0_10/SenderImpl.cpp @@ -34,6 +34,11 @@ SenderImpl::SenderImpl(SessionImpl& _parent, const std::string& _name, parent(&_parent), name(_name), address(_address), state(UNRESOLVED), capacity(50), window(0), flushed(false), unreliable(AddressResolution::is_unreliable(address)) {} +qpid::messaging::Address SenderImpl::getAddress() const +{ + return address; +} + void SenderImpl::send(const qpid::messaging::Message& message, bool sync) { if (unreliable) { // immutable, don't need lock diff --git a/qpid/cpp/src/qpid/client/amqp0_10/SenderImpl.h b/qpid/cpp/src/qpid/client/amqp0_10/SenderImpl.h index d75863c743..ee250af2d4 100644 --- a/qpid/cpp/src/qpid/client/amqp0_10/SenderImpl.h +++ b/qpid/cpp/src/qpid/client/amqp0_10/SenderImpl.h @@ -56,6 +56,7 @@ class SenderImpl : public qpid::messaging::SenderImpl void init(qpid::client::AsyncSession, AddressResolution&); const std::string& getName() const; qpid::messaging::Session getSession() const; + qpid::messaging::Address getAddress() const; private: mutable sys::Mutex lock; diff --git a/qpid/cpp/src/qpid/messaging/Address.cpp b/qpid/cpp/src/qpid/messaging/Address.cpp index a516959edb..6fbaeef661 100644 --- a/qpid/cpp/src/qpid/messaging/Address.cpp +++ b/qpid/cpp/src/qpid/messaging/Address.cpp @@ -19,6 +19,8 @@ * */ #include "qpid/messaging/Address.h" +#include "qpid/messaging/AddressImpl.h" +#include "qpid/messaging/AddressParser.h" #include "qpid/framing/Uuid.h" #include <sstream> #include <boost/format.hpp> @@ -34,51 +36,9 @@ const std::string OPTIONS_DIVIDER = ";"; const std::string SPACE = " "; const std::string TYPE = "type"; } -class AddressImpl -{ - public: - std::string name; - std::string subject; - Variant::Map options; - - AddressImpl() {} - AddressImpl(const std::string& n, const std::string& s, const Variant::Map& o) : - name(n), subject(s), options(o) {} -}; - -class AddressParser -{ - public: - AddressParser(const std::string&); - bool parse(Address& address); - private: - const std::string& input; - std::string::size_type current; - static const std::string RESERVED; - - bool readChar(char c); - bool readQuotedString(std::string& s); - bool readQuotedValue(Variant& value); - bool readString(std::string& value, char delimiter); - bool readWord(std::string& word, const std::string& delims = RESERVED); - bool readSimpleValue(Variant& word); - bool readKey(std::string& key); - bool readValue(Variant& value); - bool readKeyValuePair(Variant::Map& map); - bool readMap(Variant& value); - bool readList(Variant& value); - bool readName(std::string& name); - bool readSubject(std::string& subject); - bool error(const std::string& message); - bool eos(); - bool iswhitespace(); - bool in(const std::string& delims); - bool isreserved(); -}; - Address::Address() : impl(new AddressImpl()) {} Address::Address(const std::string& address) : impl(new AddressImpl()) -{ +{ AddressParser parser(address); parser.parse(*this); } @@ -86,7 +46,7 @@ Address::Address(const std::string& name, const std::string& subject, const Vari const std::string& type) : impl(new AddressImpl(name, subject, options)) { setType(type); } Address::Address(const Address& a) : - impl(new AddressImpl(a.impl->name, a.impl->subject, a.impl->options)) {} + impl(new AddressImpl(a.impl->name, a.impl->subject, a.impl->options)) { impl->temporary = a.impl->temporary; } Address::~Address() { delete impl; } Address& Address::operator=(const Address& a) { *impl = *a.impl; return *this; } diff --git a/qpid/cpp/src/qpid/messaging/AddressImpl.h b/qpid/cpp/src/qpid/messaging/AddressImpl.h new file mode 100644 index 0000000000..8d34bd73c4 --- /dev/null +++ b/qpid/cpp/src/qpid/messaging/AddressImpl.h @@ -0,0 +1,45 @@ +#ifndef QPID_MESSAGING_ADDRESSIMPL_H +#define QPID_MESSAGING_ADDRESSIMPL_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/messaging/Address.h" +#include "qpid/types/Variant.h" +namespace qpid { +namespace messaging { + +class AddressImpl +{ + public: + std::string name; + std::string subject; + qpid::types::Variant::Map options; + bool temporary; + + AddressImpl() : temporary(false) {} + AddressImpl(const std::string& n, const std::string& s, const qpid::types::Variant::Map& o) : + name(n), subject(s), options(o), temporary(false) {} + static void setTemporary(Address& a, bool value) { a.impl->temporary = value; } + static bool isTemporary(const Address& a) { return a.impl->temporary; } +}; +}} // namespace qpid::messaging + +#endif /*!QPID_MESSAGING_ADDRESSIMPL_H*/ diff --git a/qpid/cpp/src/qpid/messaging/AddressParser.cpp b/qpid/cpp/src/qpid/messaging/AddressParser.cpp index 67249e188e..882deba463 100644 --- a/qpid/cpp/src/qpid/messaging/AddressParser.cpp +++ b/qpid/cpp/src/qpid/messaging/AddressParser.cpp @@ -19,6 +19,7 @@ * */ #include "AddressParser.h" +#include "AddressImpl.h" #include "qpid/framing/Uuid.h" #include <boost/format.hpp> @@ -38,7 +39,10 @@ bool AddressParser::parse(Address& address) { std::string name; if (readName(name)) { - if (name.find('#') == 0) name = qpid::framing::Uuid(true).str() + name; + if (name.find('#') == 0) { + name = qpid::framing::Uuid(true).str() + name; + AddressImpl::setTemporary(address, true); + } address.setName(name); if (readChar('/')) { std::string subject; diff --git a/qpid/cpp/src/qpid/messaging/Receiver.cpp b/qpid/cpp/src/qpid/messaging/Receiver.cpp index 78e0c5daa3..c45ebd6760 100644 --- a/qpid/cpp/src/qpid/messaging/Receiver.cpp +++ b/qpid/cpp/src/qpid/messaging/Receiver.cpp @@ -19,6 +19,7 @@ * */ #include "qpid/messaging/Receiver.h" +#include "qpid/messaging/Address.h" #include "qpid/messaging/Message.h" #include "qpid/messaging/ReceiverImpl.h" #include "qpid/messaging/Session.h" @@ -45,4 +46,5 @@ void Receiver::close() { impl->close(); } const std::string& Receiver::getName() const { return impl->getName(); } Session Receiver::getSession() const { return impl->getSession(); } bool Receiver::isClosed() const { return impl->isClosed(); } +Address Receiver::getAddress() const { return impl->getAddress(); } }} // namespace qpid::messaging diff --git a/qpid/cpp/src/qpid/messaging/ReceiverImpl.h b/qpid/cpp/src/qpid/messaging/ReceiverImpl.h index e450693d2c..59ccc3214e 100644 --- a/qpid/cpp/src/qpid/messaging/ReceiverImpl.h +++ b/qpid/cpp/src/qpid/messaging/ReceiverImpl.h @@ -27,6 +27,7 @@ namespace qpid { namespace messaging { +class Address; class Duration; class Message; class MessageListener; @@ -48,6 +49,7 @@ class ReceiverImpl : public virtual qpid::RefCounted virtual const std::string& getName() const = 0; virtual Session getSession() const = 0; virtual bool isClosed() const = 0; + virtual Address getAddress() const = 0; }; }} // namespace qpid::messaging diff --git a/qpid/cpp/src/qpid/messaging/Sender.cpp b/qpid/cpp/src/qpid/messaging/Sender.cpp index 53dbb69777..a60de3d606 100644 --- a/qpid/cpp/src/qpid/messaging/Sender.cpp +++ b/qpid/cpp/src/qpid/messaging/Sender.cpp @@ -19,6 +19,7 @@ * */ #include "qpid/messaging/Sender.h" +#include "qpid/messaging/Address.h" #include "qpid/messaging/Message.h" #include "qpid/messaging/SenderImpl.h" #include "qpid/messaging/Session.h" @@ -40,5 +41,5 @@ uint32_t Sender::getUnsettled() { return impl->getUnsettled(); } uint32_t Sender::getAvailable() { return getCapacity() - getUnsettled(); } const std::string& Sender::getName() const { return impl->getName(); } Session Sender::getSession() const { return impl->getSession(); } - +Address Sender::getAddress() const { return impl->getAddress(); } }} // namespace qpid::messaging diff --git a/qpid/cpp/src/qpid/messaging/SenderImpl.h b/qpid/cpp/src/qpid/messaging/SenderImpl.h index d978463fdb..91fd9b1536 100644 --- a/qpid/cpp/src/qpid/messaging/SenderImpl.h +++ b/qpid/cpp/src/qpid/messaging/SenderImpl.h @@ -27,6 +27,7 @@ namespace qpid { namespace messaging { +class Address; class Message; class Session; @@ -41,6 +42,7 @@ class SenderImpl : public virtual qpid::RefCounted virtual uint32_t getUnsettled() = 0; virtual const std::string& getName() const = 0; virtual Session getSession() const = 0; + virtual Address getAddress() const = 0; private: }; }} // namespace qpid::messaging diff --git a/qpid/cpp/src/qpid/messaging/amqp/AddressHelper.cpp b/qpid/cpp/src/qpid/messaging/amqp/AddressHelper.cpp index 359660dce5..a46606a526 100644 --- a/qpid/cpp/src/qpid/messaging/amqp/AddressHelper.cpp +++ b/qpid/cpp/src/qpid/messaging/amqp/AddressHelper.cpp @@ -20,6 +20,7 @@ */ #include "qpid/messaging/amqp/AddressHelper.h" #include "qpid/messaging/Address.h" +#include "qpid/log/Statement.h" #include <vector> #include <boost/assign.hpp> extern "C" { @@ -56,7 +57,9 @@ const std::string MOVE("move"); const std::string COPY("copy"); const std::string SUPPORTED_DIST_MODES("supported-dist-modes"); +const std::string CREATE_ON_DEMAND("create-on-demand"); +const std::string DUMMY("."); const std::vector<std::string> RECEIVER_MODES = boost::assign::list_of<std::string>(ALWAYS) (RECEIVER); const std::vector<std::string> SENDER_MODES = boost::assign::list_of<std::string>(ALWAYS) (SENDER); @@ -155,26 +158,33 @@ const qpid::types::Variant::Map& AddressHelper::getLinkProperties() const return link; } -void AddressHelper::setNodeProperties(pn_terminus_t* terminus) +void AddressHelper::setNodeProperties(pn_terminus_t* terminus, bool dynamic) { - pn_terminus_set_dynamic(terminus, true); + if (dynamic) { + pn_terminus_set_address(terminus, DUMMY.c_str());//Workaround for proton bug + pn_terminus_set_dynamic(terminus, true); + } else { + pn_data_t* capabilities = pn_terminus_capabilities(terminus); + if (!capabilities) { + QPID_LOG(error, "!!!No capabilities!!!"); + } + pn_data_put_symbol(capabilities, convert(CREATE_ON_DEMAND)); + } //properties for dynamically created node: - pn_data_t* data = pn_terminus_properties(terminus); if (node.size()) { + pn_data_t* data = pn_terminus_properties(terminus); pn_data_put_map(data); pn_data_enter(data); - } - for (qpid::types::Variant::Map::const_iterator i = node.begin(); i != node.end(); ++i) { - if (i->first == TYPE) { - pn_data_put_symbol(data, convert(SUPPORTED_DIST_MODES)); - pn_data_put_string(data, convert(i->second == TOPIC ? COPY : MOVE)); - } else { - pn_data_put_symbol(data, convert(i->first)); - pn_data_put_string(data, convert(i->second.asString())); + for (qpid::types::Variant::Map::const_iterator i = node.begin(); i != node.end(); ++i) { + if (i->first == TYPE) { + pn_data_put_symbol(data, convert(SUPPORTED_DIST_MODES)); + pn_data_put_string(data, convert(i->second == TOPIC ? COPY : MOVE)); + } else { + pn_data_put_symbol(data, convert(i->first)); + pn_data_put_string(data, convert(i->second.asString())); + } } - } - if (node.size()) { pn_data_exit(data); } } diff --git a/qpid/cpp/src/qpid/messaging/amqp/AddressHelper.h b/qpid/cpp/src/qpid/messaging/amqp/AddressHelper.h index cd0aa1be9e..2442619ed3 100644 --- a/qpid/cpp/src/qpid/messaging/amqp/AddressHelper.h +++ b/qpid/cpp/src/qpid/messaging/amqp/AddressHelper.h @@ -40,7 +40,7 @@ class AddressHelper bool deleteEnabled(CheckMode mode) const; bool assertEnabled(CheckMode mode) const; - void setNodeProperties(pn_terminus_t*); + void setNodeProperties(pn_terminus_t*, bool dynamic); const qpid::types::Variant::Map& getNodeProperties() const; const qpid::types::Variant::Map& getLinkProperties() const; private: @@ -49,6 +49,7 @@ class AddressHelper std::string deletePolicy; qpid::types::Variant::Map node; qpid::types::Variant::Map link; + std::string name; bool enabled(const std::string& policy, CheckMode mode) const; }; diff --git a/qpid/cpp/src/qpid/messaging/amqp/ConnectionContext.cpp b/qpid/cpp/src/qpid/messaging/amqp/ConnectionContext.cpp index c74ee01898..9febe66f7e 100644 --- a/qpid/cpp/src/qpid/messaging/amqp/ConnectionContext.cpp +++ b/qpid/cpp/src/qpid/messaging/amqp/ConnectionContext.cpp @@ -26,6 +26,7 @@ #include "SessionContext.h" #include "Transport.h" #include "qpid/messaging/exceptions.h" +#include "qpid/messaging/AddressImpl.h" #include "qpid/messaging/Duration.h" #include "qpid/messaging/Message.h" #include "qpid/messaging/MessageImpl.h" @@ -285,34 +286,45 @@ void ConnectionContext::attach(boost::shared_ptr<SessionContext> ssn, boost::sha { lnk->configure(); attach(ssn->session, (pn_link_t*) lnk->sender); - if (!pn_link_remote_target((pn_link_t*) lnk->sender)) { + pn_terminus_t* t = pn_link_remote_target(lnk->sender); + if (!pn_terminus_get_address(t)) { std::string msg("No such target : "); msg += lnk->getTarget(); + QPID_LOG(debug, msg); throw qpid::messaging::NotFound(msg); + } else if (AddressImpl::isTemporary(lnk->address)) { + lnk->address.setName(pn_terminus_get_address(t)); + QPID_LOG(debug, "Dynamic target name set to " << lnk->address.getName()); } + QPID_LOG(debug, "Attach succeeded to " << lnk->getTarget()); } void ConnectionContext::attach(boost::shared_ptr<SessionContext> ssn, boost::shared_ptr<ReceiverContext> lnk) { lnk->configure(); attach(ssn->session, lnk->receiver, lnk->capacity); - if (!pn_link_remote_source(lnk->receiver)) { + pn_terminus_t* s = pn_link_remote_source(lnk->receiver); + if (!pn_terminus_get_address(s)) { std::string msg("No such source : "); msg += lnk->getSource(); + QPID_LOG(debug, msg); throw qpid::messaging::NotFound(msg); + } else if (AddressImpl::isTemporary(lnk->address)) { + lnk->address.setName(pn_terminus_get_address(s)); + QPID_LOG(debug, "Dynamic source name set to " << lnk->address.getName()); } + QPID_LOG(debug, "Attach succeeded from " << lnk->getSource()); } void ConnectionContext::attach(pn_session_t* /*session*/, pn_link_t* link, int credit) { qpid::sys::ScopedLock<qpid::sys::Monitor> l(lock); - QPID_LOG(debug, "Attaching link " << link << ", state=" << pn_link_state(link)); pn_link_open(link); - QPID_LOG(debug, "Link attached " << link << ", state=" << pn_link_state(link)); + QPID_LOG(debug, "Link attach sent for " << link << ", state=" << pn_link_state(link)); if (credit) pn_link_flow(link, credit); wakeupDriver(); while (pn_link_state(link) & PN_REMOTE_UNINIT) { - QPID_LOG(debug, "waiting for confirmation of link attach for " << link << ", state=" << pn_link_state(link)); + QPID_LOG(debug, "Waiting for confirmation of link attach for " << link << ", state=" << pn_link_state(link) << "..."); wait(); } } diff --git a/qpid/cpp/src/qpid/messaging/amqp/EncodedMessage.cpp b/qpid/cpp/src/qpid/messaging/amqp/EncodedMessage.cpp index 54de3eae45..3370199067 100644 --- a/qpid/cpp/src/qpid/messaging/amqp/EncodedMessage.cpp +++ b/qpid/cpp/src/qpid/messaging/amqp/EncodedMessage.cpp @@ -228,7 +228,7 @@ void EncodedMessage::InitialScan::onTtl(uint32_t i) { mi.setTtl(i); em.ttl = i; void EncodedMessage::InitialScan::onFirstAcquirer(bool b) { em.firstAcquirer = b; } void EncodedMessage::InitialScan::onDeliveryCount(uint32_t i) { - mi.setRedelivered(i); + mi.setRedelivered(i > 1); em.deliveryCount = i; } diff --git a/qpid/cpp/src/qpid/messaging/amqp/ReceiverContext.cpp b/qpid/cpp/src/qpid/messaging/amqp/ReceiverContext.cpp index 638b1fc674..f7b06ddc05 100644 --- a/qpid/cpp/src/qpid/messaging/amqp/ReceiverContext.cpp +++ b/qpid/cpp/src/qpid/messaging/amqp/ReceiverContext.cpp @@ -20,9 +20,11 @@ */ #include "qpid/messaging/amqp/ReceiverContext.h" #include "qpid/messaging/amqp/AddressHelper.h" +#include "qpid/messaging/AddressImpl.h" #include "qpid/messaging/Duration.h" #include "qpid/messaging/Message.h" #include "qpid/amqp/descriptors.h" +#include "qpid/log/Statement.h" extern "C" { #include <proton/engine.h> } @@ -38,7 +40,7 @@ ReceiverContext::ReceiverContext(pn_session_t* session, const std::string& n, co capacity(0) {} ReceiverContext::~ReceiverContext() { - pn_link_free(receiver); + //pn_link_free(receiver); } void ReceiverContext::setCapacity(uint32_t c) @@ -76,7 +78,7 @@ uint32_t ReceiverContext::getUnsettled() void ReceiverContext::close() { - + pn_link_close(receiver); } const std::string& ReceiverContext::getName() const @@ -113,23 +115,30 @@ void ReceiverContext::configure() const } void ReceiverContext::configure(pn_terminus_t* source) const { - pn_terminus_set_address(source, address.getName().c_str()); //dynamic create: AddressHelper helper(address); - if (helper.createEnabled(AddressHelper::FOR_RECEIVER)) { - helper.setNodeProperties(source); + if (AddressImpl::isTemporary(address)) { + //application expects a name to be generated + QPID_LOG(debug, "source is dynamic"); + helper.setNodeProperties(source, true); + } else { + pn_terminus_set_address(source, address.getName().c_str()); + if (helper.createEnabled(AddressHelper::FOR_RECEIVER)) { + //application expects name of node to be as specified + helper.setNodeProperties(source, false); + } } // Look specifically for qpid.selector link property and add a filter for it - qpid::types::Variant::Map::const_iterator i = helper.getLinkProperties().find("qpid.selector"); + qpid::types::Variant::Map::const_iterator i = helper.getLinkProperties().find("selector"); if (i!=helper.getLinkProperties().end()) { pn_data_t* filter = pn_terminus_filter(source); pn_data_put_map(filter); pn_data_enter(filter); - pn_data_put_symbol(filter, convert("qpid.selector")); + pn_data_put_symbol(filter, convert("selector")); pn_data_put_described(filter); pn_data_enter(filter); - pn_data_put_symbol(filter, convert(qpid::amqp::filters::QPID_SELECTOR_FILTER_SYMBOL)); + pn_data_put_ulong(filter, qpid::amqp::filters::SELECTOR_FILTER_CODE); pn_data_put_string(filter, convert(i->second)); pn_data_exit(filter); pn_data_exit(filter); @@ -149,6 +158,11 @@ void ReceiverContext::configure(pn_terminus_t* source) const } } +Address ReceiverContext::getAddress() const +{ + return address; +} + bool ReceiverContext::isClosed() const { return false;//TODO diff --git a/qpid/cpp/src/qpid/messaging/amqp/ReceiverContext.h b/qpid/cpp/src/qpid/messaging/amqp/ReceiverContext.h index 34ecdda6be..9c5386157b 100644 --- a/qpid/cpp/src/qpid/messaging/amqp/ReceiverContext.h +++ b/qpid/cpp/src/qpid/messaging/amqp/ReceiverContext.h @@ -55,10 +55,11 @@ class ReceiverContext const std::string& getSource() const; bool isClosed() const; void configure() const; + Address getAddress() const; private: friend class ConnectionContext; const std::string name; - const Address address; + Address address; pn_link_t* receiver; uint32_t capacity; void configure(pn_terminus_t*) const; diff --git a/qpid/cpp/src/qpid/messaging/amqp/ReceiverHandle.cpp b/qpid/cpp/src/qpid/messaging/amqp/ReceiverHandle.cpp index 9bf64ebb8d..c601d05ed0 100644 --- a/qpid/cpp/src/qpid/messaging/amqp/ReceiverHandle.cpp +++ b/qpid/cpp/src/qpid/messaging/amqp/ReceiverHandle.cpp @@ -103,4 +103,9 @@ bool ReceiverHandle::isClosed() const return receiver->isClosed(); } +Address ReceiverHandle::getAddress() const +{ + return receiver->getAddress(); +} + }}} // namespace qpid::messaging::amqp diff --git a/qpid/cpp/src/qpid/messaging/amqp/ReceiverHandle.h b/qpid/cpp/src/qpid/messaging/amqp/ReceiverHandle.h index a1a6f26025..08a95fb585 100644 --- a/qpid/cpp/src/qpid/messaging/amqp/ReceiverHandle.h +++ b/qpid/cpp/src/qpid/messaging/amqp/ReceiverHandle.h @@ -53,6 +53,7 @@ class ReceiverHandle : public qpid::messaging::ReceiverImpl const std::string& getName() const; qpid::messaging::Session getSession() const; bool isClosed() const; + Address getAddress() const; private: boost::shared_ptr<ConnectionContext> connection; boost::shared_ptr<SessionContext> session; diff --git a/qpid/cpp/src/qpid/messaging/amqp/SenderContext.cpp b/qpid/cpp/src/qpid/messaging/amqp/SenderContext.cpp index 96c4437b89..fe74a4bca8 100644 --- a/qpid/cpp/src/qpid/messaging/amqp/SenderContext.cpp +++ b/qpid/cpp/src/qpid/messaging/amqp/SenderContext.cpp @@ -21,6 +21,7 @@ #include "qpid/messaging/amqp/SenderContext.h" #include "qpid/messaging/amqp/EncodedMessage.h" #include "qpid/messaging/amqp/AddressHelper.h" +#include "qpid/messaging/AddressImpl.h" #include "qpid/amqp/descriptors.h" #include "qpid/amqp/MessageEncoder.h" #include "qpid/messaging/exceptions.h" @@ -44,12 +45,12 @@ SenderContext::SenderContext(pn_session_t* session, const std::string& n, const SenderContext::~SenderContext() { - pn_link_free(sender); + //pn_link_free(sender); } void SenderContext::close() { - + pn_link_close(sender); } void SenderContext::setCapacity(uint32_t c) @@ -347,11 +348,16 @@ void SenderContext::configure() const } void SenderContext::configure(pn_terminus_t* target) const { - pn_terminus_set_address(target, address.getName().c_str()); - //dynamic create: AddressHelper helper(address); - if (helper.createEnabled(AddressHelper::FOR_SENDER)) { - helper.setNodeProperties(target); + if (AddressImpl::isTemporary(address)) { + //application expects a name to be generated + helper.setNodeProperties(target, true); + } else { + pn_terminus_set_address(target, address.getName().c_str()); + if (helper.createEnabled(AddressHelper::FOR_SENDER)) { + //application expects name of node to be as specified + helper.setNodeProperties(target, false); + } } } @@ -360,4 +366,9 @@ bool SenderContext::settled() return processUnsettled() == 0; } +Address SenderContext::getAddress() const +{ + return address; +} + }}} // namespace qpid::messaging::amqp diff --git a/qpid/cpp/src/qpid/messaging/amqp/SenderContext.h b/qpid/cpp/src/qpid/messaging/amqp/SenderContext.h index 3595379e70..2969e75a16 100644 --- a/qpid/cpp/src/qpid/messaging/amqp/SenderContext.h +++ b/qpid/cpp/src/qpid/messaging/amqp/SenderContext.h @@ -71,12 +71,13 @@ class SenderContext Delivery* send(const qpid::messaging::Message& message); void configure() const; bool settled(); + Address getAddress() const; private: friend class ConnectionContext; typedef std::deque<Delivery> Deliveries; const std::string name; - const qpid::messaging::Address address; + qpid::messaging::Address address; pn_link_t* sender; int32_t nextId; Deliveries deliveries; diff --git a/qpid/cpp/src/qpid/messaging/amqp/SenderHandle.cpp b/qpid/cpp/src/qpid/messaging/amqp/SenderHandle.cpp index b7168e5b31..4e258e7b38 100644 --- a/qpid/cpp/src/qpid/messaging/amqp/SenderHandle.cpp +++ b/qpid/cpp/src/qpid/messaging/amqp/SenderHandle.cpp @@ -72,4 +72,9 @@ qpid::messaging::Session SenderHandle::getSession() const return qpid::messaging::Session(new SessionHandle(connection, session)); } +Address SenderHandle::getAddress() const +{ + return sender->getAddress(); +} + }}} // namespace qpid::messaging::amqp diff --git a/qpid/cpp/src/qpid/messaging/amqp/SenderHandle.h b/qpid/cpp/src/qpid/messaging/amqp/SenderHandle.h index 3c6b666582..fab158c1ef 100644 --- a/qpid/cpp/src/qpid/messaging/amqp/SenderHandle.h +++ b/qpid/cpp/src/qpid/messaging/amqp/SenderHandle.h @@ -48,6 +48,7 @@ class SenderHandle : public qpid::messaging::SenderImpl uint32_t getUnsettled(); const std::string& getName() const; Session getSession() const; + Address getAddress() const; private: boost::shared_ptr<ConnectionContext> connection; boost::shared_ptr<SessionContext> session; diff --git a/qpid/cpp/src/qpid/messaging/amqp/SessionContext.cpp b/qpid/cpp/src/qpid/messaging/amqp/SessionContext.cpp index 9bdc658bc7..bca40c6058 100644 --- a/qpid/cpp/src/qpid/messaging/amqp/SessionContext.cpp +++ b/qpid/cpp/src/qpid/messaging/amqp/SessionContext.cpp @@ -138,9 +138,13 @@ void SessionContext::acknowledge() void SessionContext::acknowledge(const qpid::framing::SequenceNumber& id, bool cumulative) { + QPID_LOG(debug, "acknowledging selected messages, id=" << id << ", cumulative=" << cumulative); DeliveryMap::iterator i = unacked.find(id); if (i != unacked.end()) { - acknowledge(cumulative ? unacked.begin() : i, ++i); + DeliveryMap::iterator start = cumulative ? unacked.begin() : i; + acknowledge(start, ++i); + } else { + QPID_LOG(debug, "selective acknowledgement failed; message not found for id " << id); } } diff --git a/qpid/cpp/src/qpid/xml/XmlExchange.cpp b/qpid/cpp/src/qpid/xml/XmlExchange.cpp index fb6c326327..29c00d9c12 100644 --- a/qpid/cpp/src/qpid/xml/XmlExchange.cpp +++ b/qpid/cpp/src/qpid/xml/XmlExchange.cpp @@ -179,17 +179,24 @@ bool XmlExchange::bind(Queue::shared_ptr queue, const std::string& bindingKey, c bool XmlExchange::unbind(Queue::shared_ptr queue, const std::string& bindingKey, const FieldTable* args) { + RWlock::ScopedWlock l(lock); + return unbindLH(queue, bindingKey, args); +} + +bool XmlExchange::unbindLH(Queue::shared_ptr queue, const std::string& bindingKey, const FieldTable* args) +{ /* * When called directly, no qpidFedOrigin argument will be * present. When called from federation, it will be present. * * This is a bit of a hack - the binding needs the origin, but * this interface, as originally defined, would not supply one. + * + * Note: caller must hold Wlock */ std::string fedOrigin; if (args) fedOrigin = args->getAsString(qpidFedOrigin); - RWlock::ScopedWlock l(lock); if (bindingsMap[bindingKey].remove_if(MatchQueueAndOrigin(queue, fedOrigin))) { if (mgmtExchange != 0) { mgmtExchange->dec_bindingCount(); @@ -389,9 +396,9 @@ void XmlExchange::propagateFedOp(const std::string& bindingKey, const std::strin bool XmlExchange::fedUnbind(const std::string& fedOrigin, const std::string& fedTags, Queue::shared_ptr queue, const std::string& bindingKey, const FieldTable* args) { - RWlock::ScopedRlock l(lock); + RWlock::ScopedWlock l(lock); - if (unbind(queue, bindingKey, args)) { + if (unbindLH(queue, bindingKey, args)) { propagateFedOp(bindingKey, fedTags, fedOpUnbind, fedOrigin); return true; } diff --git a/qpid/cpp/src/qpid/xml/XmlExchange.h b/qpid/cpp/src/qpid/xml/XmlExchange.h index 7b04781ad5..fd3f8d0278 100644 --- a/qpid/cpp/src/qpid/xml/XmlExchange.h +++ b/qpid/cpp/src/qpid/xml/XmlExchange.h @@ -107,6 +107,8 @@ class XmlExchange : public virtual Exchange { bool operator()(XmlBinding::shared_ptr b); }; + private: + bool unbindLH(Queue::shared_ptr queue, const std::string& routingKey, const qpid::framing::FieldTable* args); }; diff --git a/qpid/cpp/src/tests/CMakeLists.txt b/qpid/cpp/src/tests/CMakeLists.txt index f77863a146..001b5d2d69 100644 --- a/qpid/cpp/src/tests/CMakeLists.txt +++ b/qpid/cpp/src/tests/CMakeLists.txt @@ -293,6 +293,8 @@ install (TARGETS qpid-receive qpid-send qpid-topic-listener qpid-topic-publisher receiver sender qpid-txtest RUNTIME DESTINATION ${QPID_INSTALL_TESTDIR}) +install (PROGRAMS ${CMAKE_CURRENT_SOURCE_DIR}/qpidt + DESTINATION ${QPID_INSTALL_TESTDIR}) # This should ideally be done as part of the test run, but I don't know a way # to get these arguments and the working directory set like Makefile.am does, diff --git a/qpid/cpp/src/tests/Makefile.am b/qpid/cpp/src/tests/Makefile.am index 2e55b24c3e..3943e21b7f 100644 --- a/qpid/cpp/src/tests/Makefile.am +++ b/qpid/cpp/src/tests/Makefile.am @@ -146,8 +146,8 @@ endif # Test programs that are installed and therefore built as part of make, not make check -qpidexectest_SCRIPTS += qpid-cpp-benchmark qpid-cluster-benchmark install_env.sh -EXTRA_DIST += qpid-cpp-benchmark qpid-cluster-benchmark install_env.sh +qpidexectest_SCRIPTS += qpid-cpp-benchmark qpid-cluster-benchmark install_env.sh qpidt +EXTRA_DIST += qpid-cpp-benchmark qpid-cluster-benchmark install_env.sh qpidt qpidexectest_PROGRAMS += receiver receiver_SOURCES = \ diff --git a/qpid/cpp/src/tests/Selector.cpp b/qpid/cpp/src/tests/Selector.cpp index 1fc9a9b7bb..59bdd36b77 100644 --- a/qpid/cpp/src/tests/Selector.cpp +++ b/qpid/cpp/src/tests/Selector.cpp @@ -251,6 +251,8 @@ QPID_AUTO_TEST_CASE(tokenString) QPID_AUTO_TEST_CASE(parseStringFail) { + BOOST_CHECK_THROW(qb::Selector e("hello world"), std::range_error); + BOOST_CHECK_THROW(qb::Selector e("hello ^ world"), std::range_error); BOOST_CHECK_THROW(qb::Selector e("A is null not"), std::range_error); BOOST_CHECK_THROW(qb::Selector e("A is null or not"), std::range_error); BOOST_CHECK_THROW(qb::Selector e("A is null or and"), std::range_error); @@ -262,6 +264,10 @@ QPID_AUTO_TEST_CASE(parseStringFail) BOOST_CHECK_THROW(qb::Selector e("A not like 'eclecti_' escape happy"), std::range_error); BOOST_CHECK_THROW(qb::Selector e("A BETWEEN AND 'true'"), std::range_error); BOOST_CHECK_THROW(qb::Selector e("A NOT BETWEEN 34 OR 3.9"), std::range_error); + BOOST_CHECK_THROW(qb::Selector e("A IN ()"), std::range_error); + BOOST_CHECK_THROW(qb::Selector e("A NOT IN ()"), std::range_error); + BOOST_CHECK_THROW(qb::Selector e("A IN 'hello', 'there', 1, true, (1-17))"), std::range_error); + BOOST_CHECK_THROW(qb::Selector e("A IN ('hello', 'there' 1, true, (1-17))"), std::range_error); } QPID_AUTO_TEST_CASE(parseString) diff --git a/qpid/cpp/src/tests/ping_broker b/qpid/cpp/src/tests/ping_broker index 6c391027a3..be99a6ef46 100755 --- a/qpid/cpp/src/tests/ping_broker +++ b/qpid/cpp/src/tests/ping_broker @@ -60,6 +60,9 @@ def OptionsAndArguments(argv): help="SASL mechanism for authentication (e.g. EXTERNAL, ANONYMOUS, PLAIN, CRAM-MD, DIGEST-MD5, GSSAPI). SASL automatically picks the most secure available mechanism - use this option to override.") parser.add_option("--ssl-certificate", action="store", type="string", metavar="<cert>", help="Client SSL certificate (PEM Format)") parser.add_option("--ssl-key", action="store", type="string", metavar="<key>", help="Client SSL private key (PEM Format)") + parser.add_option("--ssl-trustfile", action="store", type="string", metavar="<CA>", help="List of trusted CAs (PEM Format)") + parser.add_option("--ssl-skip-hostname-check", action="store_true", + help="Do not validate hostname in peer certificate") parser.add_option("--ha-admin", action="store_true", help="Allow connection to a HA backup broker.") opts, args = parser.parse_args(args=argv) @@ -73,6 +76,10 @@ def OptionsAndArguments(argv): conn_options['ssl_certfile'] = opts.ssl_certificate if opts.ssl_key: conn_options['ssl_key'] = opts.ssl_key + if opts.ssl_trustfile: + conn_options['ssl_trustfile'] = opts.ssl_trustfile + if opts.ssl_skip_hostname_check: + conn_options['ssl_skip_hostname_check'] = True if opts.ha_admin: conn_options['client_properties'] = {'qpid.ha-admin' : 1} return args diff --git a/qpid/cpp/src/tests/qpidt b/qpid/cpp/src/tests/qpidt new file mode 100644 index 0000000000..5bdfb6eefd --- /dev/null +++ b/qpid/cpp/src/tests/qpidt @@ -0,0 +1,141 @@ +#!/usr/bin/env python + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +import pdb + +import os +from optparse import OptionParser +import sys + +home = os.environ.get("QPID_TOOLS_HOME", os.path.normpath("/usr/share/qpid-tools")) +sys.path.append(os.path.join(home, "python")) + +from qpid.messaging import Connection +from qpidtoollibs import BrokerAgent, BrokerObject + +desc = """Experimental generic configuration tool for qpidd. Note: this may be +modified or removed in subsequent releases. +""" +usage = """ + %prog [OPTIONS] create <type> <name> [properties] + %prog [OPTIONS] delete <type> <name> [arguments] + %prog [OPTIONS] list <type> +""" +def add_nameval(m, s): + idx = s.find("=") + if idx >= 0: + name = s[0:idx] + value = s[idx+1:] + else: + name = s + value = None + m[name] = value + +class Manager: + def __init__(self): + self.parser = OptionParser(description=desc, usage=usage) + self.url = None + self.conn_options = {} + self.command = None + self.typename = None + self.name = None + self.extra = {} + self.parser.add_option("-b", "--broker", action="store", type="string", metavar="<address>", help="Address of qpidd broker with syntax: [username/password@] hostname | ip-address [:<port>]") + self.parser.add_option("--sasl-mechanism", action="store", type="string", metavar="<mech>", help="SASL mechanism for authentication") + self.parser.add_option("--ssl-certificate", action="store", type="string", metavar="<cert>", help="Client SSL certificate (PEM Format)") + self.parser.add_option("--ssl-key", action="store", type="string", metavar="<key>", help="Client SSL private key (PEM Format)") + self.parser.add_option("--ha-admin", action="store_true", help="Allow connection to a HA backup broker.") + + def parse_args(self, argv): + opts, args = self.parser.parse_args(args=argv) + self.url = opts.broker or "localhost:5672" + self.get_connection_options(opts) + + if len(args) == 0: + self.command = "list" + elif len(args) == 1: + self.command = args.pop() + elif len(args) == 2: + self.command, self.typename = args[:2] + else: + self.command, self.typename, self.name = args[:3] + if len(args) > 3: + other = args[3:] + while len(other): + add_nameval(self.extra, other.pop()) + if self.command == "create" or self.command == "delete": + if not self.typename: + parser.error("%s requires a type to be named (e.g. queue, exchange)") + if not self.name: + parser.error("%s requires an object name to be specified") + elif self.command != "list": + parser.error("Invalid command: %s. You must specify one of 'create', 'delete' or 'list'" % command) + + def get_connection_options(self, opts): + if opts.sasl_mechanism: + self.conn_options['sasl_mechanisms'] = opts.sasl_mechanism + if opts.ssl_certificate: + self.conn_options['ssl_certfile'] = opts.ssl_certificate + if opts.ssl_key: + if not opts.ssl_certificate: + self.parser.error("missing '--ssl-certificate' (required by '--ssl-key')") + conn_options['ssl_keyfile'] = opts.ssl_key + if opts.ha_admin: + self.conn_options['client_properties'] = {'qpid.ha-admin' : 1} + + def connect(self): + self.connection = Connection.establish(self.url, **self.conn_options) + self.agent = BrokerAgent(self.connection) + + def disconnect(self): + self.connection.close() + + def execute(self): + if self.command == "list": + objects = [i["_values"] for i in self.agent._doClassQuery(self.typename.lower())] + for o in objects: + name = "" + details = "" + for k, v in o.items(): + if k == "name": + name = v + elif v: + if isinstance(v, dict) and v["_object_name"]: + v = v["_object_name"] + details += "%s=%s " %(k,v) + print "%-25s %s" % (name, details) + elif self.command == "create": + self.agent.create(self.typename, self.name, self.extra) + elif self.command == "delete": + self.agent.delete(self.typename, self.name, self.extra) + +def main(argv=None): + manager = Manager() + try: + manager.parse_args(argv) + manager.connect() + manager.execute() + manager.disconnect() + except Exception,e: + print "Failed: %s - %s" % (e.__class__.__name__, e) + +if __name__ == "__main__": + sys.exit(main()) + diff --git a/qpid/cpp/src/tests/ssl.mk b/qpid/cpp/src/tests/ssl.mk index 435db0c55b..1544dc5e71 100644 --- a/qpid/cpp/src/tests/ssl.mk +++ b/qpid/cpp/src/tests/ssl.mk @@ -19,4 +19,4 @@ TESTS+=ssl_test EXTRA_DIST+=ssl_test -CLEAN_LOCAL += test_cert_db cert.password +CLEAN_LOCAL += test_cert_dir cert.password diff --git a/qpid/cpp/src/tests/ssl_test b/qpid/cpp/src/tests/ssl_test index 89aaf44af0..cfbd253ab8 100755 --- a/qpid/cpp/src/tests/ssl_test +++ b/qpid/cpp/src/tests/ssl_test @@ -22,33 +22,77 @@ # Run a simple test over SSL source ./test_env.sh +#set -x + CONFIG=$(dirname $0)/config.null -CERT_DIR=`pwd`/test_cert_db +TEST_CERT_DIR=`pwd`/test_cert_dir +CERT_DB=${TEST_CERT_DIR}/test_cert_db CERT_PW_FILE=`pwd`/cert.password TEST_HOSTNAME=127.0.0.1 TEST_CLIENT_CERT=rumplestiltskin +CA_PEM_FILE=${TEST_CERT_DIR}/ca_cert.pem +OTHER_CA_CERT_DB=${TEST_CERT_DIR}/x_ca_cert_db +OTHER_CA_PEM_FILE=${TEST_CERT_DIR}/other_ca_cert.pem +PY_PING_BROKER=$top_srcdir/src/tests/ping_broker COUNT=10 trap cleanup EXIT error() { echo $*; exit 1; } +# create the test certificate database +# $1 = string used as Subject in server's certificate +# $2 = string used as SubjectAlternateName (SAN) in server's certificate create_certs() { - #create certificate and key databases with single, simple, self-signed certificate in it - mkdir ${CERT_DIR} - certutil -N -d ${CERT_DIR} -f ${CERT_PW_FILE} - certutil -S -d ${CERT_DIR} -n ${TEST_HOSTNAME} -s "CN=${TEST_HOSTNAME}" -t "CT,," -x -f ${CERT_PW_FILE} -z /usr/bin/certutil - certutil -S -d ${CERT_DIR} -n ${TEST_CLIENT_CERT} -s "CN=${TEST_CLIENT_CERT}" -t "CT,," -x -f ${CERT_PW_FILE} -z /usr/bin/certutil + + local CERT_SUBJECT=${1:-"CN=${TEST_HOSTNAME},O=MyCo,ST=Massachusetts,C=US"} + local CERT_SAN=${2:-"*.server.com"} + + mkdir -p ${TEST_CERT_DIR} + rm -rf ${TEST_CERT_DIR}/* + + # Set Up a CA with a self-signed Certificate + # + mkdir -p ${CERT_DB} + certutil -N -d ${CERT_DB} -f ${CERT_PW_FILE} + certutil -S -d ${CERT_DB} -n "Test-CA" -s "CN=Test-CA,O=MyCo,ST=Massachusetts,C=US" -t "CT,," -x -f ${CERT_PW_FILE} -z /bin/sh >/dev/null 2>&1 + certutil -L -d ${CERT_DB} -n "Test-CA" -a -o ${CERT_DB}/rootca.crt -f ${CERT_PW_FILE} + #certutil -L -d ${CERT_DB} -f ${CERT_PW_FILE} + + # create server certificate signed by Test-CA + # + certutil -R -d ${CERT_DB} -s "${CERT_SUBJECT}" -o server.req -f ${CERT_PW_FILE} -z /bin/sh > /dev/null 2>&1 + certutil -C -d ${CERT_DB} -c "Test-CA" -8 "${CERT_SAN}" -i server.req -o server.crt -f ${CERT_PW_FILE} -m ${RANDOM} + certutil -A -d ${CERT_DB} -n ${TEST_HOSTNAME} -i server.crt -t "Pu,," + rm server.req server.crt + + # create a certificate to identify the client + # + certutil -R -d ${CERT_DB} -s "CN=${TEST_CLIENT_CERT}" -o client.req -f ${CERT_PW_FILE} -z /bin/sh > /dev/null 2>&1 + certutil -C -d ${CERT_DB} -c "Test-CA" -8 "*.client.com" -i client.req -o client.crt -f ${CERT_PW_FILE} -m ${RANDOM} + certutil -A -d ${CERT_DB} -n ${TEST_CLIENT_CERT} -i client.crt -t "Pu,," + ### + #certutil -N -d ${SERVER_CERT_DIR} -f ${CERT_PW_FILE} + #certutil -S -d ${SERVER_CERT_DIR} -n ${TEST_HOSTNAME} -s "CN=${TEST_HOSTNAME}" -t "CT,," -x -f ${CERT_PW_FILE} -z /usr/bin/certutil + #certutil -S -d ${SERVER_CERT_DIR} -n ${TEST_CLIENT_CERT} -s "CN=${TEST_CLIENT_CERT}" -t "CT,," -x -f ${CERT_PW_FILE} -z /usr/bin/certutil + + # Set up a separate DB with its own CA for testing failure to validate scenario + # + mkdir -p ${OTHER_CA_CERT_DB} + certutil -N -d ${OTHER_CA_CERT_DB} -f ${CERT_PW_FILE} + certutil -S -d ${OTHER_CA_CERT_DB} -n "Other-Test-CA" -s "CN=Another Test CA,O=MyCo,ST=Massachusetts,C=US" -t "CT,," -x -f ${CERT_PW_FILE} -z /bin/sh >/dev/null 2>&1 + certutil -L -d ${OTHER_CA_CERT_DB} -n "Other-Test-CA" -a -o ${OTHER_CA_CERT_DB}/rootca.crt -f ${CERT_PW_FILE} + #certutil -L -d ${OTHER_CA_CERT_DB} -f ${CERT_PW_FILE} } delete_certs() { - if [[ -e ${CERT_DIR} ]] ; then - rm -rf ${CERT_DIR} + if [[ -e ${TEST_CERT_DIR} ]] ; then + rm -rf ${TEST_CERT_DIR} fi } # Don't need --no-module-dir or --no-data-dir as they are set as env vars in test_env.sh -COMMON_OPTS="--daemon --config $CONFIG --load-module $SSL_LIB --ssl-cert-db $CERT_DIR --ssl-cert-password-file $CERT_PW_FILE --ssl-cert-name $TEST_HOSTNAME" +COMMON_OPTS="--daemon --config $CONFIG --load-module $SSL_LIB --ssl-cert-db $CERT_DB --ssl-cert-password-file $CERT_PW_FILE --ssl-cert-name $TEST_HOSTNAME" # Start new brokers: # $1 must be integer @@ -89,6 +133,7 @@ pick_port() { cleanup() { stop_brokers delete_certs + rm -f ${CERT_PW_FILE} } start_ssl_broker() { @@ -123,14 +168,14 @@ if [[ !(-e ${CERT_PW_FILE}) ]] ; then echo password > ${CERT_PW_FILE} fi delete_certs -create_certs || error "Could not create test certificate" +create_certs || error "Could not create test certificate database" start_ssl_broker PORT=${PORTS[0]} echo "Running SSL test on port $PORT" export QPID_NO_MODULE_DIR=1 export QPID_LOAD_MODULE=$SSLCONNECTOR_LIB -export QPID_SSL_CERT_DB=${CERT_DIR} +export QPID_SSL_CERT_DB=${CERT_DB} export QPID_SSL_CERT_PASSWORD_FILE=${CERT_PW_FILE} ## Test connection via connection settings @@ -193,3 +238,81 @@ echo "Running SSL/TCP mux test on random port $PORT" ./qpid-perftest --count ${COUNT} --port ${PORT} -P tcp -b $TEST_HOSTNAME --summary || error "TCP connection failed!" stop_brokers + +### Additional tests that require 'openssl' and 'pk12util' to be installed (optional) + +PK12UTIL=$(type -p pk12util) +if [[ !(-x $PK12UTIL) ]] ; then + echo >&2 "'pk12util' command not available, skipping remaining tests" + exit 0 +fi + +OPENSSL=$(type -p openssl) +if [[ !(-x $OPENSSL) ]] ; then + echo >&2 "'openssl' command not available, skipping remaining tests" + exit 0 +fi + +## verify python version > 2.5 (only 2.6+ does certificate checking) +PY_VERSION=$(python -c "import sys; print hex(sys.hexversion)") +if (( PY_VERSION < 0x02060000 )); then + echo >&2 "Detected python version < 2.6 - skipping certificate verification tests" + exit 0 +fi + +echo "Testing Certificate validation and Authentication with the Python Client..." + +# extract the CA's certificate as a PEM file +get_ca_certs() { + $PK12UTIL -o ${TEST_CERT_DIR}/CA_pk12.out -d ${CERT_DB} -n "Test-CA" -w ${CERT_PW_FILE} -k ${CERT_PW_FILE} > /dev/null + $OPENSSL pkcs12 -in ${TEST_CERT_DIR}/CA_pk12.out -out ${CA_PEM_FILE} -nokeys -passin file:${CERT_PW_FILE} >/dev/null + $PK12UTIL -o ${TEST_CERT_DIR}/other_CA_pk12.out -d ${OTHER_CA_CERT_DB} -n "Other-Test-CA" -w ${CERT_PW_FILE} -k ${CERT_PW_FILE} > /dev/null + $OPENSSL pkcs12 -in ${TEST_CERT_DIR}/other_CA_pk12.out -out ${OTHER_CA_PEM_FILE} -nokeys -passin file:${CERT_PW_FILE} >/dev/null +} + +get_ca_certs || error "Could not extract CA certificates as PEM files" +start_ssl_broker +PORT=${PORTS[0]} +URL=amqps://$TEST_HOSTNAME:$PORT +# verify the python client can authenticate the broker using the CA +if `${PY_PING_BROKER} -b $URL --ssl-trustfile=${CA_PEM_FILE}`; then echo " Passed"; else { echo " Failed"; exit 1; }; fi +# verify the python client fails to authenticate the broker when using the other CA +if `${PY_PING_BROKER} -b $URL --ssl-trustfile=${OTHER_CA_PEM_FILE} > /dev/null 2>&1`; then { echo " Failed"; exit 1; }; else echo " Passed"; fi +stop_brokers + +# create a certificate without matching TEST_HOSTNAME, should fail to verify + +create_certs "O=MyCo" "*.${TEST_HOSTNAME}.com" || error "Could not create server test certificate" +get_ca_certs || error "Could not extract CA certificates as PEM files" +start_ssl_broker +PORT=${PORTS[0]} +URL=amqps://$TEST_HOSTNAME:$PORT +if `${PY_PING_BROKER} -b $URL --ssl-trustfile=${CA_PEM_FILE} > /dev/null 2>&1`; then { echo " Failed"; exit 1; }; else echo " Passed"; fi +# but disabling the check for the hostname should pass +if `${PY_PING_BROKER} -b $URL --ssl-trustfile=${CA_PEM_FILE} --ssl-skip-hostname-check`; then echo " Passed"; else { echo " Failed"; exit 1; }; fi +stop_brokers + +# test SubjectAltName parsing + +if (( PY_VERSION >= 0x02070300 )); then + # python 2.7.3+ supports SubjectAltName extraction + # create a certificate with TEST_HOSTNAME only in SAN, should verify OK + create_certs "O=MyCo" "*.foo.com,${TEST_HOSTNAME},*xyz.com" || error "Could not create server test certificate" + get_ca_certs || error "Could not extract CA certificates as PEM files" + start_ssl_broker + PORT=${PORTS[0]} + URL=amqps://$TEST_HOSTNAME:$PORT + if `${PY_PING_BROKER} -b $URL --ssl-trustfile=${CA_PEM_FILE}`; then echo " Passed"; else { echo " Failed"; exit 1; }; fi + stop_brokers + + create_certs "O=MyCo" "*${TEST_HOSTNAME}" || error "Could not create server test certificate" + get_ca_certs || error "Could not extract CA certificates as PEM files" + start_ssl_broker + PORT=${PORTS[0]} + URL=amqps://$TEST_HOSTNAME:$PORT + if `${PY_PING_BROKER} -b $URL --ssl-trustfile=${CA_PEM_FILE}`; then echo " Passed"; else { echo " Failed"; exit 1; }; fi + stop_brokers +fi + + + diff --git a/qpid/doc/book/src/java-broker/commonEntities.xml b/qpid/doc/book/src/java-broker/commonEntities.xml index a53440a467..ed0031e567 100644 --- a/qpid/doc/book/src/java-broker/commonEntities.xml +++ b/qpid/doc/book/src/java-broker/commonEntities.xml @@ -23,7 +23,7 @@ <!ENTITY qpidProgrammingBook "../../Programming-In-Apache-Qpid/html/"> <!ENTITY qpidCppBook "../../AMQP-Messaging-Broker-CPP-Book/html/"> -<!ENTITY qpidCurrentRelease "0.21"> +<!ENTITY qpidCurrentRelease "0.23"> <!-- Oracle javadoc --> <!ENTITY oracleJdkDocUrl "http://oracle.com/javase/6/docs/api/"> diff --git a/qpid/extras/qmf/setup.py b/qpid/extras/qmf/setup.py index db62ddba99..ddfba19a49 100755 --- a/qpid/extras/qmf/setup.py +++ b/qpid/extras/qmf/setup.py @@ -20,7 +20,7 @@ from distutils.core import setup setup(name="qpid-qmf", - version="0.21", + version="0.23", author="Apache Qpid", author_email="dev@qpid.apache.org", packages=["qmf"], diff --git a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/RestServlet.java b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/RestServlet.java index 8b74eb1dce..4f6f122876 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/RestServlet.java +++ b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/RestServlet.java @@ -341,6 +341,26 @@ public class RestServlet extends AbstractServlet } } + if (names.isEmpty()) + { + if (_hierarchy.length == 0) + { + try + { + doUpdate(getBroker(), providedObject); + response.setStatus(HttpServletResponse.SC_OK); + } + catch(RuntimeException e) + { + setResponseStatus(response, e); + } + return; + } + else + { + throw new ServletException("Cannot identify request target object"); + } + } providedObject.put("name", names.get(names.size()-1)); diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/util.js b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/util.js index b20169c94d..fea67d5942 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/util.js +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/util.js @@ -18,8 +18,26 @@ * under the License. * */ -define(["dojo/_base/xhr"], - function (xhr) { + +define(["dojo/_base/xhr", + "dojo/_base/event", + "dojo/_base/json", + "dojo/_base/lang", + "dojo/dom-construct", + "dojo/dom-geometry", + "dojo/window", + "dijit/Dialog", + "dijit/form/Form", + "dijit/form/Button", + "dijit/form/RadioButton", + "dijit/form/CheckBox", + "dojox/layout/TableContainer", + "dojox/layout/ScrollPane", + "dojox/validate/us", + "dojox/validate/web", + "dojo/domReady!" + ], + function (xhr, event, json, lang, dom, geometry, win) { var util = {}; if (Array.isArray) { util.isArray = function (object) { @@ -122,5 +140,125 @@ define(["dojo/_base/xhr"], return (type === "PlainPasswordFile" || type === "Base64MD5PasswordFile"); }; + util.showSetAttributesDialog = function(attributeWidgetFactories, data, putURL, dialogTitle) + { + var layout = new dojox.layout.TableContainer({ + cols: 1, + "labelWidth": "300", + showLabels: true, + orientation: "horiz", + customClass: "formLabel" + }); + var submitButton = new dijit.form.Button({label: "Submit", type: "submit"}); + var form = new dijit.form.Form(); + + var dialogContent = dom.create("div"); + var dialogContentArea = dom.create("div", { "class": "dijitDialogPaneContentArea"}); + var dialogActionBar = dom.create("div", { "class": "dijitDialogPaneActionBar"} ); + dialogContent.appendChild(dialogContentArea); + dialogContent.appendChild(dialogActionBar); + dialogContentArea.appendChild(layout.domNode) + dialogActionBar.appendChild(submitButton.domNode); + form.domNode.appendChild(dialogContent); + + var widgets = {}; + var requiredFor ={}; + for(var i in attributeWidgetFactories) + { + var attributeWidgetFactory = attributeWidgetFactories[i]; + var widget = attributeWidgetFactory.createWidget(data); + var name = attributeWidgetFactory.name ? attributeWidgetFactory.name : widget.name; + widgets[name] = widget; + widget.initialValue = widget.value; + layout.addChild(widget); + if (attributeWidgetFactory.hasOwnProperty("requiredFor")) + { + requiredFor[attributeWidgetFactory.requiredFor] = widget; + } + } + + // add onchange handler to set required property for dependent widget + for(var widgetName in requiredFor) + { + var dependent = requiredFor[widgetName]; + var widget = widgets[widgetName]; + if (widget) + { + widget.dependent = dependent; + widget.on("change", function(newValue){ + this.dependent.set("required", newValue != ""); + }); + } + } + var setAttributesDialog = new dijit.Dialog({ + title: dialogTitle, + content: form, + style: "width: 600px; max-height: 80%" + }); + form.on("submit", function(e) + { + event.stop(e); + try + { + if(form.validate()) + { + var values = {}; + for(var i in widgets) + { + var widget = widgets[i]; + var value = widget.value; + var propName = widget.name; + if ((widget instanceof dijit.form.CheckBox || widget instanceof dijit.form.RadioButton)) + { + values[ propName ] = widget.checked; + } + else if (value != widget.initialValue) + { + values[ propName ] = value ? value: null; + } + } + + var that = this; + xhr.put({url: putURL, sync: true, handleAs: "json", + headers: { "Content-Type": "application/json"}, + putData: json.toJson(values), + load: function(x) {that.success = true; }, + error: function(error) {that.success = false; that.failureReason = error;}}); + if(this.success === true) + { + setAttributesDialog.destroy(); + } + else + { + alert("Error:" + this.failureReason); + } + return false; + } + else + { + alert('Form contains invalid data. Please correct first'); + return false; + } + } + catch(e) + { + alert("Unexpected exception:" + e.message); + return false; + } + }); + form.connectChildren(true); + setAttributesDialog.startup(); + setAttributesDialog.on("show", function(){ + var data = geometry.position(layout.domNode); + var maxHeight = win.getBox().h * 0.6; + if (data.h > maxHeight) + { + dialogContentArea.style.height = maxHeight + "px"; + dialogContentArea.style.overflow= "auto"; + } + }) + setAttributesDialog.show(); + }; + return util; });
\ No newline at end of file diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/Broker.js b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/Broker.js index 98d442bf14..6c2065fe9d 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/Broker.js +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/Broker.js @@ -35,6 +35,11 @@ define(["dojo/_base/xhr", "dojox/grid/enhanced/plugins/IndirectSelection", "dijit/layout/AccordionContainer", "dijit/layout/AccordionPane", + "dijit/form/FilteringSelect", + "dijit/form/NumberSpinner", + "dijit/form/ValidationTextBox", + "dijit/form/CheckBox", + "dojo/store/Memory", "dojo/domReady!"], function (xhr, parser, query, connect, properties, updater, util, UpdatableStore, EnhancedGrid, registry, addAuthenticationProvider, addVirtualHost, addPort) { @@ -45,7 +50,301 @@ define(["dojo/_base/xhr", if(parent) { this.modelObj.parent = {}; this.modelObj.parent[ parent.type] = parent; - } + } + this.attributeWidgetFactories = [{ + name: "name", + createWidget: function(brokerData) { + return new dijit.form.ValidationTextBox({ + required: true, + value: brokerData.name, + disabled: true, + label: "Name*:", + name: "name"}) + } + }, { + name: "defaultAuthenticationProvider", + createWidget: function(brokerData) { + var providers = brokerData.authenticationproviders; + var data = []; + if (providers) { + for (var i=0; i< providers.length; i++) { + data.push({id: providers[i].name, name: providers[i].name}); + } + } + var providersStore = new dojo.store.Memory({ data: data }); + return new dijit.form.FilteringSelect({ + required: true, + store: providersStore, + value: brokerData.defaultAuthenticationProvider, + label: "Default Authentication Provider*:", + name: "defaultAuthenticationProvider"}) + } + }, { + name: "defaultVirtualHost", + createWidget: function(brokerData) { + var hosts = brokerData.virtualhosts; + var data = []; + if (hosts) { + for (var i=0; i< hosts.length; i++) { + data.push({id: hosts[i].name, name: hosts[i].name}); + } + } + var hostsStore = new dojo.store.Memory({ data: data }); + return new dijit.form.FilteringSelect({ + required: true, store: hostsStore, + value: brokerData.defaultVirtualHost, + label: "Default Virtual Host*:", + name: "defaultVirtualHost"}) + } + }, { + name: "aclFile", + createWidget: function(brokerData) { + return new dijit.form.ValidationTextBox({ + required: false, + value: brokerData.aclFile, + label: "ACL file location:", + name: "aclFile"}) + } + }, { + name: "groupFile", + createWidget: function(brokerData) + { + return new dijit.form.ValidationTextBox({ + required: false, + value: brokerData.groupFile, + label: "Group file location:", + name: "groupFile"}); + } + }, { + name: "keyStorePath", + createWidget: function(brokerData) { + return new dijit.form.ValidationTextBox({ + required: false, + value: brokerData.keyStorePath, + label: "Path to keystore:", + name: "keyStorePath"}); + } + }, { + name: "keyStoreCertAlias", + createWidget: function(brokerData) { + return new dijit.form.ValidationTextBox({ + required: false, + value: brokerData.keyStoreCertAlias, + label: "Keystore certificate alias:", + name: "keyStoreCertAlias"}); + } + }, { + name: "keyStorePassword", + requiredFor: "keyStorePath", + createWidget: function(brokerData) { + return new dijit.form.ValidationTextBox({ + required: false, + label: "Keystore password:", + invalidMessage: "Missed keystore password", + name: "keyStorePassword"}); + } + }, { + name: "trustStorePath", + createWidget: function(brokerData) + { + return new dijit.form.ValidationTextBox({ + required: false, + value: brokerData.trustStorePath, + label: "Path to truststore:", + name: "trustStorePath"}); + } + }, { + name: "trustStorePassword", + requiredFor: "trustStorePath", + createWidget: function(brokerData) { + return new dijit.form.ValidationTextBox({ + required: false, + label: "Truststore password:", + invalidMessage: "Missed trustore password", + name: "trustStorePassword"}); + } + }, { + name: "peerStorePath", + createWidget: function(brokerData) { + return new dijit.form.ValidationTextBox({ + required: false, + value: brokerData.peerStorePath, + label: "Path to peerstore:", + name: "peerStorePath"}); + } + }, { + name: "peerStorePassword", + requiredFor: "peerStorePath", + createWidget: function(brokerData) { + return new dijit.form.ValidationTextBox({ + required: false, + label: "Peerstore password:", + invalidMessage: "Missed peerstore password", + name: "peerStorePassword"}); + } + }, { + name: "alertThresholdQueueDepth", + createWidget: function(brokerData) { + return new dijit.form.ValidationTextBox({ + trim: "true", + regexp: "[0-9]+", + invalidMessage: "Invalid value", + required: false, + value: brokerData.alertThresholdQueueDepth, + placeholder: "Count of messages", + label: "Queue depth alert threshold:", + name: "alertThresholdQueueDepth" + }); + } + }, { + name: "alertThresholdMessageAge", + createWidget: function(brokerData) { + return new dijit.form.ValidationTextBox({ + trim: "true", + regexp: "[0-9]+", + invalidMessage: "Invalid value", + required: false, + value: brokerData.alertThresholdMessageAge, + placeholder: "Time in ms", + label: "Queue message age alert threshold:", + name: "alertThresholdMessageAge" + }); + } + }, { + name: "alertThresholdMessageSize", + createWidget: function(brokerData) { + return new dijit.form.ValidationTextBox({ + trim: "true", + regexp: "[0-9]+", + invalidMessage: "Invalid value", + required: false, + value: brokerData.alertThresholdMessageSize, + placeholder: "Size in bytes", + label: "Queue message size alert threshold:", + name: "alertThresholdMessageSize" + }); + } + }, { + name: "alertRepeatGap", + createWidget: function(brokerData) { + return new dijit.form.ValidationTextBox({ + trim: "true", + regexp: "[0-9]+", + invalidMessage: "Invalid value", + required: false, + value: brokerData.alertThresholdMessageSize, + value: brokerData.alertRepeatGap, + placeholder: "Time in ms", + label: "Queue alert repeat gap:", + name: "alertRepeatGap" + }); + } + }, { + name: "maximumDeliveryAttempts", + createWidget: function(brokerData) { + return new dijit.form.ValidationTextBox({ + trim: "true", + regexp: "[0-9]+", + invalidMessage: "Invalid value", + required: false, + value: brokerData.maximumDeliveryAttempts, + placeholder: "Count of messages", + label: "Queue maximum delivery retries:", + name: "maximumDeliveryAttempts" + }); + } + }, { + name: "deadLetterQueueEnabled", + createWidget: function(brokerData) { + return new dijit.form.CheckBox({ + required: false, + checked: brokerData.deadLetterQueueEnabled, + value: "true", + label: "Dead letter queue enabled:", + name: "deadLetterQueueEnabled", + }); + } + }, { + name: "queueFlowControlSizeBytes", + createWidget: function(brokerData) { + return new dijit.form.ValidationTextBox({ + trim: "true", + regexp: "[0-9]+", + invalidMessage: "Invalid value", + required: false, + value: brokerData.queueFlowControlSizeBytes, + placeholder: "Size in bytes", + label: "Queue flow capacity:", + name: "queueFlowControlSizeBytes", + }); + } + }, { + name: "queueFlowResumeSizeBytes", + createWidget: function(brokerData) { + return new dijit.form.ValidationTextBox({ + trim: "true", + regexp: "[0-9]+", + invalidMessage: "Invalid value", + required: false, + value: brokerData.queueFlowResumeSizeBytes, + placeholder: "Size in bytes", + label: "Queue flow resume capacity:", + name: "queueFlowResumeSizeBytes", + }); + } + }, { + name: "sessionCountLimit", + createWidget: function(brokerData) + { + return new dijit.form.NumberSpinner({ + invalidMessage: "Invalid value", + required: false, + value: brokerData.sessionCountLimit, + smallDelta: 1, + constraints: {min:1,max:65535,places:0, pattern: "#####"}, + label: "Connection session limit:", + name: "sessionCountLimit" + }); + } + }, { + name: "heartBeatDelay", + createWidget: function(brokerData) { + return new dijit.form.ValidationTextBox({ + trim: "true", + regexp: "[0-9]+", + invalidMessage: "Invalid value", + required: false, + value: brokerData.heartBeatDelay, + placeholder: "Time in ms", + label: "Heart beat delay:", + name: "heartBeatDelay" + }); + } + }, { + name: "statisticsReportingPeriod", + createWidget: function(brokerData) { + return new dijit.form.ValidationTextBox({ + trim: "true", + regexp: "[0-9]+", + invalidMessage: "Invalid value", + required: false, + value: brokerData.statisticsReportingPeriod, + placeholder: "Time in ms", + label: "Statistics reporting period:", + name: "statisticsReportingPeriod" + }); + } + }, { + name: "statisticsReportingResetEnabled", + createWidget: function(brokerData) + { + return new dijit.form.CheckBox({ + required: false, checked: brokerData.statisticsReportingResetEnabled, value: "true", + label: "Statistics reporting period enabled:", + name: "statisticsReportingResetEnabled" + }); + } + } ]; } Broker.prototype.getTitle = function() @@ -62,7 +361,7 @@ define(["dojo/_base/xhr", contentPane.containerNode.innerHTML = data; parser.parse(contentPane.containerNode); - that.brokerUpdater = new BrokerUpdater(contentPane.containerNode, that.modelObj, that.controller); + that.brokerUpdater = new BrokerUpdater(contentPane.containerNode, that.modelObj, that.controller, that.attributeWidgetFactories); updater.add( that.brokerUpdater ); @@ -109,6 +408,18 @@ define(["dojo/_base/xhr", "Are you sure you want to delete port"); } ); + + var editButton = query(".editBroker", contentPane.containerNode)[0]; + connect.connect(registry.byNode(editButton), "onClick", + function(evt){ + util.showSetAttributesDialog( + that.attributeWidgetFactories, + that.brokerUpdater.brokerData, + "rest/broker", + "Set broker attributes"); + } + ); + }}); }; @@ -116,16 +427,11 @@ define(["dojo/_base/xhr", updater.remove( this.brokerUpdater ); }; - function BrokerUpdater(node, brokerObj, controller) + function BrokerUpdater(node, brokerObj, controller, attributes) { this.controller = controller; - this.name = query(".broker-name", node)[0]; - /*this.state = dom.byId("state"); - this.durable = dom.byId("durable"); - this.lifetimePolicy = dom.byId("lifetimePolicy"); - */ this.query = "rest/broker"; - + this.attributes = attributes; var that = this; xhr.get({url: this.query, sync: properties.useSyncGet, handleAs: "json"}) @@ -135,6 +441,7 @@ define(["dojo/_base/xhr", util.flattenStatistics( that.brokerData); + that.showReadOnlyAttributes(); that.updateHeader(); var gridProperties = { @@ -259,11 +566,28 @@ define(["dojo/_base/xhr", BrokerUpdater.prototype.updateHeader = function() { - this.name.innerHTML = this.brokerData[ "name" ]; - /* this.state.innerHTML = this.brokerData[ "state" ]; - this.durable.innerHTML = this.brokerData[ "durable" ]; - this.lifetimePolicy.innerHTML = this.brokerData[ "lifetimePolicy" ]; -*/ + var brokerData = this.brokerData; + for(var i in this.attributes) + { + var propertyName = this.attributes[i].name; + var element = dojo.byId("brokerAttribute." + propertyName); + if (element) + { + if (brokerData.hasOwnProperty(propertyName)) + { + var container = dojo.byId("brokerAttribute." + propertyName + ".container"); + if (container) + { + container.style.display = "block"; + } + element.innerHTML = brokerData [propertyName]; + } + else + { + element.innerHTML = ""; + } + } + } }; BrokerUpdater.prototype.update = function() @@ -295,7 +619,18 @@ define(["dojo/_base/xhr", }; - + BrokerUpdater.prototype.showReadOnlyAttributes = function() + { + var brokerData = this.brokerData; + dojo.byId("brokerAttribute.name").innerHTML = brokerData.name; + dojo.byId("brokerAttribute.operatingSystem").innerHTML = brokerData.operatingSystem; + dojo.byId("brokerAttribute.platform").innerHTML = brokerData.platform; + dojo.byId("brokerAttribute.productVersion").innerHTML = brokerData.productVersion; + dojo.byId("brokerAttribute.managementVersion").innerHTML = brokerData.managementVersion; + dojo.byId("brokerAttribute.storeType").innerHTML = brokerData.storeType; + dojo.byId("brokerAttribute.storeVersion").innerHTML = brokerData.storeVersion; + dojo.byId("brokerAttribute.storePath").innerHTML = brokerData.storePath; + } return Broker; }); diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/showBroker.html b/qpid/java/broker-plugins/management-http/src/main/java/resources/showBroker.html index 60c514e262..bdcff2b7e9 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/resources/showBroker.html +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/showBroker.html @@ -19,14 +19,131 @@ - --> <div class="broker"> - <span>Name:</span><span class="broker-name" style="position:absolute; left:6em"></span> - <br/> -<!-- <span>State:</span><span class="broker-state" style="position:absolute; left:6em"></span> - <br/> - <span>Durable:</span><span class="broker-durable" style="position:absolute; left:6em"></span> - <br/> - <span>Lifespan:</span><span class="broker-lifetimePolicy" style="position:absolute; left:6em" ></span> - <br/> --> + <div data-dojo-type="dijit.TitlePane" data-dojo-props="title: 'Broker Attributes', open: false"> + <div id="brokerAttributes" style="clear:both"> + <div id="brokerAttribute.name.container" style="clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 250px;">Broker name:</div> + <div id="brokerAttribute.name" style="float:left;"></div> + </div> + <div id="brokerAttribute.operatingSystem.container" style="clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 250px;">Operation system:</div> + <div id="brokerAttribute.operatingSystem" style="float:left;"></div> + </div> + <div id="brokerAttribute.platform.container" style="clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 250px;">Platform:</div> + <div id="brokerAttribute.platform" style="float:left;"></div> + </div> + <div id="brokerAttribute.productVersion.container" style="clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 250px;">Broker version:</div> + <div id="brokerAttribute.productVersion" style="float:left;"></div> + </div> + <div id="brokerAttribute.managementVersion.container" style="clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 250px;">Broker management version:</div> + <div id="brokerAttribute.managementVersion" style="float:left;"></div> + </div> + <div id="brokerAttribute.storeType.container" style="clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 250px;">Broker store type:</div> + <div id="brokerAttribute.storeType" style="float:left;"></div> + </div> + <div id="brokerAttribute.storeVersion.container" style="clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 250px;">Broker store version:</div> + <div id="brokerAttribute.storeVersion" style="float:left;"></div> + </div> + <div id="brokerAttribute.storePath.container" style="clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 250px;">Broker store location:</div> + <div id="brokerAttribute.storePath" style="float:left;"></div> + </div> + <div id="brokerAttribute.defaultAuthenticationProvider.container" style="display: none; clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 250px;">Default authentication provider:</div> + <div id="brokerAttribute.defaultAuthenticationProvider" style="float:left;"></div> + </div> + <div id="brokerAttribute.defaultVirtualHost.container" style="display: none; clear:both; clear:both;"> + <div class="formLabel-labelCell" style="float:left; width: 250px;">Default virtual host:</div> + <div id="brokerAttribute.defaultVirtualHost" style="float:left;"></div> + </div> + <div id="brokerAttribute.aclFile.container" style="display: none; clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 250px;">ACL file location:</div> + <div id="brokerAttribute.aclFile" style="float:left;"></div> + </div> + <div id="brokerAttribute.groupFile.container" style="display: none; clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 250px;">Group file location:</div> + <div id="brokerAttribute.groupFile" style="float:left;"></div> + </div> + <div id="brokerAttribute.keyStorePath.container" style="display: none; clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 250px;">Path to keystore:</div> + <div id="brokerAttribute.keyStorePath" style="float:left;"></div><br/> + <div class="formLabel-labelCell" style="float:left; width: 250px;">Keystore alias:</div> + <div id="brokerAttribute.keyStoreCertAlias" style="float:left;"></div> + </div> + <div id="brokerAttribute.trustStorePath.container" style="display: none; clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 250px;">Path to truststore:</div> + <div id="brokerAttribute.trustStorePath" style="float:left;"></div> + </div> + <div id="brokerAttribute.peerStorePath.container" style="display: none; clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 250px;">Path to peerstore:</div> + <div id="brokerAttribute.peerStorePath" style="float:left;"></div> + </div> + <div id="brokerAttribute.statisticsReportingPeriod.container" style="display: none; clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 250px;">Statistics reporting period:</div> + <div id="brokerAttribute.statisticsReportingPeriod" style="float:left;"></div> + </div> + <div id="brokerAttribute.statisticsReportingResetEnabled.container" style="display: none; clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 250px;">Statistics reporting period enabled:</div> + <div id="brokerAttribute.statisticsReportingResetEnabled" style="float:left;"></div> + </div> + <div style="clear:both"></div> + <br/> + <div data-dojo-type="dijit.TitlePane" data-dojo-props="title: 'Queue Attributes', open: true"> + <div id="brokerAttribute.alertThresholdQueueDepth.container" style="clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 250px;">Queue depth alert threshold:</div> + <div id="brokerAttribute.alertThresholdQueueDepth" style="float:left;"></div> + </div> + <div id="brokerAttribute.alertThresholdMessageAge.container" style="clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 250px;">Queue message age alert threshold:</div> + <div id="brokerAttribute.alertThresholdMessageAge" style="float:left;"></div> ms + </div> + <div id="brokerAttribute.alertThresholdMessageSize.container" style="clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 250px;">Queue message size alert threshold:</div> + <div id="brokerAttribute.alertThresholdMessageSize" style="float:left;"></div> + </div> + <div id="brokerAttribute.alertRepeatGap.container" style="clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 250px;">Queue alert repeat gap:</div> + <div id="brokerAttribute.alertRepeatGap" style="float:left;"></div> ms + </div> + <div id="brokerAttribute.maximumDeliveryAttempts.container" style="clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 250px;">Queue maximum delivery retries:</div> + <div id="brokerAttribute.maximumDeliveryAttempts" style="float:left;"></div> + </div> + <div id="brokerAttribute.deadLetterQueueEnabled.container" style="clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 250px;">Dead letter queue enabled:</div> + <div id="brokerAttribute.deadLetterQueueEnabled" style="float:left;"></div> + </div> + <div id="brokerAttribute.queueFlowControlSizeBytes.container" style="clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 250px;">Queue flow capacity:</div> + <div id="brokerAttribute.queueFlowControlSizeBytes" style="float:left;"></div> + </div> + <div id="brokerAttribute.queueFlowResumeSizeBytes.container" style="clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 250px;">Queue flow resume capacity:</div> + <div id="brokerAttribute.queueFlowResumeSizeBytes" style="float:left;"></div> + </div> + <div style="clear:both"></div> + </div> + <br/> + <div data-dojo-type="dijit.TitlePane" data-dojo-props="title: 'Connection attributes', open: true"> + <div id="brokerAttribute.sessionCountLimit.container" style="clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 250px;">Connection session limit:</div> + <div id="brokerAttribute.sessionCountLimit" style="float:left;"></div> + </div> + <div id="brokerAttribute.heartBeatDelay.container" style="clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 250px;">Heart beat delay:</div> + <div id="brokerAttribute.heartBeatDelay" style="float:left;"></div> ms + </div> + <div style="clear:both"></div> + </div> + </div> + <br/> + <button data-dojo-type="dijit.form.Button" class="editBroker">Edit</button> + </div> <br/> <div data-dojo-type="dijit.TitlePane" data-dojo-props="title: 'Virtual Hosts'"> <div class="broker-virtualhosts"></div> diff --git a/qpid/java/broker/src/main/java/broker.bnd b/qpid/java/broker/src/main/java/broker.bnd index bf338543f7..2edbab79a5 100755 --- a/qpid/java/broker/src/main/java/broker.bnd +++ b/qpid/java/broker/src/main/java/broker.bnd @@ -17,7 +17,7 @@ # under the License. # -ver: 0.21.0 +ver: 0.23.0 Bundle-SymbolicName: qpid-broker Bundle-Version: ${ver} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/ConfigurationEntryStore.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/ConfigurationEntryStore.java index d514bf25ce..5f3589c7ef 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/ConfigurationEntryStore.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/ConfigurationEntryStore.java @@ -62,12 +62,26 @@ public interface ConfigurationEntryStore * @param target location to copy store into * @throws IllegalConfigurationException if store cannot be copied into given location */ - public void copyTo(String copyLocation); + void copyTo(String copyLocation); /** * Return the store location for the opened store or null if store has not been opened. * * @return store location for the opened store or null if store has not been opened */ - public String getStoreLocation(); + String getStoreLocation(); + + /** + * Returns the version of the store + * + * @return store version + */ + int getVersion(); + + /** + * Returns the type of the store + * + * @return store type + */ + String getType(); } diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/startup/AuthenticationProviderRecoverer.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/startup/AuthenticationProviderRecoverer.java index 9b06a2b499..3290c827c9 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/startup/AuthenticationProviderRecoverer.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/startup/AuthenticationProviderRecoverer.java @@ -47,7 +47,7 @@ public class AuthenticationProviderRecoverer implements ConfiguredObjectRecovere AuthenticationProvider authenticationProvider = _authenticationProviderFactory.create( configurationEntry.getId(), broker, - attributes, null); + attributes); return authenticationProvider; } diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/startup/BrokerRecoverer.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/startup/BrokerRecoverer.java index 3d19e781e2..0d7be75a0b 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/startup/BrokerRecoverer.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/startup/BrokerRecoverer.java @@ -18,7 +18,6 @@ import org.apache.qpid.server.model.adapter.BrokerAdapter; import org.apache.qpid.server.model.adapter.PortFactory; import org.apache.qpid.server.configuration.store.StoreConfigurationChangeListener; import org.apache.qpid.server.configuration.updater.TaskExecutor; -import org.apache.qpid.server.security.group.GroupPrincipalAccessor; import org.apache.qpid.server.stats.StatisticsGatherer; import org.apache.qpid.server.virtualhost.VirtualHostRegistry; @@ -50,7 +49,7 @@ public class BrokerRecoverer implements ConfiguredObjectRecoverer<Broker> { StoreConfigurationChangeListener storeChangeListener = new StoreConfigurationChangeListener(entry.getStore()); BrokerAdapter broker = new BrokerAdapter(entry.getId(), entry.getAttributes(), _statisticsGatherer, _virtualHostRegistry, - _logRecorder, _rootMessageLogger, _authenticationProviderFactory, _portFactory, _taskExecutor); + _logRecorder, _rootMessageLogger, _authenticationProviderFactory, _portFactory, _taskExecutor, entry.getStore()); broker.addChangeListener(storeChangeListener); Map<String, Collection<ConfigurationEntry>> childEntries = entry.getChildren(); @@ -103,12 +102,6 @@ public class BrokerRecoverer implements ConfiguredObjectRecoverer<Broker> } broker.setDefaultAuthenticationProvider(defaultAuthenticationProvider); - GroupPrincipalAccessor groupPrincipalAccessor = new GroupPrincipalAccessor(broker.getGroupProviders()); - for (AuthenticationProvider authenticationProvider : authenticationProviders) - { - authenticationProvider.setGroupAccessor(groupPrincipalAccessor); - } - Collection<Port> ports = broker.getPorts(); for (Port port : ports) { @@ -128,7 +121,7 @@ public class BrokerRecoverer implements ConfiguredObjectRecoverer<Broker> private AuthenticationProvider getAuthenticationProviderByName(BrokerAdapter broker, String authenticationProviderName) { - AuthenticationProvider provider = broker.getAuthenticationProviderByName(authenticationProviderName); + AuthenticationProvider provider = broker.findAuthenticationProviderByName(authenticationProviderName); if (provider == null) { throw new IllegalConfigurationException("Cannot find the authentication provider with name: " diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/store/JsonConfigurationEntryStore.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/store/JsonConfigurationEntryStore.java index 2ee072e5ff..413e9d2563 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/store/JsonConfigurationEntryStore.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/store/JsonConfigurationEntryStore.java @@ -57,6 +57,12 @@ public class JsonConfigurationEntryStore extends MemoryConfigurationEntryStore } @Override + public String getType() + { + return STORE_TYPE; + } + + @Override public String toString() { return "JsonConfigurationEntryStore [_storeFile=" + _storeFile + ", _rootId=" + getRootEntry().getId() + "]"; @@ -81,7 +87,7 @@ public class JsonConfigurationEntryStore extends MemoryConfigurationEntryStore ConfigurationEntry rootEntry = initialStore.getRootEntry(); Map<UUID, ConfigurationEntry> entries = new HashMap<UUID, ConfigurationEntry>(); copyEntry(rootEntry.getId(), initialStore, entries); - saveAsTree(rootEntry.getId(), entries, getObjectMapper(), storeFile); + saveAsTree(rootEntry.getId(), entries, getObjectMapper(), storeFile, getVersion()); } } } diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/store/ManagementModeStoreHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/store/ManagementModeStoreHandler.java index 59247df25a..385d2f7327 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/store/ManagementModeStoreHandler.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/store/ManagementModeStoreHandler.java @@ -149,6 +149,18 @@ public class ManagementModeStoreHandler implements ConfigurationEntryStore return _store.getStoreLocation(); } + @Override + public int getVersion() + { + return _store.getVersion(); + } + + @Override + public String getType() + { + return _store.getType(); + } + private Map<UUID, ConfigurationEntry> createPortsFromCommadLineOptions(BrokerOptions options) { int managementModeRmiPort = options.getManagementModeRmiPort(); @@ -312,4 +324,5 @@ public class ManagementModeStoreHandler implements ConfigurationEntryStore } return new ConfigurationEntry(entry.getId(), entry.getType(), attributes, children, entry.getStore()); } + } diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/store/MemoryConfigurationEntryStore.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/store/MemoryConfigurationEntryStore.java index 48ca01c312..5944adaa99 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/store/MemoryConfigurationEntryStore.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/store/MemoryConfigurationEntryStore.java @@ -66,6 +66,8 @@ public class MemoryConfigurationEntryStore implements ConfigurationEntryStore private static final String ID = "id"; private static final String TYPE = "@type"; + private static final int STORE_VERSION = 1; + private final ObjectMapper _objectMapper; private final Map<UUID, ConfigurationEntry> _entries; private final Map<String, Class<? extends ConfiguredObject>> _relationshipClasses; @@ -189,6 +191,18 @@ public class MemoryConfigurationEntryStore implements ConfigurationEntryStore } @Override + public int getVersion() + { + return STORE_VERSION; + } + + @Override + public String getType() + { + return STORE_TYPE; + } + + @Override public String toString() { return "MemoryConfigurationEntryStore [_rootId=" + _rootId + "]"; @@ -215,12 +229,13 @@ public class MemoryConfigurationEntryStore implements ConfigurationEntryStore protected void saveAsTree(File file) { - saveAsTree(_rootId, _entries, _objectMapper, file); + saveAsTree(_rootId, _entries, _objectMapper, file, STORE_VERSION); } - protected void saveAsTree(UUID rootId, Map<UUID, ConfigurationEntry> entries, ObjectMapper mapper, File file) + protected void saveAsTree(UUID rootId, Map<UUID, ConfigurationEntry> entries, ObjectMapper mapper, File file, int version) { Map<String, Object> tree = toTree(rootId, entries); + tree.put(Broker.STORE_VERSION, version); try { mapper.writeValue(file, tree); @@ -317,8 +332,11 @@ public class MemoryConfigurationEntryStore implements ConfigurationEntryStore throw new IllegalConfigurationException("Duplicate id is found: " + entryId + "! The following configuration entries have the same id: " + entries.get(entryId) + ", " + entry); } - entries.put(entryId, entry); + Set<UUID> children = entry.getChildrenIds(); + Set<UUID> childrenCopy = children == null? null : new HashSet<UUID>(children); + ConfigurationEntry copy = new ConfigurationEntry(entryId, entry.getType(), new HashMap<String, Object>(entry.getAttributes()), childrenCopy, this); + entries.put(entryId, copy); if (children != null) { for (UUID uuid : children) diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/updater/ChangeAttributesTask.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/updater/ChangeAttributesTask.java new file mode 100644 index 0000000000..4ba4057fee --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/updater/ChangeAttributesTask.java @@ -0,0 +1,62 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.configuration.updater; + +import java.util.Map; +import java.util.concurrent.Callable; + +import org.apache.qpid.server.model.ConfiguredObject; + +public class ChangeAttributesTask implements Callable<Void> +{ + private final Map<String, Object> _attributes; + private final ConfiguredObject _object; + + public ChangeAttributesTask(ConfiguredObject target, Map<String, Object> attributes) + { + super(); + _object = target; + _attributes = attributes; + } + + @Override + public Void call() throws Exception + { + _object.setAttributes(_attributes); + return null; + } + + public Map<String, Object> getAttributes() + { + return _attributes; + } + + public ConfiguredObject getObject() + { + return _object; + } + + @Override + public String toString() + { + return "ChangeAttributesTask [object=" + _object + ", attributes=" + _attributes + "]"; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/model/AuthenticationProvider.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/model/AuthenticationProvider.java index 2e5c3a0cc7..61ceae9cf0 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/model/AuthenticationProvider.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/model/AuthenticationProvider.java @@ -25,7 +25,6 @@ import java.util.Collection; import java.util.Collections; import org.apache.qpid.server.security.SubjectCreator; -import org.apache.qpid.server.security.group.GroupPrincipalAccessor; public interface AuthenticationProvider extends ConfiguredObject { @@ -67,5 +66,4 @@ public interface AuthenticationProvider extends ConfiguredObject */ SubjectCreator getSubjectCreator(); - void setGroupAccessor(GroupPrincipalAccessor groupPrincipalAccessor); } diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/model/Broker.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/model/Broker.java index 1d2fdd0452..21a22d032d 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/model/Broker.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/model/Broker.java @@ -71,6 +71,10 @@ public interface Broker extends ConfiguredObject String HEART_BEAT_DELAY = "heartBeatDelay"; String STATISTICS_REPORTING_PERIOD = "statisticsReportingPeriod"; String STATISTICS_REPORTING_RESET_ENABLED = "statisticsReportingResetEnabled"; + String STORE_TYPE = "storeType"; + String STORE_VERSION = "storeVersion"; + String STORE_PATH = "storePath"; + String MANAGEMENT_VERSION = "managementVersion"; /* * A temporary attribute to pass the path to ACL file. @@ -131,6 +135,10 @@ public interface Broker extends ConfiguredObject HEART_BEAT_DELAY, STATISTICS_REPORTING_PERIOD, STATISTICS_REPORTING_RESET_ENABLED, + STORE_TYPE, + STORE_VERSION, + STORE_PATH, + MANAGEMENT_VERSION, ACL_FILE, KEY_STORE_PATH, diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/model/Model.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/model/Model.java index 2c05dce9cb..3244d55fe2 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/model/Model.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/model/Model.java @@ -29,6 +29,12 @@ import java.util.Map; public class Model { + /* + * API version for the broker management interfaces + */ + public static final int MANAGEMENT_API_MAJOR_VERSION = 1; + public static final int MANAGEMENT_API_MINOR_VERSION = 0; + private static final Model MODEL_INSTANCE = new Model(); private final Map<Class<? extends ConfiguredObject>, Collection<Class<? extends ConfiguredObject>>> diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/AbstractAdapter.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/AbstractAdapter.java index 6b6cce3ffa..ce8e06bf4f 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/AbstractAdapter.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/AbstractAdapter.java @@ -27,12 +27,12 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; -import java.util.concurrent.Callable; import org.apache.qpid.server.model.ConfigurationChangeListener; import org.apache.qpid.server.model.ConfiguredObject; import org.apache.qpid.server.model.IllegalStateTransitionException; import org.apache.qpid.server.model.State; +import org.apache.qpid.server.configuration.updater.ChangeAttributesTask; import org.apache.qpid.server.configuration.updater.ChangeStateTask; import org.apache.qpid.server.configuration.updater.CreateChildTask; import org.apache.qpid.server.configuration.updater.SetAttributeTask; @@ -56,7 +56,14 @@ abstract class AbstractAdapter implements ConfiguredObject _id = id; if (attributes != null) { - _attributes.putAll(attributes); + Collection<String> names = getAttributeNames(); + for (String name : names) + { + if (attributes.containsKey(name)) + { + _attributes.put(name, attributes.get(name)); + } + } } if (defaults != null) { @@ -88,13 +95,17 @@ abstract class AbstractAdapter implements ConfiguredObject if (setState(currentState, desiredState)) { notifyStateChanged(currentState, desiredState); + return desiredState; + } + else + { + return getActualState(); } } else { - _taskExecutor.submitAndWait(new ChangeStateTask(this, currentState, desiredState)); + return (State)_taskExecutor.submitAndWait(new ChangeStateTask(this, currentState, desiredState)); } - return getActualState(); } /** @@ -218,13 +229,17 @@ abstract class AbstractAdapter implements ConfiguredObject if (changeAttribute(name, expected, desired)) { attributeSet(name, expected, desired); + return desired; + } + else + { + return getAttribute(name); } } else { - _taskExecutor.submitAndWait(new SetAttributeTask(this, name, expected, desired)); + return _taskExecutor.submitAndWait(new SetAttributeTask(this, name, expected, desired)); } - return getAttribute(name); } protected boolean changeAttribute(final String name, final Object expected, final Object desired) @@ -322,30 +337,23 @@ abstract class AbstractAdapter implements ConfiguredObject } else { - getTaskExecutor().submitAndWait(new Callable<Void>() - { - - @Override - public Void call() throws Exception - { - AbstractAdapter.this.setAttributes(attributes); - return null; - } - }); + getTaskExecutor().submitAndWait(new ChangeAttributesTask(this, attributes)); } - } protected void changeAttributes(final Map<String, Object> attributes) { - for (Map.Entry<String, Object> attributeEntry : attributes.entrySet()) + Collection<String> names = getAttributeNames(); + for (String name : names) { - String name = attributeEntry.getKey(); - Object desired = attributeEntry.getValue(); - Object expected = getAttribute(name); - if (changeAttribute(name, expected, desired)) + if (attributes.containsKey(name)) { - attributeSet(name, expected, desired); + Object desired = attributes.get(name); + Object expected = getAttribute(name); + if (changeAttribute(name, expected, desired)) + { + attributeSet(name, expected, desired); + } } } } diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/AuthenticationProviderAdapter.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/AuthenticationProviderAdapter.java index 16c6cb7e5e..0c17637e2f 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/AuthenticationProviderAdapter.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/AuthenticationProviderAdapter.java @@ -67,19 +67,29 @@ public abstract class AuthenticationProviderAdapter<T extends AuthenticationMana protected T _authManager; protected final Broker _broker; - private GroupPrincipalAccessor _groupAccessor; - protected Collection<String> _supportedAttributes; - Map<String, AuthenticationManagerFactory> _factories; + protected Map<String, AuthenticationManagerFactory> _factories; private AuthenticationProviderAdapter(UUID id, Broker broker, final T authManager, Map<String, Object> attributes, Collection<String> attributeNames) { - super(id, null, attributes, broker.getTaskExecutor()); + super(id, null, null, broker.getTaskExecutor()); _authManager = authManager; _broker = broker; _supportedAttributes = createSupportedAttributes(attributeNames); _factories = getAuthenticationManagerFactories(); addParent(Broker.class, broker); + + // set attributes now after all attribute names are known + if (attributes != null) + { + for (String name : _supportedAttributes) + { + if (attributes.containsKey(name)) + { + changeAttribute(name, null, attributes.get(name)); + } + } + } } T getAuthManager() @@ -198,7 +208,7 @@ public abstract class AuthenticationProviderAdapter<T extends AuthenticationMana @Override public <C extends ConfiguredObject> Collection<C> getChildren(Class<C> clazz) { - return null; + return Collections.emptySet(); } @Override @@ -222,14 +232,12 @@ public abstract class AuthenticationProviderAdapter<T extends AuthenticationMana throw new IntegrityViolationException("Authentication provider '" + providerName + "' is set on port " + port.getName()); } } + _authManager.close(); + _authManager.onDelete(); return true; } else if(desiredState == State.ACTIVE) { - if (_groupAccessor == null) - { - throw new IllegalStateTransitionException("Cannot transit into ACTIVE state with null group accessor!"); - } _authManager.initialise(); return true; } @@ -244,12 +252,7 @@ public abstract class AuthenticationProviderAdapter<T extends AuthenticationMana @Override public SubjectCreator getSubjectCreator() { - return new SubjectCreator(_authManager, _groupAccessor); - } - - public void setGroupAccessor(GroupPrincipalAccessor groupAccessor) - { - _groupAccessor = groupAccessor; + return new SubjectCreator(_authManager, new GroupPrincipalAccessor(_broker.getGroupProviders())); } @Override @@ -331,7 +334,6 @@ public abstract class AuthenticationProviderAdapter<T extends AuthenticationMana } - } public static class PrincipalDatabaseAuthenticationManagerAdapter diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/AuthenticationProviderFactory.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/AuthenticationProviderFactory.java index 721282fb9c..353e9f83bf 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/AuthenticationProviderFactory.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/AuthenticationProviderFactory.java @@ -27,13 +27,14 @@ import java.util.List; import java.util.Map; import java.util.UUID; +import org.apache.qpid.server.configuration.IllegalConfigurationException; import org.apache.qpid.server.model.AuthenticationProvider; import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.PasswordCredentialManagingAuthenticationProvider; import org.apache.qpid.server.plugin.AuthenticationManagerFactory; import org.apache.qpid.server.plugin.QpidServiceLoader; import org.apache.qpid.server.security.auth.manager.AuthenticationManager; import org.apache.qpid.server.security.auth.manager.PrincipalDatabaseAuthenticationManager; -import org.apache.qpid.server.security.group.GroupPrincipalAccessor; import org.apache.qpid.server.model.adapter.AuthenticationProviderAdapter.PrincipalDatabaseAuthenticationManagerAdapter; import org.apache.qpid.server.model.adapter.AuthenticationProviderAdapter.SimpleAuthenticationProviderAdapter; @@ -58,9 +59,8 @@ public class AuthenticationProviderFactory * <p> * The configured {@link AuthenticationManagerFactory}'s are used to try to create the {@link AuthenticationProvider}. * The first non-null instance is returned. The factories are used in non-deterministic order. - * @param groupPrincipalAccessor TODO */ - public AuthenticationProvider create(UUID id, Broker broker, Map<String, Object> attributes, GroupPrincipalAccessor groupPrincipalAccessor) + public AuthenticationProvider create(UUID id, Broker broker, Map<String, Object> attributes) { for (AuthenticationManagerFactory factory : _factories) { @@ -70,14 +70,30 @@ public class AuthenticationProviderFactory AuthenticationProviderAdapter<?> authenticationProvider; if (manager instanceof PrincipalDatabaseAuthenticationManager) { + // a temporary restriction to prevent creation of several instances + // of PlainPasswordFileAuthenticationProvider/Base64MD5PasswordFileAuthenticationProvider + // due to current limitation of JMX management which cannot cope + // with several user management MBeans as MBean type is used as a name. + + // TODO: Remove this check after fixing of JMX management + for (AuthenticationProvider provider : broker.getAuthenticationProviders()) + { + if (provider instanceof PasswordCredentialManagingAuthenticationProvider) + { + throw new IllegalConfigurationException("An authentication provider which can manage users alredy exists [" + + provider.getName() + "]. Only one instance is allowed."); + } + } + + manager.onCreate(); authenticationProvider = new PrincipalDatabaseAuthenticationManagerAdapter(id, broker, (PrincipalDatabaseAuthenticationManager) manager, attributes, factory.getAttributeNames()); } else { + manager.onCreate(); authenticationProvider = new SimpleAuthenticationProviderAdapter(id, broker, manager, attributes, factory.getAttributeNames()); } - authenticationProvider.setGroupAccessor(groupPrincipalAccessor); return authenticationProvider; } } diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/BrokerAdapter.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/BrokerAdapter.java index 1492982708..73706904e5 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/BrokerAdapter.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/BrokerAdapter.java @@ -24,6 +24,7 @@ import java.lang.reflect.Type; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.security.AccessControlException; +import java.security.KeyStoreException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -32,9 +33,11 @@ import java.util.Map; import java.util.UUID; import javax.net.ssl.KeyManagerFactory; +import java.security.cert.Certificate; import org.apache.log4j.Logger; import org.apache.qpid.common.QpidProperties; +import org.apache.qpid.server.configuration.ConfigurationEntryStore; import org.apache.qpid.server.configuration.IllegalConfigurationException; import org.apache.qpid.server.logging.LogRecorder; import org.apache.qpid.server.logging.RootMessageLogger; @@ -47,6 +50,7 @@ import org.apache.qpid.server.model.ConfiguredObject; import org.apache.qpid.server.model.GroupProvider; import org.apache.qpid.server.model.KeyStore; import org.apache.qpid.server.model.LifetimePolicy; +import org.apache.qpid.server.model.Model; import org.apache.qpid.server.model.Plugin; import org.apache.qpid.server.model.Port; import org.apache.qpid.server.model.State; @@ -59,13 +63,13 @@ import org.apache.qpid.server.security.auth.manager.Base64MD5PasswordFileAuthent import org.apache.qpid.server.security.auth.manager.PlainPasswordFileAuthenticationManagerFactory; import org.apache.qpid.server.security.group.FileGroupManager; import org.apache.qpid.server.security.group.GroupManager; -import org.apache.qpid.server.security.group.GroupPrincipalAccessor; import org.apache.qpid.server.security.SecurityManager; import org.apache.qpid.server.security.SubjectCreator; import org.apache.qpid.server.stats.StatisticsGatherer; import org.apache.qpid.server.store.MessageStoreCreator; import org.apache.qpid.server.util.MapValueConverter; import org.apache.qpid.server.virtualhost.VirtualHostRegistry; +import org.apache.qpid.transport.network.security.ssl.SSLUtil; public class BrokerAdapter extends AbstractAdapter implements Broker, ConfigurationChangeListener { @@ -124,6 +128,7 @@ public class BrokerAdapter extends AbstractAdapter implements Broker, Configurat private static final String DEFAULT_KEY_STORE_NAME = "defaultKeyStore"; private static final String DEFAULT_TRUST_STORE_NAME = "defaultTrustStore"; private static final String DEFAULT_GROUP_PROFIDER_NAME = "defaultGroupProvider"; + private static final String DEFAULT_PEER_STORE_NAME = "defaultPeerStore"; private static final String DUMMY_PASSWORD_MASK = "********"; @@ -146,7 +151,10 @@ public class BrokerAdapter extends AbstractAdapter implements Broker, Configurat put(Broker.NAME, DEFAULT_NAME); }}); - + private String[] POSITIVE_NUMERIC_ATTRIBUTES = { ALERT_THRESHOLD_MESSAGE_AGE, ALERT_THRESHOLD_MESSAGE_COUNT, + ALERT_THRESHOLD_QUEUE_DEPTH, ALERT_THRESHOLD_MESSAGE_SIZE, ALERT_REPEAT_GAP, FLOW_CONTROL_SIZE_BYTES, + FLOW_CONTROL_RESUME_SIZE_BYTES, MAXIMUM_DELIVERY_ATTEMPTS, HOUSEKEEPING_CHECK_PERIOD, SESSION_COUNT_LIMIT, + HEART_BEAT_DELAY, STATISTICS_REPORTING_PERIOD }; private final StatisticsGatherer _statisticsGatherer; @@ -171,11 +179,12 @@ public class BrokerAdapter extends AbstractAdapter implements Broker, Configurat private final UUID _defaultKeyStoreId; private final UUID _defaultTrustStoreId; - private Collection<String> _supportedStoreTypes; + private final Collection<String> _supportedStoreTypes; + private final ConfigurationEntryStore _brokerStore; public BrokerAdapter(UUID id, Map<String, Object> attributes, StatisticsGatherer statisticsGatherer, VirtualHostRegistry virtualHostRegistry, LogRecorder logRecorder, RootMessageLogger rootMessageLogger, AuthenticationProviderFactory authenticationProviderFactory, - PortFactory portFactory, TaskExecutor taskExecutor) + PortFactory portFactory, TaskExecutor taskExecutor, ConfigurationEntryStore brokerStore) { super(id, DEFAULTS, MapValueConverter.convert(attributes, ATTRIBUTE_TYPES), taskExecutor); _statisticsGatherer = statisticsGatherer; @@ -186,11 +195,12 @@ public class BrokerAdapter extends AbstractAdapter implements Broker, Configurat _authenticationProviderFactory = authenticationProviderFactory; _portFactory = portFactory; _securityManager = new SecurityManager((String)getAttribute(ACL_FILE)); - + addChangeListener(_securityManager); _defaultKeyStoreId = UUIDGenerator.generateBrokerChildUUID(KeyStore.class.getSimpleName(), DEFAULT_KEY_STORE_NAME); _defaultTrustStoreId = UUIDGenerator.generateBrokerChildUUID(TrustStore.class.getSimpleName(), DEFAULT_TRUST_STORE_NAME); createBrokerChildrenFromAttributes(); _supportedStoreTypes = new MessageStoreCreator().getStoreTypes(); + _brokerStore = brokerStore; } /* @@ -198,6 +208,14 @@ public class BrokerAdapter extends AbstractAdapter implements Broker, Configurat */ private void createBrokerChildrenFromAttributes() { + createGroupProvider(); + createKeyStore(); + createTrustStore(); + createPeerStore(); + } + + private void createGroupProvider() + { String groupFile = (String) getAttribute(GROUP_FILE); if (groupFile != null) { @@ -205,8 +223,16 @@ public class BrokerAdapter extends AbstractAdapter implements Broker, Configurat UUID groupProviderId = UUIDGenerator.generateBrokerChildUUID(GroupProvider.class.getSimpleName(), DEFAULT_GROUP_PROFIDER_NAME); GroupProviderAdapter groupProviderAdapter = new GroupProviderAdapter(groupProviderId, groupManager, this); - addGroupProvider(groupProviderAdapter); + _groupProviders.put(DEFAULT_GROUP_PROFIDER_NAME, groupProviderAdapter); + } + else + { + _groupProviders.remove(DEFAULT_GROUP_PROFIDER_NAME); } + } + + private void createKeyStore() + { Map<String, Object> actualAttributes = getActualAttributes(); String keyStorePath = (String) getAttribute(KEY_STORE_PATH); if (keyStorePath != null) @@ -218,9 +244,18 @@ public class BrokerAdapter extends AbstractAdapter implements Broker, Configurat keyStoreAttributes.put(KeyStore.TYPE, java.security.KeyStore.getDefaultType()); keyStoreAttributes.put(KeyStore.CERTIFICATE_ALIAS, getAttribute(KEY_STORE_CERT_ALIAS)); keyStoreAttributes.put(KeyStore.KEY_MANAGER_FACTORY_ALGORITHM, KeyManagerFactory.getDefaultAlgorithm()); - KeyStoreAdapter KeyStoreAdapter = new KeyStoreAdapter(_defaultKeyStoreId, this, keyStoreAttributes); - addKeyStore(KeyStoreAdapter); + KeyStoreAdapter keyStoreAdapter = new KeyStoreAdapter(_defaultKeyStoreId, this, keyStoreAttributes); + _keyStores.put(keyStoreAdapter.getId(), keyStoreAdapter); + } + else + { + _keyStores.remove(_defaultKeyStoreId); } + } + + private void createTrustStore() + { + Map<String, Object> actualAttributes = getActualAttributes(); String trustStorePath = (String) getAttribute(TRUST_STORE_PATH); if (trustStorePath != null) { @@ -232,13 +267,22 @@ public class BrokerAdapter extends AbstractAdapter implements Broker, Configurat trsustStoreAttributes.put(TrustStore.TYPE, java.security.KeyStore.getDefaultType()); trsustStoreAttributes.put(TrustStore.KEY_MANAGER_FACTORY_ALGORITHM, KeyManagerFactory.getDefaultAlgorithm()); TrustStoreAdapter trustStore = new TrustStoreAdapter(_defaultTrustStoreId, this, trsustStoreAttributes); - addTrustStore(trustStore); + _trustStores.put(trustStore.getId(), trustStore); + } + else + { + _trustStores.remove(_defaultTrustStoreId); } + } + + private void createPeerStore() + { + Map<String, Object> actualAttributes = getActualAttributes(); String peerStorePath = (String) getAttribute(PEER_STORE_PATH); + UUID peerStoreId = UUIDGenerator.generateBrokerChildUUID(TrustStore.class.getSimpleName(), DEFAULT_PEER_STORE_NAME); if (peerStorePath != null) { Map<String, Object> peerStoreAttributes = new HashMap<String, Object>(); - UUID peerStoreId = UUID.randomUUID(); peerStoreAttributes.put(TrustStore.NAME, peerStoreId.toString()); peerStoreAttributes.put(TrustStore.PATH, peerStorePath); peerStoreAttributes.put(TrustStore.PEERS_ONLY, Boolean.TRUE); @@ -246,7 +290,11 @@ public class BrokerAdapter extends AbstractAdapter implements Broker, Configurat peerStoreAttributes.put(TrustStore.TYPE, java.security.KeyStore.getDefaultType()); peerStoreAttributes.put(TrustStore.KEY_MANAGER_FACTORY_ALGORITHM, KeyManagerFactory.getDefaultAlgorithm()); TrustStoreAdapter trustStore = new TrustStoreAdapter(peerStoreId, this, peerStoreAttributes); - addTrustStore(trustStore); + _trustStores.put(trustStore.getId(), trustStore); + } + else + { + _trustStores.remove(peerStoreId); } } @@ -276,7 +324,7 @@ public class BrokerAdapter extends AbstractAdapter implements Broker, Configurat } } - public AuthenticationProvider getAuthenticationProviderByName(String authenticationProviderName) + public AuthenticationProvider findAuthenticationProviderByName(String authenticationProviderName) { Collection<AuthenticationProvider> providers = getAuthenticationProviders(); for (AuthenticationProvider authenticationProvider : providers) @@ -483,40 +531,7 @@ public class BrokerAdapter extends AbstractAdapter implements Broker, Configurat AuthenticationProvider authenticationProvider = null; synchronized (_authenticationProviders) { - String type = (String)attributes.get(AuthenticationProvider.TYPE); - if (type == null) - { - throw new IllegalConfigurationException("Authentication provider type is not specified"); - } - - // a temporary restriction to prevent creation of several instances - // of PlainPasswordFileAuthenticationProvider/Base64MD5PasswordFileAuthenticationProvider - // due to current limitation of JMX management which cannot cope - // with several user management MBeans as MBean type is used as a name. - - // TODO: Remove this check after fixing of JMX management - if (type.equals(PlainPasswordFileAuthenticationManagerFactory.PROVIDER_TYPE) - || type.equals(Base64MD5PasswordFileAuthenticationManagerFactory.PROVIDER_TYPE)) - { - - for (AuthenticationProvider provider : _authenticationProviders.values()) - { - String providerType = (String) provider.getAttribute(AuthenticationProvider.TYPE); - if (providerType.equals(PlainPasswordFileAuthenticationManagerFactory.PROVIDER_TYPE) - || providerType.equals(Base64MD5PasswordFileAuthenticationManagerFactory.PROVIDER_TYPE)) - { - throw new IllegalConfigurationException("An authentication provider which can manage users alredy exists [" - + provider.getName() + "]. Only one instance is allowed."); - } - } - - } - - // it's cheap to create the groupPrincipalAccessor on the fly - GroupPrincipalAccessor groupPrincipalAccessor = new GroupPrincipalAccessor(_groupProviders.values()); - - authenticationProvider = _authenticationProviderFactory.create(UUID.randomUUID(), this, attributes, - groupPrincipalAccessor); + authenticationProvider = _authenticationProviderFactory.create(UUID.randomUUID(), this, attributes); addAuthenticationProvider(authenticationProvider); } authenticationProvider.setDesiredState(State.INITIALISING, State.ACTIVE); @@ -674,17 +689,29 @@ public class BrokerAdapter extends AbstractAdapter implements Broker, Configurat { return _authenticationProviderFactory.getSupportedAuthenticationProviders(); } - else if (DEFAULT_AUTHENTICATION_PROVIDER.equals(name)) + else if (KEY_STORE_PASSWORD.equals(name) || TRUST_STORE_PASSWORD.equals(name) || PEER_STORE_PASSWORD.equals(name)) + { + if (getActualAttributes().get(name) != null) + { + return DUMMY_PASSWORD_MASK; + } + return null; + } + else if (MANAGEMENT_VERSION.equals(name)) + { + return Model.MANAGEMENT_API_MAJOR_VERSION + "." + Model.MANAGEMENT_API_MINOR_VERSION; + } + else if (STORE_VERSION.equals(name)) { - return _defaultAuthenticationProvider == null ? null : _defaultAuthenticationProvider.getName(); + return _brokerStore.getVersion(); } - else if (KEY_STORE_PASSWORD.equals(name)) + else if (STORE_TYPE.equals(name)) { - return DUMMY_PASSWORD_MASK; + return _brokerStore.getType(); } - else if (TRUST_STORE_PASSWORD.equals(name)) + else if (STORE_PATH.equals(name)) { - return DUMMY_PASSWORD_MASK; + return _brokerStore.getStoreLocation(); } return super.getAttribute(name); } @@ -990,6 +1017,173 @@ public class BrokerAdapter extends AbstractAdapter implements Broker, Configurat @Override protected void changeAttributes(Map<String, Object> attributes) { - super.changeAttributes(MapValueConverter.convert(attributes, ATTRIBUTE_TYPES)); + //TODO: Add ACL check + //TODO: Add management mode check + Map<String, Object> convertedAttributes = MapValueConverter.convert(attributes, ATTRIBUTE_TYPES); + validateAttributes(convertedAttributes); + + boolean keyStoreChanged = false; + boolean trustStoreChanged = false; + boolean peerStoreChanged = false; + Collection<String> names = AVAILABLE_ATTRIBUTES; + for (String name : names) + { + if (attributes.containsKey(name)) + { + Object desired = attributes.get(name); + Object expected = getAttribute(name); + if (changeAttribute(name, expected, desired)) + { + if (GROUP_FILE.equals(name)) + { + createGroupProvider(); + } + else if (DEFAULT_AUTHENTICATION_PROVIDER.equals(name)) + { + if (!_defaultAuthenticationProvider.getName().equals(desired)) + { + _defaultAuthenticationProvider = findAuthenticationProviderByName((String)desired); + } + } + else if (KEY_STORE_PATH.equals(name) || KEY_STORE_PASSWORD.equals(name) || KEY_STORE_CERT_ALIAS.equals(name)) + { + keyStoreChanged = true; + } + else if (TRUST_STORE_PATH.equals(name) || TRUST_STORE_PASSWORD.equals(name)) + { + trustStoreChanged = true; + } + else if (PEER_STORE_PATH.equals(name) || PEER_STORE_PASSWORD.equals(name)) + { + peerStoreChanged = true; + } + attributeSet(name, expected, desired); + } + } + } + + // the calls below are not thread safe but they should be fine in a management mode + // as there will be no user connected + // The new keystore/trustore/peerstore will be only used with new ports + // At the moment we cannot restart ports with new keystore/trustore/peerstore + + if (keyStoreChanged) + { + createKeyStore(); + } + if (trustStoreChanged) + { + createTrustStore(); + } + if (peerStoreChanged) + { + createPeerStore(); + } + } + + private void validateAttributes(Map<String, Object> convertedAttributes) + { + String aclFile = (String) convertedAttributes.get(ACL_FILE); + if (aclFile != null) + { + // create a security manager to validate the ACL specified in file + new SecurityManager(aclFile); + } + String groupFile = (String) convertedAttributes.get(GROUP_FILE); + if (groupFile != null) + { + // create a group manager to validate the groups specified in file + new FileGroupManager(groupFile); + } + validateKeyStoreAttributes(convertedAttributes, "key store", KEY_STORE_PATH, KEY_STORE_PASSWORD, KEY_STORE_CERT_ALIAS); + validateKeyStoreAttributes(convertedAttributes, "trust store", TRUST_STORE_PATH, TRUST_STORE_PASSWORD, null); + validateKeyStoreAttributes(convertedAttributes, "peer store", PEER_STORE_PATH, PEER_STORE_PASSWORD, null); + String defaultAuthenticationProvider = (String) convertedAttributes.get(DEFAULT_AUTHENTICATION_PROVIDER); + if (defaultAuthenticationProvider != null) + { + AuthenticationProvider provider = findAuthenticationProviderByName(defaultAuthenticationProvider); + if (provider == null) + { + throw new IllegalConfigurationException("Authentication provider with name " + defaultAuthenticationProvider + + " canot be set as a default as it does not exist"); + } + } + String defaultVirtualHost = (String) convertedAttributes.get(DEFAULT_VIRTUAL_HOST); + if (defaultVirtualHost != null) + { + VirtualHost foundHost = findVirtualHostByName(defaultVirtualHost); + if (foundHost == null) + { + throw new IllegalConfigurationException("Virtual host with name " + defaultVirtualHost + + " cannot be set as a default as it does not exist"); + } + } + Long queueFlowControlSize = (Long) convertedAttributes.get(FLOW_CONTROL_SIZE_BYTES); + if (queueFlowControlSize != null && queueFlowControlSize > 0) + { + Long queueFlowControlResumeSize = (Long) convertedAttributes.get(FLOW_CONTROL_RESUME_SIZE_BYTES); + if (queueFlowControlResumeSize == null) + { + throw new IllegalConfigurationException("Flow control resume size attribute is not specified with flow control size attribute"); + } + if (queueFlowControlResumeSize >= queueFlowControlSize) + { + throw new IllegalConfigurationException("Flow control resume size should be less then flow control size"); + } + } + for (String attributeName : POSITIVE_NUMERIC_ATTRIBUTES) + { + Number value = (Number) convertedAttributes.get(attributeName); + if (value != null && value.longValue() < 0) + { + throw new IllegalConfigurationException("Only positive integer value can be specified for the attribute " + + attributeName); + } + } + } + + private void validateKeyStoreAttributes(Map<String, Object> convertedAttributes, String type, String pathAttribute, + String passwordAttribute, String aliasAttribute) + { + String keyStoreFile = (String) convertedAttributes.get(pathAttribute); + if (keyStoreFile != null) + { + String password = (String) convertedAttributes.get(passwordAttribute); + if (password == null) + { + password = (String) getActualAttributes().get(passwordAttribute); + } + java.security.KeyStore keyStore = null; + try + { + keyStore = SSLUtil.getInitializedKeyStore(keyStoreFile, password, java.security.KeyStore.getDefaultType()); + } + catch (Exception e) + { + throw new IllegalConfigurationException("Cannot instantiate " + type + " at " + keyStoreFile, e); + } + if (aliasAttribute != null) + { + String alias = (String) convertedAttributes.get(aliasAttribute); + if (alias != null) + { + Certificate cert = null; + try + { + cert = keyStore.getCertificate(alias); + } + catch (KeyStoreException e) + { + // key store should be initialized above + throw new RuntimeException("Key store has not been initialized", e); + } + if (cert == null) + { + throw new IllegalConfigurationException("Cannot find a certificate with alias " + alias + "in " + type + + " : " + keyStoreFile); + } + } + } + } } } diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/v1_0/Subscription_1_0.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/v1_0/Subscription_1_0.java index d1b1373dcf..01eb41fd36 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/v1_0/Subscription_1_0.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/v1_0/Subscription_1_0.java @@ -716,15 +716,18 @@ class Subscription_1_0 implements Subscription getEndpoint().detach(); } - public synchronized boolean wouldSuspend(final QueueEntry msg) + public boolean wouldSuspend(final QueueEntry msg) { - final boolean hasCredit = _link.isAttached() && getEndpoint().hasCreditToSend(); - if(!hasCredit && getState() == State.ACTIVE) + synchronized (_link.getLock()) { - suspend(); - } + final boolean hasCredit = _link.isAttached() && getEndpoint().hasCreditToSend(); + if(!hasCredit && getState() == State.ACTIVE) + { + suspend(); + } - return !hasCredit; + return !hasCredit; + } } public boolean trySendLock() @@ -732,11 +735,14 @@ class Subscription_1_0 implements Subscription return _stateChangeLock.tryLock(); } - public synchronized void suspend() + public void suspend() { - if(_state.compareAndSet(State.ACTIVE, State.SUSPENDED)) + synchronized(_link.getLock()) { - _stateListener.stateChange(this, State.ACTIVE, State.SUSPENDED); + if(_state.compareAndSet(State.ACTIVE, State.SUSPENDED)) + { + _stateListener.stateChange(this, State.ACTIVE, State.SUSPENDED); + } } } @@ -807,26 +813,32 @@ class Subscription_1_0 implements Subscription return false; //TODO } - public synchronized void queueEmpty() + public void queueEmpty() { - if(_link.drained()) + synchronized(_link.getLock()) { - if(_state.compareAndSet(State.ACTIVE, State.SUSPENDED)) + if(_link.drained()) { - _stateListener.stateChange(this, State.ACTIVE, State.SUSPENDED); + if(_state.compareAndSet(State.ACTIVE, State.SUSPENDED)) + { + _stateListener.stateChange(this, State.ACTIVE, State.SUSPENDED); + } } } } - public synchronized void flowStateChanged() + public void flowStateChanged() { - if(isSuspended() && getEndpoint() != null) + synchronized(_link.getLock()) { - if(_state.compareAndSet(State.SUSPENDED, State.ACTIVE)) + if(isSuspended() && getEndpoint() != null) { - _stateListener.stateChange(this, State.SUSPENDED, State.ACTIVE); + if(_state.compareAndSet(State.SUSPENDED, State.ACTIVE)) + { + _stateListener.stateChange(this, State.SUSPENDED, State.ACTIVE); + } + _transactionId = _link.getTransactionId(); } - _transactionId = _link.getTransactionId(); } } diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/SecurityManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/SecurityManager.java index 1a1cce171b..9ef1ae1a3a 100755 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/SecurityManager.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/SecurityManager.java @@ -23,6 +23,10 @@ import org.apache.log4j.Logger; import org.apache.qpid.framing.AMQShortString; import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.ConfigurationChangeListener; +import org.apache.qpid.server.model.ConfiguredObject; +import org.apache.qpid.server.model.State; import org.apache.qpid.server.plugin.AccessControlFactory; import org.apache.qpid.server.plugin.QpidServiceLoader; import org.apache.qpid.server.queue.AMQQueue; @@ -46,9 +50,11 @@ import static org.apache.qpid.server.security.access.Operation.UNBIND; import javax.security.auth.Subject; import java.net.SocketAddress; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; @@ -60,7 +66,7 @@ import java.util.concurrent.ConcurrentHashMap; * * @see AccessControl */ -public class SecurityManager +public class SecurityManager implements ConfigurationChangeListener { private static final Logger _logger = Logger.getLogger(SecurityManager.class); @@ -69,8 +75,9 @@ public class SecurityManager public static final ThreadLocal<Boolean> _accessChecksDisabled = new ClearingThreadLocal(false); - private Map<String, AccessControl> _globalPlugins = new HashMap<String, AccessControl>(); - private Map<String, AccessControl> _hostPlugins = new HashMap<String, AccessControl>(); + private Map<String, AccessControl> _globalPlugins = new ConcurrentHashMap<String, AccessControl>(); + private Map<String, AccessControl> _hostPlugins = new ConcurrentHashMap<String, AccessControl>(); + private Map<String, List<String>> _aclConfigurationToPluginNamesMapping = new ConcurrentHashMap<String, List<String>>(); /** * A special ThreadLocal, which calls remove() on itself whenever the value is @@ -130,14 +137,22 @@ public class SecurityManager public SecurityManager(String aclFile) { + configureACLPlugin(aclFile); + } + + private void configureACLPlugin(String aclFile) + { Map<String, Object> attributes = new HashMap<String, Object>(); attributes.put("aclFile", aclFile); + for (AccessControlFactory provider : (new QpidServiceLoader<AccessControlFactory>()).instancesOf(AccessControlFactory.class)) { AccessControl accessControl = provider.createInstance(attributes); if(accessControl != null) { addHostPlugin(accessControl); + + mapAclConfigurationToPluginName(aclFile, accessControl.getClass().getName()); } } @@ -147,6 +162,17 @@ public class SecurityManager } } + private void mapAclConfigurationToPluginName(String aclFile, String pluginName) + { + List<String> pluginNames = _aclConfigurationToPluginNamesMapping.get(aclFile); + if (pluginNames == null) + { + pluginNames = new ArrayList<String>(); + _aclConfigurationToPluginNamesMapping.put(aclFile, pluginNames); + } + pluginNames.add(pluginName); + } + public static Subject getThreadSubject() { return _subject.get(); @@ -477,4 +503,50 @@ public class SecurityManager _hostPlugins.put(plugin.getClass().getName(), plugin); } + @Override + public void stateChanged(ConfiguredObject object, State oldState, State newState) + { + // no op + } + + @Override + public void childAdded(ConfiguredObject object, ConfiguredObject child) + { + // no op + } + + @Override + public void childRemoved(ConfiguredObject object, ConfiguredObject child) + { + // no op + } + + @Override + public void attributeSet(ConfiguredObject object, String attributeName, Object oldAttributeValue, Object newAttributeValue) + { + if (object instanceof Broker && Broker.ACL_FILE.equals(attributeName)) + { + // the code below is not thread safe, however, it should be fine in a management mode + // as there will be no user connected + + if (oldAttributeValue != null) + { + List<String> pluginNames = _aclConfigurationToPluginNamesMapping.remove(oldAttributeValue); + if (pluginNames != null) + { + for (String name : pluginNames) + { + _hostPlugins.remove(name); + } + } + } + if (newAttributeValue != null) + { + configureACLPlugin((String)newAttributeValue); + } + _immediatePublishPropsCache.clear(); + _publishPropsCache.clear(); + } + } + } diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/AbstractPasswordFilePrincipalDatabase.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/AbstractPasswordFilePrincipalDatabase.java index 578bb96efa..81f26a3e2f 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/AbstractPasswordFilePrincipalDatabase.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/AbstractPasswordFilePrincipalDatabase.java @@ -65,19 +65,17 @@ public abstract class AbstractPasswordFilePrincipalDatabase<U extends PasswordPr } } - public final void setPasswordFile(String passwordFile) throws IOException + public final void open(File passwordFile) throws IOException { - File f = new File(passwordFile); - getLogger().info("PasswordFile using file " + f.getAbsolutePath()); - _passwordFile = f; - if (!f.exists()) + getLogger().info("PasswordFile using file " + passwordFile.getAbsolutePath()); + _passwordFile = passwordFile; + if (!passwordFile.exists()) { - throw new FileNotFoundException("Cannot find password file " + f); + throw new FileNotFoundException("Cannot find password file " + passwordFile); } - if (!f.canRead()) + if (!passwordFile.canRead()) { - throw new FileNotFoundException("Cannot read password file " + f + - ". Check permissions."); + throw new FileNotFoundException("Cannot read password file " + passwordFile + ". Check permissions."); } loadPasswordFile(); diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PrincipalDatabase.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PrincipalDatabase.java index 605d2d019d..df770e84f8 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PrincipalDatabase.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PrincipalDatabase.java @@ -24,6 +24,8 @@ import org.apache.qpid.server.security.auth.sasl.AuthenticationProviderInitialis import javax.security.auth.callback.PasswordCallback; import javax.security.auth.login.AccountNotFoundException; + +import java.io.File; import java.io.IOException; import java.security.Principal; import java.util.List; @@ -32,7 +34,7 @@ import java.util.Map; /** Represents a "user database" which is really a way of storing principals (i.e. usernames) and passwords. */ public interface PrincipalDatabase { - void setPasswordFile(String passwordFile) throws IOException; + void open(File passwordFile) throws IOException; /** * Set the password for a given principal in the specified callback. This is used for certain SASL providers. The diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/AbstractPrincipalDatabaseAuthManagerFactory.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/AbstractPrincipalDatabaseAuthManagerFactory.java index 2cf8c4619a..5d427c4afb 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/AbstractPrincipalDatabaseAuthManagerFactory.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/AbstractPrincipalDatabaseAuthManagerFactory.java @@ -19,7 +19,6 @@ */ package org.apache.qpid.server.security.auth.manager; -import java.io.IOException; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -62,16 +61,7 @@ public abstract class AbstractPrincipalDatabaseAuthManagerFactory implements Aut } PrincipalDatabase principalDatabase = createPrincipalDatabase(); - try - { - principalDatabase.setPasswordFile(passwordFile); - } - catch (IOException e) - { - throw new RuntimeException(e.getMessage(), e); - } - - return new PrincipalDatabaseAuthenticationManager(principalDatabase); + return new PrincipalDatabaseAuthenticationManager(principalDatabase, passwordFile); } abstract PrincipalDatabase createPrincipalDatabase(); diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/AnonymousAuthenticationManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/AnonymousAuthenticationManager.java index dd4c2e717a..ae3bc5131f 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/AnonymousAuthenticationManager.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/AnonymousAuthenticationManager.java @@ -113,4 +113,16 @@ public class AnonymousAuthenticationManager implements AuthenticationManager public void close() { } + + @Override + public void onCreate() + { + // nothing to do, no external resource is required + } + + @Override + public void onDelete() + { + // nothing to do, no external resource is used + } } diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/AuthenticationManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/AuthenticationManager.java index c1a694f148..1576a73a82 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/AuthenticationManager.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/AuthenticationManager.java @@ -88,4 +88,14 @@ public interface AuthenticationManager extends Closeable * @return authentication result */ AuthenticationResult authenticate(String username, String password); + + /** + * Called after manager creation to create the required resources, for example, user databases etc. + */ + void onCreate(); + + /** + * Called before manager deletion to release and clean the resources created in {@link #onCreate()}. + */ + void onDelete(); } diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/ExternalAuthenticationManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/ExternalAuthenticationManager.java index 9ed8cf7fed..43e0a9f64f 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/ExternalAuthenticationManager.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/ExternalAuthenticationManager.java @@ -96,4 +96,16 @@ public class ExternalAuthenticationManager implements AuthenticationManager public void close() { } + + @Override + public void onCreate() + { + // nothing to do, no external resource is required + } + + @Override + public void onDelete() + { + // nothing to do, no external resource is used + } } diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/KerberosAuthenticationManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/KerberosAuthenticationManager.java index 3c1b709648..6bbf3ca6f5 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/KerberosAuthenticationManager.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/KerberosAuthenticationManager.java @@ -109,6 +109,18 @@ public class KerberosAuthenticationManager implements AuthenticationManager { } + @Override + public void onCreate() + { + // nothing to do, no external resource is required + } + + @Override + public void onDelete() + { + // nothing to do, no external resource is used + } + private static class GssApiCallbackHandler implements CallbackHandler { diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/PrincipalDatabaseAuthenticationManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/PrincipalDatabaseAuthenticationManager.java index f4c834810d..9647499783 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/PrincipalDatabaseAuthenticationManager.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/PrincipalDatabaseAuthenticationManager.java @@ -20,9 +20,12 @@ */ package org.apache.qpid.server.security.auth.manager; +import java.io.File; +import java.io.IOException; import java.security.Principal; import org.apache.log4j.Logger; +import org.apache.qpid.server.configuration.IllegalConfigurationException; import org.apache.qpid.server.security.auth.AuthenticationResult; import org.apache.qpid.server.security.auth.AuthenticationResult.AuthenticationStatus; import org.apache.qpid.server.security.auth.database.PrincipalDatabase; @@ -66,10 +69,12 @@ public class PrincipalDatabaseAuthenticationManager implements AuthenticationMan private final Map<String, Map<String, ?>> _serverCreationProperties = new HashMap<String, Map<String, ?>>(); private final PrincipalDatabase _principalDatabase; + private final String _passwordFile; - public PrincipalDatabaseAuthenticationManager(PrincipalDatabase pd) + public PrincipalDatabaseAuthenticationManager(PrincipalDatabase pd, String passwordFile) { _principalDatabase = pd; + _passwordFile = passwordFile; } public void initialise() @@ -204,4 +209,37 @@ public class PrincipalDatabaseAuthenticationManager implements AuthenticationMan { return _principalDatabase; } + + @Override + public void onCreate() + { + try + { + File passwordFile = new File(_passwordFile); + if (!passwordFile.exists()) + { + passwordFile.createNewFile(); + } + else if (!passwordFile.canRead()) + { + throw new IllegalConfigurationException("Cannot read password file" + _passwordFile + ". Check permissions."); + } + + _principalDatabase.open(passwordFile); + } + catch (IOException e) + { + throw new IllegalConfigurationException("Cannot use password database at :" + _passwordFile, e); + } + } + + @Override + public void onDelete() + { + File file = new File(_passwordFile); + if (file.exists() && file.isFile()) + { + file.delete(); + } + } } diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/SimpleLDAPAuthenticationManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/SimpleLDAPAuthenticationManager.java index 7891ef8cf5..ee00e9850d 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/SimpleLDAPAuthenticationManager.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/SimpleLDAPAuthenticationManager.java @@ -306,4 +306,16 @@ public class SimpleLDAPAuthenticationManager implements AuthenticationManager } } + + @Override + public void onCreate() + { + // nothing to do, no external resource is required + } + + @Override + public void onDelete() + { + // nothing to do, no external resource is used + } } diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HexInitialiser.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HexInitialiser.java index 4e12ac0750..8467dad60a 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HexInitialiser.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HexInitialiser.java @@ -27,6 +27,8 @@ import org.apache.qpid.server.security.auth.sasl.UsernamePasswordInitialiser; import javax.security.auth.callback.PasswordCallback; import javax.security.auth.login.AccountNotFoundException; import javax.security.sasl.SaslServerFactory; + +import java.io.File; import java.io.IOException; import java.security.Principal; import java.util.List; @@ -141,7 +143,7 @@ public class CRAMMD5HexInitialiser extends UsernamePasswordInitialiser } @Override - public void setPasswordFile(String passwordFile) throws IOException + public void open(File passwordFile) throws IOException { throw new UnsupportedOperationException(); } diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/group/GroupPrincipalAccessor.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/group/GroupPrincipalAccessor.java index d549b76aab..1b8cdc91bc 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/group/GroupPrincipalAccessor.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/group/GroupPrincipalAccessor.java @@ -25,8 +25,6 @@ import java.util.HashSet; import java.util.Set; import org.apache.qpid.server.model.GroupProvider; -import org.apache.qpid.server.model.adapter.GroupProviderAdapter; - public class GroupPrincipalAccessor { diff --git a/qpid/java/broker/src/main/resources/initial-store.json b/qpid/java/broker/src/main/resources/initial-store.json index 9fe72b3601..7e73772d6d 100644 --- a/qpid/java/broker/src/main/resources/initial-store.json +++ b/qpid/java/broker/src/main/resources/initial-store.json @@ -20,6 +20,7 @@ */ { "name": "QpidBroker", + "storeVersion": 1, "defaultAuthenticationProvider" : "passwordFile", "defaultVirtualHost" : "default", "authenticationproviders" : [ { diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/startup/BrokerRecovererTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/startup/BrokerRecovererTest.java index 8e0bfdaeb7..79dcf0cac4 100644 --- a/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/startup/BrokerRecovererTest.java +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/startup/BrokerRecovererTest.java @@ -53,7 +53,6 @@ import org.apache.qpid.server.model.VirtualHost; import org.apache.qpid.server.model.adapter.AuthenticationProviderFactory; import org.apache.qpid.server.model.adapter.PortFactory; import org.apache.qpid.server.configuration.updater.TaskExecutor; -import org.apache.qpid.server.security.group.GroupPrincipalAccessor; import org.apache.qpid.server.stats.StatisticsGatherer; import org.apache.qpid.server.virtualhost.VirtualHostRegistry; @@ -300,9 +299,6 @@ public class BrokerRecovererTest extends TestCase assertNotNull(broker); assertEquals("Unexpected number of authentication providers", 2, broker.getAuthenticationProviders().size()); - //verify that a GroupAcessor was added to the AuthenticationProviders - verify(_authenticationProvider1).setGroupAccessor(any(GroupPrincipalAccessor.class)); - verify(authenticationProvider2).setGroupAccessor(any(GroupPrincipalAccessor.class)); } public void testCreateBrokerWithGroupProvider() diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/store/ConfigurationEntryStoreTestCase.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/store/ConfigurationEntryStoreTestCase.java index b357c0b8e9..9d7f6a9cc1 100644 --- a/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/store/ConfigurationEntryStoreTestCase.java +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/store/ConfigurationEntryStoreTestCase.java @@ -110,7 +110,10 @@ public abstract class ConfigurationEntryStoreTestCase extends QpidTestCase assertEquals("Unexpected type ", Broker.class.getSimpleName(), brokerConfigEntry.getType()); Map<String, Object> attributes = brokerConfigEntry.getAttributes(); assertNotNull("Attributes cannot be null", attributes); - assertEquals("Unexpected attributes", _brokerAttributes, attributes); + for (Map.Entry<String, Object> attribute : _brokerAttributes.entrySet()) + { + assertEquals("Unexpected attribute " + attribute.getKey(), attribute.getValue(), attributes.get(attribute.getKey())); + } } public void testGetEntry() diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/store/JsonConfigurationEntryStoreTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/store/JsonConfigurationEntryStoreTest.java index 92e304ab86..1cb760f611 100644 --- a/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/store/JsonConfigurationEntryStoreTest.java +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/store/JsonConfigurationEntryStoreTest.java @@ -52,6 +52,7 @@ public class JsonConfigurationEntryStoreTest extends ConfigurationEntryStoreTest Map<String, Object> brokerObjectMap = new HashMap<String, Object>(); brokerObjectMap.put(Broker.ID, brokerId); brokerObjectMap.put("@type", Broker.class.getSimpleName()); + brokerObjectMap.put("storeVersion", 1); brokerObjectMap.putAll(brokerAttributes); StringWriter sw = new StringWriter(); @@ -114,8 +115,9 @@ public class JsonConfigurationEntryStoreTest extends ConfigurationEntryStoreTest assertEquals("Unexpected root entry", brokerId, root.getId()); Map<String, Object> attributes = root.getAttributes(); assertNotNull("Attributes not found", attributes); - assertEquals("Unexpected number of attriburtes", 1, attributes.size()); + assertEquals("Unexpected number of attriburtes", 2, attributes.size()); assertEquals("Unexpected name attribute", getTestName(), attributes.get(Broker.NAME)); + assertEquals("Unexpected version attribute", 1, attributes.get(Broker.STORE_VERSION)); } public void testCreateFromInitialStore() throws Exception @@ -135,8 +137,18 @@ public class JsonConfigurationEntryStoreTest extends ConfigurationEntryStoreTest assertEquals("Unexpected root entry", brokerId, root.getId()); Map<String, Object> attributes = root.getAttributes(); assertNotNull("Attributes not found", attributes); - assertEquals("Unexpected number of attriburtes", 1, attributes.size()); + assertEquals("Unexpected number of attriburtes", 2, attributes.size()); assertEquals("Unexpected name attribute", getTestName(), attributes.get(Broker.NAME)); + assertEquals("Unexpected version attribute", 1, attributes.get(Broker.STORE_VERSION)); } + public void testGetVersion() + { + assertEquals("Unexpected version", 1, getStore().getVersion()); + } + + public void testGetType() + { + assertEquals("Unexpected type", "json", getStore().getType()); + } } diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/store/MemoryConfigurationEntryStoreTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/store/MemoryConfigurationEntryStoreTest.java index 65ced69915..314e673c08 100644 --- a/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/store/MemoryConfigurationEntryStoreTest.java +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/store/MemoryConfigurationEntryStoreTest.java @@ -94,4 +94,14 @@ public class MemoryConfigurationEntryStoreTest extends ConfigurationEntryStoreTe assertEquals("Unexpected broker attributes", initialStoreRoot.getAttributes(), root.getAttributes()); assertEquals("Unexpected broker children", initialStoreRoot.getChildrenIds(), root.getChildrenIds()); } + + public void testGetVersion() + { + assertEquals("Unexpected version", 1, getStore().getVersion()); + } + + public void testGetType() + { + assertEquals("Unexpected type", "memory", getStore().getType()); + } } diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/model/adapter/AuthenticationProviderFactoryTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/model/adapter/AuthenticationProviderFactoryTest.java index 585fecae83..eb721d93a0 100644 --- a/qpid/java/broker/src/test/java/org/apache/qpid/server/model/adapter/AuthenticationProviderFactoryTest.java +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/model/adapter/AuthenticationProviderFactoryTest.java @@ -22,6 +22,7 @@ package org.apache.qpid.server.model.adapter; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static org.mockito.Mockito.any; import java.util.Collections; import java.util.HashMap; @@ -30,7 +31,7 @@ import java.util.UUID; import junit.framework.TestCase; -import org.apache.qpid.server.configuration.ConfigurationEntry; +import org.apache.qpid.server.configuration.IllegalConfigurationException; import org.apache.qpid.server.model.AuthenticationProvider; import org.apache.qpid.server.model.Broker; import org.apache.qpid.server.model.PasswordCredentialManagingAuthenticationProvider; @@ -64,9 +65,7 @@ public class AuthenticationProviderFactoryTest extends TestCase QpidServiceLoader<AuthenticationManagerFactory> authManagerFactoryServiceLoader = mock(QpidServiceLoader.class); AuthenticationManagerFactory authenticationManagerFactory = mock(AuthenticationManagerFactory.class); - ConfigurationEntry configurationEntry = mock(ConfigurationEntry.class); - when(configurationEntry.getId()).thenReturn(id); Broker broker = mock(Broker.class); when(authManagerFactoryServiceLoader.atLeastOneInstanceOf(AuthenticationManagerFactory.class)).thenReturn( @@ -74,7 +73,7 @@ public class AuthenticationProviderFactoryTest extends TestCase when(authenticationManagerFactory.createInstance(attributes)).thenReturn(authenticationManager); AuthenticationProviderFactory providerFactory = new AuthenticationProviderFactory(authManagerFactoryServiceLoader); - AuthenticationProvider provider = providerFactory.create(id, broker, attributes, null); + AuthenticationProvider provider = providerFactory.create(id, broker, attributes); assertNotNull("Provider is not created", provider); assertEquals("Unexpected ID", id, provider.getId()); @@ -82,4 +81,47 @@ public class AuthenticationProviderFactoryTest extends TestCase return provider; } + @SuppressWarnings("unchecked") + public void testCreatePasswordCredentialManagingAuthenticationProviderFailsWhenAnotherOneAlreadyExist() + { + Broker broker = mock(Broker.class); + PasswordCredentialManagingAuthenticationProvider anotherProvider = mock(PasswordCredentialManagingAuthenticationProvider.class); + when(broker.getAuthenticationProviders()).thenReturn(Collections.<AuthenticationProvider>singleton(anotherProvider)); + + QpidServiceLoader<AuthenticationManagerFactory> loader = mock(QpidServiceLoader.class); + AuthenticationManagerFactory managerFactory = mock(AuthenticationManagerFactory.class); + when(managerFactory.createInstance(any(Map.class))).thenReturn(mock(PrincipalDatabaseAuthenticationManager.class)); + when(loader.atLeastOneInstanceOf(AuthenticationManagerFactory.class)).thenReturn(Collections.singleton(managerFactory)); + + AuthenticationProviderFactory providerFactory = new AuthenticationProviderFactory(loader); + try + { + providerFactory.create(UUID.randomUUID(), broker, new HashMap<String, Object>()); + fail("Creation of anaother PasswordCredentialManagingAuthenticationProvider should fail"); + } + catch (IllegalConfigurationException e) + { + // pass + } + } + + @SuppressWarnings("unchecked") + public void testCreateNonPasswordCredentialManagingAuthenticationProviderWhenAnotherOneAlreadyExist() + { + Broker broker = mock(Broker.class); + AuthenticationProvider anotherProvider = mock(AuthenticationProvider.class); + when(broker.getAuthenticationProviders()).thenReturn(Collections.singleton(anotherProvider)); + + QpidServiceLoader<AuthenticationManagerFactory> loader = mock(QpidServiceLoader.class); + AuthenticationManagerFactory managerFactory = mock(AuthenticationManagerFactory.class); + when(managerFactory.createInstance(any(Map.class))).thenReturn(mock(AuthenticationManager.class)); + when(loader.atLeastOneInstanceOf(AuthenticationManagerFactory.class)).thenReturn(Collections.singleton(managerFactory)); + + AuthenticationProviderFactory providerFactory = new AuthenticationProviderFactory(loader); + UUID id = UUID.randomUUID(); + AuthenticationProvider provider = providerFactory.create(id, broker, new HashMap<String, Object>()); + + assertNotNull("Provider is not created", provider); + assertEquals("Unexpected ID", id, provider.getId()); + } } diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/Base64MD5PasswordFilePrincipalDatabaseTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/Base64MD5PasswordFilePrincipalDatabaseTest.java index 7b244e219e..4102a1fc68 100644 --- a/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/Base64MD5PasswordFilePrincipalDatabaseTest.java +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/Base64MD5PasswordFilePrincipalDatabaseTest.java @@ -84,7 +84,7 @@ public class Base64MD5PasswordFilePrincipalDatabaseTest extends TestCase _database = new Base64MD5PasswordFilePrincipalDatabase(); _pwdFile = File.createTempFile(this.getClass().getName(), "pwd"); _pwdFile.deleteOnExit(); - _database.setPasswordFile(_pwdFile.getAbsolutePath()); + _database.open(_pwdFile); _testPwdFiles.clear(); } @@ -153,7 +153,7 @@ public class Base64MD5PasswordFilePrincipalDatabaseTest extends TestCase { try { - _database.setPasswordFile(file.toString()); + _database.open(file); } catch (IOException e) { @@ -392,7 +392,7 @@ public class Base64MD5PasswordFilePrincipalDatabaseTest extends TestCase { try { - _database.setPasswordFile("DoesntExist"); + _database.open(new File("DoesntExist")); } catch (FileNotFoundException fnfe) { @@ -414,7 +414,7 @@ public class Base64MD5PasswordFilePrincipalDatabaseTest extends TestCase try { - _database.setPasswordFile(testFile.toString()); + _database.open(testFile); } catch (FileNotFoundException fnfe) { diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/PlainPasswordFilePrincipalDatabaseTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/PlainPasswordFilePrincipalDatabaseTest.java index 8e62324f7d..eecbcdf38d 100644 --- a/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/PlainPasswordFilePrincipalDatabaseTest.java +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/PlainPasswordFilePrincipalDatabaseTest.java @@ -284,7 +284,7 @@ public class PlainPasswordFilePrincipalDatabaseTest extends TestCase { try { - _database.setPasswordFile("DoesntExist"); + _database.open(new File("DoesntExist")); } catch (FileNotFoundException fnfe) { @@ -306,7 +306,7 @@ public class PlainPasswordFilePrincipalDatabaseTest extends TestCase try { - _database.setPasswordFile(testFile.toString()); + _database.open(testFile); } catch (FileNotFoundException fnfe) { @@ -403,7 +403,7 @@ public class PlainPasswordFilePrincipalDatabaseTest extends TestCase { try { - _database.setPasswordFile(file.toString()); + _database.open(file); } catch (IOException e) { diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/PropertiesPrincipalDatabase.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/PropertiesPrincipalDatabase.java index f670d80ae8..c41b9bf081 100644 --- a/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/PropertiesPrincipalDatabase.java +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/PropertiesPrincipalDatabase.java @@ -27,6 +27,8 @@ import org.apache.qpid.server.security.auth.sasl.plain.PlainInitialiser; import javax.security.auth.callback.PasswordCallback; import javax.security.auth.login.AccountNotFoundException; + +import java.io.File; import java.io.IOException; import java.security.Principal; import java.util.HashMap; @@ -151,7 +153,7 @@ public class PropertiesPrincipalDatabase implements PrincipalDatabase } @Override - public void setPasswordFile(String passwordFile) + public void open(File passwordFile) { throw new UnsupportedOperationException(); } diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/manager/PrincipalDatabaseAuthenticationManagerTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/manager/PrincipalDatabaseAuthenticationManagerTest.java index 1ae667804a..8025907e41 100644 --- a/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/manager/PrincipalDatabaseAuthenticationManagerTest.java +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/manager/PrincipalDatabaseAuthenticationManagerTest.java @@ -24,6 +24,7 @@ import static org.apache.qpid.server.security.auth.AuthenticatedPrincipalTestHel import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import java.io.File; import java.security.Provider; import java.security.Security; import java.util.Collections; @@ -54,13 +55,29 @@ public class PrincipalDatabaseAuthenticationManagerTest extends QpidTestCase private AuthenticationManager _manager = null; // Class under test private PrincipalDatabase _principalDatabase; + private String _passwordFileLocation; + + @Override + public void setUp() throws Exception + { + super.setUp(); + _passwordFileLocation = TMP_FOLDER + File.separator + PrincipalDatabaseAuthenticationManagerTest.class.getSimpleName() + "-" + getName(); + deletePasswordFileIfExists(); + } @Override public void tearDown() throws Exception { - if (_manager != null) + try + { + if (_manager != null) + { + _manager.close(); + } + } + finally { - _manager.close(); + deletePasswordFileIfExists(); } super.tearDown(); } @@ -74,7 +91,7 @@ public class PrincipalDatabaseAuthenticationManagerTest extends QpidTestCase when(_principalDatabase.getMechanisms()).thenReturn(_initialisers); - _manager = new PrincipalDatabaseAuthenticationManager(_principalDatabase); + _manager = new PrincipalDatabaseAuthenticationManager(_principalDatabase, _passwordFileLocation); _manager.initialise(); } @@ -104,7 +121,7 @@ public class PrincipalDatabaseAuthenticationManagerTest extends QpidTestCase usernamePasswordInitialiser.initialise(_principalDatabase); - _manager = new PrincipalDatabaseAuthenticationManager(_principalDatabase); + _manager = new PrincipalDatabaseAuthenticationManager(_principalDatabase, null); _manager.initialise(); } @@ -232,6 +249,34 @@ public class PrincipalDatabaseAuthenticationManagerTest extends QpidTestCase _manager = null; } + public void testOnCreate() throws Exception + { + setupMocks(); + + _manager.onCreate(); + assertTrue("Password file was not created", new File(_passwordFileLocation).exists()); + } + + public void testOnDelete() throws Exception + { + setupMocks(); + + _manager.onCreate(); + assertTrue("Password file was not created", new File(_passwordFileLocation).exists()); + + _manager.onDelete(); + assertFalse("Password file was not deleted", new File(_passwordFileLocation).exists()); + } + + private void deletePasswordFileIfExists() + { + File passwordFile = new File(_passwordFileLocation); + if (passwordFile.exists()) + { + passwordFile.delete(); + } + } + /** * Test SASL implementation used to test the authenticate() method. */ diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/CRAMMD5HexServerTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/CRAMMD5HexServerTest.java index 51c2a0a5b8..629e1b4cf5 100644 --- a/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/CRAMMD5HexServerTest.java +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/CRAMMD5HexServerTest.java @@ -186,7 +186,7 @@ public class CRAMMD5HexServerTest extends TestCase Base64MD5PasswordFilePrincipalDatabase db = new Base64MD5PasswordFilePrincipalDatabase(); File file = File.createTempFile("passwd", "db"); file.deleteOnExit(); - db.setPasswordFile(file.getCanonicalPath()); + db.open(file); db.createPrincipal( createTestPrincipal("knownuser"), "guest".toCharArray()); db.createPrincipal( createTestPrincipal("qpid3158user"), "guest2".toCharArray()); return db; diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/TestPrincipalDatabase.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/TestPrincipalDatabase.java index f94d8ddfc3..5e66bc9336 100644 --- a/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/TestPrincipalDatabase.java +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/TestPrincipalDatabase.java @@ -25,6 +25,8 @@ import org.apache.qpid.server.security.auth.database.PrincipalDatabase; import javax.security.auth.callback.PasswordCallback; import javax.security.auth.login.AccountNotFoundException; + +import java.io.File; import java.io.IOException; import java.security.Principal; import java.util.List; @@ -87,7 +89,7 @@ public class TestPrincipalDatabase implements PrincipalDatabase } @Override - public void setPasswordFile(String passwordFile) throws IOException + public void open(File passwordFile) throws IOException { // TODO Auto-generated method stub } diff --git a/qpid/java/client/src/main/java/client.bnd b/qpid/java/client/src/main/java/client.bnd index 0a47b30c72..1d78bee554 100755 --- a/qpid/java/client/src/main/java/client.bnd +++ b/qpid/java/client/src/main/java/client.bnd @@ -17,7 +17,7 @@ # under the License. # -ver: 0.21.0 +ver: 0.23.0 Bundle-SymbolicName: qpid-client Bundle-Version: ${ver} diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/AMQDestination.java b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQDestination.java index 6ca2988186..fc7762e77d 100644 --- a/qpid/java/client/src/main/java/org/apache/qpid/client/AMQDestination.java +++ b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQDestination.java @@ -653,6 +653,17 @@ public abstract class AMQDestination implements Destination, Referenceable { return false; } + if (_subject == null) + { + if (that.getSubject() != null) + { + return false; + } + } + else if (!_subject.equals(that.getSubject())) + { + return false; + } } else { @@ -679,6 +690,10 @@ public abstract class AMQDestination implements Destination, Referenceable if (_destSyntax == DestSyntax.ADDR) { result = 29 * _addressType + _name.hashCode(); + if (_subject != null) + { + result = 29 * result + _subject.hashCode(); + } } else { diff --git a/qpid/java/client/src/test/java/org/apache/qpid/client/AMQDestinationTest.java b/qpid/java/client/src/test/java/org/apache/qpid/client/AMQDestinationTest.java index 1a411c99bc..e034fa5b3f 100644 --- a/qpid/java/client/src/test/java/org/apache/qpid/client/AMQDestinationTest.java +++ b/qpid/java/client/src/test/java/org/apache/qpid/client/AMQDestinationTest.java @@ -27,10 +27,10 @@ public class AMQDestinationTest extends TestCase public void testEqaulsAndHashCodeForAddressBasedDestinations() throws Exception { AMQDestination dest = new AMQQueue("ADDR:Foo; {node :{type:queue}}"); - AMQDestination dest1 = new AMQQueue("ADDR:Foo; {node :{type:topic}}"); + AMQDestination dest1 = new AMQTopic("ADDR:Foo; {node :{type:topic}}"); AMQDestination dest2 = new AMQQueue( "ADDR:Foo; {create:always,node :{type:queue}}"); - String bUrl = "direct://amq.direct/test-route/Foo?routingkey='Foo'"; + String bUrl = "BURL:direct://amq.direct/test-route/Foo?routingkey='Foo'"; AMQDestination dest3 = new AMQQueue(bUrl); assertTrue(dest.equals(dest)); @@ -42,5 +42,9 @@ public class AMQDestinationTest extends TestCase assertTrue(dest.hashCode() != dest1.hashCode()); assertTrue(dest.hashCode() == dest2.hashCode()); assertTrue(dest.hashCode() != dest3.hashCode()); + + AMQDestination dest4 = new AMQQueue("ADDR:Foo/Bar; {node :{type:queue}}"); + AMQDestination dest5 = new AMQQueue("ADDR:Foo/Bar2; {node :{type:queue}}"); + assertTrue(dest4.hashCode() != dest5.hashCode()); } } diff --git a/qpid/java/common.xml b/qpid/java/common.xml index ce5693fd28..85cc7fc30b 100644 --- a/qpid/java/common.xml +++ b/qpid/java/common.xml @@ -24,9 +24,9 @@ <property name="project.name" value="qpid"/> <!-- Version used for standard build output --> - <property name="project.version" value="0.21"/> + <property name="project.version" value="0.23"/> <!-- The release version used for maven output. SNAPSHOT added via maven.version.suffix --> - <property name="project.version.maven" value="0.22"/> + <property name="project.version.maven" value="0.24"/> <property name="project.url" value="http://qpid.apache.org"/> <property name="project.groupid" value="org.apache.qpid"/> <property name="project.namever" value="${project.name}-${project.version}"/> diff --git a/qpid/java/common/src/main/java/common.bnd b/qpid/java/common/src/main/java/common.bnd index 84350fdc75..5635012faa 100755 --- a/qpid/java/common/src/main/java/common.bnd +++ b/qpid/java/common/src/main/java/common.bnd @@ -17,7 +17,7 @@ # under the License.
#
-ver: 0.21.0
+ver: 0.23.0
Bundle-SymbolicName: qpid-common
Bundle-Version: ${ver}
diff --git a/qpid/java/management/common/src/main/java/management-common.bnd b/qpid/java/management/common/src/main/java/management-common.bnd index 5ae9791299..dad6055a13 100644 --- a/qpid/java/management/common/src/main/java/management-common.bnd +++ b/qpid/java/management/common/src/main/java/management-common.bnd @@ -17,7 +17,7 @@ # under the License. # -ver: 0.21.0 +ver: 0.23.0 Bundle-SymbolicName: qpid-management-common Bundle-Version: ${ver} diff --git a/qpid/java/perftests/etc/testdefs/Latency-VaryingNumberOfParticipants.json b/qpid/java/perftests/etc/testdefs/Latency-VaryingNumberOfParticipants.json index c147866591..aefd51dde0 100644 --- a/qpid/java/perftests/etc/testdefs/Latency-VaryingNumberOfParticipants.json +++ b/qpid/java/perftests/etc/testdefs/Latency-VaryingNumberOfParticipants.json @@ -1,7 +1,7 @@ { "_tests":[ { - "_name": "Varying number of participants: 1 consumer - 1 producer - PERSISTENT", + "_name": "Latency of varying number of participants: 1 consumer - 1 producer - PERSISTENT", "_queues":[ { "_name": "direct://amq.direct//latency-varying-consumers?durable='true'", @@ -59,1849 +59,10 @@ } ] }, - { - "_name": "Varying number of participants: 2 consumers - 1 producer - PERSISTENT", - "_queues":[ - { - "_name": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_durable": true - } - ], - "_clients":[ - { - "_name": "producingClient", - "_connections":[ - { - "_name": "connection1", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session1", - "_acknowledgeMode": 1, - "_producers": [ - { - "_name": "Producer1", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_deliveryMode": 2, - "_messageSize": 1024, - "_interval": 1000 - } - ] - } - ] - } - ] - }, - { - "_name": "consumingClient", - "_connections":[ - { - "_name": "connection1", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session2", - "_acknowledgeMode": 1, - "_consumers": [ - { - "_name": "Consumer1", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_evaluateLatency": true - } - ] - } - ] - }, - { - "_name": "connection2", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session3", - "_acknowledgeMode": 1, - "_consumers": [ - { - "_name": "Consumer2", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_evaluateLatency": true - } - ] - } - ] - } - ] - } - ] - }, - { - "_name": "Varying number of participants: 5 consumers - 1 producer - PERSISTENT", - "_queues":[ - { - "_name": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_durable": true - } - ], - "_clients":[ - { - "_name": "producingClient", - "_connections":[ - { - "_name": "connection0", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session0", - "_acknowledgeMode": 1, - "_producers": [ - { - "_name": "Producer1", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_interval": 1000, - "_deliveryMode": 2, - "_messageSize": 1024 - } - ] - } - ] - } - ] - }, - { - "_name": "consumingClient", - "_connections":[ - { - "_name": "connection1", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session1", - "_acknowledgeMode": 1, - "_consumers": [ - { - "_name": "Consumer1", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_evaluateLatency": true - } - ] - } - ] - }, - { - "_name": "connection2", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session2", - "_acknowledgeMode": 1, - "_consumers": [ - { - "_name": "Consumer2", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_evaluateLatency": true - } - ] - } - ] - }, - { - "_name": "connection3", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session3", - "_acknowledgeMode": 1, - "_consumers": [ - { - "_name": "Consumer3", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_evaluateLatency": true - } - ] - } - ] - }, - { - "_name": "connection4", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session4", - "_acknowledgeMode": 1, - "_consumers": [ - { - "_name": "Consumer4", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_evaluateLatency": true - } - ] - } - ] - }, - { - "_name": "connection5", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session5", - "_acknowledgeMode": 1, - "_consumers": [ - { - "_name": "Consumer5", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_evaluateLatency": true - } - ] - } - ] - } - ] - } - ] - }, - - - - { - "_name": "Varying number of participants: 10 consumers - 1 producer - PERSISTENT", - "_queues":[ - { - "_name": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_durable": true - } - ], - "_clients":[ - { - "_name": "producingClient", - "_connections":[ - { - "_name": "connection0", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session0", - "_acknowledgeMode": 1, - "_producers": [ - { - "_name": "Producer1", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_interval": 1000, - "_deliveryMode": 2, - "_messageSize": 1024 - } - ] - } - ] - } - ] - }, - { - "_name": "consumingClient", - "_connections":[ - { - "_name": "connection1", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session1", - "_acknowledgeMode": 1, - "_consumers": [ - { - "_name": "Consumer1", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_evaluateLatency": true - } - ] - } - ] - }, - { - "_name": "connection2", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session2", - "_acknowledgeMode": 1, - "_consumers": [ - { - "_name": "Consumer2", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_evaluateLatency": true - } - ] - } - ] - }, - { - "_name": "connection3", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session3", - "_acknowledgeMode": 1, - "_consumers": [ - { - "_name": "Consumer3", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_evaluateLatency": true - } - ] - } - ] - }, - { - "_name": "connection4", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session4", - "_acknowledgeMode": 1, - "_consumers": [ - { - "_name": "Consumer4", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_evaluateLatency": true - } - ] - } - ] - }, - { - "_name": "connection5", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session5", - "_acknowledgeMode": 1, - "_consumers": [ - { - "_name": "Consumer5", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_evaluateLatency": true - } - ] - } - ] - }, - { - "_name": "connection6", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session6", - "_acknowledgeMode": 1, - "_consumers": [ - { - "_name": "Consumer6", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_evaluateLatency": true - } - ] - } - ] - }, - { - "_name": "connection7", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session7", - "_acknowledgeMode": 1, - "_consumers": [ - { - "_name": "Consumer7", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_evaluateLatency": true - } - ] - } - ] - }, - { - "_name": "connection8", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session8", - "_acknowledgeMode": 1, - "_consumers": [ - { - "_name": "Consumer8", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_evaluateLatency": true - } - ] - } - ] - }, - { - "_name": "connection9", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session9", - "_acknowledgeMode": 1, - "_consumers": [ - { - "_name": "Consumer9", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_evaluateLatency": true - } - ] - } - ] - }, - { - "_name": "connection10", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session10", - "_acknowledgeMode": 1, - "_consumers": [ - { - "_name": "Consumer10", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_evaluateLatency": true - } - ] - } - ] - } - ] - } - ] - }, - - - - { - "_name": "Varying number of participants: 1 consumer - 2 producers - PERSISTENT", - "_queues":[ - { - "_name": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_durable": true - } - ], - "_clients":[ - { - "_name": "producingClient", - "_connections":[ - { - "_name": "connection1", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session1", - "_acknowledgeMode": 1, - "_producers": [ - { - "_name": "Producer1", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_interval": 1500, - "_deliveryMode": 2, - "_messageSize": 1024 - } - ] - } - ] - }, - { - "_name": "connection2", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session2", - "_acknowledgeMode": 1, - "_producers": [ - { - "_name": "Producer2", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_interval": 1500, - "_deliveryMode": 2, - "_messageSize": 1024 - } - ] - } - ] - } - ] - }, - { - "_name": "consumingClient", - "_connections":[ - { - "_name": "connection1", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session1", - "_acknowledgeMode": 1, - "_consumers": [ - { - "_name": "Consumer1", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_evaluateLatency": true - } - ] - } - ] - } - ] - } - ] - }, - - - - - { - "_name": "Varying number of participants: 2 consumers - 2 producers - PERSISTENT", - "_queues":[ - { - "_name": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_durable": true - } - ], - "_clients":[ - { - "_name": "producingClient", - "_connections":[ - { - "_name": "connection1", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session1", - "_acknowledgeMode": 1, - "_producers": [ - { - "_name": "Producer1", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_interval": 1500, - "_deliveryMode": 2, - "_messageSize": 1024 - } - ] - } - ] - }, - { - "_name": "connection2", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session2", - "_acknowledgeMode": 1, - "_producers": [ - { - "_name": "Producer2", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_interval": 1500, - "_deliveryMode": 2, - "_messageSize": 1024 - } - ] - } - ] - } - ] - }, - { - "_name": "consumingClient", - "_connections":[ - { - "_name": "connection1", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session2", - "_acknowledgeMode": 1, - "_consumers": [ - { - "_name": "Consumer1", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_evaluateLatency": true - } - ] - } - ] - }, - { - "_name": "connection2", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session3", - "_acknowledgeMode": 1, - "_consumers": [ - { - "_name": "Consumer2", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_evaluateLatency": true - } - ] - } - ] - } - ] - } - ] - }, - - - - - { - "_name": "Varying number of participants: 5 consumers - 2 producers - PERSISTENT", - "_queues":[ - { - "_name": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_durable": true - } - ], - "_clients":[ - { - "_name": "producingClient", - "_connections":[ - { - "_name": "connection1", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session1", - "_acknowledgeMode": 1, - "_producers": [ - { - "_name": "Producer1", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_interval": 1500, - "_deliveryMode": 2, - "_messageSize": 1024 - } - ] - } - ] - }, - { - "_name": "connection2", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session2", - "_acknowledgeMode": 1, - "_producers": [ - { - "_name": "Producer2", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_interval": 1500, - "_deliveryMode": 2, - "_messageSize": 1024 - } - ] - } - ] - } - ] - }, - { - "_name": "consumingClient", - "_connections":[ - { - "_name": "connection1", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session1", - "_acknowledgeMode": 1, - "_consumers": [ - { - "_name": "Consumer1", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_evaluateLatency": true - } - ] - } - ] - }, - { - "_name": "connection2", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session2", - "_acknowledgeMode": 1, - "_consumers": [ - { - "_name": "Consumer2", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_evaluateLatency": true - } - ] - } - ] - }, - { - "_name": "connection3", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session3", - "_acknowledgeMode": 1, - "_consumers": [ - { - "_name": "Consumer3", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_evaluateLatency": true - } - ] - } - ] - }, - { - "_name": "connection4", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session4", - "_acknowledgeMode": 1, - "_consumers": [ - { - "_name": "Consumer4", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_evaluateLatency": true - } - ] - } - ] - }, - { - "_name": "connection5", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session5", - "_acknowledgeMode": 1, - "_consumers": [ - { - "_name": "Consumer5", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_evaluateLatency": true - } - ] - } - ] - } - ] - } - ] - }, - - - - { - "_name": "Varying number of participants: 10 consumers - 2 producers - PERSISTENT", - "_queues":[ - { - "_name": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_durable": true - } - ], - "_clients":[ - { - "_name": "producingClient", - "_connections":[ - { - "_name": "connection1", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session1", - "_acknowledgeMode": 1, - "_producers": [ - { - "_name": "Producer1", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_interval": 1500, - "_deliveryMode": 2, - "_messageSize": 1024 - } - ] - } - ] - }, - { - "_name": "connection2", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session2", - "_acknowledgeMode": 1, - "_producers": [ - { - "_name": "Producer2", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_interval": 1500, - "_deliveryMode": 2, - "_messageSize": 1024 - } - ] - } - ] - } - ] - }, - { - "_name": "consumingClient", - "_connections":[ - { - "_name": "connection1", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session1", - "_acknowledgeMode": 1, - "_consumers": [ - { - "_name": "Consumer1", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_evaluateLatency": true - } - ] - } - ] - }, - { - "_name": "connection2", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session2", - "_acknowledgeMode": 1, - "_consumers": [ - { - "_name": "Consumer2", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_evaluateLatency": true - } - ] - } - ] - }, - { - "_name": "connection3", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session3", - "_acknowledgeMode": 1, - "_consumers": [ - { - "_name": "Consumer3", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_evaluateLatency": true - } - ] - } - ] - }, - { - "_name": "connection4", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session4", - "_acknowledgeMode": 1, - "_consumers": [ - { - "_name": "Consumer4", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_evaluateLatency": true - } - ] - } - ] - }, - { - "_name": "connection5", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session5", - "_acknowledgeMode": 1, - "_consumers": [ - { - "_name": "Consumer5", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_evaluateLatency": true - } - ] - } - ] - }, - { - "_name": "connection6", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session6", - "_acknowledgeMode": 1, - "_consumers": [ - { - "_name": "Consumer6", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_evaluateLatency": true - } - ] - } - ] - }, - { - "_name": "connection7", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session7", - "_acknowledgeMode": 1, - "_consumers": [ - { - "_name": "Consumer7", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_evaluateLatency": true - } - ] - } - ] - }, - { - "_name": "connection8", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session8", - "_acknowledgeMode": 1, - "_consumers": [ - { - "_name": "Consumer8", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_evaluateLatency": true - } - ] - } - ] - }, - { - "_name": "connection9", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session9", - "_acknowledgeMode": 1, - "_consumers": [ - { - "_name": "Consumer9", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_evaluateLatency": true - } - ] - } - ] - }, - { - "_name": "connection10", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session10", - "_acknowledgeMode": 1, - "_consumers": [ - { - "_name": "Consumer10", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_evaluateLatency": true - } - ] - } - ] - } - ] - } - ] - }, - - - - { - "_name": "Varying number of participants: 1 consumer - 5 producers - PERSISTENT", - "_queues":[ - { - "_name": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_durable": true - } - ], - "_clients":[ - { - "_name": "producingClient", - "_connections":[ - { - "_name": "connection1", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session1", - "_acknowledgeMode": 1, - "_producers": [ - { - "_name": "Producer1", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_interval": 2000, - "_deliveryMode": 2, - "_messageSize": 1024 - } - ] - } - ] - }, - { - "_name": "connection2", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session2", - "_acknowledgeMode": 1, - "_producers": [ - { - "_name": "Producer2", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_interval": 2000, - "_deliveryMode": 2, - "_messageSize": 1024 - } - ] - } - ] - }, - { - "_name": "connection3", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session3", - "_acknowledgeMode": 1, - "_producers": [ - { - "_name": "Producer3", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_interval": 2000, - "_deliveryMode": 2, - "_messageSize": 1024 - } - ] - } - ] - }, - { - "_name": "connection4", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session4", - "_acknowledgeMode": 1, - "_producers": [ - { - "_name": "Producer4", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_interval": 2000, - "_deliveryMode": 2, - "_messageSize": 1024 - } - ] - } - ] - }, - { - "_name": "connection5", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session5", - "_acknowledgeMode": 1, - "_producers": [ - { - "_name": "Producer5", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_interval": 2000, - "_deliveryMode": 2, - "_messageSize": 1024 - } - ] - } - ] - } - ] - }, - { - "_name": "consumingClient", - "_connections":[ - { - "_name": "connection1", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session1", - "_acknowledgeMode": 1, - "_consumers": [ - { - "_name": "Consumer1", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_evaluateLatency": true - } - ] - } - ] - } - ] - } - ] - }, - - - - - { - "_name": "Varying number of participants: 2 consumers - 5 producers - PERSISTENT", - "_queues":[ - { - "_name": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_durable": true - } - ], - "_clients":[ - { - "_name": "producingClient", - "_connections":[ - { - "_name": "connection1", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session1", - "_acknowledgeMode": 1, - "_producers": [ - { - "_name": "Producer1", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_interval": 2000, - "_deliveryMode": 2, - "_messageSize": 1024 - } - ] - } - ] - }, - { - "_name": "connection2", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session2", - "_acknowledgeMode": 1, - "_producers": [ - { - "_name": "Producer2", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_interval": 2000, - "_deliveryMode": 2, - "_messageSize": 1024 - } - ] - } - ] - }, - { - "_name": "connection3", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session3", - "_acknowledgeMode": 1, - "_producers": [ - { - "_name": "Producer3", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_interval": 2000, - "_deliveryMode": 2, - "_messageSize": 1024 - } - ] - } - ] - }, - { - "_name": "connection4", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session4", - "_acknowledgeMode": 1, - "_producers": [ - { - "_name": "Producer4", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_interval": 2000, - "_deliveryMode": 2, - "_messageSize": 1024 - } - ] - } - ] - }, - { - "_name": "connection5", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session5", - "_acknowledgeMode": 1, - "_producers": [ - { - "_name": "Producer5", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_interval": 2000, - "_deliveryMode": 2, - "_messageSize": 1024 - } - ] - } - ] - } - ] - }, - { - "_name": "consumingClient", - "_connections":[ - { - "_name": "connection1", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session2", - "_acknowledgeMode": 1, - "_consumers": [ - { - "_name": "Consumer1", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_evaluateLatency": true - } - ] - } - ] - }, - { - "_name": "connection2", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session3", - "_acknowledgeMode": 1, - "_consumers": [ - { - "_name": "Consumer2", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_evaluateLatency": true - } - ] - } - ] - } - ] - } - ] - }, - - - - - - { - "_name": "Varying number of participants: 5 consumers - 5 producers - PERSISTENT", - "_queues":[ - { - "_name": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_durable": true - } - ], - "_clients":[ - { - "_name": "producingClient", - "_connections":[ - { - "_name": "connection1", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session1", - "_acknowledgeMode": 1, - "_producers": [ - { - "_name": "Producer1", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_interval": 2000, - "_deliveryMode": 2, - "_messageSize": 1024 - } - ] - } - ] - }, - { - "_name": "connection2", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session2", - "_acknowledgeMode": 1, - "_producers": [ - { - "_name": "Producer2", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_interval": 2000, - "_deliveryMode": 2, - "_messageSize": 1024 - } - ] - } - ] - }, - { - "_name": "connection3", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session3", - "_acknowledgeMode": 1, - "_producers": [ - { - "_name": "Producer3", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_interval": 2000, - "_deliveryMode": 2, - "_messageSize": 1024 - } - ] - } - ] - }, - { - "_name": "connection4", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session4", - "_acknowledgeMode": 1, - "_producers": [ - { - "_name": "Producer4", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_interval": 2000, - "_deliveryMode": 2, - "_messageSize": 1024 - } - ] - } - ] - }, - { - "_name": "connection5", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session5", - "_acknowledgeMode": 1, - "_producers": [ - { - "_name": "Producer5", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_interval": 2000, - "_deliveryMode": 2, - "_messageSize": 1024 - } - ] - } - ] - } - ] - }, - { - "_name": "consumingClient", - "_connections":[ - { - "_name": "connection1", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session1", - "_acknowledgeMode": 1, - "_consumers": [ - { - "_name": "Consumer1", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_evaluateLatency": true - } - ] - } - ] - }, - { - "_name": "connection2", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session2", - "_acknowledgeMode": 1, - "_consumers": [ - { - "_name": "Consumer2", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_evaluateLatency": true - } - ] - } - ] - }, - { - "_name": "connection3", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session3", - "_acknowledgeMode": 1, - "_consumers": [ - { - "_name": "Consumer3", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_evaluateLatency": true - } - ] - } - ] - }, - { - "_name": "connection4", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session4", - "_acknowledgeMode": 1, - "_consumers": [ - { - "_name": "Consumer4", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_evaluateLatency": true - } - ] - } - ] - }, - { - "_name": "connection5", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session5", - "_acknowledgeMode": 1, - "_consumers": [ - { - "_name": "Consumer5", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_evaluateLatency": true - } - ] - } - ] - } - ] - } - ] - }, - - - - { - "_name": "Varying number of participants: 10 consumers - 5 producers - PERSISTENT", - "_queues":[ - { - "_name": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_durable": true - } - ], - "_clients":[ - { - "_name": "producingClient", - "_connections":[ - { - "_name": "connection1", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session1", - "_acknowledgeMode": 1, - "_producers": [ - { - "_name": "Producer1", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_interval": 2000, - "_deliveryMode": 2, - "_messageSize": 1024 - } - ] - } - ] - }, - { - "_name": "connection2", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session2", - "_acknowledgeMode": 1, - "_producers": [ - { - "_name": "Producer2", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_interval": 2000, - "_deliveryMode": 2, - "_messageSize": 1024 - } - ] - } - ] - }, - { - "_name": "connection3", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session3", - "_acknowledgeMode": 1, - "_producers": [ - { - "_name": "Producer3", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_interval": 2000, - "_deliveryMode": 2, - "_messageSize": 1024 - } - ] - } - ] - }, - { - "_name": "connection4", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session4", - "_acknowledgeMode": 1, - "_producers": [ - { - "_name": "Producer4", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_interval": 2000, - "_deliveryMode": 2, - "_messageSize": 1024 - } - ] - } - ] - }, - { - "_name": "connection5", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session5", - "_acknowledgeMode": 1, - "_producers": [ - { - "_name": "Producer5", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_interval": 2000, - "_deliveryMode": 2, - "_messageSize": 1024 - } - ] - } - ] - } - ] - }, - { - "_name": "consumingClient", - "_connections":[ - { - "_name": "connection1", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session1", - "_acknowledgeMode": 1, - "_consumers": [ - { - "_name": "Consumer1", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_evaluateLatency": true - } - ] - } - ] - }, - { - "_name": "connection2", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session2", - "_acknowledgeMode": 1, - "_consumers": [ - { - "_name": "Consumer2", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_evaluateLatency": true - } - ] - } - ] - }, - { - "_name": "connection3", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session3", - "_acknowledgeMode": 1, - "_consumers": [ - { - "_name": "Consumer3", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_evaluateLatency": true - } - ] - } - ] - }, - { - "_name": "connection4", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session4", - "_acknowledgeMode": 1, - "_consumers": [ - { - "_name": "Consumer4", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_evaluateLatency": true - } - ] - } - ] - }, - { - "_name": "connection5", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session5", - "_acknowledgeMode": 1, - "_consumers": [ - { - "_name": "Consumer5", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_evaluateLatency": true - } - ] - } - ] - }, - { - "_name": "connection6", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session6", - "_acknowledgeMode": 1, - "_consumers": [ - { - "_name": "Consumer6", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_evaluateLatency": true - } - ] - } - ] - }, - { - "_name": "connection7", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session7", - "_acknowledgeMode": 1, - "_consumers": [ - { - "_name": "Consumer7", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_evaluateLatency": true - } - ] - } - ] - }, - { - "_name": "connection8", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session8", - "_acknowledgeMode": 1, - "_consumers": [ - { - "_name": "Consumer8", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_evaluateLatency": true - } - ] - } - ] - }, - { - "_name": "connection9", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session9", - "_acknowledgeMode": 1, - "_consumers": [ - { - "_name": "Consumer9", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_evaluateLatency": true - } - ] - } - ] - }, - { - "_name": "connection10", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session10", - "_acknowledgeMode": 1, - "_consumers": [ - { - "_name": "Consumer10", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_evaluateLatency": true - } - ] - } - ] - } - ] - } - ] - }, - - { - "_name": "Varying number of participants: 1 consumer - 10 producers - PERSISTENT", + "_name": "Latency of varying number of participants: 1 consumer - 10 producers - PERSISTENT", "_queues":[ { "_name": "direct://amq.direct//latency-varying-consumers?durable='true'", @@ -2140,588 +301,8 @@ ] }, - - - - { - "_name": "Varying number of participants: 2 consumers - 10 producers - PERSISTENT", - "_queues":[ - { - "_name": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_durable": true - } - ], - "_clients":[ - { - "_name": "producingClient", - "_connections":[ - { - "_name": "connection1", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session1", - "_acknowledgeMode": 1, - "_producers": [ - { - "_name": "Producer1", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_interval": 3000, - "_deliveryMode": 2, - "_messageSize": 1024 - } - ] - } - ] - }, - { - "_name": "connection2", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session2", - "_acknowledgeMode": 1, - "_producers": [ - { - "_name": "Producer2", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_interval": 3000, - "_deliveryMode": 2, - "_messageSize": 1024 - } - ] - } - ] - }, - { - "_name": "connection3", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session3", - "_acknowledgeMode": 1, - "_producers": [ - { - "_name": "Producer3", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_interval": 3000, - "_deliveryMode": 2, - "_messageSize": 1024 - } - ] - } - ] - }, - { - "_name": "connection4", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session4", - "_acknowledgeMode": 1, - "_producers": [ - { - "_name": "Producer4", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_interval": 3000, - "_deliveryMode": 2, - "_messageSize": 1024 - } - ] - } - ] - }, - { - "_name": "connection5", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session5", - "_acknowledgeMode": 1, - "_producers": [ - { - "_name": "Producer5", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_interval": 3000, - "_deliveryMode": 2, - "_messageSize": 1024 - } - ] - } - ] - }, - { - "_name": "connection6", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session6", - "_acknowledgeMode": 1, - "_producers": [ - { - "_name": "Producer6", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_interval": 3000, - "_deliveryMode": 2, - "_messageSize": 1024 - } - ] - } - ] - }, - { - "_name": "connection7", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session7", - "_acknowledgeMode": 1, - "_producers": [ - { - "_name": "Producer7", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_interval": 3000, - "_deliveryMode": 2, - "_messageSize": 1024 - } - ] - } - ] - }, - { - "_name": "connection8", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session8", - "_acknowledgeMode": 1, - "_producers": [ - { - "_name": "Producer8", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_interval": 3000, - "_deliveryMode": 2, - "_messageSize": 1024 - } - ] - } - ] - }, - { - "_name": "connection9", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session9", - "_acknowledgeMode": 1, - "_producers": [ - { - "_name": "Producer9", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_interval": 3000, - "_deliveryMode": 2, - "_messageSize": 1024 - } - ] - } - ] - }, - { - "_name": "connection10", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session10", - "_acknowledgeMode": 1, - "_producers": [ - { - "_name": "Producer10", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_interval": 3000, - "_deliveryMode": 2, - "_messageSize": 1024 - } - ] - } - ] - } - ] - }, - { - "_name": "consumingClient", - "_connections":[ - { - "_name": "connection1", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session2", - "_acknowledgeMode": 1, - "_consumers": [ - { - "_name": "Consumer1", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_evaluateLatency": true - } - ] - } - ] - }, - { - "_name": "connection2", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session3", - "_acknowledgeMode": 1, - "_consumers": [ - { - "_name": "Consumer2", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_evaluateLatency": true - } - ] - } - ] - } - ] - } - ] - }, - - - - - - { - "_name": "Varying number of participants: 5 consumers - 10 producers - PERSISTENT", - "_queues":[ - { - "_name": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_durable": true - } - ], - "_clients":[ - { - "_name": "producingClient", - "_connections":[ - { - "_name": "connection1", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session1", - "_acknowledgeMode": 1, - "_producers": [ - { - "_name": "Producer1", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_interval": 3000, - "_deliveryMode": 2, - "_messageSize": 1024 - } - ] - } - ] - }, - { - "_name": "connection2", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session2", - "_acknowledgeMode": 1, - "_producers": [ - { - "_name": "Producer2", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_interval": 3000, - "_deliveryMode": 2, - "_messageSize": 1024 - } - ] - } - ] - }, - { - "_name": "connection3", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session3", - "_acknowledgeMode": 1, - "_producers": [ - { - "_name": "Producer3", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_interval": 3000, - "_deliveryMode": 2, - "_messageSize": 1024 - } - ] - } - ] - }, - { - "_name": "connection4", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session4", - "_acknowledgeMode": 1, - "_producers": [ - { - "_name": "Producer4", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_interval": 3000, - "_deliveryMode": 2, - "_messageSize": 1024 - } - ] - } - ] - }, - { - "_name": "connection5", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session5", - "_acknowledgeMode": 1, - "_producers": [ - { - "_name": "Producer5", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_interval": 3000, - "_deliveryMode": 2, - "_messageSize": 1024 - } - ] - } - ] - }, - { - "_name": "connection6", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session6", - "_acknowledgeMode": 1, - "_producers": [ - { - "_name": "Producer6", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_interval": 3000, - "_deliveryMode": 2, - "_messageSize": 1024 - } - ] - } - ] - }, - { - "_name": "connection7", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session7", - "_acknowledgeMode": 1, - "_producers": [ - { - "_name": "Producer7", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_interval": 3000, - "_deliveryMode": 2, - "_messageSize": 1024 - } - ] - } - ] - }, - { - "_name": "connection8", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session8", - "_acknowledgeMode": 1, - "_producers": [ - { - "_name": "Producer8", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_interval": 3000, - "_deliveryMode": 2, - "_messageSize": 1024 - } - ] - } - ] - }, - { - "_name": "connection9", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session9", - "_acknowledgeMode": 1, - "_producers": [ - { - "_name": "Producer9", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_interval": 3000, - "_deliveryMode": 2, - "_messageSize": 1024 - } - ] - } - ] - }, - { - "_name": "connection10", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session10", - "_acknowledgeMode": 1, - "_producers": [ - { - "_name": "Producer10", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_interval": 3000, - "_deliveryMode": 2, - "_messageSize": 1024 - } - ] - } - ] - } - ] - }, - { - "_name": "consumingClient", - "_connections":[ - { - "_name": "connection1", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session1", - "_acknowledgeMode": 1, - "_consumers": [ - { - "_name": "Consumer1", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_evaluateLatency": true - } - ] - } - ] - }, - { - "_name": "connection2", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session2", - "_acknowledgeMode": 1, - "_consumers": [ - { - "_name": "Consumer2", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_evaluateLatency": true - } - ] - } - ] - }, - { - "_name": "connection3", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session3", - "_acknowledgeMode": 1, - "_consumers": [ - { - "_name": "Consumer3", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_evaluateLatency": true - } - ] - } - ] - }, - { - "_name": "connection4", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session4", - "_acknowledgeMode": 1, - "_consumers": [ - { - "_name": "Consumer4", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_evaluateLatency": true - } - ] - } - ] - }, - { - "_name": "connection5", - "_factory": "connectionfactory", - "_sessions": [ - { - "_sessionName": "session5", - "_acknowledgeMode": 1, - "_consumers": [ - { - "_name": "Consumer5", - "_destinationName": "direct://amq.direct//latency-varying-consumers?durable='true'", - "_maximumDuration": 60000, - "_evaluateLatency": true - } - ] - } - ] - } - ] - } - ] - }, - - - - { - "_name": "Varying number of participants: 10 consumers - 10 producers - PERSISTENT", + "_name": "Latency of varying number of participants: 10 consumers - 10 producers - PERSISTENT", "_queues":[ { "_name": "direct://amq.direct//latency-varying-consumers?durable='true'", diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/AuthenticationProviderRestTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/AuthenticationProviderRestTest.java index 078a1bb483..4ba2069dfd 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/AuthenticationProviderRestTest.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/AuthenticationProviderRestTest.java @@ -41,14 +41,21 @@ public class AuthenticationProviderRestTest extends QpidRestTestCase { List<Map<String, Object>> providerDetails = getRestTestHelper().getJsonAsList("/rest/authenticationprovider"); assertNotNull("Providers details cannot be null", providerDetails); - assertEquals("Unexpected number of providers", 1, providerDetails.size()); + assertEquals("Unexpected number of providers", 2, providerDetails.size()); for (Map<String, Object> provider : providerDetails) { - assertProvider(true, PlainPasswordFileAuthenticationManagerFactory.PROVIDER_TYPE, provider); + boolean managesPrincipals = true; + String type = PlainPasswordFileAuthenticationManagerFactory.PROVIDER_TYPE; + if (ANONYMOUS_AUTHENTICATION_PROVIDER.equals(provider.get(AuthenticationProvider.NAME))) + { + type = AnonymousAuthenticationManagerFactory.PROVIDER_TYPE; + managesPrincipals = false; + } + assertProvider(managesPrincipals, type , provider); Map<String, Object> data = getRestTestHelper().getJsonAsSingletonList("/rest/authenticationprovider/" + provider.get(AuthenticationProvider.NAME)); assertNotNull("Cannot load data for " + provider.get(AuthenticationProvider.NAME), data); - assertProvider(true, PlainPasswordFileAuthenticationManagerFactory.PROVIDER_TYPE, data); + assertProvider(managesPrincipals, type, data); } } diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/BrokerRestTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/BrokerRestTest.java index 63691e9915..fe4115b4c0 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/BrokerRestTest.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/BrokerRestTest.java @@ -20,8 +20,10 @@ */ package org.apache.qpid.systest.rest; +import java.io.File; import java.util.Arrays; import java.util.Collection; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -32,7 +34,10 @@ import org.apache.qpid.server.model.LifetimePolicy; import org.apache.qpid.server.model.Port; import org.apache.qpid.server.model.State; import org.apache.qpid.server.model.VirtualHost; +import org.apache.qpid.server.model.adapter.BrokerAdapter; +import org.apache.qpid.test.utils.QpidTestCase; import org.apache.qpid.test.utils.TestBrokerConfiguration; +import org.apache.qpid.test.utils.TestSSLConstants; public class BrokerRestTest extends QpidRestTestCase { @@ -86,6 +91,107 @@ public class BrokerRestTest extends QpidRestTestCase new HashSet<String>(port2Protocols)); } + public void testPutToUpdateWithValidAttributeValues() throws Exception + { + Map<String, Object> brokerAttributes = getValidBrokerAttributes(); + + int response = getRestTestHelper().submitRequest("/rest/broker", "PUT", brokerAttributes); + assertEquals("Unexpected update response", 200, response); + + restartBroker(); + Map<String, Object> brokerDetails = getRestTestHelper().getJsonAsSingletonList("/rest/broker"); + assertBrokerAttributes(brokerAttributes, brokerDetails); + } + + public void testPutToUpdateWithInvalidAttributeValues() throws Exception + { + Map<String, Object> invalidAttributes = new HashMap<String, Object>(); + invalidAttributes.put(Broker.DEFAULT_AUTHENTICATION_PROVIDER, "non-existing-provider"); + invalidAttributes.put(Broker.DEFAULT_VIRTUAL_HOST, "non-existing-host"); + invalidAttributes.put(Broker.ALERT_THRESHOLD_MESSAGE_AGE, -1000); + invalidAttributes.put(Broker.ALERT_THRESHOLD_MESSAGE_COUNT, -2000); + invalidAttributes.put(Broker.ALERT_THRESHOLD_QUEUE_DEPTH, -3000); + invalidAttributes.put(Broker.ALERT_THRESHOLD_MESSAGE_SIZE, -4000); + invalidAttributes.put(Broker.ALERT_REPEAT_GAP, -5000); + invalidAttributes.put(Broker.FLOW_CONTROL_SIZE_BYTES, -7000); + invalidAttributes.put(Broker.FLOW_CONTROL_RESUME_SIZE_BYTES, -16000); + invalidAttributes.put(Broker.MAXIMUM_DELIVERY_ATTEMPTS, -8); + invalidAttributes.put(Broker.HOUSEKEEPING_CHECK_PERIOD, -90000); + invalidAttributes.put(Broker.SESSION_COUNT_LIMIT, -10); + invalidAttributes.put(Broker.HEART_BEAT_DELAY, -11000); + invalidAttributes.put(Broker.STATISTICS_REPORTING_PERIOD, -12000); + invalidAttributes.put(Broker.ACL_FILE, QpidTestCase.QPID_HOME + File.separator + "etc" + File.separator + "non-existing-acl.acl"); + invalidAttributes.put(Broker.KEY_STORE_PATH, QpidTestCase.QPID_HOME + File.separator + "etc" + File.separator + "non-existing-keystore.jks"); + invalidAttributes.put(Broker.KEY_STORE_PASSWORD, "password1"); + invalidAttributes.put(Broker.KEY_STORE_CERT_ALIAS, "java-broker1"); + invalidAttributes.put(Broker.TRUST_STORE_PATH, QpidTestCase.QPID_HOME + File.separator + "etc" + File.separator + "non-existing-truststore.jks"); + invalidAttributes.put(Broker.TRUST_STORE_PASSWORD, "password2"); + invalidAttributes.put(Broker.PEER_STORE_PATH, QpidTestCase.QPID_HOME + File.separator + "etc" + File.separator + "non-existing-peerstore.jks"); + invalidAttributes.put(Broker.PEER_STORE_PASSWORD, "password3"); + invalidAttributes.put(Broker.GROUP_FILE, QpidTestCase.QPID_HOME + File.separator + "etc" + File.separator + "groups-non-existing"); + + for (Map.Entry<String, Object> entry : invalidAttributes.entrySet()) + { + Map<String, Object> brokerAttributes = getValidBrokerAttributes(); + brokerAttributes.put(entry.getKey(), entry.getValue()); + int response = getRestTestHelper().submitRequest("/rest/broker", "PUT", brokerAttributes); + assertEquals("Unexpected update response for invalid attribute " + entry.getKey() + "=" + entry.getValue(), 409, response); + } + + // a special case when FLOW_CONTROL_RESUME_SIZE_BYTES > FLOW_CONTROL_SIZE_BYTES + Map<String, Object> brokerAttributes = getValidBrokerAttributes(); + brokerAttributes.put(Broker.FLOW_CONTROL_SIZE_BYTES, 1000); + brokerAttributes.put(Broker.FLOW_CONTROL_RESUME_SIZE_BYTES, 2000); + int response = getRestTestHelper().submitRequest("/rest/broker", "PUT", brokerAttributes); + assertEquals("Unexpected update response for flow resume size > flow size", 409, response); + } + + private Map<String, Object> getValidBrokerAttributes() + { + Map<String, Object> brokerAttributes = new HashMap<String, Object>(); + brokerAttributes.put(Broker.DEFAULT_AUTHENTICATION_PROVIDER, ANONYMOUS_AUTHENTICATION_PROVIDER); + brokerAttributes.put(Broker.DEFAULT_VIRTUAL_HOST, TEST3_VIRTUALHOST); + brokerAttributes.put(Broker.ALERT_THRESHOLD_MESSAGE_AGE, 1000); + brokerAttributes.put(Broker.ALERT_THRESHOLD_MESSAGE_COUNT, 2000); + brokerAttributes.put(Broker.ALERT_THRESHOLD_QUEUE_DEPTH, 3000); + brokerAttributes.put(Broker.ALERT_THRESHOLD_MESSAGE_SIZE, 4000); + brokerAttributes.put(Broker.ALERT_REPEAT_GAP, 5000); + brokerAttributes.put(Broker.FLOW_CONTROL_SIZE_BYTES, 7000); + brokerAttributes.put(Broker.FLOW_CONTROL_RESUME_SIZE_BYTES, 6000); + brokerAttributes.put(Broker.MAXIMUM_DELIVERY_ATTEMPTS, 8); + brokerAttributes.put(Broker.DEAD_LETTER_QUEUE_ENABLED, true); + brokerAttributes.put(Broker.HOUSEKEEPING_CHECK_PERIOD, 90000); + brokerAttributes.put(Broker.SESSION_COUNT_LIMIT, 10); + brokerAttributes.put(Broker.HEART_BEAT_DELAY, 11000); + brokerAttributes.put(Broker.STATISTICS_REPORTING_PERIOD, 12000); + brokerAttributes.put(Broker.STATISTICS_REPORTING_RESET_ENABLED, true); + brokerAttributes.put(Broker.ACL_FILE, QpidTestCase.QPID_HOME + File.separator + "etc" + File.separator + "broker_example.acl"); + brokerAttributes.put(Broker.KEY_STORE_PATH, TestSSLConstants.BROKER_KEYSTORE); + brokerAttributes.put(Broker.KEY_STORE_PASSWORD, TestSSLConstants.BROKER_KEYSTORE_PASSWORD); + brokerAttributes.put(Broker.KEY_STORE_CERT_ALIAS, "java-broker"); + brokerAttributes.put(Broker.TRUST_STORE_PATH, TestSSLConstants.TRUSTSTORE); + brokerAttributes.put(Broker.TRUST_STORE_PASSWORD, TestSSLConstants.TRUSTSTORE_PASSWORD); + brokerAttributes.put(Broker.PEER_STORE_PATH, TestSSLConstants.TRUSTSTORE); + brokerAttributes.put(Broker.PEER_STORE_PASSWORD, TestSSLConstants.TRUSTSTORE_PASSWORD); + brokerAttributes.put(Broker.GROUP_FILE, QpidTestCase.QPID_HOME + File.separator + "etc" + File.separator + "groups"); + return brokerAttributes; + } + + private void assertBrokerAttributes(Map<String, Object> expectedAttributes, Map<String, Object> actualAttributes) + { + for (Map.Entry<String, Object> entry : expectedAttributes.entrySet()) + { + String attributeName = entry.getKey(); + Object attributeValue = entry.getValue(); + if (attributeName.equals(Broker.KEY_STORE_PASSWORD) || attributeName.equals(Broker.TRUST_STORE_PASSWORD) || attributeName.equals(Broker.PEER_STORE_PASSWORD)) + { + attributeValue = "********"; + } + Object currentValue = actualAttributes.get(attributeName); + assertEquals("Unexpected attribute " + attributeName + " value:", attributeValue, currentValue); + } + } + protected void assertBrokerAttributes(Map<String, Object> brokerDetails) { Asserts.assertAttributesPresent(brokerDetails, Broker.AVAILABLE_ATTRIBUTES, diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/QpidRestTestCase.java b/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/QpidRestTestCase.java index e2b73aa2b5..30d5b195f1 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/QpidRestTestCase.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/QpidRestTestCase.java @@ -21,14 +21,21 @@ package org.apache.qpid.systest.rest; import java.io.IOException; +import java.util.HashMap; +import java.util.Map; import org.apache.commons.configuration.ConfigurationException; +import org.apache.qpid.server.model.AuthenticationProvider; import org.apache.qpid.server.model.Port; +import org.apache.qpid.server.plugin.AuthenticationManagerFactory; +import org.apache.qpid.server.security.auth.manager.AnonymousAuthenticationManagerFactory; +import org.apache.qpid.server.security.auth.manager.ExternalAuthenticationManagerFactory; import org.apache.qpid.test.utils.TestBrokerConfiguration; import org.apache.qpid.test.utils.QpidBrokerTestCase; public class QpidRestTestCase extends QpidBrokerTestCase { + public static final String ANONYMOUS_AUTHENTICATION_PROVIDER = "testAnonymous"; public static final String TEST1_VIRTUALHOST = "test"; public static final String TEST2_VIRTUALHOST = "test2"; public static final String TEST3_VIRTUALHOST = "test3"; @@ -77,6 +84,11 @@ public class QpidRestTestCase extends QpidBrokerTestCase config.setObjectAttribute(TestBrokerConfiguration.ENTRY_NAME_HTTP_PORT, Port.PORT, _restTestHelper.getHttpPort()); config.removeObjectConfiguration(TestBrokerConfiguration.ENTRY_NAME_JMX_PORT); config.removeObjectConfiguration(TestBrokerConfiguration.ENTRY_NAME_RMI_PORT); + + Map<String, Object> anonymousProviderAttributes = new HashMap<String, Object>(); + anonymousProviderAttributes.put(AuthenticationProvider.TYPE, AnonymousAuthenticationManagerFactory.PROVIDER_TYPE); + anonymousProviderAttributes.put(AuthenticationProvider.NAME, ANONYMOUS_AUTHENTICATION_PROVIDER); + config.addAuthenticationProviderConfiguration(anonymousProviderAttributes); } public RestTestHelper getRestTestHelper() diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/StructureRestTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/StructureRestTest.java index f5e326f90b..5593ad0b42 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/StructureRestTest.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/StructureRestTest.java @@ -45,7 +45,7 @@ public class StructureRestTest extends QpidRestTestCase @SuppressWarnings("unchecked") List<Map<String, Object>> providers = (List<Map<String, Object>>) structure.get("authenticationproviders"); - assertEquals("Unexpected number of authentication providers", 1, providers.size()); + assertEquals("Unexpected number of authentication providers", 2, providers.size()); for (String hostName : EXPECTED_VIRTUALHOSTS) { diff --git a/qpid/packaging/windows/INSTALL_NOTES.html b/qpid/packaging/windows/INSTALL_NOTES.html index 54f427ed79..66a24253a2 100644 --- a/qpid/packaging/windows/INSTALL_NOTES.html +++ b/qpid/packaging/windows/INSTALL_NOTES.html @@ -1,11 +1,11 @@ <html>
<head>
-<title>Apache Qpid C++ 0.21 Installation Notes</title>
+<title>Apache Qpid C++ 0.23 Installation Notes</title>
</head>
<body>
-<H1>Apache Qpid C++ 0.21 Installation Notes</H1>
+<H1>Apache Qpid C++ 0.23 Installation Notes</H1>
-<p>Thank you for installing Apache Qpid version 0.21 for Windows.
+<p>Thank you for installing Apache Qpid version 0.23 for Windows.
If the requisite features were installed, you can now run a broker,
use the example programs, and design your own messaging programs while
reading the Qpid C++ API reference documentation.</p>
@@ -83,7 +83,7 @@ default; therefore, to gain support for durable items the persistence plugin must be loaded into the broker. This can be done using the
<code>--load-module</code> option to load the needed plugins. For example:
<pre>
-cd "C:\Program Files\Apache\qpidc-0.21"
+cd "C:\Program Files\Apache\qpidc-0.23"
qpidd.exe --load-module plugins\broker\store.dll --load-module plugins\broker\msclfs_store.dll
</pre>
The <code>--load-module</code> option can also take a full path. The option
diff --git a/qpid/packaging/windows/installer.proj b/qpid/packaging/windows/installer.proj index b2d1d6fb2f..5402559a18 100644 --- a/qpid/packaging/windows/installer.proj +++ b/qpid/packaging/windows/installer.proj @@ -32,7 +32,7 @@ <source_root>$(MSBuildProjectDirectory)\..\..</source_root>
<staging_dir>$(MSBuildProjectDirectory)\stage</staging_dir>
<bits Condition="'$(bits)' == ''">32</bits>
- <qpid_version>0.21</qpid_version>
+ <qpid_version>0.23</qpid_version>
<OutputName>qpidc</OutputName>
<OutputType>Package</OutputType>
<WixToolPath>C:\Program Files (x86)\Windows Installer XML v3.5\bin</WixToolPath>
diff --git a/qpid/python/qpid/messaging/endpoints.py b/qpid/python/qpid/messaging/endpoints.py index 95ff5516d0..143daf616a 100644 --- a/qpid/python/qpid/messaging/endpoints.py +++ b/qpid/python/qpid/messaging/endpoints.py @@ -122,6 +122,10 @@ class Connection(Endpoint): @param ssl_certfile: file with client's public (eventually priv+pub) key (PEM format) @type ssl_trustfile: str @param ssl_trustfile: file trusted certificates to validate the server + @type ssl_skip_hostname_check: bool + @param ssl_skip_hostname_check: disable verification of hostname in + certificate. Use with caution - disabling hostname checking leaves you + vulnerable to Man-in-the-Middle attacks. @rtype: Connection @return: a disconnected Connection @@ -170,6 +174,7 @@ class Connection(Endpoint): self.ssl_keyfile = options.get("ssl_keyfile", None) self.ssl_certfile = options.get("ssl_certfile", None) self.ssl_trustfile = options.get("ssl_trustfile", None) + self.ssl_skip_hostname_check = options.get("ssl_skip_hostname_check", False) self.client_properties = options.get("client_properties", {}) self.options = options diff --git a/qpid/python/qpid/messaging/transports.py b/qpid/python/qpid/messaging/transports.py index e901e98258..c76db1f395 100644 --- a/qpid/python/qpid/messaging/transports.py +++ b/qpid/python/qpid/messaging/transports.py @@ -53,7 +53,7 @@ TRANSPORTS["tcp"] = tcp try: from ssl import wrap_socket, SSLError, SSL_ERROR_WANT_READ, \ - SSL_ERROR_WANT_WRITE + SSL_ERROR_WANT_WRITE, CERT_REQUIRED, CERT_NONE except ImportError: ## try the older python SSL api: @@ -69,6 +69,15 @@ except ImportError: ssl_certfile = conn.ssl_certfile if ssl_certfile and not ssl_keyfile: ssl_keyfile = ssl_certfile + + # this version of SSL does NOT perform certificate validation. If the + # connection has been configured with CA certs (via ssl_trustfile), then + # the application expects the certificate to be validated against the + # supplied CA certs. Since this version cannot validate, the peer cannot + # be trusted. + if conn.ssl_trustfile: + raise SSLError("This version of Python does not support verification of the peer's certificate.") + self.ssl = ssl(self.socket, keyfile=ssl_keyfile, certfile=ssl_certfile) self.socket.setblocking(1) @@ -95,7 +104,39 @@ else: def __init__(self, conn, host, port): SocketTransport.__init__(self, conn, host, port) - self.tls = wrap_socket(self.socket, keyfile=conn.ssl_keyfile, certfile=conn.ssl_certfile, ca_certs=conn.ssl_trustfile) + if conn.ssl_trustfile: + validate = CERT_REQUIRED + else: + validate = CERT_NONE + + self.tls = wrap_socket(self.socket, keyfile=conn.ssl_keyfile, + certfile=conn.ssl_certfile, + ca_certs=conn.ssl_trustfile, + cert_reqs=validate) + + if validate == CERT_REQUIRED and not conn.ssl_skip_hostname_check: + match_found = False + peer_cert = self.tls.getpeercert() + if peer_cert: + peer_names = [] + if 'subjectAltName' in peer_cert: + for san in peer_cert['subjectAltName']: + if san[0] == 'DNS': + peer_names.append(san[1].lower()) + if 'subject' in peer_cert: + for sub in peer_cert['subject']: + while isinstance(sub, tuple) and isinstance(sub[0],tuple): + sub = sub[0] # why the extra level of indirection??? + if sub[0] == 'commonName': + peer_names.append(sub[1].lower()) + for pattern in peer_names: + if _match_dns_pattern( host.lower(), pattern ): + #print "Match found %s" % pattern + match_found = True + break + if not match_found: + raise SSLError("Connection hostname '%s' does not match names from peer certificate: %s" % (host, peer_names)) + self.socket.setblocking(0) self.state = None @@ -146,5 +187,31 @@ else: # this closes the underlying socket self.tls.close() + def _match_dns_pattern( hostname, pattern ): + """ For checking the hostnames provided by the peer's certificate + """ + if pattern.find("*") == -1: + return hostname == pattern + + # DNS wildcarded pattern - see RFC2818 + h_labels = hostname.split(".") + p_labels = pattern.split(".") + + while h_labels and p_labels: + if p_labels[0].find("*") == -1: + if p_labels[0] != h_labels[0]: + return False + else: + p = p_labels[0].split("*") + if not h_labels[0].startswith(p[0]): + return False + if not h_labels[0].endswith(p[1]): + return False + h_labels.pop(0) + p_labels.pop(0) + + return not h_labels and not p_labels + + TRANSPORTS["ssl"] = tls TRANSPORTS["tcp+tls"] = tls diff --git a/qpid/python/setup.py b/qpid/python/setup.py index 56af530b43..5afa913183 100755 --- a/qpid/python/setup.py +++ b/qpid/python/setup.py @@ -298,7 +298,7 @@ class install_lib(_install_lib): return outfiles + extra setup(name="qpid-python", - version="0.21", + version="0.23", author="Apache Qpid", author_email="dev@qpid.apache.org", packages=["mllib", "qpid", "qpid.messaging", "qpid.tests", diff --git a/qpid/tests/setup.py b/qpid/tests/setup.py index 67d2c87ad9..519c47c416 100755 --- a/qpid/tests/setup.py +++ b/qpid/tests/setup.py @@ -20,7 +20,7 @@ from distutils.core import setup setup(name="qpid-tests", - version="0.21", + version="0.23", author="Apache Qpid", author_email="dev@qpid.apache.org", packages=["qpid_tests", "qpid_tests.broker_0_10", "qpid_tests.broker_0_9", diff --git a/qpid/tools/setup.py b/qpid/tools/setup.py index 438a2af14f..87ae63c903 100755 --- a/qpid/tools/setup.py +++ b/qpid/tools/setup.py @@ -20,7 +20,7 @@ from distutils.core import setup setup(name="qpid-tools", - version="0.21", + version="0.23", author="Apache Qpid", author_email="dev@qpid.apache.org", package_dir={'' : 'src/py'}, diff --git a/qpid/tools/src/ruby/qpid_management/.gitignore b/qpid/tools/src/ruby/qpid_management/.gitignore new file mode 100644 index 0000000000..6a65a36504 --- /dev/null +++ b/qpid/tools/src/ruby/qpid_management/.gitignore @@ -0,0 +1,3 @@ +*.gem +.bundle +pkg/* diff --git a/qpid/tools/src/ruby/qpid_management/.rspec b/qpid/tools/src/ruby/qpid_management/.rspec new file mode 100644 index 0000000000..4e1e0d2f72 --- /dev/null +++ b/qpid/tools/src/ruby/qpid_management/.rspec @@ -0,0 +1 @@ +--color diff --git a/qpid/tools/src/ruby/qpid_management/Gemfile b/qpid/tools/src/ruby/qpid_management/Gemfile new file mode 100644 index 0000000000..1bd80c10c1 --- /dev/null +++ b/qpid/tools/src/ruby/qpid_management/Gemfile @@ -0,0 +1,30 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +source "http://rubygems.org" + +# Specify your gem's dependencies in qpid_config.gemspec +gemspec + +# development deps +gem 'rspec' +gem 'pry' +gem 'pry-stack_explorer' +gem 'pry-debugger' +gem 'yard' diff --git a/qpid/tools/src/ruby/qpid_management/Gemfile.lock b/qpid/tools/src/ruby/qpid_management/Gemfile.lock new file mode 100644 index 0000000000..bf35564e90 --- /dev/null +++ b/qpid/tools/src/ruby/qpid_management/Gemfile.lock @@ -0,0 +1,55 @@ +PATH + remote: . + specs: + qpid_management (1.0) + qpid_messaging + +GEM + remote: http://rubygems.org/ + specs: + binding_of_caller (0.7.1) + debug_inspector (>= 0.0.1) + coderay (1.0.9) + columnize (0.3.6) + debug_inspector (0.0.2) + debugger (1.4.0) + columnize (>= 0.3.1) + debugger-linecache (~> 1.1.1) + debugger-ruby_core_source (~> 1.2.0) + debugger-linecache (1.1.2) + debugger-ruby_core_source (>= 1.1.1) + debugger-ruby_core_source (1.2.0) + diff-lcs (1.2.1) + method_source (0.8.1) + pry (0.9.12) + coderay (~> 1.0.5) + method_source (~> 0.8) + slop (~> 3.4) + pry-debugger (0.2.2) + debugger (~> 1.3) + pry (~> 0.9.10) + pry-stack_explorer (0.4.9) + binding_of_caller (>= 0.7) + pry (~> 0.9.11) + qpid_messaging (0.20.2) + rspec (2.13.0) + rspec-core (~> 2.13.0) + rspec-expectations (~> 2.13.0) + rspec-mocks (~> 2.13.0) + rspec-core (2.13.0) + rspec-expectations (2.13.0) + diff-lcs (>= 1.1.3, < 2.0) + rspec-mocks (2.13.0) + slop (3.4.3) + yard (0.8.5.2) + +PLATFORMS + ruby + +DEPENDENCIES + pry + pry-debugger + pry-stack_explorer + qpid_management! + rspec + yard diff --git a/qpid/tools/src/ruby/qpid_management/Rakefile b/qpid/tools/src/ruby/qpid_management/Rakefile new file mode 100644 index 0000000000..7f295eda5c --- /dev/null +++ b/qpid/tools/src/ruby/qpid_management/Rakefile @@ -0,0 +1,27 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +require "bundler/gem_tasks" +require 'rspec/core/rake_task' + +require 'rake/clean' +CLOBBER.include('pkg') + +RSpec::Core::RakeTask.new('spec') + diff --git a/qpid/tools/src/ruby/qpid_management/lib/qpid_management.rb b/qpid/tools/src/ruby/qpid_management/lib/qpid_management.rb new file mode 100644 index 0000000000..0529710693 --- /dev/null +++ b/qpid/tools/src/ruby/qpid_management/lib/qpid_management.rb @@ -0,0 +1,81 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +require 'qpid_management/broker_agent' +require 'qpid_management/broker_object' +require 'qpid_management/acl' +require 'qpid_management/binding' +require 'qpid_management/bridge' +require 'qpid_management/broker' +require 'qpid_management/cluster' +require 'qpid_management/connection' +require 'qpid_management/errors' +require 'qpid_management/exchange' +require 'qpid_management/ha_broker' +require 'qpid_management/link' +require 'qpid_management/memory' +require 'qpid_management/queue' +require 'qpid_management/session' +require 'qpid_management/subscription' + +module Qpid + # The Qpid Management framework is a management framework for Qpid brokers + # that uses QMF2. + # + # ==== Example Usage + # + # Here is a simple example. It TODO. + # + # require 'rubygems' + # require 'qpid_messaging' + # require 'qpid_management' + # + # # create a connection and open it + # conn = Qpid::Messaging::Connection.new(:url => "broker.myqpiddomain.com") + # conn.open() + # + # # create a broker agent + # agent = Qpid::Management::BrokerAgent.new(conn) + # + # # get a reference to the broker + # broker = agent.broker + # + # # print out all exchange names + # puts broker.exchanges.map(&:name) + # + # # print out info about a single exchange + # amq_direct = broker.exchange('amq.direct') + # puts amq_direct + # puts amq_direct.msgDrops + # + # # create an exchange + # broker.add_exchange('topic', 'myexchange') + # + # # print out all queue names + # puts broker.queues.map(&:name) + # + # # create a queue + # broker.add_queue('myqueue') + # + # # print out info about a single queue + # myqueue = broker.queue('myqueue') + # puts myqueue.msgDepth + module Management + end +end diff --git a/qpid/tools/src/ruby/qpid_management/lib/qpid_management/acl.rb b/qpid/tools/src/ruby/qpid_management/lib/qpid_management/acl.rb new file mode 100644 index 0000000000..589b11fa59 --- /dev/null +++ b/qpid/tools/src/ruby/qpid_management/lib/qpid_management/acl.rb @@ -0,0 +1,38 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +module Qpid + module Management + # Representation of the access control list (ACL) for the broker. Properties + # include: + # - aclDenyCount + # - brokerRef + # - connectionDenyCount + # - enforcingAcl + # - lastAclLoad + # - maxConnectionsPerIp + # - maxConnectionsPerUser + # - maxQueuesPerUser + # - policyFile + # - queueQuotaDenyCount + # - transferAcl + class Acl < BrokerObject + end + end +end diff --git a/qpid/tools/src/ruby/qpid_management/lib/qpid_management/binding.rb b/qpid/tools/src/ruby/qpid_management/lib/qpid_management/binding.rb new file mode 100644 index 0000000000..cc46d84eeb --- /dev/null +++ b/qpid/tools/src/ruby/qpid_management/lib/qpid_management/binding.rb @@ -0,0 +1,31 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +module Qpid + module Management + # Representation of a binding in the broker. Properties include: + # - arguments + # - bindingKey + # - exchangeRef + # - msgMatched + # - queueRef + class Binding < BrokerObject + end + end +end diff --git a/qpid/tools/src/ruby/qpid_management/lib/qpid_management/bridge.rb b/qpid/tools/src/ruby/qpid_management/lib/qpid_management/bridge.rb new file mode 100644 index 0000000000..cece0ba2ed --- /dev/null +++ b/qpid/tools/src/ruby/qpid_management/lib/qpid_management/bridge.rb @@ -0,0 +1,39 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +module Qpid + module Management + # Representation of a bridge to another broker. Properties include: + # - channelId + # - dest + # - durable + # - dynamic + # - excludes + # - key + # - linkRef + # - name + # - src + # - srcIsLocal + # - srcIsQueue + # - sync + # - tag + class Bridge < BrokerObject + end + end +end diff --git a/qpid/tools/src/ruby/qpid_management/lib/qpid_management/broker.rb b/qpid/tools/src/ruby/qpid_management/lib/qpid_management/broker.rb new file mode 100644 index 0000000000..31171bdf35 --- /dev/null +++ b/qpid/tools/src/ruby/qpid_management/lib/qpid_management/broker.rb @@ -0,0 +1,278 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +module Qpid + module Management + # Representation of the broker. Properties include: + # - abandoned + # - abandonedViaAlt + # - acquires + # - byteDepth + # - byteFtdDepth + # - byteFtdDequeues + # - byteFtdEnqueues + # - bytePersistDequeues + # - bytePersistEnqueues + # - byteTotalDequeues + # - byteTotalEnqueues + # - byteTxnDequeues + # - byteTxnEnqueues + # - connBacklog + # - dataDir + # - discardsLvq + # - discardsNoRoute + # - discardsOverflow + # - discardsPurge + # - discardsRing + # - discardsSubscriber + # - discardsTtl + # - maxConns + # - mgmtPubInterval + # - mgmtPublish + # - msgDepth + # - msgFtdDepth + # - msgFtdDequeues + # - msgFtdEnqueues + # - msgPersistDequeues + # - msgPersistEnqueues + # - msgTotalDequeues + # - msgTotalEnqueues + # - msgTxnDequeues + # - msgTxnEnqueues + # - name + # - port + # - queueCount + # - releases + # - reroutes + # - stagingThreshold + # - systemRef + # - uptime + # - version + # - workerThreads + class Broker < BrokerObject + # Adds methods for the specified collections to be able to access all instances + # of a given collection, as well as a single instance by oid. + # + # == Example + # <tt>has_many :queues</tt> which will add: + # * <tt>#queues</tt> to retrieve all queues + # * <tt>#queue(oid)</tt> to retrieve a queue by oid (note, this is the short form of the object id, e.g. "myqueue" for a queue instead of "org.apache.qpid.broker:queue:myqueue" + # + # @param collections one or more symbols for the collections of objects a broker manages + def self.has_many(*collections) + [*collections].each do |collection| + singular_form = collection.to_s[0..-2] + capitalized_type = singular_form.gsub(/^\w/) { $&.upcase } + + define_method(collection) do + @agent.find_all_by_class(Qpid::Management::const_get(capitalized_type)) + end + + define_method(singular_form) do |oid| + @agent.find_by_object_id(Qpid::Management::const_get(capitalized_type), "org.apache.qpid.broker:#{singular_form}:#{oid}") + end + end + end + + # Adds method for the specified types to be able to access the singular + # instance of a given type. + # + # == Example + # <tt>has_one :acl</tt> which will add: + # * <tt>#acl</tt> to retrieve the Acl data for the Broker + # + # @param types one or more symbols for the singular objects a broker manages + def self.has_one(*types) + [*types].each do |type| + capitalized_type = type.to_s.gsub(/^\w/) { $&.upcase } + + define_method("#{type}") do + @agent.find_first_by_class(Qpid::Management::const_get(capitalized_type)) + end + end + end + + has_many :connections, :sessions, :subscriptions, :exchanges, :queues, :bindings, :links, :bridges + has_one :acl, :memory + + # Adds an exchange to the broker + # @param [String] type exchange type (fanout, direct, topic, headers, xml) + # @param [String] name exchange name + # @param [Hash] options exchange creation options + def add_exchange(type, name, options={}) + create_broker_object('exchange', name, options.merge!({'exchange-type' => type})) + end + + # Deletes an exchange from the broekr + # @param [String] name exchange name + def delete_exchange(name) + invoke_method('delete', {'type' => 'exchange', 'name' => name}) + end + + # Adds a queue to the broker + # @param [String] name queue name + # @param [Hash] options queue creation options + def add_queue(name, options={}) + create_broker_object('queue', name, options) + end + + # Deletes a queue from the broker + # @param [String] name queue name + def delete_queue(name) + invoke_method('delete', {'type' => 'queue', 'name' => name}) + end + + # Adds a binding from an exchange to a queue + # @param [String] exchange exchange name + # @param [String] queue queue name + # @param [String] key binding key + # @param [Hash] options binding creation options + def add_binding(exchange, queue, key="", options={}) + create_broker_object('binding', "#{exchange}/#{queue}/#{key}", options) + end + + # Deletes a binding from an exchange to a queue + # @param [String] exchange exchange name + # @param [String] queue queue name + # @param [String] key binding key + def delete_binding(exchange, queue, key="") + invoke_method('delete', {'type' => 'binding', 'name' => "#{exchange}/#{queue}/#{key}"}) + end + + # Adds a link to a remote broker + # @param [String] name link name + # @param [String] host remote broker host name or IP address + # @param [Fixnum] port remote broker port + # @param [String] transport transport mechanism used to connect to the remote broker + # @param [Boolean] durable should this link be persistent + # @param [String] auth_mechanism authentication mechanism to use + # @param [String] username user name to authenticate with the remote broker + # @param [String] password password for the user name + def add_link(name, host, port, transport='tcp', durable=false, auth_mechanism="", username="", password="") + options = { + 'host' => host, + 'port' => port, + 'transport' => transport, + 'durable' => durable, + 'authMechanism' => auth_mechanism, + 'username' => username, + 'password' => password + } + + create_broker_object('link', name, options) + end + + # Deletes a link to a remote broker + # @param [String] name link name + def delete_link(name) + invoke_method('delete', {'type' => 'link', 'name' => name}) + end + + # Adds a queue route + # @param [String] name the name of the bridge to create + # @param [Hash] options options for the queue route + # @option options [String] :link the name of the link to use (required) + # @option options [String] :queue the name of the source queue from which messages are pulled (required) + # @option options [String] :exchange the name of the destination exchange to which messages are sent (required) + # @option options [Fixnum] :sync the number of messages to send before issuing an explicit session sync (required) + def add_queue_route(name, options={}) + validate_options(options, [:link, :queue, :exchange, :sync]) + + properties = { + 'link' => options[:link], + 'src' => options[:queue], + 'dest' => options[:exchange], + 'srcIsQueue' => true, + 'sync' => options[:sync] + } + + create_broker_object('bridge', name, properties) + end + + # Adds an exchange route + # @param [String] name the name of the bridge to create + # @param [Hash] options options for the exchange route + # @option options [String] :link the name of the link to use (required) + # @option options [String] :exchange the name of the exchange to use (required) + # @option options [String] :key routing key to federate (required) + # @option options [Fixnum] :sync the number of messages to send before issuing an explicit session sync (required) + # @option options [String] :bridge_queue name of the queue to use as a bridge queue (optional) + def add_exchange_route(name, options={}) + validate_options(options, [:link, :exchange, :key, :sync]) + + properties = { + 'link' => options[:link], + 'src' => options[:exchange], + 'dest' => options[:exchange], + 'key' => options[:key], + 'sync' => options[:sync] + } + + properties['queue'] = options[:bridge_queue] if options.has_key?(:bridge_queue) + + create_broker_object('bridge', name, properties) + end + + # Adds a dynamic route + # @param [String] name the name of the bridge to create + # @param [Hash] options options for the dynamic route + # @option options [String] :link the name of the link to use (required) + # @option options [String] :exchange the name of the exchange to use (required) + # @option options [Fixnum] :sync the number of messages to send before issuing an explicit session sync (required) + # @option options [String] :bridge_queue name of the queue to use as a bridge queue (optional) + def add_dynamic_route(name, options={}) + validate_options(options, [:link, :exchange, :sync]) + + properties = { + 'link' => options[:link], + 'src' => options[:exchange], + 'dest' => options[:exchange], + 'dynamic' => true, + 'sync' => options[:sync] + } + + properties['queue'] = options[:bridge_queue] if options.has_key?(:bridge_queue) + + create_broker_object('bridge', name, properties) + end + + # Deletes a bridge (route) + # @param [String] name bridge name + def delete_bridge(name) + invoke_method('delete', {'type' => 'bridge', 'name' => name}) + end + + private + + def create_broker_object(type, name, options) + invoke_method('create', {'type' => type, + 'name' => name, + 'properties' => options, + 'strict' => true}) + end + + def validate_options(options, required) + required.each do |req| + raise "Option :#{req.to_s} is required" unless options.has_key?(req) + end + end + + end + end +end diff --git a/qpid/tools/src/ruby/qpid_management/lib/qpid_management/broker_agent.rb b/qpid/tools/src/ruby/qpid_management/lib/qpid_management/broker_agent.rb new file mode 100644 index 0000000000..800dcf26dc --- /dev/null +++ b/qpid/tools/src/ruby/qpid_management/lib/qpid_management/broker_agent.rb @@ -0,0 +1,173 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# for simplistic UUID - may want to consider something better in the future +require 'securerandom' + +# Ruby 1.8 doesn't include SecureRandom#uuid, so let's add it if it's missing +unless SecureRandom.respond_to? :uuid + module SecureRandom + def self.uuid + ary = self.random_bytes(16).unpack("NnnnnN") + ary[2] = (ary[2] & 0x0fff) | 0x4000 + ary[3] = (ary[3] & 0x3fff) | 0x8000 + "%08x-%04x-%04x-%04x-%04x%08x" % ary + end + end +end + +module Qpid + module Management + # This is the primary class that interacts with a Qpid messaging broker for + # querying information from the broker and for configuring it. + class BrokerAgent + # Creates a new BrokerAgent instance. A new Qpid::Messaging::Session, + # Qpid::Messaging::Receiver, and Qpid::Messaging::Sender will be created + # so this instance of the BrokerAgent may send requests to the broker + # and receive replies back. + # @param [Qpid::Messaging::Connection] connection a valid, opened connection + def initialize(connection) + @connection = connection + @session = @connection.create_session() + @reply_to = "qmf.default.topic/direct.#{SecureRandom.uuid}; {node: {type:topic}, link:{x-declare:{auto-delete:True,exclusive:True}}}" + @reply_rx = @session.create_receiver(@reply_to) + @reply_rx.capacity = 10 + @tx = @session.create_sender("qmf.default.direct/broker") + end + + # Closes the Qpid::Messaging::Session for this BrokerAgent. + def close() + @session.close() + end + + # Queries the broker for the Broker QMF object. + # @return [Broker] the broker QMF object + def broker() + find_first_by_class(Broker) + end + + # Queries the broker for the Cluster QMF object. + # @return [Cluster] the cluster QMF object + def cluster + find_first_by_class(Cluster) + end + + # Queries the broker for the HaBroker QMF object. + # @return [HaBroker] the HA broker QMF object + def ha_broker + find_first_by_class(HaBroker) + end + + # Invokes a method on a target object. + # @param [String] method the name of the method to invoke + # @param [Hash] args the arguments to pass to the method + # @param [String] addr the full id of the target object + # @param [Fixnum] timeout the amount of time to wait for the broker to respond to the method invocation + def invoke_method(method, args, addr="org.apache.qpid.broker:broker:amqp-broker", timeout=10) + content = {'_object_id' => {'_object_name' => addr}, + '_method_name' => method, + '_arguments' => args} + + message = Qpid::Messaging::Message.new() + message.content = content + message.reply_to = @reply_to + message['method'] = 'request' + message['qmf.opcode'] = '_method_request' + message['x-amqp-0-10.app-id'] = 'qmf2' + message.subject = 'broker' + + @tx.send(message) + + response = @reply_rx.fetch(Qpid::Messaging::Duration.new(timeout * 1000)) + @session.acknowledge() + + raise "Exception from Agent: #{response.content['_values']}" if response.properties['qmf.opcode'] == '_exception' + raise "Bad response: #{response.properties}" if response.properties['qmf.opcode'] != '_method_response' + + return response.content['_arguments'] + end + + def send_query(query) + message = Qpid::Messaging::Message.new() + message.content = query + message.reply_to = @reply_to + message['method'] = 'request' + message['qmf.opcode'] = '_query_request' + message['x-amqp-0-10.app-id'] = 'qmf2' + message.subject = 'broker' + + @tx.send(message) + + response = @reply_rx.fetch(Qpid::Messaging::Duration.new(10*1000)) + @session.acknowledge() + + raise 'Bad response' if response.properties['qmf.opcode'] != '_query_response' + + items = response.content + + while response.properties.has_key?('partial') + response = @reply_rx.fetch(Qpid::Messaging::Duration.new(10*1000)) + items += response.content + @session.acknowledge() + end + + return items + end + + def find_all_by_class(clazz) + query = { + '_what' => 'OBJECT', + '_schema_id' => { + '_class_name' => BrokerObject.qmf_class(clazz) + } + } + + items = send_query(query) + + [].tap do |objs| + for item in items + objs << clazz.new(self, item) + end + end + end + + def find_first_by_class(clazz) + objects = find_all_by_class(clazz) + return objects[0] if objects.size > 0 + return nil + end + + def find_by_object_id(clazz, oid) + query = { + '_what' => 'OBJECT', + '_object_id' => { + '_object_name' => oid + } + } + + results = send_query(query) + + return clazz.new(self, results[0]) if results.count == 1 and not results[0].nil? + + # return nil if not found + return nil + end + end + end +end diff --git a/qpid/tools/src/ruby/qpid_management/lib/qpid_management/broker_object.rb b/qpid/tools/src/ruby/qpid_management/lib/qpid_management/broker_object.rb new file mode 100644 index 0000000000..fbbe5ff6e2 --- /dev/null +++ b/qpid/tools/src/ruby/qpid_management/lib/qpid_management/broker_object.rb @@ -0,0 +1,126 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +module Qpid + module Management + # Representation of an object in the broker retrieved via QMF + class BrokerObject + attr_reader :content + + # Creates a new BrokerObject + # @param [BrokerAgent] agent the agent used to query the data from the broker + # @param [Hash] content the raw QMF response data from the broker + def initialize(agent, content) + @agent = agent + @content = content + @values = content['_values'] + end + + # Refreshes the information associated with this instance by requerying the broker + # @raise [ObjectDeletedError] if the object has been deleted + def refresh! + refreshed = @agent.named_object(self.class, id) + if refreshed + @content = refreshed.content + @values = @content['_values'] + else + raise ObjectDeletedError + end + end + + # Returns the full object id + # @return [String] the full object id + def id + @content['_object_id']['_object_name'] + end + + # Helper method to convert a Class to its QMF name counterpart. For + # example, QpidConfig::Connection will be converted to connection. + # @param [Class] clazz the Class to convert + # @return [String] the converted QMF name counterpart for this Class + def self.qmf_class(clazz) + clazz.name.split(/::/).last.downcase + end + + # Returns the short object id, i.e. without the leading org.apache.qpid.broker:<class name>: + # @return [String] the short object id + def short_id + clazz = BrokerObject.qmf_class(self.class) + if id =~ /org.apache.qpid.broker:#{clazz}:(.*)/ + return $1; + end + return nil + end + + # Returns the time at which this object was created + # @return [Time] the time at which this object was created + def created_at + Time.at(content['_create_ts'] / 1000000000.0) + end + + # Returns the time at which this object was deleted. Only ever applies to + # BrokerObject instances created from a QMF event. + # @return [Time] the time at which this object was deleted + def deleted_at + Time.at(content['_delete_ts'] / 1000000000.0) + end + + # Returns the time at which this object was last updated + # @return [Time] the time at which this object was last updated + def updated_at + Time.at(content['_update_ts'] / 1000000000.0) + end + + # Exposes data from the QMF response + # @param [String] key the key to look up a value, e.g. msgDepth for a queue + # @return the value associated with the key, or nil if not found + def [](key) + return nil unless @values.has_key?(key) + value = @values[key] + if value.is_a?(Hash) and value.has_key?('_object_name') + full_name = value['_object_name'] + colon = full_name.index(':') + unless colon.nil? + full_name = full_name[colon+1..-1] + colon = full_name.index(':') + return full_name[colon+1..-1] unless colon.nil? + end + end + + return value + end + + # Exposes data from the QMF response via methods, e.g. queue.msgDepth + def method_missing(method, *args, &block) + key = method.to_s + return self[key] if args.empty? and not self[key].nil? + super + end + + def to_s + @values.to_s + end + + # Invokes a QMF method + # @see BrokerAgent#invoke_method + def invoke_method(*args) + @agent.invoke_method(*args) + end + end + end +end diff --git a/qpid/tools/src/ruby/qpid_management/lib/qpid_management/cluster.rb b/qpid/tools/src/ruby/qpid_management/lib/qpid_management/cluster.rb new file mode 100644 index 0000000000..4fa7f146a7 --- /dev/null +++ b/qpid/tools/src/ruby/qpid_management/lib/qpid_management/cluster.rb @@ -0,0 +1,26 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +module Qpid + module Management + # Representation of a cluster + class Cluster < BrokerObject + end + end +end diff --git a/qpid/tools/src/ruby/qpid_management/lib/qpid_management/connection.rb b/qpid/tools/src/ruby/qpid_management/lib/qpid_management/connection.rb new file mode 100644 index 0000000000..153ccee684 --- /dev/null +++ b/qpid/tools/src/ruby/qpid_management/lib/qpid_management/connection.rb @@ -0,0 +1,51 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +module Qpid + module Management + # Representation of a client connection. Properties include: + # - SystemConnection + # - address + # - authIdentity + # - bytesFromClient + # - bytesToClient + # - closing + # - federationLink + # - framesFromClient + # - framesToClient + # - incoming + # - msgsFromClient + # - msgsToClient + # - remoteParentPid + # - remotePid + # - remoteProcessName + # - remoteProperties + # - saslMechanism + # - saslSsf + # - shadow + # - userProxyAuth + # - vhostRef + class Connection < BrokerObject + # Closes this connection to the broker + def close + invoke_method('close', {}, "org.apache.qpid.broker:connection:#{address}") + end + end + end +end diff --git a/qpid/tools/src/ruby/qpid_management/lib/qpid_management/errors.rb b/qpid/tools/src/ruby/qpid_management/lib/qpid_management/errors.rb new file mode 100644 index 0000000000..b922cda680 --- /dev/null +++ b/qpid/tools/src/ruby/qpid_management/lib/qpid_management/errors.rb @@ -0,0 +1,28 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +module Qpid + module Management + class ObjectNotFoundError < RuntimeError + end + + class ObjectDeletedError < RuntimeError + end + end +end diff --git a/qpid/tools/src/ruby/qpid_management/lib/qpid_management/exchange.rb b/qpid/tools/src/ruby/qpid_management/lib/qpid_management/exchange.rb new file mode 100644 index 0000000000..5a3223aba6 --- /dev/null +++ b/qpid/tools/src/ruby/qpid_management/lib/qpid_management/exchange.rb @@ -0,0 +1,44 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +module Qpid + module Management + # Representation of an exchange. Properties include: + # - arguments + # - autoDelete + # - bindingCount + # - bindingCountHigh + # - bindingCountLow + # - byteDrops + # - byteReceives + # - byteRoutes + # - durable + # - msgDrops + # - msgReceives + # - msgRoutes + # - name + # - producerCount + # - producerCountHigh + # - producerCountLow + # - type + # - vhostRef + class Exchange < BrokerObject + end + end +end diff --git a/qpid/tools/src/ruby/qpid_management/lib/qpid_management/ha_broker.rb b/qpid/tools/src/ruby/qpid_management/lib/qpid_management/ha_broker.rb new file mode 100644 index 0000000000..1ac9ea7a17 --- /dev/null +++ b/qpid/tools/src/ruby/qpid_management/lib/qpid_management/ha_broker.rb @@ -0,0 +1,26 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +module Qpid + module Management + # Representation of an HA broker + class HaBroker < BrokerObject + end + end +end diff --git a/qpid/tools/src/ruby/qpid_management/lib/qpid_management/link.rb b/qpid/tools/src/ruby/qpid_management/lib/qpid_management/link.rb new file mode 100644 index 0000000000..455b26a440 --- /dev/null +++ b/qpid/tools/src/ruby/qpid_management/lib/qpid_management/link.rb @@ -0,0 +1,35 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +module Qpid + module Management + # Representation of a link to a remote broker. Properties include: + # - connectionRef + # - durable + # - host + # - lastError + # - name + # - port + # - state + # - transport + # - vhostRef + class Link < BrokerObject + end + end +end diff --git a/qpid/tools/src/ruby/qpid_management/lib/qpid_management/memory.rb b/qpid/tools/src/ruby/qpid_management/lib/qpid_management/memory.rb new file mode 100644 index 0000000000..39dd803c9a --- /dev/null +++ b/qpid/tools/src/ruby/qpid_management/lib/qpid_management/memory.rb @@ -0,0 +1,34 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +module Qpid + module Management + # Representation of the broker's memory. Properties include: + # - malloc_arena + # - malloc_fordblks + # - malloc_hblkhd + # - malloc_hblks + # - malloc_keepcost + # - malloc_ordblks + # - malloc_uordblks + # - name + class Memory < BrokerObject + end + end +end diff --git a/qpid/tools/src/ruby/qpid_management/lib/qpid_management/queue.rb b/qpid/tools/src/ruby/qpid_management/lib/qpid_management/queue.rb new file mode 100644 index 0000000000..c4fae3a53e --- /dev/null +++ b/qpid/tools/src/ruby/qpid_management/lib/qpid_management/queue.rb @@ -0,0 +1,97 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +module Qpid + module Management + # Representation of a queue. Properties include: + # - acquires + # - arguments + # - autoDelete + # - bindingCount + # - bindingCountHigh + # - bindingCountLow + # - byteDepth + # - byteFtdDepth + # - byteFtdDequeues + # - byteFtdEnqueues + # - bytePersistDequeues + # - bytePersistEnqueues + # - byteTotalDequeues + # - byteTotalEnqueues + # - byteTxnDequeues + # - byteTxnEnqueues + # - consumerCount + # - consumerCountHigh + # - consumerCountLow + # - discardsLvq + # - discardsOverflow + # - discardsPurge + # - discardsRing + # - discardsSubscriber + # - discardsTtl + # - durable + # - exclusive + # - flowStopped + # - flowStoppedCount + # - messageLatencyAvg + # - messageLatencyCount + # - messageLatencyMax + # - messageLatencyMin + # - msgDepth + # - msgFtdDepth + # - msgFtdDequeues + # - msgFtdEnqueues + # - msgPersistDequeues + # - msgPersistEnqueues + # - msgTotalDequeues + # - msgTotalEnqueues + # - msgTxnDequeues + # - msgTxnEnqueues + # - name + # - releases + # - reroutes + # - unackedMessages + # - unackedMessagesHigh + # - unackedMessagesLow + # - vhostRef + class Queue < BrokerObject + # Purges (removes) messages from this queue + # @param [Fixnum] message_count number of messages to remove from the queue, or 0 for all messages + # @param [Hash] filter an optional filter to use when removing messages + def purge(message_count, filter={}) + invoke_method('purge', {'request' => message_count, 'filter' => filter}, "org.apache.qpid.broker:queue:#{name}") + end + + # Reroutes messages from this queue to an exchange, either the queue's + # alternate exchange, or the specified exchange + # @param [Fixnum] message_count number of messages to reroute from the queue, or 0 for all messages + # @param [Boolean] use_alternate_exchange whether to use the queue's alternate exchange as the destination + # @param [String] exchange name of destination exchange + # @param [Hash] filter an optional filter to use when rerouting messages + def reroute(message_count, use_alternate_exchange, exchange, filter) + args = {'request' => message_count, + 'useAltExchange' => use_alternate_exchange, + 'exchange' => exchange, + 'filter' => filter} + + invoke_method('reroute', args, "org.apache.qpid.broker:queue:#{name}") + end + end + end +end diff --git a/qpid/tools/src/ruby/qpid_management/lib/qpid_management/session.rb b/qpid/tools/src/ruby/qpid_management/lib/qpid_management/session.rb new file mode 100644 index 0000000000..b4fcc7da59 --- /dev/null +++ b/qpid/tools/src/ruby/qpid_management/lib/qpid_management/session.rb @@ -0,0 +1,38 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +module Qpid + module Management + # Representation of a session to the broker. Properties include: + # - TxnCommits + # - TxnCount + # - TxnRejects + # - TxnStarts + # - attached + # - channelId + # - clientCredit + # - connectionRef + # - detachedLifespan + # - name + # - unackedMessages + # - vhostRef + class Session < BrokerObject + end + end +end diff --git a/qpid/tools/src/ruby/qpid_management/lib/qpid_management/subscription.rb b/qpid/tools/src/ruby/qpid_management/lib/qpid_management/subscription.rb new file mode 100644 index 0000000000..fcff2831c0 --- /dev/null +++ b/qpid/tools/src/ruby/qpid_management/lib/qpid_management/subscription.rb @@ -0,0 +1,35 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +module Qpid + module Management + # Representation of a subscription. Properties include: + # - acknowledged + # - arguments + # - browsing + # - creditMode + # - delivered + # - exclusive + # - name + # - queueRef + # - sessionRef + class Subscription < BrokerObject + end + end +end diff --git a/qpid/tools/src/ruby/qpid_management/qpid_management.gemspec b/qpid/tools/src/ruby/qpid_management/qpid_management.gemspec new file mode 100644 index 0000000000..a6cc1b828e --- /dev/null +++ b/qpid/tools/src/ruby/qpid_management/qpid_management.gemspec @@ -0,0 +1,36 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# -*- encoding: utf-8 -*- +$:.push File.expand_path("../lib", __FILE__) + +Gem::Specification.new do |s| + s.name = "qpid_management" + s.version = "1.0" + s.authors = ["Apache Qpid Project"] + s.email = ["dev@qpid.apache.org"] + s.homepage = "http://qpid.apache.org" + s.summary = %q{Qpid management library} + s.description = %q{Qpid management library} + s.files = `git ls-files`.split("\n") + s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") + s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } + s.require_paths = ["lib"] + s.add_runtime_dependency 'qpid_messaging' +end diff --git a/qpid/tools/src/ruby/qpid_management/spec/broker_agent_spec.rb b/qpid/tools/src/ruby/qpid_management/spec/broker_agent_spec.rb new file mode 100644 index 0000000000..7ffbf842e2 --- /dev/null +++ b/qpid/tools/src/ruby/qpid_management/spec/broker_agent_spec.rb @@ -0,0 +1,43 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +require 'spec_helper' + +describe Qpid::Management::BrokerAgent do + before(:each) do + @broker_port = `qpidd --no-data-dir --auth=no --no-module-dir --daemon --port 0`.chop + @connection = Qpid::Messaging::Connection.new(url:"localhost:#{@broker_port}") + @connection.open() + @agent = Qpid::Management::BrokerAgent.new(@connection) + end + + after(:each) do + @agent.close() + @connection.close() + `qpidd -q --port #{@broker_port}` + end + + describe '#broker' do + let(:broker) { @agent.broker } + + it 'returns the broker' do + broker.class.should == Qpid::Management::Broker + end + end +end diff --git a/qpid/tools/src/ruby/qpid_management/spec/broker_spec.rb b/qpid/tools/src/ruby/qpid_management/spec/broker_spec.rb new file mode 100644 index 0000000000..6d6e1106a4 --- /dev/null +++ b/qpid/tools/src/ruby/qpid_management/spec/broker_spec.rb @@ -0,0 +1,373 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +require 'spec_helper' + +describe Qpid::Management::Broker do + before(:each) do + @broker_port = `qpidd --no-data-dir --auth=no --no-module-dir --daemon --port 0`.chop + @connection = Qpid::Messaging::Connection.new(url:"localhost:#{@broker_port}") + @connection.open() + @agent = Qpid::Management::BrokerAgent.new(@connection) + @broker = @agent.broker + end + + after(:each) do + @agent.close() + @connection.close() + `qpidd --quit --port #{@broker_port}` + end + + def setup_queue_route + @other_port = `qpidd --no-data-dir --auth=no --no-module-dir --daemon --port 0`.chop + @broker.add_link('link1', 'localhost', @other_port) + @broker.add_queue('queue') + @broker.add_queue_route('qr1', + link: 'link1', + queue: 'queue', + exchange: 'amq.direct', + sync: 2) + end + + %w(connection session subscription exchange queue binding link bridge).each do |type| + describe "##{type}s" do + before(:each) do + setup_queue_route if %w(link bridge).include?(type) + end + + after(:each) do + if %w(link bridge).include?(type) + `qpidd --quit --port #{@other_port}` + end + end + + let(:collection) { @broker.send("#{type}s") } + + it "returns at least 1 #{type}" do + if type == 'subscription' + session = @connection.create_session + receiver = session.create_receiver("amq.direct/temp") + end + collection.count.should be > 0 + end + end + + describe "##{type}" do + before(:each) do + setup_queue_route if %w(link bridge).include?(type) + end + + after(:each) do + if %w(link bridge).include?(type) + `qpidd --quit --port #{@other_port}` + end + end + + let(:object) { @broker.send("#{type}s")[0] } + + it "returns a #{type} by oid" do + if type == 'subscription' + session = @connection.create_session + receiver = session.create_receiver("amq.direct/temp") + end + @broker.send(type, object.short_id).id.should == object.id + end + end + end + + describe '#add_exchange' do + %w(fanout direct topic headers).each do |type| + context "when adding a #{type} exchange" do + let(:exchange_name) { "#{type}1" } + before(:each) do + @before_creation = Time.now + @broker.add_exchange(type, exchange_name, {'qpid.replicate' => 'none'}) + end + + subject { @broker.exchange(exchange_name) } + its(:short_id) { should == exchange_name } + its(:type) { should == type } + its(:created_at) { should be > @before_creation } + it 'has the correct arguments' do + subject.arguments.should == {'qpid.replicate' => 'none'} + end + end + end + end + + describe "#delete_exchange" do + before(:each) do + @before_creation = Time.now + @broker.add_exchange('fanout', 'fanout_to_delete') + end + + let(:exchange) { @broker.exchange('fanout_to_delete') } + + context "with a valid exchange name" do + it "deletes the exchange" do + @broker.delete_exchange(exchange.short_id) + expect { exchange.refresh! }.to raise_error + end + end + + context "with an invalid exchange name" do + it "raises a not-found exception" do + expect { @broker.delete_exchange("badname") }.to raise_error(/not-found.*badname/) + end + end + end + + describe "#add_queue" do + before(:each) do + @before_creation = Time.now + @queue_name = 'myqueue' + @broker.add_queue(@queue_name, {'qpid.replicate' => 'none'}) + end + + subject { @broker.queue(@queue_name) } + its(:short_id) { should == @queue_name } + its(:created_at) { should be > @before_creation } + it 'has the correct arguments' do + subject.arguments.should == {'qpid.replicate' => 'none'} + end + end + + describe "#delete_queue" do + before(:each) do + @before_creation = Time.now + @broker.add_queue('queue_to_delete') + end + + let(:queue) { @broker.queue('queue_to_delete') } + + context "with a valid queue name" do + it "deletes the queue" do + @broker.delete_queue(queue.short_id) + expect { queue.refresh! }.to raise_error + end + end + + context "with an invalid name" do + it "raises a not-found exception" do + expect { @broker.delete_queue("badname") }.to raise_error(/not-found.*badname/) + end + end + end + + describe "#add_binding" do + before(:each) do + @broker.add_queue('queue') + end + + it "creates a binding for a fanout exchange" do + @broker.add_exchange('fanout', 'fanout') + @broker.add_binding('fanout', 'queue') + expect { @broker.binding('org.apache.qpid.broker:exchange:fanout,org.apache.qpid.broker:queue:queue,') }.to_not raise_error + end + + it "creates a binding for a direct exchange" do + @broker.add_exchange('direct', 'direct') + @broker.add_binding('direct', 'queue', 'mykey') + expect { @broker.binding('org.apache.qpid.broker:exchange:direct,org.apache.qpid.broker:queue:queue,mykey') }.to_not raise_error + end + + it "creates a binding for a topic exchange" do + @broker.add_exchange('topic', 'topic') + @broker.add_binding('topic', 'queue', 'us.#') + expect { @broker.binding('org.apache.qpid.broker:exchange:topic,org.apache.qpid.broker:queue:queue,us.#') }.to_not raise_error + end + end + + describe "#delete_binding" do + it "deletes an existing binding" do + @broker.add_queue('queue') + @broker.add_exchange('fanout', 'fanout') + @broker.add_binding('fanout', 'queue') + expect { @broker.delete_binding('fanout', 'queue') }.to_not raise_error + end + end + + describe "#add_link" do + before(:each) do + @other_port = `/usr/sbin/qpidd --no-data-dir --auth=no --no-module-dir --daemon --port 0`.chop + end + + after(:each) do + `/usr/sbin/qpidd -q --port #{@other_port}` + end + + it "adds a link" do + @broker.add_link('link1', 'localhost', @other_port) + @broker.links.count.should == 1 + end + end + + describe "#delete_link" do + before(:each) do + @other_port = `/usr/sbin/qpidd --no-data-dir --auth=no --no-module-dir --daemon --port 0`.chop + @broker.add_link('link1', 'localhost', @other_port) + end + + after(:each) do + `/usr/sbin/qpidd -q --port #{@other_port}` + end + + it "deletes a link" do + @broker.delete_link('link1') + @broker.links.count.should == 0 + end + end + + describe "#add_queue_route" do + context "with missing options" do + [:link, :queue, :exchange, :sync].each do |opt| + opts = {link: 'l', queue: 'q', exchange: 'e', sync:2} + opts.delete(opt) + it "raises an error when :#{opt} is missing" do + expect { @broker.add_queue_route('name', opts) }.to raise_error(/Option :#{opt} is required/) + end + end + end + + context "with all required options" do + before(:each) do + @other_port = `/usr/sbin/qpidd --no-data-dir --auth=no --no-module-dir --daemon --port 0`.chop + @broker.add_link('link1', 'localhost', @other_port) + @broker.add_queue('queue') + @broker.add_queue_route('qr1', + link: 'link1', + queue: 'queue', + exchange: 'amq.direct', + sync: 2) + end + + after(:each) do + `/usr/sbin/qpidd -q --port #{@other_port}` + end + + it "adds a queue route" do + @broker.bridges.count.should == 1 + end + + subject { @broker.bridges[0] } + its(:dest) { should == 'amq.direct' } + its(:durable) { should == false } + its(:dynamic) { should == false } + its(:excludes) { should == "" } + its(:key) { should == "" } + its(:name) { should == "qr1" } + its(:src) { should == "queue" } + its(:srcIsLocal) { should == false } + its(:srcIsQueue) { should == true } + its(:sync) { should == 2 } + its(:tag) { should == "" } + end + end + + describe "#add_exchange_route" do + context "with missing options" do + [:link, :exchange, :key, :sync].each do |opt| + opts = {link: 'l', exchange: 'e', key:'rk', sync:2} + opts.delete(opt) + it "raises an error when :#{opt} is missing" do + expect { @broker.add_exchange_route('name', opts) }.to raise_error(/Option :#{opt} is required/) + end + end + end + + context "with all required options" do + before(:each) do + @other_port = `/usr/sbin/qpidd --no-data-dir --auth=no --no-module-dir --daemon --port 0`.chop + @broker.add_link('link1', 'localhost', @other_port) + @broker.add_queue('queue') + @broker.add_exchange_route('er1', + link: 'link1', + exchange: 'amq.direct', + key: 'foo', + sync: 2) + end + + after(:each) do + `/usr/sbin/qpidd -q --port #{@other_port}` + end + + it "adds an exchange route" do + @broker.bridges.count.should == 1 + end + + subject { @broker.bridges[0] } + its(:dest) { should == 'amq.direct' } + its(:durable) { should == false } + its(:dynamic) { should == false } + its(:excludes) { should == "" } + its(:key) { should == "foo" } + its(:name) { should == "er1" } + its(:src) { should == "amq.direct" } + its(:srcIsLocal) { should == false } + its(:srcIsQueue) { should == false } + its(:sync) { should == 2 } + its(:tag) { should == "" } + end + end + + describe "#add_dynamic_route" do + context "with missing options" do + [:link, :exchange, :sync].each do |opt| + opts = {link: 'l', exchange: 'e', sync:2} + opts.delete(opt) + it "raises an error when :#{opt} is missing" do + expect { @broker.add_dynamic_route('name', opts) }.to raise_error(/Option :#{opt} is required/) + end + end + end + + context "with all required options" do + before(:each) do + @other_port = `/usr/sbin/qpidd --no-data-dir --auth=no --no-module-dir --daemon --port 0`.chop + @broker.add_link('link1', 'localhost', @other_port) + @broker.add_queue('queue') + @broker.add_dynamic_route('dr1', + link: 'link1', + exchange: 'amq.direct', + sync: 2) + end + + after(:each) do + `/usr/sbin/qpidd -q --port #{@other_port}` + end + + it "adds an exchange route" do + @broker.bridges.count.should == 1 + end + + subject { @broker.bridges[0] } + its(:dest) { should == 'amq.direct' } + its(:durable) { should == false } + its(:dynamic) { should == true } + its(:excludes) { should == "" } + its(:key) { should == "" } + its(:name) { should == "dr1" } + its(:src) { should == "amq.direct" } + its(:srcIsLocal) { should == false } + its(:srcIsQueue) { should == false } + its(:sync) { should == 2 } + its(:tag) { should == "" } + end + end +end diff --git a/qpid/tools/src/ruby/qpid_management/spec/spec_helper.rb b/qpid/tools/src/ruby/qpid_management/spec/spec_helper.rb new file mode 100644 index 0000000000..f552c55888 --- /dev/null +++ b/qpid/tools/src/ruby/qpid_management/spec/spec_helper.rb @@ -0,0 +1,21 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +require 'qpid_messaging' +require 'qpid_management' |