diff options
82 files changed, 11603 insertions, 0 deletions
diff --git a/BUILD.in b/BUILD.in new file mode 100644 index 00000000..b013bc3c --- /dev/null +++ b/BUILD.in @@ -0,0 +1,10 @@ +Please see http://www.rabbitmq.com/build.html for build +instructions. + +For your convenience, a text copy of these instructions is available +below. Please be aware that the instructions here may not be as up to +date as those at the above URL. + +=========================================================================== + + diff --git a/INSTALL.in b/INSTALL.in new file mode 100644 index 00000000..d1fa81df --- /dev/null +++ b/INSTALL.in @@ -0,0 +1,10 @@ +Please see http://www.rabbitmq.com/install.html for install +instructions. + +For your convenience, a text copy of these instructions is available +below. Please be aware that the instructions here may not be as up to +date as those at the above URL. + +=========================================================================== + + diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..d7042b92 --- /dev/null +++ b/LICENSE @@ -0,0 +1,5 @@ +This package, the RabbitMQ server is licensed under the MPL. For the +MPL, please see LICENSE-MPL-RabbitMQ. + +If you have any questions regarding licensing, please contact us at +info@rabbitmq.com. diff --git a/LICENSE-MPL-RabbitMQ b/LICENSE-MPL-RabbitMQ new file mode 100644 index 00000000..7c2ee3ca --- /dev/null +++ b/LICENSE-MPL-RabbitMQ @@ -0,0 +1,467 @@ + MOZILLA PUBLIC LICENSE + Version 1.1 + + --------------- + +1. Definitions. + + 1.0.1. "Commercial Use" means distribution or otherwise making the + Covered Code available to a third party. + + 1.1. "Contributor" means each entity that creates or contributes to + the creation of Modifications. + + 1.2. "Contributor Version" means the combination of the Original + Code, prior Modifications used by a Contributor, and the Modifications + made by that particular Contributor. + + 1.3. "Covered Code" means the Original Code or Modifications or the + combination of the Original Code and Modifications, in each case + including portions thereof. + + 1.4. "Electronic Distribution Mechanism" means a mechanism generally + accepted in the software development community for the electronic + transfer of data. + + 1.5. "Executable" means Covered Code in any form other than Source + Code. + + 1.6. "Initial Developer" means the individual or entity identified + as the Initial Developer in the Source Code notice required by Exhibit + A. + + 1.7. "Larger Work" means a work which combines Covered Code or + portions thereof with code not governed by the terms of this License. + + 1.8. "License" means this document. + + 1.8.1. "Licensable" means having the right to grant, to the maximum + extent possible, whether at the time of the initial grant or + subsequently acquired, any and all of the rights conveyed herein. + + 1.9. "Modifications" means any addition to or deletion from the + substance or structure of either the Original Code or any previous + Modifications. When Covered Code is released as a series of files, a + Modification is: + A. Any addition to or deletion from the contents of a file + containing Original Code or previous Modifications. + + B. Any new file that contains any part of the Original Code or + previous Modifications. + + 1.10. "Original Code" means Source Code of computer software code + which is described in the Source Code notice required by Exhibit A as + Original Code, and which, at the time of its release under this + License is not already Covered Code governed by this License. + + 1.10.1. "Patent Claims" means any patent claim(s), now owned or + hereafter acquired, including without limitation, method, process, + and apparatus claims, in any patent Licensable by grantor. + + 1.11. "Source Code" means the preferred form of the Covered Code for + making modifications to it, including all modules it contains, plus + any associated interface definition files, scripts used to control + compilation and installation of an Executable, or source code + differential comparisons against either the Original Code or another + well known, available Covered Code of the Contributor's choice. The + Source Code can be in a compressed or archival form, provided the + appropriate decompression or de-archiving software is widely available + for no charge. + + 1.12. "You" (or "Your") means an individual or a legal entity + exercising rights under, and complying with all of the terms of, this + License or a future version of this License issued under Section 6.1. + For legal entities, "You" includes any entity which controls, is + controlled by, or is under common control with You. For purposes of + this definition, "control" means (a) the power, direct or indirect, + to cause the direction or management of such entity, whether by + contract or otherwise, or (b) ownership of more than fifty percent + (50%) of the outstanding shares or beneficial ownership of such + entity. + +2. Source Code License. + + 2.1. The Initial Developer Grant. + The Initial Developer hereby grants You a world-wide, royalty-free, + non-exclusive license, subject to third party intellectual property + claims: + (a) under intellectual property rights (other than patent or + trademark) Licensable by Initial Developer to use, reproduce, + modify, display, perform, sublicense and distribute the Original + Code (or portions thereof) with or without Modifications, and/or + as part of a Larger Work; and + + (b) under Patents Claims infringed by the making, using or + selling of Original Code, to make, have made, use, practice, + sell, and offer for sale, and/or otherwise dispose of the + Original Code (or portions thereof). + + (c) the licenses granted in this Section 2.1(a) and (b) are + effective on the date Initial Developer first distributes + Original Code under the terms of this License. + + (d) Notwithstanding Section 2.1(b) above, no patent license is + granted: 1) for code that You delete from the Original Code; 2) + separate from the Original Code; or 3) for infringements caused + by: i) the modification of the Original Code or ii) the + combination of the Original Code with other software or devices. + + 2.2. Contributor Grant. + Subject to third party intellectual property claims, each Contributor + hereby grants You a world-wide, royalty-free, non-exclusive license + + (a) under intellectual property rights (other than patent or + trademark) Licensable by Contributor, to use, reproduce, modify, + display, perform, sublicense and distribute the Modifications + created by such Contributor (or portions thereof) either on an + unmodified basis, with other Modifications, as Covered Code + and/or as part of a Larger Work; and + + (b) under Patent Claims infringed by the making, using, or + selling of Modifications made by that Contributor either alone + and/or in combination with its Contributor Version (or portions + of such combination), to make, use, sell, offer for sale, have + made, and/or otherwise dispose of: 1) Modifications made by that + Contributor (or portions thereof); and 2) the combination of + Modifications made by that Contributor with its Contributor + Version (or portions of such combination). + + (c) the licenses granted in Sections 2.2(a) and 2.2(b) are + effective on the date Contributor first makes Commercial Use of + the Covered Code. + + (d) Notwithstanding Section 2.2(b) above, no patent license is + granted: 1) for any code that Contributor has deleted from the + Contributor Version; 2) separate from the Contributor Version; + 3) for infringements caused by: i) third party modifications of + Contributor Version or ii) the combination of Modifications made + by that Contributor with other software (except as part of the + Contributor Version) or other devices; or 4) under Patent Claims + infringed by Covered Code in the absence of Modifications made by + that Contributor. + +3. Distribution Obligations. + + 3.1. Application of License. + The Modifications which You create or to which You contribute are + governed by the terms of this License, including without limitation + Section 2.2. The Source Code version of Covered Code may be + distributed only under the terms of this License or a future version + of this License released under Section 6.1, and You must include a + copy of this License with every copy of the Source Code You + distribute. You may not offer or impose any terms on any Source Code + version that alters or restricts the applicable version of this + License or the recipients' rights hereunder. However, You may include + an additional document offering the additional rights described in + Section 3.5. + + 3.2. Availability of Source Code. + Any Modification which You create or to which You contribute must be + made available in Source Code form under the terms of this License + either on the same media as an Executable version or via an accepted + Electronic Distribution Mechanism to anyone to whom you made an + Executable version available; and if made available via Electronic + Distribution Mechanism, must remain available for at least twelve (12) + months after the date it initially became available, or at least six + (6) months after a subsequent version of that particular Modification + has been made available to such recipients. You are responsible for + ensuring that the Source Code version remains available even if the + Electronic Distribution Mechanism is maintained by a third party. + + 3.3. Description of Modifications. + You must cause all Covered Code to which You contribute to contain a + file documenting the changes You made to create that Covered Code and + the date of any change. You must include a prominent statement that + the Modification is derived, directly or indirectly, from Original + Code provided by the Initial Developer and including the name of the + Initial Developer in (a) the Source Code, and (b) in any notice in an + Executable version or related documentation in which You describe the + origin or ownership of the Covered Code. + + 3.4. Intellectual Property Matters + (a) Third Party Claims. + If Contributor has knowledge that a license under a third party's + intellectual property rights is required to exercise the rights + granted by such Contributor under Sections 2.1 or 2.2, + Contributor must include a text file with the Source Code + distribution titled "LEGAL" which describes the claim and the + party making the claim in sufficient detail that a recipient will + know whom to contact. If Contributor obtains such knowledge after + the Modification is made available as described in Section 3.2, + Contributor shall promptly modify the LEGAL file in all copies + Contributor makes available thereafter and shall take other steps + (such as notifying appropriate mailing lists or newsgroups) + reasonably calculated to inform those who received the Covered + Code that new knowledge has been obtained. + + (b) Contributor APIs. + If Contributor's Modifications include an application programming + interface and Contributor has knowledge of patent licenses which + are reasonably necessary to implement that API, Contributor must + also include this information in the LEGAL file. + + (c) Representations. + Contributor represents that, except as disclosed pursuant to + Section 3.4(a) above, Contributor believes that Contributor's + Modifications are Contributor's original creation(s) and/or + Contributor has sufficient rights to grant the rights conveyed by + this License. + + 3.5. Required Notices. + You must duplicate the notice in Exhibit A in each file of the Source + Code. If it is not possible to put such notice in a particular Source + Code file due to its structure, then You must include such notice in a + location (such as a relevant directory) where a user would be likely + to look for such a notice. If You created one or more Modification(s) + You may add your name as a Contributor to the notice described in + Exhibit A. You must also duplicate this License in any documentation + for the Source Code where You describe recipients' rights or ownership + rights relating to Covered Code. You may choose to offer, and to + charge a fee for, warranty, support, indemnity or liability + obligations to one or more recipients of Covered Code. However, You + may do so only on Your own behalf, and not on behalf of the Initial + Developer or any Contributor. You must make it absolutely clear than + any such warranty, support, indemnity or liability obligation is + offered by You alone, and You hereby agree to indemnify the Initial + Developer and every Contributor for any liability incurred by the + Initial Developer or such Contributor as a result of warranty, + support, indemnity or liability terms You offer. + + 3.6. Distribution of Executable Versions. + You may distribute Covered Code in Executable form only if the + requirements of Section 3.1-3.5 have been met for that Covered Code, + and if You include a notice stating that the Source Code version of + the Covered Code is available under the terms of this License, + including a description of how and where You have fulfilled the + obligations of Section 3.2. The notice must be conspicuously included + in any notice in an Executable version, related documentation or + collateral in which You describe recipients' rights relating to the + Covered Code. You may distribute the Executable version of Covered + Code or ownership rights under a license of Your choice, which may + contain terms different from this License, provided that You are in + compliance with the terms of this License and that the license for the + Executable version does not attempt to limit or alter the recipient's + rights in the Source Code version from the rights set forth in this + License. If You distribute the Executable version under a different + license You must make it absolutely clear that any terms which differ + from this License are offered by You alone, not by the Initial + Developer or any Contributor. You hereby agree to indemnify the + Initial Developer and every Contributor for any liability incurred by + the Initial Developer or such Contributor as a result of any such + terms You offer. + + 3.7. Larger Works. + You may create a Larger Work by combining Covered Code with other code + not governed by the terms of this License and distribute the Larger + Work as a single product. In such a case, You must make sure the + requirements of this License are fulfilled for the Covered Code. + +4. Inability to Comply Due to Statute or Regulation. + + If it is impossible for You to comply with any of the terms of this + License with respect to some or all of the Covered Code due to + statute, judicial order, or regulation then You must: (a) comply with + the terms of this License to the maximum extent possible; and (b) + describe the limitations and the code they affect. Such description + must be included in the LEGAL file described in Section 3.4 and must + be included with all distributions of the Source Code. Except to the + extent prohibited by statute or regulation, such description must be + sufficiently detailed for a recipient of ordinary skill to be able to + understand it. + +5. Application of this License. + + This License applies to code to which the Initial Developer has + attached the notice in Exhibit A and to related Covered Code. + +6. Versions of the License. + + 6.1. New Versions. + Netscape Communications Corporation ("Netscape") may publish revised + and/or new versions of the License from time to time. Each version + will be given a distinguishing version number. + + 6.2. Effect of New Versions. + Once Covered Code has been published under a particular version of the + License, You may always continue to use it under the terms of that + version. You may also choose to use such Covered Code under the terms + of any subsequent version of the License published by Netscape. No one + other than Netscape has the right to modify the terms applicable to + Covered Code created under this License. + + 6.3. Derivative Works. + If You create or use a modified version of this License (which you may + only do in order to apply it to code which is not already Covered Code + governed by this License), You must (a) rename Your license so that + the phrases "Mozilla", "MOZILLAPL", "MOZPL", "Netscape", + "MPL", "NPL" or any confusingly similar phrase do not appear in your + license (except to note that your license differs from this License) + and (b) otherwise make it clear that Your version of the license + contains terms which differ from the Mozilla Public License and + Netscape Public License. (Filling in the name of the Initial + Developer, Original Code or Contributor in the notice described in + Exhibit A shall not of themselves be deemed to be modifications of + this License.) + +7. DISCLAIMER OF WARRANTY. + + COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, + WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, + WITHOUT LIMITATION, WARRANTIES THAT THE COVERED CODE IS FREE OF + DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING. + THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED CODE + IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, + YOU (NOT THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE + COST OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER + OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF + ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER. + +8. TERMINATION. + + 8.1. This License and the rights granted hereunder will terminate + automatically if You fail to comply with terms herein and fail to cure + such breach within 30 days of becoming aware of the breach. All + sublicenses to the Covered Code which are properly granted shall + survive any termination of this License. Provisions which, by their + nature, must remain in effect beyond the termination of this License + shall survive. + + 8.2. If You initiate litigation by asserting a patent infringement + claim (excluding declatory judgment actions) against Initial Developer + or a Contributor (the Initial Developer or Contributor against whom + You file such action is referred to as "Participant") alleging that: + + (a) such Participant's Contributor Version directly or indirectly + infringes any patent, then any and all rights granted by such + Participant to You under Sections 2.1 and/or 2.2 of this License + shall, upon 60 days notice from Participant terminate prospectively, + unless if within 60 days after receipt of notice You either: (i) + agree in writing to pay Participant a mutually agreeable reasonable + royalty for Your past and future use of Modifications made by such + Participant, or (ii) withdraw Your litigation claim with respect to + the Contributor Version against such Participant. If within 60 days + of notice, a reasonable royalty and payment arrangement are not + mutually agreed upon in writing by the parties or the litigation claim + is not withdrawn, the rights granted by Participant to You under + Sections 2.1 and/or 2.2 automatically terminate at the expiration of + the 60 day notice period specified above. + + (b) any software, hardware, or device, other than such Participant's + Contributor Version, directly or indirectly infringes any patent, then + any rights granted to You by such Participant under Sections 2.1(b) + and 2.2(b) are revoked effective as of the date You first made, used, + sold, distributed, or had made, Modifications made by that + Participant. + + 8.3. If You assert a patent infringement claim against Participant + alleging that such Participant's Contributor Version directly or + indirectly infringes any patent where such claim is resolved (such as + by license or settlement) prior to the initiation of patent + infringement litigation, then the reasonable value of the licenses + granted by such Participant under Sections 2.1 or 2.2 shall be taken + into account in determining the amount or value of any payment or + license. + + 8.4. In the event of termination under Sections 8.1 or 8.2 above, + all end user license agreements (excluding distributors and resellers) + which have been validly granted by You or any distributor hereunder + prior to termination shall survive termination. + +9. LIMITATION OF LIABILITY. + + UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT + (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE INITIAL + DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF COVERED CODE, + OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO ANY PERSON FOR + ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY + CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF GOODWILL, + WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER + COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN + INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF + LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY + RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT APPLICABLE LAW + PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE + EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO + THIS EXCLUSION AND LIMITATION MAY NOT APPLY TO YOU. + +10. U.S. GOVERNMENT END USERS. + + The Covered Code is a "commercial item," as that term is defined in + 48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial computer + software" and "commercial computer software documentation," as such + terms are used in 48 C.F.R. 12.212 (Sept. 1995). Consistent with 48 + C.F.R. 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4 (June 1995), + all U.S. Government End Users acquire Covered Code with only those + rights set forth herein. + +11. MISCELLANEOUS. + + This License represents the complete agreement concerning subject + matter hereof. If any provision of this License is held to be + unenforceable, such provision shall be reformed only to the extent + necessary to make it enforceable. This License shall be governed by + California law provisions (except to the extent applicable law, if + any, provides otherwise), excluding its conflict-of-law provisions. + With respect to disputes in which at least one party is a citizen of, + or an entity chartered or registered to do business in the United + States of America, any litigation relating to this License shall be + subject to the jurisdiction of the Federal Courts of the Northern + District of California, with venue lying in Santa Clara County, + California, with the losing party responsible for costs, including + without limitation, court costs and reasonable attorneys' fees and + expenses. The application of the United Nations Convention on + Contracts for the International Sale of Goods is expressly excluded. + Any law or regulation which provides that the language of a contract + shall be construed against the drafter shall not apply to this + License. + +12. RESPONSIBILITY FOR CLAIMS. + + As between Initial Developer and the Contributors, each party is + responsible for claims and damages arising, directly or indirectly, + out of its utilization of rights under this License and You agree to + work with Initial Developer and Contributors to distribute such + responsibility on an equitable basis. Nothing herein is intended or + shall be deemed to constitute any admission of liability. + +13. MULTIPLE-LICENSED CODE. + + Initial Developer may designate portions of the Covered Code as + "Multiple-Licensed". "Multiple-Licensed" means that the Initial + Developer permits you to utilize portions of the Covered Code under + Your choice of the NPL or the alternative licenses, if any, specified + by the Initial Developer in the file described in Exhibit A. + +EXHIBIT A -Mozilla Public License. + + ``The contents of this file are subject to the Mozilla Public License + Version 1.1 (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.mozilla.org/MPL/ + + Software distributed under the License is distributed on an "AS IS" + basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the + License for the specific language governing rights and limitations + under the License. + + The Original Code is RabbitMQ. + + The Initial Developers of the Original Code are LShift Ltd., + Cohesive Financial Technologies LLC., and Rabbit Technologies Ltd. + + Portions created by LShift Ltd., Cohesive Financial Technologies + LLC., and Rabbit Technologies Ltd. are Copyright (C) 2007-2008 + LShift Ltd., Cohesive Financial Technologies LLC., and Rabbit + Technologies Ltd.; + + All Rights Reserved. + + Contributor(s): ______________________________________.'' + + [NOTE: The text of this Exhibit A may differ slightly from the text of + the notices in the Source Code files of the Original Code. You should + use the text of this Exhibit A rather than the text found in the + Original Code Source Code for Your Modifications.] + + + diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..680003cf --- /dev/null +++ b/Makefile @@ -0,0 +1,147 @@ +NODENAME=rabbit +RABBIT_ARGS= +SOURCE_DIR=src +EBIN_DIR=ebin +INCLUDE_DIR=include +SOURCES=$(wildcard $(SOURCE_DIR)/*.erl) +TARGETS=$(EBIN_DIR)/rabbit_framing.beam $(patsubst $(SOURCE_DIR)/%.erl, $(EBIN_DIR)/%.beam,$(SOURCES)) +PLT=rabbit.plt +WEB_URL=http://stage.rabbitmq.com/ + +ifndef USE_SPECS +# our type specs rely on features / bug fixes in dialyzer that are +# only available in R12B-3 upwards +# +# NB: the test assumes that version number will only contain single digits +USE_SPECS=$(shell if [ $$(erl -noshell -eval 'io:format(erlang:system_info(version)), halt().') \> "5.6.2" ]; then echo "true"; else echo "false"; fi) +endif + +#other args: +native +"{hipe,[o3,verbose]}" -Ddebug=true +debug_info +no_strict_record_tests +ERLC_OPTS=-I $(INCLUDE_DIR) -o $(EBIN_DIR) -Wall -v +debug_info $(shell [ $(USE_SPECS) = "true" ] && echo "-Duse_specs") + +MNESIA_DIR=/tmp/rabbitmq-$(NODENAME)-mnesia +LOG_BASE=/tmp + +VERSION=0.0.0 +TARBALL_NAME=rabbitmq-server-$(VERSION) + +SIBLING_CODEGEN_DIR=../rabbitmq-codegen/ +AMQP_CODEGEN_DIR=$(shell [ -d $(SIBLING_CODEGEN_DIR) ] && echo $(SIBLING_CODEGEN_DIR) || echo codegen) +AMQP_SPEC_JSON_PATH=$(AMQP_CODEGEN_DIR)/amqp-0.8.json + +ERL_CALL=erl_call -sname $(NODENAME) -e + +# for the moment we don't use boot files because they introduce a +# dependency on particular versions of OTP applications +#all: $(EBIN_DIR)/rabbit.boot +all: $(TARGETS) + +$(EBIN_DIR)/%.beam: $(SOURCE_DIR)/%.erl $(INCLUDE_DIR)/rabbit_framing.hrl $(INCLUDE_DIR)/rabbit.hrl + erlc $(ERLC_OPTS) $< +# ERLC_EMULATOR="erl -smp" erlc $(ERLC_OPTS) $< + +$(INCLUDE_DIR)/rabbit_framing.hrl $(SOURCE_DIR)/rabbit_framing.erl: codegen.py $(AMQP_CODEGEN_DIR)/amqp_codegen.py $(AMQP_SPEC_JSON_PATH) + python codegen.py header $(AMQP_SPEC_JSON_PATH) > $(INCLUDE_DIR)/rabbit_framing.hrl + python codegen.py body $(AMQP_SPEC_JSON_PATH) > $(SOURCE_DIR)/rabbit_framing.erl + +$(EBIN_DIR)/rabbit.boot $(EBIN_DIR)/rabbit.script: $(EBIN_DIR)/rabbit.app $(EBIN_DIR)/rabbit.rel $(TARGETS) + erl -noshell -eval 'systools:make_script("ebin/rabbit", [{path, ["ebin"]}]), halt().' + +$(PLT): $(TARGETS) + dialyzer -c $? --output_plt $@ $(shell if [ -f $@ ] ; then echo "--plt $@"; fi) + +dialyze: $(PLT) + +clean: cleandb + rm -f $(EBIN_DIR)/*.beam + rm -f $(EBIN_DIR)/rabbit.boot $(EBIN_DIR)/rabbit.script + rm -f $(INCLUDE_DIR)/rabbit_framing.hrl $(SOURCE_DIR)/rabbit_framing.erl codegen.pyc + rm -f $(PLT) + +cleandb: stop-node + erl -mnesia dir '"$(MNESIA_DIR)"' -noshell -eval 'lists:foreach(fun file:delete/1, filelib:wildcard(mnesia:system_info(directory) ++ "/*")), halt().' + +############ various tasks to interact with RabbitMQ ################### + +run: all + NODE_IP_ADDRESS=$(NODE_IP_ADDRESS) NODE_PORT=$(NODE_PORT) NODE_ONLY=true LOG_BASE=$(LOG_BASE) RABBIT_ARGS="$(RABBIT_ARGS) -s rabbit" MNESIA_DIR=$(MNESIA_DIR) ./scripts/rabbitmq-server + +run-node: all + NODE_IP_ADDRESS=$(NODE_IP_ADDRESS) NODE_PORT=$(NODE_PORT) NODE_ONLY=true LOG_BASE=$(LOG_BASE) RABBIT_ARGS="$(RABBIT_ARGS)" MNESIA_DIR=$(MNESIA_DIR) ./scripts/rabbitmq-server + +run-tests: all + echo "rabbit_tests:all_tests()." | $(ERL_CALL) + +start-background-node: stop-node + NODE_IP_ADDRESS=$(NODE_IP_ADDRESS) NODE_PORT=$(NODE_PORT) NODE_ONLY=true LOG_BASE=$(LOG_BASE) RABBIT_ARGS="$(RABBIT_ARGS) -detached" MNESIA_DIR=$(MNESIA_DIR) ./scripts/rabbitmq-server ; sleep 1 + +start-rabbit-on-node: all + echo "rabbit:start()." | $(ERL_CALL) + +stop-rabbit-on-node: all + echo "rabbit:stop()." | $(ERL_CALL) + +force-snapshot: all + echo "rabbit_persister:force_snapshot()." | $(ERL_CALL) + +stop-node: + -$(ERL_CALL) -q + +start-cover: all + echo "cover:start(), rabbit_misc:enable_cover()." | $(ERL_CALL) + +stop-cover: all + echo "rabbit_misc:report_cover(), cover:stop()." | $(ERL_CALL) + cat cover/summary.txt + +######################################################################## + +generic_stage: + mkdir -p $(GENERIC_STAGE_DIR) + cp -r ebin include src $(GENERIC_STAGE_DIR) + cp LICENSE LICENSE-MPL-RabbitMQ $(GENERIC_STAGE_DIR) + + if [ -f INSTALL.in ]; then \ + cp INSTALL.in $(GENERIC_STAGE_DIR)/INSTALL; \ + elinks -dump -no-references -no-numbering $(WEB_URL)install.html \ + >> $(GENERIC_STAGE_DIR)/INSTALL; \ + cp BUILD.in $(GENERIC_STAGE_DIR)/BUILD; \ + elinks -dump -no-references -no-numbering $(WEB_URL)build.html \ + >> $(GENERIC_STAGE_DIR)/BUILD; \ + else \ + cp INSTALL $(GENERIC_STAGE_DIR); \ + cp BUILD $(GENERIC_STAGE_DIR); \ + fi + + sed -i 's/%%VERSION%%/$(VERSION)/' $(GENERIC_STAGE_DIR)/ebin/rabbit.app + +srcdist: distclean + $(MAKE) VERSION=$(VERSION) GENERIC_STAGE_DIR=dist/$(TARBALL_NAME) generic_stage + + cp -r $(AMQP_CODEGEN_DIR) dist/$(TARBALL_NAME)/codegen + cp codegen.py Makefile dist/$(TARBALL_NAME) + + cp -r scripts dist/$(TARBALL_NAME) + chmod 0755 dist/$(TARBALL_NAME)/scripts/* + + (cd dist; tar -zcf $(TARBALL_NAME).tar.gz $(TARBALL_NAME)) + (cd dist; zip -r $(TARBALL_NAME).zip $(TARBALL_NAME)) + rm -rf dist/$(TARBALL_NAME) + +distclean: clean + make -C $(AMQP_CODEGEN_DIR) clean + rm -rf dist + find . -name '*~' -exec rm {} \; + +install: all + @[ -n "$(TARGET_DIR)" ] || (echo "Please set TARGET_DIR."; false) + @[ -n "$(SBIN_DIR)" ] || (echo "Please set SBIN_DIR."; false) + + $(MAKE) VERSION=$(VERSION) GENERIC_STAGE_DIR=$(TARGET_DIR) generic_stage + + chmod 0755 scripts/* + mkdir -p $(SBIN_DIR) + cp scripts/rabbitmq-server $(SBIN_DIR) + cp scripts/rabbitmqctl $(SBIN_DIR) + cp scripts/rabbitmq-multi $(SBIN_DIR) + rm -f $(TARGET_DIR)/BUILD diff --git a/codegen.py b/codegen.py new file mode 100644 index 00000000..242c2418 --- /dev/null +++ b/codegen.py @@ -0,0 +1,305 @@ +## The contents of this file are subject to the Mozilla Public License +## Version 1.1 (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.mozilla.org/MPL/ +## +## Software distributed under the License is distributed on an "AS IS" +## basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +## License for the specific language governing rights and limitations +## under the License. +## +## The Original Code is RabbitMQ. +## +## The Initial Developers of the Original Code are LShift Ltd., +## Cohesive Financial Technologies LLC., and Rabbit Technologies Ltd. +## +## Portions created by LShift Ltd., Cohesive Financial Technologies +## LLC., and Rabbit Technologies Ltd. are Copyright (C) 2007-2008 +## LShift Ltd., Cohesive Financial Technologies LLC., and Rabbit +## Technologies Ltd.; +## +## All Rights Reserved. +## +## Contributor(s): ______________________________________. +## + +from __future__ import nested_scopes + +import sys +sys.path.append("../rabbitmq-codegen") # in case we're next to an experimental revision +sys.path.append("codegen") # in case we're building from a distribution package + +from amqp_codegen import * +import string +import re + +erlangTypeMap = { + 'octet': 'octet', + 'shortstr': 'shortstr', + 'longstr': 'longstr', + 'short': 'shortint', + 'long': 'longint', + 'longlong': 'longlongint', + 'bit': 'bit', + 'table': 'table', + 'timestamp': 'timestamp', +} + +def erlangize(s): + s = s.replace('-', '_') + s = s.replace(' ', '_') + return s + +AmqpMethod.erlangName = lambda m: "'" + erlangize(m.klass.name) + '.' + erlangize(m.name) + "'" + +def erlangConstantName(s): + return '_'.join(re.split('[- ]', s.upper())) + +class PackedMethodBitField: + def __init__(self, index): + self.index = index + self.domain = 'bit' + self.contents = [] + + def extend(self, f): + self.contents.append(f) + + def count(self): + return len(self.contents) + + def full(self): + return self.count() == 8 + +def genErl(spec): + def erlType(domain): + return erlangTypeMap[spec.resolveDomain(domain)] + + def fieldTypeList(fields): + return '[' + ', '.join([erlType(f.domain) for f in fields]) + ']' + + def fieldNameList(fields): + return '[' + ', '.join([erlangize(f.name) for f in fields]) + ']' + + def fieldTempList(fields): + return '[' + ', '.join(['F' + str(f.index) for f in fields]) + ']' + + def fieldMapList(fields): + return ', '.join([erlangize(f.name) + " = F" + str(f.index) for f in fields]) + + def genLookupMethodName(m): + print "lookup_method_name({%d, %d}) -> %s;" % (m.klass.index, m.index, m.erlangName()) + + def genMethodId(m): + print "method_id(%s) -> {%d, %d};" % (m.erlangName(), m.klass.index, m.index) + + def genMethodHasContent(m): + print "method_has_content(%s) -> %s;" % (m.erlangName(), str(m.hasContent).lower()) + + def genMethodFieldTypes(m): + """Not currently used - may be useful in future?""" + print "method_fieldtypes(%s) -> %s;" % (m.erlangName(), fieldTypeList(m.arguments)) + + def genMethodFieldNames(m): + print "method_fieldnames(%s) -> %s;" % (m.erlangName(), fieldNameList(m.arguments)) + + def packMethodFields(fields): + packed = [] + bitfield = None + for f in fields: + if erlType(f.domain) == 'bit': + if not(bitfield) or bitfield.full(): + bitfield = PackedMethodBitField(f.index) + packed.append(bitfield) + bitfield.extend(f) + else: + bitfield = None + packed.append(f) + return packed + + def methodFieldFragment(f): + type = erlType(f.domain) + p = 'F' + str(f.index) + if type == 'shortstr': + return p+'Len:8/unsigned, '+p+':'+p+'Len/binary' + elif type == 'longstr': + return p+'Len:32/unsigned, '+p+':'+p+'Len/binary' + elif type == 'octet': + return p+':8/unsigned' + elif type == 'shortint': + return p+':16/unsigned' + elif type == 'longint': + return p+':32/unsigned' + elif type == 'longlongint': + return p+':64/unsigned' + elif type == 'timestamp': + return p+':64/unsigned' + elif type == 'bit': + return p+'Bits:8' + elif type == 'table': + return p+'Len:32/unsigned, '+p+'Tab:'+p+'Len/binary' + + def genFieldPostprocessing(packed): + for f in packed: + type = erlType(f.domain) + if type == 'bit': + for index in range(f.count()): + print " F%d = ((F%dBits band %d) /= 0)," % \ + (f.index + index, + f.index, + 1 << index) + elif type == 'table': + print " F%d = rabbit_binary_parser:parse_table(F%dTab)," % \ + (f.index, f.index) + else: + pass + + def genDecodeMethodFields(m): + packedFields = packMethodFields(m.arguments) + binaryPattern = ', '.join([methodFieldFragment(f) for f in packedFields]) + if binaryPattern: + restSeparator = ', ' + else: + restSeparator = '' + recordConstructorExpr = '#%s{%s}' % (m.erlangName(), fieldMapList(m.arguments)) + print "decode_method_fields(%s, <<%s>>) ->" % (m.erlangName(), binaryPattern) + genFieldPostprocessing(packedFields) + print " %s;" % (recordConstructorExpr,) + + def genDecodeProperties(c): + print "decode_properties(%d, PropBin) ->" % (c.index) + print " %s = rabbit_binary_parser:parse_properties(%s, PropBin)," % \ + (fieldTempList(c.fields), fieldTypeList(c.fields)) + print " #'P_%s'{%s};" % (erlangize(c.name), fieldMapList(c.fields)) + + def genFieldPreprocessing(packed): + for f in packed: + type = erlType(f.domain) + if type == 'bit': + print " F%dBits = (%s)," % \ + (f.index, + ' bor '.join(['(bitvalue(F%d) bsl %d)' % (x.index, x.index - f.index) + for x in f.contents])) + elif type == 'table': + print " F%dTab = rabbit_binary_generator:generate_table(F%d)," % (f.index, f.index) + print " F%dLen = size(F%dTab)," % (f.index, f.index) + elif type in ['shortstr', 'longstr']: + print " F%dLen = size(F%d)," % (f.index, f.index) + else: + pass + + def genEncodeMethodFields(m): + packedFields = packMethodFields(m.arguments) + print "encode_method_fields(#%s{%s}) ->" % (m.erlangName(), fieldMapList(m.arguments)) + genFieldPreprocessing(packedFields) + print " <<%s>>;" % (', '.join([methodFieldFragment(f) for f in packedFields])) + + def genEncodeProperties(c): + print "encode_properties(#'P_%s'{%s}) ->" % (erlangize(c.name), fieldMapList(c.fields)) + print " rabbit_binary_generator:encode_properties(%s, %s);" % \ + (fieldTypeList(c.fields), fieldTempList(c.fields)) + + def massageConstantClass(cls): + # We do this because 0.8 uses "soft error" and 8.1 uses "soft-error". + return erlangConstantName(cls) + + def genLookupException(c,v,cls): + mCls = massageConstantClass(cls) + if mCls == 'SOFT_ERROR': genLookupException1(c,'false') + elif mCls == 'HARD_ERROR': genLookupException1(c, 'true') + elif mCls == '': pass + else: raise 'Unknown constant class', cls + + def genLookupException1(c,hardErrorBoolStr): + n = erlangConstantName(c) + print 'lookup_amqp_exception(%s) -> {%s, ?%s, <<"%s">>};' % \ + (n.lower(), hardErrorBoolStr, n, n) + + methods = spec.allMethods() + + print """-module(rabbit_framing). +-include("rabbit_framing.hrl"). + +-export([lookup_method_name/1]). + +-export([method_id/1]). +-export([method_has_content/1]). +-export([method_fieldnames/1]). +-export([decode_method_fields/2]). +-export([decode_properties/2]). +-export([encode_method_fields/1]). +-export([encode_properties/1]). +-export([lookup_amqp_exception/1]). + +bitvalue(true) -> 1; +bitvalue(false) -> 0; +bitvalue(undefined) -> 0. +""" + for m in methods: genLookupMethodName(m) + print "lookup_method_name({_ClassId, _MethodId} = Id) -> exit({unknown_method_id, Id})." + + for m in methods: genMethodId(m) + print "method_id(Name) -> exit({unknown_method_name, Name})." + + for m in methods: genMethodHasContent(m) + print "method_has_content(Name) -> exit({unknown_method_name, Name})." + + for m in methods: genMethodFieldNames(m) + print "method_fieldnames(Name) -> exit({unknown_method_name, Name})." + + for m in methods: genDecodeMethodFields(m) + print "decode_method_fields(Name, BinaryFields) ->" + print " rabbit_misc:frame_error(Name, BinaryFields)." + + for c in spec.allClasses(): genDecodeProperties(c) + print "decode_properties(ClassId, _BinaryFields) -> exit({unknown_class_id, ClassId})." + + for m in methods: genEncodeMethodFields(m) + print "encode_method_fields(Record) -> exit({unknown_method_name, element(1, Record)})." + + for c in spec.allClasses(): genEncodeProperties(c) + print "encode_properties(Record) -> exit({unknown_properties_record, Record})." + + for (c,v,cls) in spec.constants: genLookupException(c,v,cls) + print "lookup_amqp_exception(Code) ->" + print " rabbit_log:warning(\"Unknown AMQP error code '~p'~n\", [Code])," + print " {true, ?INTERNAL_ERROR, <<\"INTERNAL_ERROR\">>}." + + +def genHrl(spec): + def erlType(domain): + return erlangTypeMap[spec.resolveDomain(domain)] + + def fieldNameList(fields): + return ', '.join([erlangize(f.name) for f in fields]) + + methods = spec.allMethods() + + print "-define(PROTOCOL_VERSION_MAJOR, %d)." % (spec.major) + print "-define(PROTOCOL_VERSION_MINOR, %d)." % (spec.minor) + print "-define(PROTOCOL_PORT, %d)." % (spec.port) + + for (c,v,cls) in spec.constants: + print "-define(%s, %s)." % (erlangConstantName(c), v) + + print "%% Method field records." + for m in methods: + print "-record(%s, {%s})." % (m.erlangName(), fieldNameList(m.arguments)) + + print "%% Class property records." + for c in spec.allClasses(): + print "-record('P_%s', {%s})." % (erlangize(c.name), fieldNameList(c.fields)) + +#--------------------------------------------------------------------------- + +def generateErl(specPath): + genErl(AmqpSpec(specPath)) + +def generateHrl(specPath): + genHrl(AmqpSpec(specPath)) + +if __name__ == "__main__": + do_main(generateHrl, generateErl) + + + + diff --git a/ebin/rabbit.app b/ebin/rabbit.app new file mode 100644 index 00000000..20d5afcf --- /dev/null +++ b/ebin/rabbit.app @@ -0,0 +1,53 @@ +{application, rabbit, %% -*- erlang -*- + [{description, "RabbitMQ"}, + {id, "RabbitMQ"}, + {vsn, "%%VERSION%%"}, + {modules, [rabbit, + buffering_proxy, + rabbit_access_control, + rabbit_amqqueue, + rabbit_amqqueue_process, + rabbit_amqqueue_sup, + rabbit_binary_generator, + rabbit_binary_parser, + rabbit_channel, + rabbit_control, + rabbit_error_logger, + rabbit_exchange, + rabbit_framing, + rabbit_framing_channel, + rabbit_heartbeat, + rabbit_load, + rabbit_log, + rabbit_misc, + rabbit_mnesia, + rabbit_networking, + rabbit_node_monitor, + rabbit_persister, + rabbit_reader, + rabbit_realm, + rabbit_router, + rabbit_sup, + rabbit_tests, + rabbit_ticket, + rabbit_tracer, + rabbit_writer, + tcp_acceptor, + tcp_acceptor_sup, + tcp_client_sup, + tcp_listener, + tcp_listener_sup]}, + {registered, [rabbit_amqqueue_sup, + rabbit_log, + rabbit_node_monitor, + rabbit_persister, + rabbit_router, + rabbit_sup, + rabbit_tcp_client_sup]}, + {applications, [kernel, stdlib, sasl, mnesia, os_mon]}, + {mod, {rabbit, []}}, + {env, [{tcp_listeners, [{"0.0.0.0", 5672}]}, + {extra_startup_steps, []}, + {default_user, <<"guest">>}, + {default_pass, <<"guest">>}, + {default_vhost, <<"/">>}]}]}. diff --git a/ebin/rabbit.rel b/ebin/rabbit.rel new file mode 100644 index 00000000..c2d2067b --- /dev/null +++ b/ebin/rabbit.rel @@ -0,0 +1,7 @@ +{release, {"rabbit", "1.1.0-alpha"}, {erts, "1.14.2"}, + [{rabbit, "1.1.0-alpha"}, + {mnesia, "4.3.4"}, + {os_mon, "2.1.2"}, + {sasl, "2.1.5"}, + {stdlib, "1.14.4"}, + {kernel, "2.11.4"}]}. diff --git a/include/rabbit.hrl b/include/rabbit.hrl new file mode 100644 index 00000000..d8af670a --- /dev/null +++ b/include/rabbit.hrl @@ -0,0 +1,159 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (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.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developers of the Original Code are LShift Ltd., +%% Cohesive Financial Technologies LLC., and Rabbit Technologies Ltd. +%% +%% Portions created by LShift Ltd., Cohesive Financial Technologies +%% LLC., and Rabbit Technologies Ltd. are Copyright (C) 2007-2008 +%% LShift Ltd., Cohesive Financial Technologies LLC., and Rabbit +%% Technologies Ltd.; +%% +%% All Rights Reserved. +%% +%% Contributor(s): ______________________________________. +%% + +-record(user, {username, password}). +-record(user_vhost, {username, virtual_host}). + +-record(vhost, {virtual_host, dummy}). +-record(vhost_realm, {virtual_host, realm}). + +-record(realm, {name, exchanges, queues}). +-record(user_realm, {username, realm, ticket_pattern}). + +-record(realm_visitor, {realm, pid}). + +-record(connection, {user, timeout_sec, frame_max, vhost}). + +-record(content, {class_id, + properties, %% either 'none', or a decoded record/tuple + properties_bin, %% either 'none', or an encoded properties binary + %% Note: at most one of properties and properties_bin can be 'none' at once. + payload_fragments_rev %% list of binaries, in reverse order (!) + }). + +-record(resource, {virtual_host, kind, name}). + +-record(ticket, {realm_name, passive_flag, active_flag, write_flag, read_flag}). + +-record(exchange, {name, type, durable, auto_delete, arguments}). + +-record(amqqueue, {name, durable, auto_delete, arguments, binding_specs, pid}). +-record(binding_spec, {exchange_name, routing_key, arguments}). + +-record(binding, {key, handlers}). +-record(handler, {binding_spec, queue, qpid}). + +-record(listener, {node, protocol, host, port}). + +-record(basic_message, {exchange_name, routing_key, content, persistent_key}). + +%%---------------------------------------------------------------------------- + +-ifdef(use_specs). + +-include("rabbit_framing_spec.hrl"). + +-type(maybe(T) :: T | 'none'). +-type(node() :: atom()). +-type(socket() :: port()). +-type(thunk(T) :: fun(() -> T)). + +%% this is really an abstract type, but dialyzer does not support them +-type(guid() :: any()). +-type(txn() :: guid()). +-type(pkey() :: guid()). +-type(r(Kind) :: + #resource{virtual_host :: vhost(), + kind :: Kind, + name :: name()}). +-type(realm_name() :: r('realm')). +-type(queue_name() :: r('queue')). +-type(exchange_name() :: r('exchange')). +-type(user() :: + #user{username :: username(), + password :: password()}). +-type(ticket() :: + #ticket{realm_name :: realm_name(), + passive_flag :: bool(), + active_flag :: bool(), + write_flag :: bool(), + read_flag :: bool()}). +-type(permission() :: 'passive' | 'active' | 'write' | 'read'). +-type(binding_spec() :: + #binding_spec{exchange_name :: exchange_name(), + routing_key :: routing_key(), + arguments :: amqp_table()}). +-type(amqqueue() :: + #amqqueue{name :: queue_name(), + durable :: bool(), + auto_delete :: bool(), + arguments :: amqp_table(), + binding_specs :: [binding_spec()], + pid :: maybe(pid())}). +-type(exchange() :: + #exchange{name :: exchange_name(), + type :: exchange_type(), + durable :: bool(), + auto_delete :: bool(), + arguments :: amqp_table()}). +%% TODO: make this more precise by tying specific class_ids to +%% specific properties +-type(undecoded_content() :: + #content{class_id :: amqp_class_id(), + properties :: 'none', + properties_bin :: binary(), + payload_fragments_rev :: [binary()]} | + #content{class_id :: amqp_class_id(), + properties :: amqp_properties(), + properties_bin :: 'none', + payload_fragments_rev :: [binary()]}). +-type(decoded_content() :: + #content{class_id :: amqp_class_id(), + properties :: amqp_properties(), + properties_bin :: maybe(binary()), + payload_fragments_rev :: [binary()]}). +-type(content() :: undecoded_content() | decoded_content()). +-type(basic_message() :: + #basic_message{exchange_name :: exchange_name(), + routing_key :: routing_key(), + content :: content(), + persistent_key :: maybe(pkey())}). +-type(message() :: basic_message()). +%% this really should be an abstract type +-type(msg_id() :: non_neg_integer()). +-type(msg() :: {queue_name(), pid(), msg_id(), bool(), message()}). +-type(listener() :: + #listener{node :: node(), + protocol :: atom(), + host :: string() | atom(), + port :: non_neg_integer()}). +-type(not_found() :: {'error', 'not_found'}). + +-endif. + +%%---------------------------------------------------------------------------- + +-define(COPYRIGHT_MESSAGE, "Copyright (C) 2007-2008 LShift Ltd., Cohesive Financial Technologies LLC., and Rabbit Technologies Ltd."). +-define(INFORMATION_MESSAGE, "Licensed under the MPL. See http://www.rabbitmq.com/"). + +-ifdef(debug). +-define(LOGDEBUG0(F), rabbit_log:debug(F)). +-define(LOGDEBUG(F,A), rabbit_log:debug(F,A)). +-define(LOGMESSAGE(D,C,M,Co), rabbit_log:message(D,C,M,Co)). +-else. +-define(LOGDEBUG0(F), ok). +-define(LOGDEBUG(F,A), ok). +-define(LOGMESSAGE(D,C,M,Co), ok). +-endif. diff --git a/include/rabbit_framing_spec.hrl b/include/rabbit_framing_spec.hrl new file mode 100644 index 00000000..ef9ab584 --- /dev/null +++ b/include/rabbit_framing_spec.hrl @@ -0,0 +1,55 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (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.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developers of the Original Code are LShift Ltd., +%% Cohesive Financial Technologies LLC., and Rabbit Technologies Ltd. +%% +%% Portions created by LShift Ltd., Cohesive Financial Technologies +%% LLC., and Rabbit Technologies Ltd. are Copyright (C) 2007-2008 +%% LShift Ltd., Cohesive Financial Technologies LLC., and Rabbit +%% Technologies Ltd.; +%% +%% All Rights Reserved. +%% +%% Contributor(s): ______________________________________. +%% + +%% TODO: much of this should be generated + +-type(amqp_field_type() :: + 'longstr' | 'signedint' | 'decimal' | 'timestamp' | + 'table' | 'byte' | 'double' | 'float' | 'long' | + 'short' | 'bool' | 'binary' | 'void'). +-type(amqp_property_type() :: + 'shortstr' | 'longstr' | 'octet' | 'shortint' | 'longint' | + 'longlongint' | 'timestamp' | 'bit' | 'table'). +%% we could make this more precise but ultimately are limited by +%% dialyzer's lack of support for recursive types +-type(amqp_table() :: [{binary(), amqp_field_type(), any()}]). +%% TODO: make this more precise +-type(amqp_class_id() :: non_neg_integer()). +%% TODO: make this more precise +-type(amqp_properties() :: tuple()). +%% TODO: make this more precise +-type(amqp_method() :: tuple()). +%% TODO: make this more precise +-type(amqp_method_name() :: atom()). +-type(channel_number() :: non_neg_integer()). +%% TODO: make this more precise +-type(amqp_error() :: {bool(), non_neg_integer(), binary()}). +-type(name() :: binary()). +-type(routing_key() :: binary()). +-type(username() :: binary()). +-type(password() :: binary()). +-type(vhost() :: binary()). +-type(ctag() :: binary()). +-type(exchange_type() :: 'direct' | 'topic' | 'fanout'). diff --git a/packaging/Makefile b/packaging/Makefile new file mode 100644 index 00000000..44a9b328 --- /dev/null +++ b/packaging/Makefile @@ -0,0 +1,3 @@ +check_tools: + @sh ./checks.sh + @echo All the needed tools seem to be installed, great! diff --git a/packaging/RPMS/Fedora/Makefile b/packaging/RPMS/Fedora/Makefile new file mode 100644 index 00000000..c8e979a7 --- /dev/null +++ b/packaging/RPMS/Fedora/Makefile @@ -0,0 +1,34 @@ +# Read README.txt for instructions + +VERSION=0.0.0 +SOURCE_TARBALL_DIR=../../../dist +TARBALL=$(SOURCE_TARBALL_DIR)/rabbitmq-server-$(VERSION).tar.gz +TOP_DIR=$(shell pwd) +RPM_VERSION=$(shell echo $(VERSION) | tr - _) +DEFINES=--define '_topdir $(TOP_DIR)' --define '_tmppath $(TOP_DIR)/tmp' --define 'main_version $(VERSION)' --define 'rpm_version $(RPM_VERSION)' + +rpms: clean server + +#Create proper environment for making rpms +prepare: + mkdir -p $(TOP_DIR)/BUILD + mkdir -p $(TOP_DIR)/SOURCES + mkdir -p $(TOP_DIR)/SPECS + mkdir -p $(TOP_DIR)/SRPMS + mkdir -p $(TOP_DIR)/RPMS + mkdir -p $(TOP_DIR)/tmp + cp $(TOP_DIR)/$(TARBALL) $(TOP_DIR)/SOURCES + cp $(TOP_DIR)/rabbitmq-server.spec $(TOP_DIR)/SPECS + cp $(TOP_DIR)/init.d $(TOP_DIR)/BUILD + cp $(TOP_DIR)/rabbitmqctl_wrapper $(TOP_DIR)/BUILD + +server: prepare + rpmbuild -ba $(TOP_DIR)/SPECS/rabbitmq-server.spec $(DEFINES) --target noarch + +clean: + rm -rf $(TOP_DIR)/SOURCES/ + rm -rf $(TOP_DIR)/SPECS/ + rm -rf $(TOP_DIR)/RPMS/ + rm -rf $(TOP_DIR)/SRPMS/ + rm -rf $(TOP_DIR)/BUILD/ + rm -rf $(TOP_DIR)/tmp/ diff --git a/packaging/RPMS/Fedora/README.txt b/packaging/RPMS/Fedora/README.txt new file mode 100644 index 00000000..a7db530b --- /dev/null +++ b/packaging/RPMS/Fedora/README.txt @@ -0,0 +1,53 @@ +Notes on creating rpms for rabbitmq + +Assuming that rpm will be built under $TOP_DIR/rpm +directory the rpm macros configuration file +would look like: + +%_topdir $TOP_DIR/rpm +%_tmppath $TOP_DIR/rpm/tmp +%_bindir /usr/bin +%_libdir /usr/lib +%_includedir /usr/include +%_mandir /usr/share/man + +Where $TOP_DIR can be any directory (usually $HOME) +However this configuration has to be under the following +path: +$HOME/.rpmmacros +since this is a fixed place where rpmbuild looks for macros. + +The $TOP_DIR/rpm directory has following structure: + +rpm + +---- BUILD // directory where tarballs are unpacked + +---- SOURCES // where source tarballs are put + +---- SPECS // directory containing specs + +---- SRPMS // rpmbuild puts here srpms + +---- RPMS // rpmbuils puts here rpms + +---- tmp // where rpm packages are built + +Makefile will copy the source tarball from fixed directory +specified by $TARBALL_DIR to SOURCES directory and +similarly specs from $SPEC_DIR to SPECS directory. + +'make rpms' should create both client and server rabbitmq. +If there are any errors reported by rpmbuild this is +possibly due to incorrect name of the packages +(if all dependencies are satisifed) - different distros +can have slightly different names. + +rpms and srpms are placed in their respective directories. + +'make prepare' will create the necessary structure and +create the rpmmacros file. Change top variables to adjust +it to your system. Note that it will *overwrite* any current +rpmmacros configuration file. + +The first thing to do for building rpms is to create you own +source tarball of AMQ. In the spec files two top variables +determine the name of the tarball. Adjust it to you needs. +The final name has to match the *Source* tag in specs' headers. + +For information on how to sign the package see: +http://fedoranews.org/tchung/gpg/ diff --git a/packaging/RPMS/Fedora/init.d b/packaging/RPMS/Fedora/init.d new file mode 100644 index 00000000..09ca02c9 --- /dev/null +++ b/packaging/RPMS/Fedora/init.d @@ -0,0 +1,79 @@ +#!/bin/sh +### BEGIN INIT INFO +# Provides: rabbitmq +# Required-Start: $remote_fs $network +# Required-Stop: $remote_fs $network +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +### END INIT INFO + +#chkconfig: 2345 80 05 +#description: RabbitMQ Server + +PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin +DAEMON=/usr/sbin/rabbitmq-multi +NAME=rabbitmq-server +DESC=rabbitmq-server +USER=rabbitmq +NODE_COUNT=1 + +test -x $DAEMON || exit 0 + +# Include rabbitmq defaults if available +if [ -f /etc/default/rabbitmq ] ; then + . /etc/default/rabbitmq +fi + +set -e +cd / + +start_rabbitmq () { + set +e + su rabbitmq -s /bin/sh -c "$DAEMON start_all ${NODE_COUNT}" > /var/log/rabbitmq/startup.log 2> /var/log/rabbitmq/startup.err + case "$?" in + 0) + echo SUCCESS;; + 1) + echo TIMEOUT - check /var/log/rabbitmq/startup.\{log,err\};; + *) + echo FAILED - check /var/log/rabbitmq/startup.log, .err + exit 1;; + esac + set -e +} + +stop_rabbitmq () { + set +e + su rabbitmq -s /bin/sh -c "$DAEMON stop_all" > /var/log/rabbitmq/shutdown.log 2> /var/log/rabbitmq/shutdown.err + if [ $? != 0 ] ; then + echo FAILED - check /var/log/rabbitmq/shutdown.log, .err + exit 0 + fi + set -e +} + +case "$1" in + start) + echo -n "Starting $DESC: " + start_rabbitmq + echo "$NAME." + ;; + stop) + echo -n "Stopping $DESC: " + stop_rabbitmq + echo "$NAME." + ;; + force-reload|restart) + echo -n "Restarting $DESC: " + stop_rabbitmq + start_rabbitmq + echo "$NAME." + ;; + *) + N=/etc/init.d/$NAME + echo "Usage: $N {start|stop|restart|force-reload}" >&2 + exit 1 + ;; +esac + +exit 0 diff --git a/packaging/RPMS/Fedora/rabbitmq-server.spec b/packaging/RPMS/Fedora/rabbitmq-server.spec new file mode 100644 index 00000000..4f3dafaa --- /dev/null +++ b/packaging/RPMS/Fedora/rabbitmq-server.spec @@ -0,0 +1,138 @@ +%define source_name rabbitmq-server + +Name: rabbitmq-server +Version: %{rpm_version} +Release: 1 +License: Mozilla Public License +Group: Development/Libraries +Source: http://www.rabbitmq.com/releases/%{source_name}-%{main_version}.tar.gz +URL: http://www.rabbitmq.com/ +Vendor: LShift Ltd., Cohesive Financial Technologies LLC., Rabbit Technlogies Ltd. +Requires: erlang +Packager: Hubert Plociniczak <hubert@lshift.net> +BuildRoot: %{_tmppath}/%{name}-%{main_version}-%{release}-root +Summary: The RabbitMQ server + +%description +RabbitMQ is an implementation of AMQP, the emerging standard for high +performance enterprise messaging. The RabbitMQ server is a robust and +scalable implementation of an AMQP broker. + +%define _libdir /usr/lib/erlang +%define _docdir /usr/share/doc +%define _maindir $RPM_BUILD_ROOT%{_libdir}/lib/rabbitmq_server-%{main_version} +%define package_name rabbitmq-server-dist + +%pre +if [ $1 -gt 1 ]; then + #Upgrade - stop and remove previous instance of rabbitmq init.d script + /etc/init.d/rabbitmq-server stop + /sbin/chkconfig --del rabbitmq-server +fi + +%prep +%setup -n %{source_name}-%{main_version} + +%build +mkdir %{package_name} +mkdir %{package_name}/sbin +make install TARGET_DIR=`pwd`/%{package_name} \ + SBIN_DIR=`pwd`/%{package_name}/sbin \ + VERSION=%{main_version} + +%install +mkdir -p %{_maindir} +mkdir -p $RPM_BUILD_ROOT%{_docdir}/rabbitmq-server +mkdir -p $RPM_BUILD_ROOT/etc/init.d +mkdir -p $RPM_BUILD_ROOT/usr/sbin + +mkdir -p $RPM_BUILD_ROOT/var/lib/rabbitmq/mnesia +mkdir -p $RPM_BUILD_ROOT/var/log/rabbitmq + +#Copy all necessary lib files etc. +cp -r %{package_name}/ebin %{_maindir} +cp -r %{package_name}/src %{_maindir} +cp -r %{package_name}/include %{_maindir} +chmod 755 %{package_name}/sbin/* +cp %{package_name}/sbin/* $RPM_BUILD_ROOT/usr/sbin/ + +cp ../init.d $RPM_BUILD_ROOT/etc/init.d/rabbitmq-server +chmod 775 $RPM_BUILD_ROOT/etc/init.d/rabbitmq-server + +mv $RPM_BUILD_ROOT/usr/sbin/rabbitmqctl $RPM_BUILD_ROOT/usr/sbin/rabbitmqctl_real +cp ../rabbitmqctl_wrapper $RPM_BUILD_ROOT/usr/sbin/rabbitmqctl +chmod 755 $RPM_BUILD_ROOT/usr/sbin/rabbitmqctl + +%post +# create rabbitmq group +if ! getent group rabbitmq >/dev/null; then + groupadd -r rabbitmq +fi + +# create rabbitmq user +if ! getent passwd rabbitmq >/dev/null; then + useradd -r -g rabbitmq --home /var/lib/rabbitmq rabbitmq + usermod -c "Rabbit AMQP Messaging Server" rabbitmq +fi + +# On 64bit /usr/lib64 contains Erlang, not /usr/lib. Fix with a symlink +ERL_LIB_DIR=$(erl -noshell -eval "io:format(\"~s~n\", [code:lib_dir()]), halt().") +if [ ! ${ERL_LIB_DIR} = "/usr/lib/erlang/lib" ] ; then + ln -s /usr/lib/erlang/lib/rabbitmq_server-%{main_version} ${ERL_LIB_DIR} +fi + +chown -R rabbitmq:rabbitmq /var/lib/rabbitmq +chown -R rabbitmq:rabbitmq /var/log/rabbitmq + +/sbin/chkconfig --add rabbitmq-server +/etc/init.d/rabbitmq-server start + +%preun +if [ $1 = 0 ]; then + #Complete uninstall + /etc/init.d/rabbitmq-server stop + /sbin/chkconfig --del rabbitmq-server + + # Remove symlink we added above + ERL_LIB_DIR=$(erl -noshell -eval "io:format(\"~s~n\", [code:lib_dir()]), halt().") + if [ ! ${ERL_LIB_DIR} = "/usr/lib/erlang/lib" ] ; then + rm ${ERL_LIB_DIR}/rabbitmq_server-%{main_version} + fi + + # We do not remove log and lib directories + # Leave rabbitmq user and group +fi + +%files +%defattr(-,root,root) +%{_libdir}/lib/rabbitmq_server-%{main_version}/ +%{_docdir}/rabbitmq-server/ +/usr/sbin/rabbitmq-server +/usr/sbin/rabbitmq-multi +/usr/sbin/rabbitmqctl +/usr/sbin/rabbitmqctl_real +/var/lib/rabbitmq +/var/log/rabbitmq +/etc/init.d/rabbitmq-server + +%clean +rm -rf $RPM_BUILD_ROOT + +%changelog +* Mon Mar 3 2008 Adrien Pierard <adrian@lshift.net> 1.3.0 +- New upstream release + +* Wed Sep 26 2007 Simon MacMullen <simon@lshift.net> 1.2.0 +- New upstream release + +* Wed Aug 29 2007 Simon MacMullen <simon@lshift.net> 1.1.1 +- New upstream release + +* Mon Jul 30 2007 Simon MacMullen <simon@lshift.net> 1.1.0-alpha +- New upstream release + +* Tue Jun 12 2007 Hubert Plociniczak <hubert@lshift.net> hubert-20070607 +- Building from source tarball, added starting script, stopping + +* Mon May 21 2007 Hubert Plociniczak <hubert@lshift.net> 1.0.0-alpha +- Initial build of server library of RabbitMQ package diff --git a/packaging/RPMS/Fedora/rabbitmqctl_wrapper b/packaging/RPMS/Fedora/rabbitmqctl_wrapper new file mode 100644 index 00000000..c484bb27 --- /dev/null +++ b/packaging/RPMS/Fedora/rabbitmqctl_wrapper @@ -0,0 +1,14 @@ +#!/bin/bash +# Escape spaces and quotes, because shell is revolting. +for arg in "$@" ; do + # Escape quotes in parameters, so that they're passed through cleanly. + arg=$(sed -e 's/"/\\"/' <<-END + $arg + END + ) + CMDLINE="${CMDLINE} \"${arg}\"" +done + +cd / +su rabbitmq -s /bin/sh -c "/usr/sbin/rabbitmqctl_real ${CMDLINE}" + diff --git a/packaging/checks.sh b/packaging/checks.sh new file mode 100755 index 00000000..63e88701 --- /dev/null +++ b/packaging/checks.sh @@ -0,0 +1,45 @@ +#! /bin/sh + +# We check for the presence of the tools necessary to build a release on a +# Debian based OS. + +TOOLS_STOP=0 + +checker () { + if [ ! `which $1` ] + then + echo "$1 is missing, please install it" + TOOLS_STOP=1 + NEW_NAME=`echo $1 | sed -e 's/-/_/g'` + eval "$NEW_NAME=1" + else + echo "$1 found" + fi +}; + +echo ~~~~~~~~~~~~ Looking for mandatory programs ~~~~~~~~~~~~ + +for i in cdbs-edit-patch reprepro rpm elinks wget zip gpg rsync +do + checker $i +done +echo ~~~~~~~~~~~~~~~~~~~~~~~~~~ DONE ~~~~~~~~~~~~~~~~~~~~~~~ + +if [ 1 = $TOOLS_STOP ] +then + [ $cdbs_edit_patch ] && cdbs_edit_patch="cdbs " + [ $reprepro ] && reprepro="reprepro " + [ $rpm ] && rpm="rpm " + [ $elinks ] && elinks="elinks " + [ $wget ] && wget="wget " + [ $zip ] && zip="zip " + [ $gpg ] && gpg="gpg " + [ $rsync ] && rsync="rsync " + + echo + echo We suggest you run the command + echo "apt-get install ${cdbs_edit_patch}${reprepro}${rpm}${elinks}${wget}${zip}${gpg}${rsync}" + echo +fi + +exit $TOOLS_STOP diff --git a/packaging/debs/Debian/Makefile b/packaging/debs/Debian/Makefile new file mode 100644 index 00000000..b49094e2 --- /dev/null +++ b/packaging/debs/Debian/Makefile @@ -0,0 +1,32 @@ +TARBALL_DIR=../../../dist +TARBALL=$(shell (cd $(TARBALL_DIR); echo rabbitmq-server-[0-9]*.tar.gz)) +VERSION=$(shell echo $(TARBALL) | sed -e 's:rabbitmq-server-\(.*\)\.tar\.gz:\1:g') +UNPACKED_DIR=rabbitmq-server-$(VERSION) +PACKAGENAME=rabbitmq-server +SIGNING_KEY_ID=056E8E56 + +ifneq "$(UNOFFICIAL_RELEASE)" "" + SIGNING=-us -uc +else + SIGNING=-k$(SIGNING_KEY_ID) +endif + +all: + @echo 'Please choose a target from the Makefile.' + +package: clean + make -C ../.. check_tools + tar -zxvf $(TARBALL_DIR)/$(TARBALL) + cp -r debian $(UNPACKED_DIR) + chmod -R a+x $(UNPACKED_DIR)/debian + UNOFFICIAL_RELEASE=$(UNOFFICIAL_RELEASE) VERSION=$(VERSION) ./check-changelog.sh rabbitmq-server $(UNPACKED_DIR) + cd $(UNPACKED_DIR); dpkg-buildpackage -rfakeroot $(SIGNING) + rm -rf $(UNPACKED_DIR) + +clean: + rm -rf $(UNPACKED_DIR) + rm -f $(PACKAGENAME)_*.tar.gz + rm -f $(PACKAGENAME)_*.diff.gz + rm -f $(PACKAGENAME)_*.dsc + rm -f $(PACKAGENAME)_*_*.changes + rm -f $(PACKAGENAME)_*_*.deb diff --git a/packaging/debs/Debian/check-changelog.sh b/packaging/debs/Debian/check-changelog.sh new file mode 100755 index 00000000..ff25e648 --- /dev/null +++ b/packaging/debs/Debian/check-changelog.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +PACKAGE_NAME=$1 +cd $2 + +CHANGELOG_VERSION=$(dpkg-parsechangelog | sed -n 's/^Version: \(.*\)-[^-]*$/\1/p') + +if [ "${CHANGELOG_VERSION}" != "${VERSION}" ]; then + if [ -n "${UNOFFICIAL_RELEASE}" ]; then + echo "${PACKAGE_NAME} (${VERSION}-1) unstable; urgency=low" > debian/changelog.tmp + echo >> debian/changelog.tmp + echo " * Unofficial release" >> debian/changelog.tmp + echo >> debian/changelog.tmp + echo " -- Nobody <nobody@example.com> $(date -R)" >> debian/changelog.tmp + echo >> debian/changelog.tmp + cat debian/changelog >> debian/changelog.tmp + mv -f debian/changelog.tmp debian/changelog + + exit 0 + else + echo + echo There is no entry in debian/changelog for version ${VERSION}! + echo Please create a changelog entry, or set the variable + echo UNOFFICIAL_RELEASE to automatically create one. + echo + + exit 1 + fi +fi diff --git a/packaging/debs/Debian/debian/changelog b/packaging/debs/Debian/debian/changelog new file mode 100644 index 00000000..08b760e9 --- /dev/null +++ b/packaging/debs/Debian/debian/changelog @@ -0,0 +1,42 @@ +rabbitmq-server (1.3.0-1) testing; urgency=low + + * New Upstream Release + + -- Adrien Pierard <adrien@lshift.net> Mon, 03 Mar 2008 15:34:38 +0000 + +rabbitmq-server (1.2.0-2) testing; urgency=low + + * Fixed rabbitmqctl wrapper script + + -- Simon MacMullen <simon@lshift.net> Fri, 05 Oct 2007 11:55:00 +0100 + +rabbitmq-server (1.2.0-1) testing; urgency=low + + * New upstream release + + -- Simon MacMullen <simon@lshift.net> Wed, 26 Sep 2007 11:49:26 +0100 + +rabbitmq-server (1.1.1-1) testing; urgency=low + + * New upstream release + + -- Simon MacMullen <simon@lshift.net> Wed, 29 Aug 2007 12:03:15 +0100 + +rabbitmq-server (1.1.0-alpha-2) testing; urgency=low + + * Fixed erlang-nox dependency + + -- Simon MacMullen <simon@lshift.net> Thu, 02 Aug 2007 11:27:13 +0100 + +rabbitmq-server (1.1.0-alpha-1) testing; urgency=low + + * New upstream release + + -- Simon MacMullen <simon@lshift.net> Fri, 20 Jul 2007 18:17:33 +0100 + +rabbitmq-server (1.0.0-alpha-1) unstable; urgency=low + + * Initial release + + -- Tony Garnock-Jones <tonyg@shortstop.lshift.net> Wed, 31 Jan 2007 19:06:33 +0000 + diff --git a/packaging/debs/Debian/debian/compat b/packaging/debs/Debian/debian/compat new file mode 100644 index 00000000..7ed6ff82 --- /dev/null +++ b/packaging/debs/Debian/debian/compat @@ -0,0 +1 @@ +5 diff --git a/packaging/debs/Debian/debian/control b/packaging/debs/Debian/debian/control new file mode 100644 index 00000000..ae698e1e --- /dev/null +++ b/packaging/debs/Debian/debian/control @@ -0,0 +1,15 @@ +Source: rabbitmq-server +Section: net +Priority: extra +Maintainer: Tony Garnock-Jones <tonyg@rabbitmq.com> +Build-Depends: cdbs, debhelper (>= 5), erlang-base | erlang-base-hipe, erlang-nox, erlang-dev, erlang-src, make, python +Standards-Version: 3.7.2 + +Package: rabbitmq-server +Architecture: all +Depends: erlang-nox, adduser +Description: An AMQP server written in Erlang + RabbitMQ is an implementation of AMQP, the emerging standard for high + performance enterprise messaging. The RabbitMQ server is a robust and + scalable implementation of an AMQP broker. + Homepage: http://www.rabbitmq.com/ diff --git a/packaging/debs/Debian/debian/copyright b/packaging/debs/Debian/debian/copyright new file mode 100644 index 00000000..e16996e5 --- /dev/null +++ b/packaging/debs/Debian/debian/copyright @@ -0,0 +1,491 @@ +This package was debianized by Tony Garnock-Jones <tonyg@rabbitmq.com> on +Wed, 3 Jan 2007 15:43:44 +0000. + +It was downloaded from http://www.rabbitmq.com/ + +Upstream Author: The RabbitMQ team <info@rabbitmq.com> + +Copyright: 2006-2008 LShift Ltd. + +License: +The RabbitMQ server is licensed under the MPL. + + + + + MOZILLA PUBLIC LICENSE + Version 1.1 + + --------------- + +1. Definitions. + + 1.0.1. "Commercial Use" means distribution or otherwise making the + Covered Code available to a third party. + + 1.1. "Contributor" means each entity that creates or contributes to + the creation of Modifications. + + 1.2. "Contributor Version" means the combination of the Original + Code, prior Modifications used by a Contributor, and the Modifications + made by that particular Contributor. + + 1.3. "Covered Code" means the Original Code or Modifications or the + combination of the Original Code and Modifications, in each case + including portions thereof. + + 1.4. "Electronic Distribution Mechanism" means a mechanism generally + accepted in the software development community for the electronic + transfer of data. + + 1.5. "Executable" means Covered Code in any form other than Source + Code. + + 1.6. "Initial Developer" means the individual or entity identified + as the Initial Developer in the Source Code notice required by Exhibit + A. + + 1.7. "Larger Work" means a work which combines Covered Code or + portions thereof with code not governed by the terms of this License. + + 1.8. "License" means this document. + + 1.8.1. "Licensable" means having the right to grant, to the maximum + extent possible, whether at the time of the initial grant or + subsequently acquired, any and all of the rights conveyed herein. + + 1.9. "Modifications" means any addition to or deletion from the + substance or structure of either the Original Code or any previous + Modifications. When Covered Code is released as a series of files, a + Modification is: + A. Any addition to or deletion from the contents of a file + containing Original Code or previous Modifications. + + B. Any new file that contains any part of the Original Code or + previous Modifications. + + 1.10. "Original Code" means Source Code of computer software code + which is described in the Source Code notice required by Exhibit A as + Original Code, and which, at the time of its release under this + License is not already Covered Code governed by this License. + + 1.10.1. "Patent Claims" means any patent claim(s), now owned or + hereafter acquired, including without limitation, method, process, + and apparatus claims, in any patent Licensable by grantor. + + 1.11. "Source Code" means the preferred form of the Covered Code for + making modifications to it, including all modules it contains, plus + any associated interface definition files, scripts used to control + compilation and installation of an Executable, or source code + differential comparisons against either the Original Code or another + well known, available Covered Code of the Contributor's choice. The + Source Code can be in a compressed or archival form, provided the + appropriate decompression or de-archiving software is widely available + for no charge. + + 1.12. "You" (or "Your") means an individual or a legal entity + exercising rights under, and complying with all of the terms of, this + License or a future version of this License issued under Section 6.1. + For legal entities, "You" includes any entity which controls, is + controlled by, or is under common control with You. For purposes of + this definition, "control" means (a) the power, direct or indirect, + to cause the direction or management of such entity, whether by + contract or otherwise, or (b) ownership of more than fifty percent + (50%) of the outstanding shares or beneficial ownership of such + entity. + +2. Source Code License. + + 2.1. The Initial Developer Grant. + The Initial Developer hereby grants You a world-wide, royalty-free, + non-exclusive license, subject to third party intellectual property + claims: + (a) under intellectual property rights (other than patent or + trademark) Licensable by Initial Developer to use, reproduce, + modify, display, perform, sublicense and distribute the Original + Code (or portions thereof) with or without Modifications, and/or + as part of a Larger Work; and + + (b) under Patents Claims infringed by the making, using or + selling of Original Code, to make, have made, use, practice, + sell, and offer for sale, and/or otherwise dispose of the + Original Code (or portions thereof). + + (c) the licenses granted in this Section 2.1(a) and (b) are + effective on the date Initial Developer first distributes + Original Code under the terms of this License. + + (d) Notwithstanding Section 2.1(b) above, no patent license is + granted: 1) for code that You delete from the Original Code; 2) + separate from the Original Code; or 3) for infringements caused + by: i) the modification of the Original Code or ii) the + combination of the Original Code with other software or devices. + + 2.2. Contributor Grant. + Subject to third party intellectual property claims, each Contributor + hereby grants You a world-wide, royalty-free, non-exclusive license + + (a) under intellectual property rights (other than patent or + trademark) Licensable by Contributor, to use, reproduce, modify, + display, perform, sublicense and distribute the Modifications + created by such Contributor (or portions thereof) either on an + unmodified basis, with other Modifications, as Covered Code + and/or as part of a Larger Work; and + + (b) under Patent Claims infringed by the making, using, or + selling of Modifications made by that Contributor either alone + and/or in combination with its Contributor Version (or portions + of such combination), to make, use, sell, offer for sale, have + made, and/or otherwise dispose of: 1) Modifications made by that + Contributor (or portions thereof); and 2) the combination of + Modifications made by that Contributor with its Contributor + Version (or portions of such combination). + + (c) the licenses granted in Sections 2.2(a) and 2.2(b) are + effective on the date Contributor first makes Commercial Use of + the Covered Code. + + (d) Notwithstanding Section 2.2(b) above, no patent license is + granted: 1) for any code that Contributor has deleted from the + Contributor Version; 2) separate from the Contributor Version; + 3) for infringements caused by: i) third party modifications of + Contributor Version or ii) the combination of Modifications made + by that Contributor with other software (except as part of the + Contributor Version) or other devices; or 4) under Patent Claims + infringed by Covered Code in the absence of Modifications made by + that Contributor. + +3. Distribution Obligations. + + 3.1. Application of License. + The Modifications which You create or to which You contribute are + governed by the terms of this License, including without limitation + Section 2.2. The Source Code version of Covered Code may be + distributed only under the terms of this License or a future version + of this License released under Section 6.1, and You must include a + copy of this License with every copy of the Source Code You + distribute. You may not offer or impose any terms on any Source Code + version that alters or restricts the applicable version of this + License or the recipients' rights hereunder. However, You may include + an additional document offering the additional rights described in + Section 3.5. + + 3.2. Availability of Source Code. + Any Modification which You create or to which You contribute must be + made available in Source Code form under the terms of this License + either on the same media as an Executable version or via an accepted + Electronic Distribution Mechanism to anyone to whom you made an + Executable version available; and if made available via Electronic + Distribution Mechanism, must remain available for at least twelve (12) + months after the date it initially became available, or at least six + (6) months after a subsequent version of that particular Modification + has been made available to such recipients. You are responsible for + ensuring that the Source Code version remains available even if the + Electronic Distribution Mechanism is maintained by a third party. + + 3.3. Description of Modifications. + You must cause all Covered Code to which You contribute to contain a + file documenting the changes You made to create that Covered Code and + the date of any change. You must include a prominent statement that + the Modification is derived, directly or indirectly, from Original + Code provided by the Initial Developer and including the name of the + Initial Developer in (a) the Source Code, and (b) in any notice in an + Executable version or related documentation in which You describe the + origin or ownership of the Covered Code. + + 3.4. Intellectual Property Matters + (a) Third Party Claims. + If Contributor has knowledge that a license under a third party's + intellectual property rights is required to exercise the rights + granted by such Contributor under Sections 2.1 or 2.2, + Contributor must include a text file with the Source Code + distribution titled "LEGAL" which describes the claim and the + party making the claim in sufficient detail that a recipient will + know whom to contact. If Contributor obtains such knowledge after + the Modification is made available as described in Section 3.2, + Contributor shall promptly modify the LEGAL file in all copies + Contributor makes available thereafter and shall take other steps + (such as notifying appropriate mailing lists or newsgroups) + reasonably calculated to inform those who received the Covered + Code that new knowledge has been obtained. + + (b) Contributor APIs. + If Contributor's Modifications include an application programming + interface and Contributor has knowledge of patent licenses which + are reasonably necessary to implement that API, Contributor must + also include this information in the LEGAL file. + + (c) Representations. + Contributor represents that, except as disclosed pursuant to + Section 3.4(a) above, Contributor believes that Contributor's + Modifications are Contributor's original creation(s) and/or + Contributor has sufficient rights to grant the rights conveyed by + this License. + + 3.5. Required Notices. + You must duplicate the notice in Exhibit A in each file of the Source + Code. If it is not possible to put such notice in a particular Source + Code file due to its structure, then You must include such notice in a + location (such as a relevant directory) where a user would be likely + to look for such a notice. If You created one or more Modification(s) + You may add your name as a Contributor to the notice described in + Exhibit A. You must also duplicate this License in any documentation + for the Source Code where You describe recipients' rights or ownership + rights relating to Covered Code. You may choose to offer, and to + charge a fee for, warranty, support, indemnity or liability + obligations to one or more recipients of Covered Code. However, You + may do so only on Your own behalf, and not on behalf of the Initial + Developer or any Contributor. You must make it absolutely clear than + any such warranty, support, indemnity or liability obligation is + offered by You alone, and You hereby agree to indemnify the Initial + Developer and every Contributor for any liability incurred by the + Initial Developer or such Contributor as a result of warranty, + support, indemnity or liability terms You offer. + + 3.6. Distribution of Executable Versions. + You may distribute Covered Code in Executable form only if the + requirements of Section 3.1-3.5 have been met for that Covered Code, + and if You include a notice stating that the Source Code version of + the Covered Code is available under the terms of this License, + including a description of how and where You have fulfilled the + obligations of Section 3.2. The notice must be conspicuously included + in any notice in an Executable version, related documentation or + collateral in which You describe recipients' rights relating to the + Covered Code. You may distribute the Executable version of Covered + Code or ownership rights under a license of Your choice, which may + contain terms different from this License, provided that You are in + compliance with the terms of this License and that the license for the + Executable version does not attempt to limit or alter the recipient's + rights in the Source Code version from the rights set forth in this + License. If You distribute the Executable version under a different + license You must make it absolutely clear that any terms which differ + from this License are offered by You alone, not by the Initial + Developer or any Contributor. You hereby agree to indemnify the + Initial Developer and every Contributor for any liability incurred by + the Initial Developer or such Contributor as a result of any such + terms You offer. + + 3.7. Larger Works. + You may create a Larger Work by combining Covered Code with other code + not governed by the terms of this License and distribute the Larger + Work as a single product. In such a case, You must make sure the + requirements of this License are fulfilled for the Covered Code. + +4. Inability to Comply Due to Statute or Regulation. + + If it is impossible for You to comply with any of the terms of this + License with respect to some or all of the Covered Code due to + statute, judicial order, or regulation then You must: (a) comply with + the terms of this License to the maximum extent possible; and (b) + describe the limitations and the code they affect. Such description + must be included in the LEGAL file described in Section 3.4 and must + be included with all distributions of the Source Code. Except to the + extent prohibited by statute or regulation, such description must be + sufficiently detailed for a recipient of ordinary skill to be able to + understand it. + +5. Application of this License. + + This License applies to code to which the Initial Developer has + attached the notice in Exhibit A and to related Covered Code. + +6. Versions of the License. + + 6.1. New Versions. + Netscape Communications Corporation ("Netscape") may publish revised + and/or new versions of the License from time to time. Each version + will be given a distinguishing version number. + + 6.2. Effect of New Versions. + Once Covered Code has been published under a particular version of the + License, You may always continue to use it under the terms of that + version. You may also choose to use such Covered Code under the terms + of any subsequent version of the License published by Netscape. No one + other than Netscape has the right to modify the terms applicable to + Covered Code created under this License. + + 6.3. Derivative Works. + If You create or use a modified version of this License (which you may + only do in order to apply it to code which is not already Covered Code + governed by this License), You must (a) rename Your license so that + the phrases "Mozilla", "MOZILLAPL", "MOZPL", "Netscape", + "MPL", "NPL" or any confusingly similar phrase do not appear in your + license (except to note that your license differs from this License) + and (b) otherwise make it clear that Your version of the license + contains terms which differ from the Mozilla Public License and + Netscape Public License. (Filling in the name of the Initial + Developer, Original Code or Contributor in the notice described in + Exhibit A shall not of themselves be deemed to be modifications of + this License.) + +7. DISCLAIMER OF WARRANTY. + + COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, + WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, + WITHOUT LIMITATION, WARRANTIES THAT THE COVERED CODE IS FREE OF + DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING. + THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED CODE + IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, + YOU (NOT THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE + COST OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER + OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF + ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER. + +8. TERMINATION. + + 8.1. This License and the rights granted hereunder will terminate + automatically if You fail to comply with terms herein and fail to cure + such breach within 30 days of becoming aware of the breach. All + sublicenses to the Covered Code which are properly granted shall + survive any termination of this License. Provisions which, by their + nature, must remain in effect beyond the termination of this License + shall survive. + + 8.2. If You initiate litigation by asserting a patent infringement + claim (excluding declatory judgment actions) against Initial Developer + or a Contributor (the Initial Developer or Contributor against whom + You file such action is referred to as "Participant") alleging that: + + (a) such Participant's Contributor Version directly or indirectly + infringes any patent, then any and all rights granted by such + Participant to You under Sections 2.1 and/or 2.2 of this License + shall, upon 60 days notice from Participant terminate prospectively, + unless if within 60 days after receipt of notice You either: (i) + agree in writing to pay Participant a mutually agreeable reasonable + royalty for Your past and future use of Modifications made by such + Participant, or (ii) withdraw Your litigation claim with respect to + the Contributor Version against such Participant. If within 60 days + of notice, a reasonable royalty and payment arrangement are not + mutually agreed upon in writing by the parties or the litigation claim + is not withdrawn, the rights granted by Participant to You under + Sections 2.1 and/or 2.2 automatically terminate at the expiration of + the 60 day notice period specified above. + + (b) any software, hardware, or device, other than such Participant's + Contributor Version, directly or indirectly infringes any patent, then + any rights granted to You by such Participant under Sections 2.1(b) + and 2.2(b) are revoked effective as of the date You first made, used, + sold, distributed, or had made, Modifications made by that + Participant. + + 8.3. If You assert a patent infringement claim against Participant + alleging that such Participant's Contributor Version directly or + indirectly infringes any patent where such claim is resolved (such as + by license or settlement) prior to the initiation of patent + infringement litigation, then the reasonable value of the licenses + granted by such Participant under Sections 2.1 or 2.2 shall be taken + into account in determining the amount or value of any payment or + license. + + 8.4. In the event of termination under Sections 8.1 or 8.2 above, + all end user license agreements (excluding distributors and resellers) + which have been validly granted by You or any distributor hereunder + prior to termination shall survive termination. + +9. LIMITATION OF LIABILITY. + + UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT + (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE INITIAL + DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF COVERED CODE, + OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO ANY PERSON FOR + ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY + CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF GOODWILL, + WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER + COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN + INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF + LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY + RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT APPLICABLE LAW + PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE + EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO + THIS EXCLUSION AND LIMITATION MAY NOT APPLY TO YOU. + +10. U.S. GOVERNMENT END USERS. + + The Covered Code is a "commercial item," as that term is defined in + 48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial computer + software" and "commercial computer software documentation," as such + terms are used in 48 C.F.R. 12.212 (Sept. 1995). Consistent with 48 + C.F.R. 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4 (June 1995), + all U.S. Government End Users acquire Covered Code with only those + rights set forth herein. + +11. MISCELLANEOUS. + + This License represents the complete agreement concerning subject + matter hereof. If any provision of this License is held to be + unenforceable, such provision shall be reformed only to the extent + necessary to make it enforceable. This License shall be governed by + California law provisions (except to the extent applicable law, if + any, provides otherwise), excluding its conflict-of-law provisions. + With respect to disputes in which at least one party is a citizen of, + or an entity chartered or registered to do business in the United + States of America, any litigation relating to this License shall be + subject to the jurisdiction of the Federal Courts of the Northern + District of California, with venue lying in Santa Clara County, + California, with the losing party responsible for costs, including + without limitation, court costs and reasonable attorneys' fees and + expenses. The application of the United Nations Convention on + Contracts for the International Sale of Goods is expressly excluded. + Any law or regulation which provides that the language of a contract + shall be construed against the drafter shall not apply to this + License. + +12. RESPONSIBILITY FOR CLAIMS. + + As between Initial Developer and the Contributors, each party is + responsible for claims and damages arising, directly or indirectly, + out of its utilization of rights under this License and You agree to + work with Initial Developer and Contributors to distribute such + responsibility on an equitable basis. Nothing herein is intended or + shall be deemed to constitute any admission of liability. + +13. MULTIPLE-LICENSED CODE. + + Initial Developer may designate portions of the Covered Code as + "Multiple-Licensed". "Multiple-Licensed" means that the Initial + Developer permits you to utilize portions of the Covered Code under + Your choice of the NPL or the alternative licenses, if any, specified + by the Initial Developer in the file described in Exhibit A. + +EXHIBIT A -Mozilla Public License. + + ``The contents of this file are subject to the Mozilla Public License + Version 1.1 (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.mozilla.org/MPL/ + + Software distributed under the License is distributed on an "AS IS" + basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the + License for the specific language governing rights and limitations + under the License. + + The Original Code is RabbitMQ. + + The Initial Developers of the Original Code are LShift Ltd., + Cohesive Financial Technologies LLC., and Rabbit Technologies Ltd. + + Portions created by LShift Ltd., Cohesive Financial Technologies + LLC., and Rabbit Technologies Ltd. are Copyright (C) 2007-2008 + LShift Ltd., Cohesive Financial Technologies LLC., and Rabbit + Technologies Ltd.; + + All Rights Reserved. + + Contributor(s): ______________________________________.'' + + [NOTE: The text of this Exhibit A may differ slightly from the text of + the notices in the Source Code files of the Original Code. You should + use the text of this Exhibit A rather than the text found in the + Original Code Source Code for Your Modifications.] + + + + + +If you have any questions regarding licensing, please contact us at +info@rabbitmq.com. + +The Debian packaging is (C) 2007, Tony Garnock-Jones <tonyg@rabbitmq.com> and +is licensed under the GPL, see `/usr/share/common-licenses/GPL'. + + diff --git a/packaging/debs/Debian/debian/dirs b/packaging/debs/Debian/debian/dirs new file mode 100644 index 00000000..74ff60e2 --- /dev/null +++ b/packaging/debs/Debian/debian/dirs @@ -0,0 +1,6 @@ +usr/lib/erlang/lib +usr/sbin +usr/share/linda/overrides +var/lib/rabbitmq/mnesia +var/log/rabbitmq + diff --git a/packaging/debs/Debian/debian/init.d b/packaging/debs/Debian/debian/init.d new file mode 100644 index 00000000..f3999888 --- /dev/null +++ b/packaging/debs/Debian/debian/init.d @@ -0,0 +1,77 @@ +#!/bin/sh +### BEGIN INIT INFO +# Provides: rabbitmq +# Required-Start: $remote_fs $network +# Required-Stop: $remote_fs $network +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: Enable AMQP service provided by RabbitMQ. +### END INIT INFO + +PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin +DAEMON=/usr/sbin/rabbitmq-multi +NAME=rabbitmq-server +DESC=rabbitmq-server +USER=rabbitmq +NODE_COUNT=1 + +test -x $DAEMON || exit 0 + +# Include rabbitmq defaults if available +if [ -f /etc/default/rabbitmq ] ; then + . /etc/default/rabbitmq +fi + +set -e +cd / + +start_rabbitmq () { + set +e + su rabbitmq -s /bin/sh -c "$DAEMON start_all ${NODE_COUNT}" > /var/log/rabbitmq/startup.log 2> /var/log/rabbitmq/startup.err + case "$?" in + 0) + echo SUCCESS;; + 1) + echo TIMEOUT - check /var/log/rabbitmq/startup.\{log,err\};; + *) + echo FAILED - check /var/log/rabbitmq/startup.log, .err + exit 1;; + esac + set -e +} + +stop_rabbitmq () { + set +e + su rabbitmq -s /bin/sh -c "$DAEMON stop_all" > /var/log/rabbitmq/shutdown.log 2> /var/log/rabbitmq/shutdown.err + if [ $? != 0 ] ; then + echo FAILED - check /var/log/rabbitmq/shutdown.log, .err + exit 0 + fi + set -e +} + +case "$1" in + start) + echo -n "Starting $DESC: " + start_rabbitmq + echo "$NAME." + ;; + stop) + echo -n "Stopping $DESC: " + stop_rabbitmq + echo "$NAME." + ;; + force-reload|restart) + echo -n "Restarting $DESC: " + stop_rabbitmq + start_rabbitmq + echo "$NAME." + ;; + *) + N=/etc/init.d/$NAME + echo "Usage: $N {start|stop|restart|force-reload}" >&2 + exit 1 + ;; +esac + +exit 0 diff --git a/packaging/debs/Debian/debian/postinst b/packaging/debs/Debian/debian/postinst new file mode 100644 index 00000000..f92131d0 --- /dev/null +++ b/packaging/debs/Debian/debian/postinst @@ -0,0 +1,55 @@ +#!/bin/sh +# postinst script for rabbitmq +# +# see: dh_installdeb(1) + +set -e + +# summary of how this script can be called: +# * <postinst> `configure' <most-recently-configured-version> +# * <old-postinst> `abort-upgrade' <new version> +# * <conflictor's-postinst> `abort-remove' `in-favour' <package> +# <new-version> +# * <postinst> `abort-remove' +# * <deconfigured's-postinst> `abort-deconfigure' `in-favour' +# <failed-install-package> <version> `removing' +# <conflicting-package> <version> +# for details, see http://www.debian.org/doc/debian-policy/ or +# the debian-policy package + + +# create rabbitmq group +if ! getent group rabbitmq >/dev/null; then + addgroup --system rabbitmq +fi + +# create rabbitmq user +if ! getent passwd rabbitmq >/dev/null; then + adduser --system --ingroup rabbitmq --home /var/lib/rabbitmq --no-create-home rabbitmq + usermod -c "Rabbit AMQP Messaging Server" rabbitmq +fi + +chown -R rabbitmq:rabbitmq /var/lib/rabbitmq +chown -R rabbitmq:rabbitmq /var/log/rabbitmq + +case "$1" in + configure) + ;; + + abort-upgrade|abort-remove|abort-deconfigure) + ;; + + *) + echo "postinst called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +#DEBHELPER# + +exit 0 + + diff --git a/packaging/debs/Debian/debian/postrm b/packaging/debs/Debian/debian/postrm new file mode 100644 index 00000000..8d1e92a9 --- /dev/null +++ b/packaging/debs/Debian/debian/postrm @@ -0,0 +1,58 @@ +#!/bin/sh +# postrm script for rabbitmq +# +# see: dh_installdeb(1) + +set -e + +# summary of how this script can be called: +# * <postrm> `remove' +# * <postrm> `purge' +# * <old-postrm> `upgrade' <new-version> +# * <new-postrm> `failed-upgrade' <old-version> +# * <new-postrm> `abort-install' +# * <new-postrm> `abort-install' <old-version> +# * <new-postrm> `abort-upgrade' <old-version> +# * <disappearer's-postrm> `disappear' <overwriter> +# <overwriter-version> +# for details, see http://www.debian.org/doc/debian-policy/ or +# the debian-policy package + + +case "$1" in + purge) + rm -f /etc/default/rabbitmq + if [ -d /var/lib/rabbitmq ]; then + rm -r /var/lib/rabbitmq + fi + if [ -d /var/log/rabbitmq ]; then + rm -r /var/log/rabbitmq + fi + if [ -d /var/run/rabbitmq ]; then + rm -r /var/run/rabbitmq + fi + if getent passwd rabbitmq >/dev/null; then + deluser rabbitmq + fi + if getent group rabbitmq >/dev/null; then + delgroup rabbitmq + fi + ;; + + remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear) + ;; + + *) + echo "postrm called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +#DEBHELPER# + +exit 0 + + diff --git a/packaging/debs/Debian/debian/preinst.ex b/packaging/debs/Debian/debian/preinst.ex new file mode 100644 index 00000000..044ce6ba --- /dev/null +++ b/packaging/debs/Debian/debian/preinst.ex @@ -0,0 +1,37 @@ +#!/bin/sh +# preinst script for rabbitmq +# +# see: dh_installdeb(1) + +set -e + +# summary of how this script can be called: +# * <new-preinst> `install' +# * <new-preinst> `install' <old-version> +# * <new-preinst> `upgrade' <old-version> +# * <old-preinst> `abort-upgrade' <new-version> +# for details, see http://www.debian.org/doc/debian-policy/ or +# the debian-policy package + + +case "$1" in + install|upgrade) + ;; + + abort-upgrade) + ;; + + *) + echo "preinst called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +#DEBHELPER# + +exit 0 + + diff --git a/packaging/debs/Debian/debian/prerm.ex b/packaging/debs/Debian/debian/prerm.ex new file mode 100644 index 00000000..c74dc325 --- /dev/null +++ b/packaging/debs/Debian/debian/prerm.ex @@ -0,0 +1,40 @@ +#!/bin/sh +# prerm script for rabbitmq +# +# see: dh_installdeb(1) + +set -e + +# summary of how this script can be called: +# * <prerm> `remove' +# * <old-prerm> `upgrade' <new-version> +# * <new-prerm> `failed-upgrade' <old-version> +# * <conflictor's-prerm> `remove' `in-favour' <package> <new-version> +# * <deconfigured's-prerm> `deconfigure' `in-favour' +# <package-being-installed> <version> `removing' +# <conflicting-package> <version> +# for details, see http://www.debian.org/doc/debian-policy/ or +# the debian-policy package + + +case "$1" in + remove|upgrade|deconfigure) + ;; + + failed-upgrade) + ;; + + *) + echo "prerm called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +#DEBHELPER# + +exit 0 + + diff --git a/packaging/debs/Debian/debian/rabbitmqctl_wrapper b/packaging/debs/Debian/debian/rabbitmqctl_wrapper new file mode 100644 index 00000000..c484bb27 --- /dev/null +++ b/packaging/debs/Debian/debian/rabbitmqctl_wrapper @@ -0,0 +1,14 @@ +#!/bin/bash +# Escape spaces and quotes, because shell is revolting. +for arg in "$@" ; do + # Escape quotes in parameters, so that they're passed through cleanly. + arg=$(sed -e 's/"/\\"/' <<-END + $arg + END + ) + CMDLINE="${CMDLINE} \"${arg}\"" +done + +cd / +su rabbitmq -s /bin/sh -c "/usr/sbin/rabbitmqctl_real ${CMDLINE}" + diff --git a/packaging/debs/Debian/debian/rules b/packaging/debs/Debian/debian/rules new file mode 100644 index 00000000..15b0d50a --- /dev/null +++ b/packaging/debs/Debian/debian/rules @@ -0,0 +1,18 @@ +#!/usr/bin/make -f + +include /usr/share/cdbs/1/rules/debhelper.mk +include /usr/share/cdbs/1/class/makefile.mk + +RABBIT_LIB=$(DEB_DESTDIR)usr/lib/erlang/lib/rabbitmq_server-$(DEB_UPSTREAM_VERSION) + +DEB_MAKE_INSTALL_TARGET := install TARGET_DIR=$(RABBIT_LIB)/ SBIN_DIR=$(DEB_DESTDIR)usr/sbin + +DOCDIR=$(DEB_DESTDIR)usr/share/doc/rabbitmq-server/ + +install/rabbitmq-server:: + mkdir -p $(DOCDIR) + rm $(RABBIT_LIB)/LICENSE* + mv $(DEB_DESTDIR)usr/sbin/rabbitmqctl $(DEB_DESTDIR)usr/sbin/rabbitmqctl_real + cp debian/rabbitmqctl_wrapper $(DEB_DESTDIR)usr/sbin/rabbitmqctl + chmod a+x $(DEB_DESTDIR)usr/sbin/rabbitmqctl + echo "Tag: usr-lib-in-arch-all" > $(DEB_DESTDIR)usr/share/linda/overrides/rabbitmq-server diff --git a/packaging/debs/apt-repository/Makefile b/packaging/debs/apt-repository/Makefile new file mode 100644 index 00000000..ce4347bc --- /dev/null +++ b/packaging/debs/apt-repository/Makefile @@ -0,0 +1,28 @@ +SIGNING_USER_EMAIL=info@rabbitmq.com + +ifeq "$(UNOFFICIAL_RELEASE)" "" +HOME_ARG=HOME=$(GNUPG_PATH) +endif + +all: debian_apt_repository + +clean: + rm -rf debian + +CAN_HAS_REPREPRO=$(shell [ -f /usr/bin/reprepro ] && echo true) +ifeq ($(CAN_HAS_REPREPRO), true) +debian_apt_repository: clean + mkdir -p debian/conf + cp -a distributions debian/conf +ifeq "$(UNOFFICIAL_RELEASE)" "" + echo SignWith: $(SIGNING_USER_EMAIL) >> debian/conf/distributions +endif + for FILE in ../Debian/*.changes ; do \ + $(HOME_ARG) reprepro --ignore=wrongdistribution \ + -Vb debian include kitten $${FILE} ; \ + done + reprepro -Vb debian createsymlinks +else +debian_apt_repository: + @echo Not building APT repository as reprepro could not be found +endif diff --git a/packaging/debs/apt-repository/README b/packaging/debs/apt-repository/README new file mode 100644 index 00000000..514a37f3 --- /dev/null +++ b/packaging/debs/apt-repository/README @@ -0,0 +1,17 @@ +APT repository for RabbitMQ + +Previously we've attempted to run a repository in the same way that +Debian would: have repository management software installed on the +server, and upload new packages to the repository as and when they're +ready. + +This turned out to be both fiddly and annoying to do (and more +particularly to automate) so since our repository is always going to be +small it's easier just to create the entire repository as part of the +build process, just like a package. It can then be moved into place as a +single unit. The make target "debian_apt_repository" (invoked by "dist") +will create it, and it can get moved onto the server with the rest of +the packages. + +Read "README-real-repository" for information on how we used to do +this. diff --git a/packaging/debs/apt-repository/README-real-repository b/packaging/debs/apt-repository/README-real-repository new file mode 100644 index 00000000..b1526227 --- /dev/null +++ b/packaging/debs/apt-repository/README-real-repository @@ -0,0 +1,130 @@ +APT Repository for RabbitMQ in Debian +===================================== + +First, a note on what we're trying to do. We want a single "testing" +repository. When RabbitMQ is more stable we will also want a +"stable" repository. It is very important to understand that these refer +to the state of the rabbit code, *NOT* which Debian distribution they go +with. At the moment our dependencies are very simple so our packages can +be used with any current Debian version (etch, lenny, sid) as well as +with Ubuntu. So although we have a "testing" distribution, this is not +codenamed "lenny". Instead it's currently codenamed "kitten" since +that's a baby rabbit. + +Secondly, a note on software. We need a tool to manage the repository, +and a tool to perform uploads to the repository. Debian being Debian +there are quite a few of each. We will use "rerepro" to manage the +repository since it's modern, maintained, and fairly simple. We will use +"dupload" to perform the uploads since it gives us the ability to run +arbitrary commands after the upload, which means we don't need to run a +cron job on the web server to process uploads. + +Creating a repository +===================== + +Much of this was cribbed from: +http://www.debian-administration.org/articles/286 + +The repository is fundamentally just some files in a folder, served over +HTTP (or FTP etc). So let's make it "debian" in the root of +www.rabbitmq.com. + +This means the repository will be at http://www.rabbitmq.com/debian/ and +can be added to a sources.list as: + +deb http://www.rabbitmq.com/debian/ testing main +deb-src http://www.rabbitmq.com/debian/ testing main + +Inside this folder we need a "conf" folder, and in +that we need a "distributions" configuration file - see the file in this +folder. Note that: + +* We list all architectures so that people can install rabbitmq-server + on to anything. +* We don't list the "all" architecture even though we use it; it's + implied. +* We only have a "main" component, we could have non-free and contrib + here if it was relevant. +* We list the email address associated with the key we want to use to + sign the repository. Yes, even after signing packages we still want to + sign the repository. + +We're now ready to go. Assuming the path to our repository is /path, +(and hence configuration is in /path/conf) we can upload a file to the +repository (creating it in the process) by doing something like this on +the repository host: + +$ reprepro --ignore=wrongdistribution -Vb /path include kitten \ + rabbitmq-server_1.0.0-alpha-1_i386.changes + +Note that we upload to the distribution "kitten" rather than "testing". +We also pass --ignore=wrongdistribution since the current packages are +built to go in "unstable" (this will be changed obviously). + +Note also that the .changes file claims to be for i386 even though the +package is for architecture "all". This is a bug in debhelper. + +Finally, if you've just created a repository, you want to run: + +$ reprepro -Vb /path createsymlinks + +since this will create "kitten" -> "testing" symlinks. You only need to +do this once. + +Removing packages +================= + +Fairly simple: + +$ reprepro --ignore=wrongdistribution -Vb /path remove kitten \ + rabbitmq-server + +Subsequent updates and "dupload" +================================ + +You can run the "reprepro" command above again to update the versions of +software in the repository. Since we probably don't want to have to log +into the machine in question to do this, we can use "dupload". This is a +tool which uploads Debian packages. The supplied file "dupload.conf" can +be renamed to ~/.dupload.conf. If you then run: + +$ dupload -to rabbit --nomail . + +in the folder with the .changes file, dupload will: + +* create an incoming folder in your home directory on the repository +machine +* upload everything there +* run reprepro to move the packages into the repository +* "rm -rf" the uploads folder + +This is a bit cheesy but should be enough for our purposes. The +dupload.conf uses scp and ssh so you need a public-key login (or tpye +your password lots). + +There's still an open question as to whether dupload is really needed +for our case. + +Keys and signing +================ + +We currently sign the package as we build it; but we also need to sign +the repository. The key is currently on my machine (mrforgetful) and has +ID 056E8E56. We should put it on CDs though. + +reprepro will automatically sign the repository if we have the right +SignWith line in the configuration, AND the secret key is installed on +the repository server. This is obviously not ideal; not sure what the +solution is right now. + +You can export the public key with: + +$ gpg --export --armor 056E8E56 > rabbit.pub + +(Open question: do we want to get our key on subkeys.pgp.net?) + +We can then add this key to the website and tell our users to import the +key into apt with: + +# apt-key add rabbit.pub + diff --git a/packaging/debs/apt-repository/distributions b/packaging/debs/apt-repository/distributions new file mode 100644 index 00000000..183eb034 --- /dev/null +++ b/packaging/debs/apt-repository/distributions @@ -0,0 +1,7 @@ +Origin: RabbitMQ +Label: RabbitMQ Repository for Debian / Ubuntu etc +Suite: testing +Codename: kitten +Architectures: arm hppa ia64 mips mipsel s390 sparc i386 amd64 powerpc source +Components: main +Description: RabbitMQ Repository for Debian / Ubuntu etc diff --git a/packaging/debs/apt-repository/dupload.conf b/packaging/debs/apt-repository/dupload.conf new file mode 100644 index 00000000..9ceed760 --- /dev/null +++ b/packaging/debs/apt-repository/dupload.conf @@ -0,0 +1,16 @@ +package config; + +$rabbit_user = "simon"; +$rabbit_host = "mrforgetful.lshift.net"; +$rabbit_repo_path = "/srv/debian"; +$rabbit_reprepro_extra_args = "--ignore=wrongdistribution"; + +$cfg{'rabbit'} = { + fqdn => "$rabbit_host", + login => "$rabbit_user", + method => "scp", + incoming => "incoming", +}; + +$preupload{'deb'} = "ssh ${rabbit_host} mkdir incoming"; +$postupload{'deb'} = "ssh ${rabbit_host} \"cd incoming && reprepro ${$rabbit_reprepro_extra_args} -Vb ${rabbit_repo_path} include kitten *.changes && cd .. && rm -r incoming\""; diff --git a/packaging/generic-unix/Makefile b/packaging/generic-unix/Makefile new file mode 100644 index 00000000..13257522 --- /dev/null +++ b/packaging/generic-unix/Makefile @@ -0,0 +1,25 @@ +VERSION=0.0.0 +SOURCE_DIR=rabbitmq-server-$(VERSION) +TARGET_DIR=rabbitmq_server-$(VERSION) +TARGET_TARBALL=rabbitmq-server-generic-unix-$(VERSION) + +dist: + make -C ../.. VERSION=$(VERSION) srcdist + tar -zxvf ../../dist/$(SOURCE_DIR).tar.gz + + mkdir $(TARGET_DIR) + mkdir $(TARGET_DIR)/sbin + make -C $(SOURCE_DIR) \ + TARGET_DIR=`pwd`/$(TARGET_DIR) \ + SBIN_DIR=`pwd`/$(TARGET_DIR)/sbin \ + install + + tar -zcf $(TARGET_TARBALL).tar.gz $(TARGET_DIR) + rm -rf $(SOURCE_DIR) $(TARGET_DIR) + +clean: clean_partial + rm -f rabbitmq-server-generic-unix-*.tar.gz + +clean_partial: + rm -rf $(SOURCE_DIR) + rm -rf $(TARGET_DIR) diff --git a/packaging/macports/net/rabbitmq-server/Portfile b/packaging/macports/net/rabbitmq-server/Portfile new file mode 100644 index 00000000..82ae62aa --- /dev/null +++ b/packaging/macports/net/rabbitmq-server/Portfile @@ -0,0 +1,79 @@ +# $Id$ -*- coding: utf-8; mode: tcl; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- vim:fenc=utf-8:filetype=tcl:et:sw=4:ts=4:sts=4 + +PortSystem 1.0 +name rabbitmq-server +version 1.3.0 +revision 0 +categories net +maintainers tonyg@rabbitmq.com +platforms darwin +description The RabbitMQ AMQP Server +long_description \ + RabbitMQ is an implementation of AMQP, the emerging standard for \ + high performance enterprise messaging. The RabbitMQ server is a \ + robust and scalable implementation of an AMQP broker. + +homepage http://www.rabbitmq.com/ +master_sites http://www.rabbitmq.com/releases/source/ + +distname rabbitmq-${version} + +checksums \ + md5 46ee6dbbacdc67b25cc6ccd9c394b6f2 \ + sha1 67e1e640136a1993567ace97dc5f67b1ad8e6304 \ + rmd160 9e92502d36ab5cd1e3f0d39a46bb512b9440f35a + +depends_build port:erlang +depends_run port:erlang + +use_configure no + +worksrcdir rabbitmq-${version}/erlang/rabbit + +use_parallel_build yes + +destroot.destdir \ + DIST_DIR=${destroot}${prefix}/lib/erlang/lib/rabbitmq_server-${version} \ + SBIN_DIR=${destroot}${prefix}/sbin +destroot.target dist-unix + +destroot.keepdirs \ + ${destroot}${prefix}/var/lib/rabbitmq/pids \ + ${destroot}${prefix}/var/log/rabbitmq \ + ${destroot}${prefix}/var/lib/rabbitmq/mnesia + +pre-destroot { + addgroup rabbitmq + adduser rabbitmq gid=[existsgroup rabbitmq] realname=RabbitMQ\ Server home=${prefix}/var/lib/rabbitmq +} + +post-destroot { + xinstall -d ${destroot}${prefix}/etc/default + xinstall -d -g [existsgroup rabbitmq] -m 775 ${destroot}${prefix}/var/log/rabbitmq + xinstall -d -g [existsgroup rabbitmq] -m 775 ${destroot}${prefix}/var/lib/rabbitmq + xinstall -d -g [existsgroup rabbitmq] -m 775 ${destroot}${prefix}/var/lib/rabbitmq/pids + xinstall -d -g [existsgroup rabbitmq] -m 775 ${destroot}${prefix}/var/lib/rabbitmq/mnesia + file rename ${destroot}${prefix}/sbin/rabbitmqctl ${destroot}${prefix}/sbin/rabbitmqctl_real + xinstall -m 555 ${filespath}/rabbitmqctl_wrapper ${destroot}${prefix}/sbin + file rename ${destroot}${prefix}/sbin/rabbitmqctl_wrapper ${destroot}${prefix}/sbin/rabbitmqctl + file copy ${filespath}/rabbitmq-defaults ${destroot}${prefix}/etc/default/rabbitmq + reinplace "s:^CLUSTER_CONFIG_FILE=:CLUSTER_CONFIG_FILE=${prefix}:" \ + ${destroot}${prefix}/sbin/rabbitmq-multi \ + ${destroot}${prefix}/sbin/rabbitmq-server \ + ${destroot}${prefix}/sbin/rabbitmqctl \ + ${destroot}${prefix}/sbin/rabbitmqctl_real + reinplace "s:^CONFIG_FILE=:CONFIG_FILE=${prefix}:" \ + ${destroot}${prefix}/sbin/rabbitmq-multi \ + ${destroot}${prefix}/sbin/rabbitmq-server \ + ${destroot}${prefix}/sbin/rabbitmqctl \ + ${destroot}${prefix}/sbin/rabbitmqctl_real + reinplace "s|@PREFIX@|${prefix}|" \ + ${destroot}${prefix}/sbin/rabbitmqctl \ + ${destroot}${prefix}/etc/default/rabbitmq +} + +startupitem.create yes +startupitem.init "PATH=${prefix}/bin:${prefix}/sbin:\$PATH; export PATH" +startupitem.start "su rabbitmq -c rabbitmq-server 2>&1" +startupitem.stop "rabbitmqctl stop 2>&1" +startupitem.logfile ${prefix}/var/log/rabbitmq/startupitem.log diff --git a/packaging/macports/net/rabbitmq-server/files/rabbitmq-defaults b/packaging/macports/net/rabbitmq-server/files/rabbitmq-defaults new file mode 100644 index 00000000..1f9aad11 --- /dev/null +++ b/packaging/macports/net/rabbitmq-server/files/rabbitmq-defaults @@ -0,0 +1,7 @@ +#!/bin/sh +# defaults file for rabbitmq-server +# + +PIDS_FILE=@PREFIX@/var/lib/rabbitmq/pids +LOG_BASE=@PREFIX@/var/log/rabbitmq +MNESIA_BASE=@PREFIX@/var/lib/rabbitmq/mnesia diff --git a/packaging/macports/net/rabbitmq-server/files/rabbitmqctl_wrapper b/packaging/macports/net/rabbitmq-server/files/rabbitmqctl_wrapper new file mode 100644 index 00000000..392c82ff --- /dev/null +++ b/packaging/macports/net/rabbitmq-server/files/rabbitmqctl_wrapper @@ -0,0 +1,13 @@ +#!/bin/bash +# Escape spaces and quotes, because shell is revolting. +for arg in "$@" ; do + # Escape quotes in parameters, so that they're passed through cleanly. + arg=$(sed -e 's/"/\\"/' <<-END + $arg + END + ) + CMDLINE="${CMDLINE} \"${arg}\"" +done + +cd / +exec su rabbitmq -c "@PREFIX@/sbin/rabbitmqctl_real ${CMDLINE}" diff --git a/packaging/windows/Makefile b/packaging/windows/Makefile new file mode 100644 index 00000000..077461c5 --- /dev/null +++ b/packaging/windows/Makefile @@ -0,0 +1,28 @@ +VERSION=0.0.0 +SOURCE_DIR=rabbitmq-server-$(VERSION) +TARGET_DIR=rabbitmq_server-$(VERSION) +TARGET_ZIP=rabbitmq-server-windows-$(VERSION) + +dist: + make -C ../.. VERSION=$(VERSION) srcdist + tar -zxvf ../../dist/$(SOURCE_DIR).tar.gz + make -C $(SOURCE_DIR) + + mkdir $(SOURCE_DIR)/sbin + mv $(SOURCE_DIR)/scripts/rabbitmq-server.bat $(SOURCE_DIR)/sbin + mv $(SOURCE_DIR)/scripts/rabbitmqctl.bat $(SOURCE_DIR)/sbin + mv $(SOURCE_DIR)/scripts/rabbitmq-multi.bat $(SOURCE_DIR)/sbin + rm -rf $(SOURCE_DIR)/scripts + rm -rf $(SOURCE_DIR)/codegen* $(SOURCE_DIR)/Makefile + rm -f $(SOURCE_DIR)/BUILD + + mv $(SOURCE_DIR) $(TARGET_DIR) + zip -r $(TARGET_ZIP).zip $(TARGET_DIR) + rm -rf $(TARGET_DIR) + +clean: clean_partial + rm -f rabbitmq-server-windows-*.zip + +clean_partial: + rm -rf $(SOURCE_DIR) + rm -rf $(TARGET_DIR) diff --git a/scripts/rabbitmq-multi b/scripts/rabbitmq-multi new file mode 100755 index 00000000..4709ca0b --- /dev/null +++ b/scripts/rabbitmq-multi @@ -0,0 +1,47 @@ +#!/bin/sh +## The contents of this file are subject to the Mozilla Public License +## Version 1.1 (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.mozilla.org/MPL/ +## +## Software distributed under the License is distributed on an "AS IS" +## basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +## License for the specific language governing rights and limitations +## under the License. +## +## The Original Code is RabbitMQ. +## +## The Initial Developers of the Original Code are LShift Ltd., +## Cohesive Financial Technologies LLC., and Rabbit Technologies Ltd. +## +## Portions created by LShift Ltd., Cohesive Financial Technologies +## LLC., and Rabbit Technologies Ltd. are Copyright (C) 2007-2008 +## LShift Ltd., Cohesive Financial Technologies LLC., and Rabbit +## Technologies Ltd.; +## +## All Rights Reserved. +## +## Contributor(s): ______________________________________. +## + +[ "x" = "x$NODENAME" ] && NODENAME=rabbit +[ "x" = "x$NODE_IP_ADDRESS" ] && NODE_IP_ADDRESS=0.0.0.0 +[ "x" = "x$NODE_PORT" ] && NODE_PORT=5672 + +ERL_ARGS= +SCRIPT_HOME=$(dirname $0) + +[ "x" = "x$PIDS_FILE" ] && PIDS_FILE=/var/lib/rabbitmq/pids + +export NODENAME NODE_IP_ADDRESS NODE_PORT SCRIPT_HOME PIDS_FILE + +exec erl \ + -pa ../ebin \ + -noinput \ + -hidden \ + ${ERL_ARGS} \ + -sname rabbitmq_multi$$ \ + -s rabbit_multi \ + ${START_ARGS} \ + -extra "$@" + diff --git a/scripts/rabbitmq-multi.bat b/scripts/rabbitmq-multi.bat new file mode 100644 index 00000000..34e34ef9 --- /dev/null +++ b/scripts/rabbitmq-multi.bat @@ -0,0 +1,63 @@ +@echo off
+REM The contents of this file are subject to the Mozilla Public License
+REM Version 1.1 (the "License"); you may not use this file except in
+REM compliance with the License. You may obtain a copy of the License at
+REM http://www.mozilla.org/MPL/
+REM
+REM Software distributed under the License is distributed on an "AS IS"
+REM basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
+REM License for the specific language governing rights and limitations
+REM under the License.
+REM
+REM The Original Code is RabbitMQ.
+REM
+REM The Initial Developers of the Original Code are LShift Ltd.,
+REM Cohesive Financial Technologies LLC., and Rabbit Technologies Ltd.
+REM
+REM Portions created by LShift Ltd., Cohesive Financial Technologies
+REM LLC., and Rabbit Technologies Ltd. are Copyright (C) 2007-2008
+REM LShift Ltd., Cohesive Financial Technologies LLC., and Rabbit
+REM Technologies Ltd.;
+REM
+REM All Rights Reserved.
+REM
+REM Contributor(s): ______________________________________.
+REM
+
+if "%RABBITMQ_BASE%"=="" (
+ set RABBITMQ_BASE=%APPDATA%\RabbitMQ
+)
+
+if "%NODENAME%"=="" (
+ set NODENAME=rabbit
+)
+
+if "%NODE_IP_ADDRESS%"=="" (
+ set NODE_IP_ADDRESS=0.0.0.0
+)
+
+if "%NODE_PORT%"=="" (
+ set NODE_PORT=5672
+)
+
+set PIDS_FILE=%RABBITMQ_BASE%\rabbitmq.pids
+set SCRIPT_HOME=.
+
+if "%ERLANG_HOME%"=="" (
+ set ERLANG_HOME=%~dp0%..\..\..
+)
+
+if not exist "%ERLANG_HOME%\bin\erl.exe" (
+ echo.
+ echo ******************************
+ echo ERLANG_HOME not set correctly.
+ echo ******************************
+ echo.
+ echo Please either set ERLANG_HOME to point to your Erlang installation or place the
+ echo RabbitMQ server distribution in the Erlang lib folder.
+ echo.
+ exit /B
+)
+
+"%ERLANG_HOME%\bin\erl.exe" -pa ../ebin -noinput -hidden -sname rabbitmq_multi -s rabbit_multi %START_ARGS% -extra %*
+
diff --git a/scripts/rabbitmq-server b/scripts/rabbitmq-server new file mode 100755 index 00000000..a44dd6da --- /dev/null +++ b/scripts/rabbitmq-server @@ -0,0 +1,72 @@ +#!/bin/sh +## The contents of this file are subject to the Mozilla Public License +## Version 1.1 (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.mozilla.org/MPL/ +## +## Software distributed under the License is distributed on an "AS IS" +## basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +## License for the specific language governing rights and limitations +## under the License. +## +## The Original Code is RabbitMQ. +## +## The Initial Developers of the Original Code are LShift Ltd., +## Cohesive Financial Technologies LLC., and Rabbit Technologies Ltd. +## +## Portions created by LShift Ltd., Cohesive Financial Technologies +## LLC., and Rabbit Technologies Ltd. are Copyright (C) 2007-2008 +## LShift Ltd., Cohesive Financial Technologies LLC., and Rabbit +## Technologies Ltd.; +## +## All Rights Reserved. +## +## Contributor(s): ______________________________________. +## + +[ "x" = "x$NODENAME" ] && NODENAME=rabbit +[ "x" = "x$NODE_IP_ADDRESS" ] && NODE_IP_ADDRESS=0.0.0.0 +[ "x" = "x$NODE_PORT" ] && NODE_PORT=5672 + +ERL_ARGS="+K true +A30 -kernel inet_default_listen_options [{sndbuf,16384},{recbuf,4096}]" +CLUSTER_CONFIG_FILE=/etc/default/rabbitmq_cluster.config + +[ "x" = "x$LOG_BASE" ] && LOG_BASE=/var/log/rabbitmq +[ "x" = "x$MNESIA_BASE" ] && MNESIA_BASE=/var/lib/rabbitmq/mnesia +[ "x" = "x$MNESIA_DIR" ] && MNESIA_DIR=${MNESIA_BASE}/${NODENAME} +[ "x" = "x$NODE_ONLY" ] && START_RABBIT='-noinput -s rabbit' + +## Log rotation +LOGS="${LOG_BASE}/${NODENAME}.log" +SASL_LOGS="${LOG_BASE}/${NODENAME}-sasl.log" +BACKUP_EXTENSION=".bak" + +[ -f "${LOGS}" ] && cat "${LOGS}" >> "${LOGS}${BACKUP_EXTENSION}" +[ -f "${SASL_LOGS}" ] && cat "${SASL_LOGS}" >> "${SASL_LOGS}${BACKUP_EXTENSION}" + +if [ -f "$CLUSTER_CONFIG_FILE" ]; then + CLUSTER_CONFIG="-rabbit cluster_config \"$CLUSTER_CONFIG_FILE\"" +else + CLUSTER_CONFIG="" +fi + +erl \ + -pa $(dirname $0)/../ebin \ + ${START_RABBIT} \ + -sname ${NODENAME} \ + -boot start_sasl \ + +W w \ + ${ERL_ARGS} \ + -rabbit tcp_listeners '[{"'${NODE_IP_ADDRESS}'", '${NODE_PORT}'}]' \ + -sasl errlog_type error \ + -kernel error_logger '{file,"'${LOGS}'"}' \ + -sasl sasl_error_logger '{file,"'${SASL_LOGS}'"}' \ + -os_mon start_cpu_sup true \ + -os_mon start_disksup false \ + -os_mon start_memsup false \ + -os_mon start_os_sup false \ + -mnesia dir "\"${MNESIA_DIR}\"" \ + ${CLUSTER_CONFIG} \ + ${RABBIT_ARGS} \ + "$@" + diff --git a/scripts/rabbitmq-server.bat b/scripts/rabbitmq-server.bat new file mode 100644 index 00000000..46f4bd92 --- /dev/null +++ b/scripts/rabbitmq-server.bat @@ -0,0 +1,115 @@ +@echo off
+REM The contents of this file are subject to the Mozilla Public License
+REM Version 1.1 (the "License"); you may not use this file except in
+REM compliance with the License. You may obtain a copy of the License at
+REM http://www.mozilla.org/MPL/
+REM
+REM Software distributed under the License is distributed on an "AS IS"
+REM basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
+REM License for the specific language governing rights and limitations
+REM under the License.
+REM
+REM The Original Code is RabbitMQ.
+REM
+REM The Initial Developers of the Original Code are LShift Ltd.,
+REM Cohesive Financial Technologies LLC., and Rabbit Technologies Ltd.
+REM
+REM Portions created by LShift Ltd., Cohesive Financial Technologies
+REM LLC., and Rabbit Technologies Ltd. are Copyright (C) 2007-2008
+REM LShift Ltd., Cohesive Financial Technologies LLC., and Rabbit
+REM Technologies Ltd.;
+REM
+REM All Rights Reserved.
+REM
+REM Contributor(s): ______________________________________.
+REM
+
+if "%RABBITMQ_BASE%"=="" (
+ set RABBITMQ_BASE=%APPDATA%\RabbitMQ
+)
+
+if "%NODENAME%"=="" (
+ set NODENAME=rabbit
+)
+
+if "%NODE_IP_ADDRESS%"=="" (
+ set NODE_IP_ADDRESS=0.0.0.0
+)
+
+if "%NODE_PORT%"=="" (
+ set NODE_PORT=5672
+)
+
+if "%ERLANG_HOME%"=="" (
+ set ERLANG_HOME=%~dp0%..\..\..
+)
+
+if not exist "%ERLANG_HOME%\bin\erl.exe" (
+ echo.
+ echo ******************************
+ echo ERLANG_HOME not set correctly.
+ echo ******************************
+ echo.
+ echo Please either set ERLANG_HOME to point to your Erlang installation or place the
+ echo RabbitMQ server distribution in the Erlang lib folder.
+ echo.
+ exit /B
+)
+
+set RABBITMQ_BASE_UNIX=%RABBITMQ_BASE:\=/%
+
+set MNESIA_BASE=%RABBITMQ_BASE_UNIX%/db
+set LOG_BASE=%RABBITMQ_BASE_UNIX%/log
+
+
+rem We save the previous logs in their respective backup
+rem Log management (rotation, filtering based of size...) is left as an exercice for the user.
+
+set BACKUP_EXTENSION=.bak
+
+set LOGS="%RABBITMQ_BASE%\log\%NODENAME%.log"
+set SASL_LOGS="%RABBITMQ_BASE%\log\%NODENAME%-sasl.log"
+
+set LOGS_BACKUP="%RABBITMQ_BASE%\log\%NODENAME%.log%BACKUP_EXTENSION%"
+set SASL_LOGS_BAKCUP="%RABBITMQ_BASE%\log\%NODENAME%-sasl.log%BACKUP_EXTENSION%"
+
+if exist %LOGS% (
+ type %LOGS% >> %LOGS_BACKUP%
+)
+if exist %SASL_LOGS% (
+ type %SASL_LOGS% >> %SASL_LOGS_BAKCUP%
+)
+
+rem End of log management
+
+
+set CLUSTER_CONFIG_FILE=%RABBITMQ_BASE%\rabbitmq_cluster.config
+set CLUSTER_CONFIG=
+if not exist "%CLUSTER_CONFIG_FILE%" GOTO L1
+set CLUSTER_CONFIG=-rabbit cluster_config \""%CLUSTER_CONFIG_FILE:\=/%"\"
+:L1
+
+set MNESIA_DIR=%MNESIA_BASE%/%NODENAME%-mnesia
+
+"%ERLANG_HOME%\bin\erl.exe" ^
+-pa ..\ebin ^
+-noinput ^
+-boot start_sasl ^
+-sname %NODENAME% ^
+-s rabbit ^
++W w ^
++A30 ^
+-kernel inet_default_listen_options "[{sndbuf, 16384}, {recbuf, 4096}]" ^
+-rabbit tcp_listeners "[{\"%NODE_IP_ADDRESS%\", %NODE_PORT%}]" ^
+-kernel error_logger {file,\""%LOG_BASE%/%NODENAME%.log"\"} ^
+-sasl errlog_type error ^
+-sasl sasl_error_logger {file,\""%LOG_BASE%/%NODENAME%-sasl.log"\"} ^
+-os_mon start_cpu_sup true ^
+-os_mon start_disksup false ^
+-os_mon start_memsup false ^
+-os_mon start_os_sup false ^
+-mnesia dir \""%MNESIA_DIR%"\" ^
+%CLUSTER_CONFIG% ^
+%RABBIT_ARGS% ^
+%*
+
diff --git a/scripts/rabbitmqctl b/scripts/rabbitmqctl new file mode 100755 index 00000000..eb359dad --- /dev/null +++ b/scripts/rabbitmqctl @@ -0,0 +1,40 @@ +#!/bin/sh +## The contents of this file are subject to the Mozilla Public License +## Version 1.1 (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.mozilla.org/MPL/ +## +## Software distributed under the License is distributed on an "AS IS" +## basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +## License for the specific language governing rights and limitations +## under the License. +## +## The Original Code is RabbitMQ. +## +## The Initial Developers of the Original Code are LShift Ltd., +## Cohesive Financial Technologies LLC., and Rabbit Technologies Ltd. +## +## Portions created by LShift Ltd., Cohesive Financial Technologies +## LLC., and Rabbit Technologies Ltd. are Copyright (C) 2007-2008 +## LShift Ltd., Cohesive Financial Technologies LLC., and Rabbit +## Technologies Ltd.; +## +## All Rights Reserved. +## +## Contributor(s): ______________________________________. +## + +[ "x" = "x$NODENAME" ] && NODENAME=rabbit +MNESIA_BASE=/var/lib/rabbitmq/mnesia +LOG_BASE=/var/log/rabbitmq +ERL_ARGS= +MNESIA_DIR=${MNESIA_BASE}/${NODENAME} + +exec erl \ + -pa ../ebin \ + -noinput \ + -hidden \ + ${ERL_ARGS} \ + -sname rabbitmqctl$$ \ + -s rabbit_control \ + -extra "$@" diff --git a/scripts/rabbitmqctl.bat b/scripts/rabbitmqctl.bat new file mode 100644 index 00000000..b34adebe --- /dev/null +++ b/scripts/rabbitmqctl.bat @@ -0,0 +1,43 @@ +@echo off
+REM The contents of this file are subject to the Mozilla Public License
+REM Version 1.1 (the "License"); you may not use this file except in
+REM compliance with the License. You may obtain a copy of the License at
+REM http://www.mozilla.org/MPL/
+REM
+REM Software distributed under the License is distributed on an "AS IS"
+REM basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
+REM License for the specific language governing rights and limitations
+REM under the License.
+REM
+REM The Original Code is RabbitMQ.
+REM
+REM The Initial Developers of the Original Code are LShift Ltd.,
+REM Cohesive Financial Technologies LLC., and Rabbit Technologies Ltd.
+REM
+REM Portions created by LShift Ltd., Cohesive Financial Technologies
+REM LLC., and Rabbit Technologies Ltd. are Copyright (C) 2007-2008
+REM LShift Ltd., Cohesive Financial Technologies LLC., and Rabbit
+REM Technologies Ltd.;
+REM
+REM All Rights Reserved.
+REM
+REM Contributor(s): ______________________________________.
+REM
+
+if "%ERLANG_HOME%"=="" (
+ set ERLANG_HOME=%~dp0%..\..\..
+)
+
+if not exist "%ERLANG_HOME%\bin\erl.exe" (
+ echo.
+ echo ******************************
+ echo ERLANG_HOME not set correctly.
+ echo ******************************
+ echo.
+ echo Please either set ERLANG_HOME to point to your Erlang installation or place the
+ echo RabbitMQ server distribution in the Erlang lib folder.
+ echo.
+ exit /B
+)
+
+"%ERLANG_HOME%\bin\erl.exe" -pa ..\ebin -noinput -hidden -sname rabbitmqctl -s rabbit_control -extra %*
diff --git a/src/buffering_proxy.erl b/src/buffering_proxy.erl new file mode 100644 index 00000000..d2505701 --- /dev/null +++ b/src/buffering_proxy.erl @@ -0,0 +1,92 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (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.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developers of the Original Code are LShift Ltd., +%% Cohesive Financial Technologies LLC., and Rabbit Technologies Ltd. +%% +%% Portions created by LShift Ltd., Cohesive Financial Technologies +%% LLC., and Rabbit Technologies Ltd. are Copyright (C) 2007-2008 +%% LShift Ltd., Cohesive Financial Technologies LLC., and Rabbit +%% Technologies Ltd.; +%% +%% All Rights Reserved. +%% +%% Contributor(s): ______________________________________. +%% + +-module(buffering_proxy). + +-export([start_link/2]). + +%% internal + +-export([mainloop/4, drain/2]). +-export([proxy_loop/3]). + +%%---------------------------------------------------------------------------- + +start_link(M, A) -> + spawn_link( + fun () -> process_flag(trap_exit, true), + ProxyPid = self(), + Ref = make_ref(), + Pid = spawn_link( + fun () -> mainloop(ProxyPid, Ref, M, + M:init(ProxyPid, A)) end), + proxy_loop(Ref, Pid, empty) + end). + +%%---------------------------------------------------------------------------- + +mainloop(ProxyPid, Ref, M, State) -> + ProxyPid ! Ref, + NewState = + receive + {Ref, Messages} -> + lists:foldl(fun (Msg, S) -> + drain(M, M:handle_message(Msg, S)) + end, State, lists:reverse(Messages)); + Msg -> M:handle_message(Msg, State) + end, + ?MODULE:mainloop(ProxyPid, Ref, M, NewState). + +drain(M, State) -> + receive + Msg -> ?MODULE:drain(M, M:handle_message(Msg, State)) + after 0 -> + State + end. + +proxy_loop(Ref, Pid, State) -> + receive + Ref -> + ?MODULE:proxy_loop( + Ref, Pid, + case State of + empty -> waiting; + waiting -> exit(duplicate_next); + Messages -> Pid ! {Ref, Messages}, empty + end); + {'EXIT', Pid, Reason} -> + exit(Reason); + {'EXIT', _, Reason} -> + exit(Pid, Reason), + ?MODULE:proxy_loop(Ref, Pid, State); + Msg -> + ?MODULE:proxy_loop( + Ref, Pid, + case State of + empty -> [Msg]; + waiting -> Pid ! {Ref, [Msg]}, empty; + Messages -> [Msg | Messages] + end) + end. diff --git a/src/rabbit.erl b/src/rabbit.erl new file mode 100644 index 00000000..e65d532b --- /dev/null +++ b/src/rabbit.erl @@ -0,0 +1,282 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (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.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developers of the Original Code are LShift Ltd., +%% Cohesive Financial Technologies LLC., and Rabbit Technologies Ltd. +%% +%% Portions created by LShift Ltd., Cohesive Financial Technologies +%% LLC., and Rabbit Technologies Ltd. are Copyright (C) 2007-2008 +%% LShift Ltd., Cohesive Financial Technologies LLC., and Rabbit +%% Technologies Ltd.; +%% +%% All Rights Reserved. +%% +%% Contributor(s): ______________________________________. +%% + +-module(rabbit). + +-behaviour(application). + +-export([start/0, stop/0, stop_and_halt/0, status/0]). + +-export([start/2, stop/1]). + +-import(application). +-import(mnesia). +-import(lists). +-import(inet). +-import(gen_tcp). + +-include("rabbit_framing.hrl"). +-include("rabbit.hrl"). + +-define(APPS, [os_mon, mnesia, rabbit]). + +%%---------------------------------------------------------------------------- + +-ifdef(use_specs). + +-spec(start/0 :: () -> 'ok'). +-spec(stop/0 :: () -> 'ok'). +-spec(stop_and_halt/0 :: () -> 'ok'). +-spec(status/0 :: () -> + [{running_applications, [{atom(), string(), string()}]} | + {nodes, [node()]} | + {running_nodes, [node()]}]). + +-endif. + +%%---------------------------------------------------------------------------- + +start() -> + try + ok = ensure_working_log_config(), + ok = rabbit_mnesia:ensure_mnesia_dir(), + ok = start_applications(?APPS) + after + %%give the error loggers some time to catch up + timer:sleep(100) + end. + +stop() -> + ok = stop_applications(?APPS). + +stop_and_halt() -> + spawn(fun () -> + SleepTime = 1000, + rabbit_log:info("Stop-and-halt request received; halting in ~p milliseconds~n", + [SleepTime]), + timer:sleep(SleepTime), + init:stop() + end), + case catch stop() of _ -> ok end. + +status() -> + [{running_applications, application:which_applications()}] ++ + rabbit_mnesia:status(). + +%%-------------------------------------------------------------------- + +manage_applications(Iterate, Do, Undo, SkipError, ErrorTag, Apps) -> + Iterate(fun (App, Acc) -> + case Do(App) of + ok -> [App | Acc]; + {error, {SkipError, _}} -> Acc; + {error, Reason} -> + lists:foreach(Undo, Acc), + throw({error, {ErrorTag, App, Reason}}) + end + end, [], Apps), + ok. + +start_applications(Apps) -> + manage_applications(fun lists:foldl/3, + fun application:start/1, + fun application:stop/1, + already_started, + cannot_start_application, + Apps). + +stop_applications(Apps) -> + manage_applications(fun lists:foldr/3, + fun application:stop/1, + fun application:start/1, + not_started, + cannot_stop_application, + Apps). + +start(normal, []) -> + + {ok, SupPid} = rabbit_sup:start_link(), + + print_banner(), + + {ok, ExtraSteps} = application:get_env(extra_startup_steps), + + lists:foreach( + fun ({Msg, Thunk}) -> + io:format("starting ~-20s ...", [Msg]), + Thunk(), + io:format("done~n"); + ({Msg, M, F, A}) -> + io:format("starting ~-20s ...", [Msg]), + apply(M, F, A), + io:format("done~n") + end, + [{"database", + fun () -> ok = rabbit_mnesia:init() end}, + {"core processes", + fun () -> + ok = start_child(rabbit_log), + + ok = rabbit_amqqueue:start(), + + ok = rabbit_binary_generator: + check_empty_content_body_frame_size(), + + ok = start_child(rabbit_router), + ok = start_child(rabbit_node_monitor) + end}, + {"recovery", + fun () -> + ok = maybe_insert_default_data(), + + ok = rabbit_exchange:recover(), + ok = rabbit_amqqueue:recover(), + ok = rabbit_realm:recover() + end}, + {"persister", + fun () -> + ok = start_child(rabbit_persister) + end}, + {"builtin applications", + fun () -> + {ok, DefaultVHost} = application:get_env(default_vhost), + ok = error_logger:add_report_handler( + rabbit_error_logger, [DefaultVHost]), + ok = start_builtin_amq_applications() + end}, + {"TCP listeners", + fun () -> + ok = rabbit_networking:start(), + {ok, TCPListeners} = application:get_env(tcp_listeners), + lists:foreach( + fun ({Host, Port}) -> + ok = rabbit_networking:start_tcp_listener(Host, Port) + end, + TCPListeners) + end}] + ++ ExtraSteps), + + io:format("~nbroker running~n"), + + {ok, SupPid}. + +stop(_State) -> + terminated_ok = error_logger:delete_report_handler(rabbit_error_logger), + ok. + +%--------------------------------------------------------------------------- + +print_banner() -> + {ok, Product} = application:get_key(id), + {ok, Version} = application:get_key(vsn), + io:format("~s ~s (AMQP ~p-~p)~n~s~n~s~n~n", + [Product, Version, + ?PROTOCOL_VERSION_MAJOR, ?PROTOCOL_VERSION_MINOR, + ?COPYRIGHT_MESSAGE, ?INFORMATION_MESSAGE]), + io:format("Logging to ~p~nSASL logging to ~p~n~n", + [error_log_location(), sasl_log_location()]). + +start_child(Mod) -> + {ok,_} = supervisor:start_child(rabbit_sup, + {Mod, {Mod, start_link, []}, + transient, 100, worker, [Mod]}), + ok. + +maybe_insert_default_data() -> + case rabbit_mnesia:is_db_empty() of + true -> insert_default_data(); + false -> ok + end. + +insert_default_data() -> + {ok, DefaultUser} = application:get_env(default_user), + {ok, DefaultPass} = application:get_env(default_pass), + {ok, DefaultVHost} = application:get_env(default_vhost), + ok = rabbit_access_control:add_vhost(DefaultVHost), + ok = insert_default_user(DefaultUser, DefaultPass, + [{DefaultVHost, [<<"/data">>, <<"/admin">>]}]), + ok. + +insert_default_user(Username, Password, VHostSpecs) -> + ok = rabbit_access_control:add_user(Username, Password), + lists:foreach( + fun ({VHostPath, Realms}) -> + ok = rabbit_access_control:map_user_vhost( + Username, VHostPath), + lists:foreach( + fun (Realm) -> + RealmFullName = + rabbit_misc:r(VHostPath, realm, Realm), + ok = rabbit_access_control:map_user_realm( + Username, + rabbit_access_control:full_ticket( + RealmFullName)) + end, Realms) + end, VHostSpecs), + ok. + +start_builtin_amq_applications() -> + %%TODO: we may want to create a separate supervisor for these so + %%they don't bring down the entire app when they die and fail to + %%restart + ok. + +ensure_working_log_config() -> + case error_logger:logfile(filename) of + {error, no_log_file} -> + %% either no log file was configured or opening it failed. + case application:get_env(kernel, error_logger) of + {ok, {file, Filename}} -> + case filelib:ensure_dir(Filename) of + ok -> ok; + {error, Reason1} -> + throw({error, {cannot_log_to_file, + Filename, Reason1}}) + end, + case error_logger:logfile({open, Filename}) of + ok -> ok; + {error, Reason2} -> + throw({error, {cannot_log_to_file, + Filename, Reason2}}) + end; + _ -> ok + end; + _Filename -> ok + end. + +error_log_location() -> + case error_logger:logfile(filename) of + {error,no_log_file} -> tty; + File -> File + end. + +sasl_log_location() -> + case application:get_env(sasl, sasl_error_logger) of + {ok, {file, File}} -> File; + {ok, false} -> undefined; + {ok, tty} -> tty; + {ok, Bad} -> throw({error, {cannot_log_to_file, Bad}}); + _ -> undefined + end. diff --git a/src/rabbit_access_control.erl b/src/rabbit_access_control.erl new file mode 100644 index 00000000..2be07b19 --- /dev/null +++ b/src/rabbit_access_control.erl @@ -0,0 +1,366 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (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.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developers of the Original Code are LShift Ltd., +%% Cohesive Financial Technologies LLC., and Rabbit Technologies Ltd. +%% +%% Portions created by LShift Ltd., Cohesive Financial Technologies +%% LLC., and Rabbit Technologies Ltd. are Copyright (C) 2007-2008 +%% LShift Ltd., Cohesive Financial Technologies LLC., and Rabbit +%% Technologies Ltd.; +%% +%% All Rights Reserved. +%% +%% Contributor(s): ______________________________________. +%% + +-module(rabbit_access_control). +-include_lib("stdlib/include/qlc.hrl"). +-include("rabbit.hrl"). + +-export([check_login/2, user_pass_login/2, + check_vhost_access/2, lookup_realm_access/2]). +-export([add_user/2, delete_user/1, change_password/2, list_users/0, + lookup_user/1]). +-export([add_vhost/1, delete_vhost/1, list_vhosts/0, list_vhost_users/1]). +-export([list_user_vhosts/1, map_user_vhost/2, unmap_user_vhost/2]). +-export([list_user_realms/2, map_user_realm/2, full_ticket/1]). + +%%---------------------------------------------------------------------------- + +-ifdef(use_specs). + +-spec(check_login/2 :: (binary(), binary()) -> user()). +-spec(user_pass_login/2 :: (username(), password()) -> user()). +-spec(check_vhost_access/2 :: (user(), vhost()) -> 'ok'). +-spec(lookup_realm_access/2 :: (user(), realm_name()) -> maybe(ticket())). +-spec(add_user/2 :: (username(), password()) -> 'ok'). +-spec(delete_user/1 :: (username()) -> 'ok'). +-spec(change_password/2 :: (username(), password()) -> 'ok'). +-spec(list_users/0 :: () -> [username()]). +-spec(lookup_user/1 :: (username()) -> {'ok', user()} | not_found()). +-spec(add_vhost/1 :: (vhost()) -> 'ok'). +-spec(delete_vhost/1 :: (vhost()) -> 'ok'). +-spec(list_vhosts/0 :: () -> [vhost()]). +-spec(list_vhost_users/1 :: (vhost()) -> [username()]). +-spec(list_user_vhosts/1 :: (username()) -> [vhost()]). +-spec(map_user_vhost/2 :: (username(), vhost()) -> 'ok'). +-spec(unmap_user_vhost/2 :: (username(), vhost()) -> 'ok'). +-spec(map_user_realm/2 :: (username(), ticket()) -> 'ok'). +-spec(list_user_realms/2 :: (username(), vhost()) -> [{name(), ticket()}]). +-spec(full_ticket/1 :: (realm_name()) -> ticket()). + +-endif. + +%%---------------------------------------------------------------------------- + +%% SASL PLAIN, as used by the Qpid Java client and our clients. Also, +%% apparently, by OpenAMQ. +check_login(<<"PLAIN">>, Response) -> + [User, Pass] = [list_to_binary(T) || + T <- string:tokens(binary_to_list(Response), [0])], + user_pass_login(User, Pass); +%% AMQPLAIN, as used by Qpid Python test suite. The 0-8 spec actually +%% defines this as PLAIN, but in 0-9 that definition is gone, instead +%% referring generically to "SASL security mechanism", i.e. the above. +check_login(<<"AMQPLAIN">>, Response) -> + LoginTable = rabbit_binary_parser:parse_table(Response), + case {lists:keysearch(<<"LOGIN">>, 1, LoginTable), + lists:keysearch(<<"PASSWORD">>, 1, LoginTable)} of + {{value, {_, longstr, User}}, + {value, {_, longstr, Pass}}} -> + user_pass_login(User, Pass); + _ -> + %% Is this an information leak? + rabbit_misc:protocol_error( + access_refused, + "AMQPPLAIN auth info ~w is missing LOGIN or PASSWORD field", + [LoginTable]) + end; + +check_login(Mechanism, _Response) -> + rabbit_misc:protocol_error( + access_refused, "unsupported authentication mechanism '~s'", + [Mechanism]). + +user_pass_login(User, Pass) -> + ?LOGDEBUG("Login with user ~p pass ~p~n", [User, Pass]), + case lookup_user(User) of + {ok, U} -> + if + Pass == U#user.password -> U; + true -> + rabbit_misc:protocol_error( + access_refused, "login refused for user '~s'", [User]) + end; + {error, not_found} -> + rabbit_misc:protocol_error( + access_refused, "login refused for user '~s'", [User]) + end. + +internal_lookup_vhost_access(Username, VHostPath) -> + %% TODO: use dirty ops instead + rabbit_misc:execute_mnesia_transaction( + fun () -> + case mnesia:match_object( + #user_vhost{username = Username, + virtual_host = VHostPath}) of + [] -> not_found; + [R] -> {ok, R} + end + end). + +check_vhost_access(#user{username = Username}, VHostPath) -> + ?LOGDEBUG("Checking VHost access for ~p to ~p~n", [Username, VHostPath]), + case internal_lookup_vhost_access(Username, VHostPath) of + {ok, _R} -> + ok; + not_found -> + rabbit_misc:protocol_error( + access_refused, "access to vhost '~s' refused for user '~s'", + [VHostPath, Username]) + end. + +lookup_realm_access(#user{username = Username}, RealmName = #resource{kind = realm}) -> + %% TODO: use dirty ops instead + rabbit_misc:execute_mnesia_transaction( + fun () -> + case user_realms(Username, RealmName) of + [] -> + none; + [#user_realm{ticket_pattern = TicketPattern}] -> + TicketPattern + end + end). + +add_user(Username, Password) -> + R = rabbit_misc:execute_mnesia_transaction( + fun () -> + case mnesia:read({user, Username}) of + [] -> + ok = mnesia:write(#user{username = Username, + password = Password}); + _ -> + mnesia:abort({user_already_exists, Username}) + end + end), + rabbit_log:info("Created user ~p~n", [Username]), + R. + +delete_user(Username) -> + R = rabbit_misc:execute_mnesia_transaction( + rabbit_misc:with_user( + Username, + fun () -> + ok = mnesia:delete({user, Username}), + ok = mnesia:delete({user_vhost, Username}), + ok = mnesia:delete({user_realm, Username}) + end)), + rabbit_log:info("Deleted user ~p~n", [Username]), + R. + +change_password(Username, Password) -> + R = rabbit_misc:execute_mnesia_transaction( + rabbit_misc:with_user( + Username, + fun () -> + ok = mnesia:write(#user{username = Username, + password = Password}) + end)), + rabbit_log:info("Changed password for user ~p~n", [Username]), + R. + +list_users() -> + mnesia:dirty_all_keys(user). + +lookup_user(Username) -> + rabbit_misc:dirty_read({user, Username}). + +add_vhost(VHostPath) -> + R = rabbit_misc:execute_mnesia_transaction( + fun () -> + case mnesia:read({vhost, VHostPath}) of + [] -> + ok = mnesia:write(#vhost{virtual_host = VHostPath}), + DataRealm = + rabbit_misc:r(VHostPath, realm, <<"/data">>), + AdminRealm = + rabbit_misc:r(VHostPath, realm, <<"/admin">>), + ok = rabbit_realm:add_realm(DataRealm), + ok = rabbit_realm:add_realm(AdminRealm), + #exchange{} = rabbit_exchange:declare( + DataRealm, <<"">>, + direct, true, false, []), + #exchange{} = rabbit_exchange:declare( + DataRealm, <<"amq.direct">>, + direct, true, false, []), + #exchange{} = rabbit_exchange:declare( + DataRealm, <<"amq.topic">>, + topic, true, false, []), + #exchange{} = rabbit_exchange:declare( + DataRealm, <<"amq.fanout">>, + fanout, true, false, []), + ok; + [_] -> + mnesia:abort({vhost_already_exists, VHostPath}) + end + end), + rabbit_log:info("Added vhost ~p~n", [VHostPath]), + R. + +delete_vhost(VHostPath) -> + %%FIXME: We are forced to delete the queues outside the TX below + %%because queue deletion involves sending messages to the queue + %%process, which in turn results in further mnesia actions and + %%eventually the termination of that process. + lists:foreach(fun (Q) -> + {ok,_} = rabbit_amqqueue:delete(Q, false, false) + end, + rabbit_amqqueue:list_vhost_queues(VHostPath)), + R = rabbit_misc:execute_mnesia_transaction( + rabbit_misc:with_vhost( + VHostPath, + fun () -> + ok = internal_delete_vhost(VHostPath) + end)), + rabbit_log:info("Deleted vhost ~p~n", [VHostPath]), + R. + +internal_delete_vhost(VHostPath) -> + lists:foreach(fun (#exchange{name=Name}) -> + ok = rabbit_exchange:delete(Name, false) + end, + rabbit_exchange:list_vhost_exchanges(VHostPath)), + lists:foreach(fun (RealmName) -> + ok = rabbit_realm:delete_realm( + rabbit_misc:r(VHostPath, realm, RealmName)) + end, + rabbit_realm:list_vhost_realms(VHostPath)), + lists:foreach(fun (Username) -> + ok = unmap_user_vhost(Username, VHostPath) + end, + list_vhost_users(VHostPath)), + ok = mnesia:delete({vhost, VHostPath}), + ok. + +list_vhosts() -> + mnesia:dirty_all_keys(vhost). + +list_vhost_users(VHostPath) -> + [Username || + #user_vhost{username = Username} <- + %% TODO: use dirty ops instead + rabbit_misc:execute_mnesia_transaction( + rabbit_misc:with_vhost( + VHostPath, + fun () -> mnesia:index_read(user_vhost, VHostPath, + #user_vhost.virtual_host) + end))]. + +list_user_vhosts(Username) -> + [VHostPath || + #user_vhost{virtual_host = VHostPath} <- + %% TODO: use dirty ops instead + rabbit_misc:execute_mnesia_transaction( + rabbit_misc:with_user( + Username, + fun () -> mnesia:read({user_vhost, Username}) end))]. + +map_user_vhost(Username, VHostPath) -> + rabbit_misc:execute_mnesia_transaction( + rabbit_misc:with_user_and_vhost( + Username, VHostPath, + fun () -> + ok = mnesia:write( + #user_vhost{username = Username, + virtual_host = VHostPath}) + end)). + +unmap_user_vhost(Username, VHostPath) -> + rabbit_misc:execute_mnesia_transaction( + rabbit_misc:with_user_and_vhost( + Username, VHostPath, + fun () -> + lists:foreach(fun mnesia:delete_object/1, + user_realms(Username, + rabbit_misc:r(VHostPath, realm))), + ok = mnesia:delete_object( + #user_vhost{username = Username, + virtual_host = VHostPath}) + end)). + +map_user_realm(Username, + Ticket = #ticket{realm_name = RealmName = + #resource{virtual_host = VHostPath, + kind = realm}}) -> + rabbit_misc:execute_mnesia_transaction( + rabbit_misc:with_user_and_vhost( + Username, VHostPath, + rabbit_misc:with_realm( + RealmName, + fun () -> + lists:foreach(fun mnesia:delete_object/1, + user_realms(Username, RealmName)), + case internal_lookup_vhost_access(Username, VHostPath) of + {ok, _R} -> + case ticket_liveness(Ticket) of + alive -> + ok = mnesia:write( + #user_realm{username = Username, + realm = RealmName, + ticket_pattern = Ticket}); + dead -> + ok + end; + not_found -> + mnesia:abort(not_mapped_to_vhost) + end + end))). + +list_user_realms(Username, VHostPath) -> + [{Name, Pattern} || + #user_realm{realm = #resource{name = Name}, + ticket_pattern = Pattern} <- + %% TODO: use dirty ops instead + rabbit_misc:execute_mnesia_transaction( + rabbit_misc:with_user_and_vhost( + Username, VHostPath, + fun () -> + case internal_lookup_vhost_access( + Username, VHostPath) of + {ok, _R} -> + user_realms(Username, + rabbit_misc:r(VHostPath, realm)); + not_found -> + mnesia:abort(not_mapped_to_vhost) + end + end))]. + +ticket_liveness(#ticket{passive_flag = false, + active_flag = false, + write_flag = false, + read_flag = false}) -> + dead; +ticket_liveness(_) -> + alive. + +full_ticket(RealmName) -> + #ticket{realm_name = RealmName, + passive_flag = true, + active_flag = true, + write_flag = true, + read_flag = true}. + +user_realms(Username, RealmName) -> + mnesia:match_object(#user_realm{username = Username, + realm = RealmName, + _ = '_'}). diff --git a/src/rabbit_amqqueue.erl b/src/rabbit_amqqueue.erl new file mode 100644 index 00000000..63f043ba --- /dev/null +++ b/src/rabbit_amqqueue.erl @@ -0,0 +1,379 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (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.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developers of the Original Code are LShift Ltd., +%% Cohesive Financial Technologies LLC., and Rabbit Technologies Ltd. +%% +%% Portions created by LShift Ltd., Cohesive Financial Technologies +%% LLC., and Rabbit Technologies Ltd. are Copyright (C) 2007-2008 +%% LShift Ltd., Cohesive Financial Technologies LLC., and Rabbit +%% Technologies Ltd.; +%% +%% All Rights Reserved. +%% +%% Contributor(s): ______________________________________. +%% + +-module(rabbit_amqqueue). + +-export([start/0, recover/0, declare/5, delete/3, purge/1, internal_delete/1]). +-export([pseudo_queue/3]). +-export([lookup/1, with/2, with_or_die/2, list_vhost_queues/1, + stat/1, stat_all/0, deliver/5, redeliver/2, requeue/3, ack/4, + commit/2, rollback/2]). +-export([add_binding/4, delete_binding/4, binding_forcibly_removed/2]). +-export([claim_queue/2]). +-export([basic_get/3, basic_consume/7, basic_cancel/4]). +-export([notify_sent/2, notify_down/2]). +-export([on_node_down/1]). + +-import(mnesia). +-import(gen_server). +-import(lists). +-import(queue). + +-include("rabbit.hrl"). +-include_lib("stdlib/include/qlc.hrl"). + +%%---------------------------------------------------------------------------- + +-ifdef(use_specs). + +-type(qstats() :: {'ok', queue_name(), non_neg_integer(), non_neg_integer()}). +-type(qlen() :: {'ok', non_neg_integer()}). +-type(qfun(A) :: fun ((amqqueue()) -> A)). +-type(bind_res() :: {'ok', non_neg_integer()} | + {'error', 'queue_not_found' | 'exchange_not_found'}). +-spec(start/0 :: () -> 'ok'). +-spec(recover/0 :: () -> 'ok'). +-spec(declare/5 :: (realm_name(), name(), bool(), bool(), amqp_table()) -> + amqqueue()). +-spec(add_binding/4 :: + (queue_name(), exchange_name(), routing_key(), amqp_table()) -> + bind_res() | {'error', 'durability_settings_incompatible'}). +-spec(delete_binding/4 :: + (queue_name(), exchange_name(), routing_key(), amqp_table()) -> + bind_res() | {'error', 'binding_not_found'}). +-spec(lookup/1 :: (queue_name()) -> {'ok', amqqueue()} | not_found()). +-spec(with/2 :: (queue_name(), qfun(A)) -> A | not_found()). +-spec(with_or_die/2 :: (queue_name(), qfun(A)) -> A). +-spec(list_vhost_queues/1 :: (vhost()) -> [amqqueue()]). +-spec(stat/1 :: (amqqueue()) -> qstats()). +-spec(stat_all/0 :: () -> [qstats()]). +-spec(delete/3 :: + (amqqueue(), 'false', 'false') -> qlen(); + (amqqueue(), 'true' , 'false') -> qlen() | {'error', 'in_use'}; + (amqqueue(), 'false', 'true' ) -> qlen() | {'error', 'not_empty'}; + (amqqueue(), 'true' , 'true' ) -> qlen() | + {'error', 'in_use'} | + {'error', 'not_empty'}). +-spec(purge/1 :: (amqqueue()) -> qlen()). +-spec(deliver/5 :: (bool(), bool(), maybe(txn()), message(), pid()) -> bool()). +-spec(redeliver/2 :: (pid(), [{message(), bool()}]) -> 'ok'). +-spec(requeue/3 :: (pid(), [msg_id()], pid()) -> 'ok'). +-spec(ack/4 :: (pid(), maybe(txn()), [msg_id()], pid()) -> 'ok'). +-spec(commit/2 :: (pid(), txn()) -> 'ok'). +-spec(rollback/2 :: (pid(), txn()) -> 'ok'). +-spec(notify_down/2 :: (amqqueue(), pid()) -> 'ok'). +-spec(binding_forcibly_removed/2 :: (binding_spec(), queue_name()) -> 'ok'). +-spec(claim_queue/2 :: (amqqueue(), pid()) -> 'ok' | 'locked'). +-spec(basic_get/3 :: (amqqueue(), pid(), bool()) -> + {'ok', non_neg_integer(), msg()} | 'empty'). +-spec(basic_consume/7 :: + (amqqueue(), bool(), pid(), pid(), ctag(), bool(), any()) -> + 'ok' | {'error', 'queue_owned_by_another_connection' | + 'exclusive_consume_unavailable'}). +-spec(basic_cancel/4 :: (amqqueue(), pid(), ctag(), any()) -> 'ok'). +-spec(notify_sent/2 :: (pid(), pid()) -> 'ok'). +-spec(internal_delete/1 :: (queue_name()) -> 'ok' | not_found()). +-spec(on_node_down/1 :: (node()) -> 'ok'). +-spec(pseudo_queue/3 :: (realm_name(), binary(), pid()) -> amqqueue()). + +-endif. + +%%---------------------------------------------------------------------------- + +start() -> + {ok,_} = supervisor:start_child( + rabbit_sup, + {rabbit_amqqueue_sup, + {rabbit_amqqueue_sup, start_link, []}, + transient, infinity, supervisor, [rabbit_amqqueue_sup]}), + ok. + +recover() -> + ok = recover_durable_queues(), + ok. + +recover_durable_queues() -> + Node = node(), + %% TODO: use dirty ops instead + R = rabbit_misc:execute_mnesia_transaction( + fun () -> + qlc:e(qlc:q([Q || Q = #amqqueue{pid = Pid} + <- mnesia:table(durable_queues), + node(Pid) == Node])) + end), + Queues = lists:map(fun start_queue_process/1, R), + rabbit_misc:execute_mnesia_transaction( + fun () -> + lists:foreach(fun recover_queue/1, Queues), + ok + end). + +declare(RealmName, NameBin, Durable, AutoDelete, Args) -> + QName = rabbit_misc:r(RealmName, queue, NameBin), + Q = start_queue_process(#amqqueue{name = QName, + durable = Durable, + auto_delete = AutoDelete, + arguments = Args, + binding_specs = [], + pid = none}), + case rabbit_misc:execute_mnesia_transaction( + fun () -> + case mnesia:wread({amqqueue, QName}) of + [] -> ok = recover_queue(Q), + ok = rabbit_realm:add(RealmName, QName), + Q; + [ExistingQ] -> ExistingQ + end + end) of + Q -> Q; + ExistingQ -> exit(Q#amqqueue.pid, shutdown), + ExistingQ + end. + +store_queue(Q = #amqqueue{durable = true}) -> + ok = mnesia:write(durable_queues, Q, write), + ok = mnesia:write(Q), + ok; +store_queue(Q = #amqqueue{durable = false}) -> + ok = mnesia:write(Q), + ok. + +start_queue_process(Q) -> + {ok, Pid} = supervisor:start_child(rabbit_amqqueue_sup, [Q]), + Q#amqqueue{pid = Pid}. + +recover_queue(Q) -> + ok = store_queue(Q), + ok = recover_bindings(Q), + ok. + +default_binding_spec(#resource{virtual_host = VHost, name = Name}) -> + #binding_spec{exchange_name = rabbit_misc:r(VHost, exchange, <<>>), + routing_key = Name, + arguments = []}. + +recover_bindings(Q = #amqqueue{name = QueueName, binding_specs = Specs}) -> + ok = rabbit_exchange:add_binding(default_binding_spec(QueueName), Q), + lists:foreach(fun (B) -> + ok = rabbit_exchange:add_binding(B, Q) + end, Specs), + ok. + +modify_bindings(QueueName, ExchangeName, RoutingKey, Arguments, + SpecPresentFun, SpecAbsentFun) -> + rabbit_misc:execute_mnesia_transaction( + fun () -> + case mnesia:wread({amqqueue, QueueName}) of + [Q = #amqqueue{binding_specs = Specs0}] -> + Spec = #binding_spec{exchange_name = ExchangeName, + routing_key = RoutingKey, + arguments = Arguments}, + case (case lists:member(Spec, Specs0) of + true -> SpecPresentFun; + false -> SpecAbsentFun + end)(Q, Spec) of + {ok, #amqqueue{binding_specs = Specs}} -> + {ok, length(Specs)}; + {error, not_found} -> + {error, exchange_not_found}; + Other -> Other + end; + [] -> {error, queue_not_found} + end + end). + +update_bindings(Q = #amqqueue{binding_specs = Specs0}, Spec, + UpdateSpecFun, UpdateExchangeFun) -> + Q1 = Q#amqqueue{binding_specs = UpdateSpecFun(Spec, Specs0)}, + case UpdateExchangeFun(Spec, Q1) of + ok -> store_queue(Q1), + {ok, Q1}; + Other -> Other + end. + +add_binding(QueueName, ExchangeName, RoutingKey, Arguments) -> + modify_bindings( + QueueName, ExchangeName, RoutingKey, Arguments, + fun (Q, _Spec) -> {ok, Q} end, + fun (Q, Spec) -> update_bindings( + Q, Spec, + fun (S, Specs) -> [S | Specs] end, + fun rabbit_exchange:add_binding/2) + end). + +delete_binding(QueueName, ExchangeName, RoutingKey, Arguments) -> + modify_bindings( + QueueName, ExchangeName, RoutingKey, Arguments, + fun (Q, Spec) -> update_bindings( + Q, Spec, + fun lists:delete/2, + fun rabbit_exchange:delete_binding/2) + end, + fun (Q, Spec) -> + %% the following is essentially a no-op, though crucially + %% it produces {error, not_found} when the exchange does + %% not exist. + case rabbit_exchange:delete_binding(Spec, Q) of + ok -> {error, binding_not_found}; + Other -> Other + end + end). + +lookup(Name) -> + rabbit_misc:dirty_read({amqqueue, Name}). + +with(Name, F, E) -> + case lookup(Name) of + {ok, Q} -> rabbit_misc:with_exit_handler(E, fun () -> F(Q) end); + {error, not_found} -> E() + end. + +with(Name, F) -> + with(Name, F, fun () -> {error, not_found} end). +with_or_die(Name, F) -> + with(Name, F, fun () -> rabbit_misc:protocol_error( + not_found, "no ~s", [rabbit_misc:rs(Name)]) + end). + +list_vhost_queues(VHostPath) -> + mnesia:dirty_match_object( + #amqqueue{name = rabbit_misc:r(VHostPath, queue), _ = '_'}). + +stat(#amqqueue{pid = QPid}) -> gen_server:call(QPid, stat). + +stat_all() -> + lists:map(fun stat/1, rabbit_misc:dirty_read_all(amqqueue)). + +delete(#amqqueue{ pid = QPid }, IfUnused, IfEmpty) -> + gen_server:call(QPid, {delete, IfUnused, IfEmpty}). + +purge(#amqqueue{ pid = QPid }) -> gen_server:call(QPid, purge). + +deliver(_IsMandatory, true, Txn, Message, QPid) -> + gen_server:call(QPid, {deliver_immediately, Txn, Message}); +deliver(true, _IsImmediate, Txn, Message, QPid) -> + gen_server:call(QPid, {deliver, Txn, Message}), + true; +deliver(false, _IsImmediate, Txn, Message, QPid) -> + gen_server:cast(QPid, {deliver, Txn, Message}), + true. + +redeliver(QPid, Messages) -> + gen_server:cast(QPid, {redeliver, Messages}). + +requeue(QPid, MsgIds, ChPid) -> + gen_server:cast(QPid, {requeue, MsgIds, ChPid}). + +ack(QPid, Txn, MsgIds, ChPid) -> + gen_server:cast(QPid, {ack, Txn, MsgIds, ChPid}). + +commit(QPid, Txn) -> + gen_server:call(QPid, {commit, Txn}). + +rollback(QPid, Txn) -> + gen_server:cast(QPid, {rollback, Txn}). + +notify_down(#amqqueue{ pid = QPid }, ChPid) -> + gen_server:call(QPid, {notify_down, ChPid}). + +binding_forcibly_removed(BindingSpec, QueueName) -> + rabbit_misc:execute_mnesia_transaction( + fun () -> + case mnesia:wread({amqqueue, QueueName}) of + [] -> ok; + [Q = #amqqueue{binding_specs = Specs}] -> + store_queue(Q#amqqueue{binding_specs = + lists:delete(BindingSpec, Specs)}) + end + end). + +claim_queue(#amqqueue{pid = QPid}, ReaderPid) -> + gen_server:call(QPid, {claim_queue, ReaderPid}). + +basic_get(#amqqueue{pid = QPid}, ChPid, NoAck) -> + gen_server:call(QPid, {basic_get, ChPid, NoAck}). + +basic_consume(#amqqueue{pid = QPid}, NoAck, ReaderPid, ChPid, + ConsumerTag, ExclusiveConsume, OkMsg) -> + gen_server:call(QPid, {basic_consume, NoAck, ReaderPid, ChPid, + ConsumerTag, ExclusiveConsume, OkMsg}). + +basic_cancel(#amqqueue{pid = QPid}, ChPid, ConsumerTag, OkMsg) -> + ok = gen_server:call(QPid, {basic_cancel, ChPid, ConsumerTag, OkMsg}). + +notify_sent(QPid, ChPid) -> + gen_server:cast(QPid, {notify_sent, ChPid}). + +delete_bindings(Q = #amqqueue{binding_specs = Specs}) -> + lists:foreach(fun (BindingSpec) -> + ok = rabbit_exchange:delete_binding( + BindingSpec, Q) + end, Specs). + +internal_delete(QueueName) -> + rabbit_misc:execute_mnesia_transaction( + fun () -> + case mnesia:wread({amqqueue, QueueName}) of + [] -> {error, not_found}; + [Q] -> + ok = delete_temp(Q), + ok = mnesia:delete({durable_queues, QueueName}), + ok = rabbit_realm:delete_from_all(QueueName), + ok + end + end). + +delete_temp(Q = #amqqueue{name = QueueName}) -> + ok = delete_bindings(Q), + ok = rabbit_exchange:delete_binding( + default_binding_spec(QueueName), Q), + ok = mnesia:delete({amqqueue, QueueName}), + ok. + +delete_queue(Q = #amqqueue{name = QueueName, durable = Durable}) -> + ok = delete_temp(Q), + if + Durable -> ok; + true -> ok = rabbit_realm:delete_from_all(QueueName) + end. + +on_node_down(Node) -> + rabbit_misc:execute_mnesia_transaction( + fun () -> + qlc:fold( + fun (Q, Acc) -> ok = delete_queue(Q), Acc end, + ok, + qlc:q([Q || Q = #amqqueue{pid = Pid} + <- mnesia:table(amqqueue), + node(Pid) == Node])) + end). + +pseudo_queue(RealmName, NameBin, Pid) -> + #amqqueue{name = rabbit_misc:r(RealmName, queue, NameBin), + durable = false, + auto_delete = false, + arguments = [], + binding_specs = [], + pid = Pid}. diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl new file mode 100644 index 00000000..7716ef16 --- /dev/null +++ b/src/rabbit_amqqueue_process.erl @@ -0,0 +1,686 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (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.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developers of the Original Code are LShift Ltd., +%% Cohesive Financial Technologies LLC., and Rabbit Technologies Ltd. +%% +%% Portions created by LShift Ltd., Cohesive Financial Technologies +%% LLC., and Rabbit Technologies Ltd. are Copyright (C) 2007-2008 +%% LShift Ltd., Cohesive Financial Technologies LLC., and Rabbit +%% Technologies Ltd.; +%% +%% All Rights Reserved. +%% +%% Contributor(s): ______________________________________. +%% + +-module(rabbit_amqqueue_process). +-include("rabbit.hrl"). +-include("rabbit_framing.hrl"). + +-behaviour(gen_server). + +-define(UNSENT_MESSAGE_LIMIT, 100). + +-export([start_link/1]). + +-export([init/1, terminate/2, code_change/3, handle_call/3, handle_cast/2, handle_info/2]). + +-import(queue). +-import(erlang). +-import(lists). + +% Queue's state +-record(q, {q, + owner, + exclusive_consumer, + has_had_consumers, + next_msg_id, + message_buffer, + round_robin}). + +-record(consumer, {tag, ack_required}). + +-record(tx, {ch_pid, is_persistent, pending_messages, pending_acks}). + +%% These are held in our process dictionary +-record(cr, {consumers, + ch_pid, + monitor_ref, + unacked_messages, + is_overload_protection_active, + unsent_message_count}). + +%%---------------------------------------------------------------------------- + +start_link(Q) -> + gen_server:start_link(?MODULE, Q, []). + +%%---------------------------------------------------------------------------- + +init(Q) -> + ?LOGDEBUG("Queue starting - ~p~n", [Q]), + {ok, #q{q = Q, + owner = none, + exclusive_consumer = none, + has_had_consumers = false, + next_msg_id = 1, + message_buffer = queue:new(), + round_robin = queue:new()}}. + +terminate(_Reason, State) -> + %% FIXME: How do we cancel active subscriptions? + QName = qname(State), + lists:foreach(fun (Txn) -> ok = rollback_work(Txn, QName) end, + all_tx()), + ok = purge_message_buffer(QName, State#q.message_buffer), + ok = rabbit_amqqueue:internal_delete(QName). + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%%---------------------------------------------------------------------------- + +lookup_ch(ChPid) -> + case get({ch, ChPid}) of + undefined -> not_found; + C -> C + end. + +ch_record(ChPid) -> + Key = {ch, ChPid}, + case get(Key) of + undefined -> + MonitorRef = erlang:monitor(process, ChPid), + C = #cr{consumers = [], + ch_pid = ChPid, + monitor_ref = MonitorRef, + unacked_messages = dict:new(), + is_overload_protection_active = false, + unsent_message_count = 0}, + put(Key, C), + C; + C = #cr{} -> C + end. + +store_ch_record(C = #cr{ch_pid = ChPid}) -> + put({ch, ChPid}, C). + +all_ch_record() -> + [C || {{ch, _}, C} <- get()]. + +update_store_and_maybe_block_ch( + C = #cr{is_overload_protection_active = Active, + unsent_message_count = Count}) -> + {Result, NewActive} = + if + not(Active) and (Count > ?UNSENT_MESSAGE_LIMIT) -> + {block_ch, true}; + Active and (Count == 0) -> + {unblock_ch, false}; + true -> + {ok, Active} + end, + store_ch_record(C#cr{is_overload_protection_active = NewActive}), + Result. + +deliver_immediately(Message, Delivered, + State = #q{q = #amqqueue{name = QName}, + round_robin = RoundRobin, + next_msg_id = NextId}) -> + ?LOGDEBUG("AMQQUEUE ~p DELIVERY:~n~p~n", [QName, Message]), + case queue:out(RoundRobin) of + {{value, QEntry = {ChPid, #consumer{tag = ConsumerTag, + ack_required = AckRequired}}}, + RoundRobinTail} -> + rabbit_channel:deliver( + ChPid, ConsumerTag, AckRequired, + {QName, self(), NextId, Delivered, Message}), + C = #cr{unsent_message_count = Count, + unacked_messages = UAM} = ch_record(ChPid), + NewUAM = case AckRequired of + true -> dict:store(NextId, Message, UAM); + false -> UAM + end, + NewConsumers = + case update_store_and_maybe_block_ch( + C#cr{unsent_message_count = Count + 1, + unacked_messages = NewUAM}) of + ok -> queue:in(QEntry, RoundRobinTail); + block_ch -> block_consumers(ChPid, RoundRobinTail) + end, + {offered, AckRequired, State#q{round_robin = NewConsumers, + next_msg_id = NextId +1}}; + {empty, _} -> + not_offered + end. + +attempt_delivery(none, Message, State) -> + case deliver_immediately(Message, false, State) of + {offered, false, State1} -> + {true, State1}; + {offered, true, State1} -> + persist_message(none, qname(State), Message), + persist_delivery(qname(State), Message, false), + {true, State1}; + not_offered -> + {false, State} + end; +attempt_delivery(Txn, Message, State) -> + persist_message(Txn, qname(State), Message), + record_pending_message(Txn, Message), + {true, State}. + +deliver_or_enqueue(Txn, Message, State) -> + case attempt_delivery(Txn, Message, State) of + {true, NewState} -> + {true, NewState}; + {false, NewState} -> + persist_message(Txn, qname(State), Message), + NewMB = queue:in({Message, false}, NewState#q.message_buffer), + {false, NewState#q{message_buffer = NewMB}} + end. + +deliver_or_enqueue_n(Messages, State = #q{message_buffer = MessageBuffer}) -> + run_poke_burst(queue:join(MessageBuffer, queue:from_list(Messages)), + State). + +block_consumers(ChPid, RoundRobin) -> + %%?LOGDEBUG("~p Blocking ~p from ~p~n", [self(), ChPid, queue:to_list(RoundRobin)]), + queue:from_list(lists:filter(fun ({CP, _}) -> CP /= ChPid end, + queue:to_list(RoundRobin))). + +unblock_consumers(ChPid, Consumers, RoundRobin) -> + %%?LOGDEBUG("Unblocking ~p ~p ~p~n", [ChPid, Consumers, queue:to_list(RoundRobin)]), + queue:join(RoundRobin, + queue:from_list([{ChPid, Con} || Con <- Consumers])). + +block_consumer(ChPid, ConsumerTag, RoundRobin) -> + %%?LOGDEBUG("~p Blocking ~p from ~p~n", [self(), ConsumerTag, queue:to_list(RoundRobin)]), + queue:from_list(lists:filter( + fun ({CP, #consumer{tag = CT}}) -> + (CP /= ChPid) or (CT /= ConsumerTag) + end, queue:to_list(RoundRobin))). + +possibly_unblock(C = #cr{consumers = Consumers, ch_pid = ChPid}, + State = #q{round_robin = RoundRobin}) -> + case update_store_and_maybe_block_ch(C) of + ok -> + State; + unblock_ch -> + run_poke_burst(State#q{round_robin = + unblock_consumers(ChPid, Consumers, RoundRobin)}) + end. + +check_auto_delete(State = #q{q = #amqqueue{auto_delete = false}}) -> + {continue, State}; +check_auto_delete(State = #q{has_had_consumers = false}) -> + {continue, State}; +check_auto_delete(State = #q{round_robin = RoundRobin}) -> + % The clauses above rule out cases where no-one has consumed from + % this queue yet, and cases where we are not an auto_delete queue + % in any case. Thus it remains to check whether we have any active + % listeners at this point. + case queue:is_empty(RoundRobin) of + true -> + % There are no waiting listeners. It's possible that we're + % completely unused. Check. + case is_unused() of + true -> + % There are no active consumers at this + % point. This is the signal to autodelete. + {stop, State}; + false -> + % There is at least one active consumer, so we + % shouldn't delete ourselves. + {continue, State} + end; + false -> + % There are some waiting listeners, thus we are not + % unused, so can continue life as normal without needing + % to check the process dictionary. + {continue, State} + end. + +handle_ch_down(DownPid, State = #q{exclusive_consumer = Holder, + round_robin = ActiveConsumers}) -> + case lookup_ch(DownPid) of + not_found -> {noreply, State}; + #cr{monitor_ref = MonitorRef, ch_pid = ChPid, unacked_messages = UAM} -> + NewActive = block_consumers(ChPid, ActiveConsumers), + erlang:demonitor(MonitorRef), + erase({ch, ChPid}), + case check_auto_delete( + deliver_or_enqueue_n( + [{Message, true} || + {_Messsage_id, Message} <- dict:to_list(UAM)], + State#q{ + exclusive_consumer = case Holder of + {ChPid, _} -> none; + Other -> Other + end, + round_robin = NewActive})) of + {continue, NewState} -> + {noreply, NewState}; + {stop, NewState} -> + {stop, normal, NewState} + end + end. + +cancel_holder(ChPid, ConsumerTag, {ChPid, ConsumerTag}) -> + none; +cancel_holder(_ChPid, _ConsumerTag, Holder) -> + Holder. + +check_queue_owner(none, _) -> ok; +check_queue_owner({ReaderPid, _}, ReaderPid) -> ok; +check_queue_owner({_, _}, _) -> mismatch. + +check_exclusive_access({_ChPid, _ConsumerTag}, _ExclusiveConsume) -> + in_use; +check_exclusive_access(none, false) -> + ok; +check_exclusive_access(none, true) -> + case is_unused() of + true -> ok; + false -> in_use + end. + +run_poke_burst(State = #q{message_buffer = MessageBuffer}) -> + run_poke_burst(MessageBuffer, State). + +run_poke_burst(MessageBuffer, State) -> + case queue:out(MessageBuffer) of + {{value, {Message, Delivered}}, BufferTail} -> + case deliver_immediately(Message, Delivered, State) of + {offered, true, NewState} -> + persist_delivery(qname(State), Message, Delivered), + run_poke_burst(BufferTail, NewState); + {offered, false, NewState} -> + persist_auto_ack(qname(State), Message), + run_poke_burst(BufferTail, NewState); + not_offered -> + State#q{message_buffer = MessageBuffer} + end; + {empty, _} -> + State#q{message_buffer = MessageBuffer} + end. + +is_unused() -> + is_unused1(get()). + +is_unused1([]) -> + true; +is_unused1([{{ch, _}, #cr{consumers = Consumers}} | _Rest]) + when Consumers /= [] -> + false; +is_unused1([_ | Rest]) -> + is_unused1(Rest). + +maybe_send_reply(_ChPid, undefined) -> ok; +maybe_send_reply(ChPid, Msg) -> ok = rabbit_channel:send_command(ChPid, Msg). + +qname(#q{q = #amqqueue{name = QName}}) -> QName. + +persist_message(_Txn, _QName, #basic_message{persistent_key = none}) -> + ok; +persist_message(Txn, QName, Message) -> + M = Message#basic_message{ + %% don't persist any recoverable decoded properties, rebuild from properties_bin on restore + content = rabbit_binary_parser:clear_decoded_content( + Message#basic_message.content)}, + persist_work(Txn, QName, + [{publish, M, {QName, M#basic_message.persistent_key}}]). + +persist_delivery(_QName, _Message, + true) -> + ok; +persist_delivery(_QName, #basic_message{persistent_key = none}, + _Delivered) -> + ok; +persist_delivery(QName, #basic_message{persistent_key = PKey}, + _Delivered) -> + persist_work(none, QName, [{deliver, {QName, PKey}}]). + +persist_acks(Txn, QName, Messages) -> + persist_work(Txn, QName, + [{ack, {QName, PKey}} || + #basic_message{persistent_key = PKey} <- Messages, + PKey =/= none]). + +persist_auto_ack(_QName, #basic_message{persistent_key = none}) -> + ok; +persist_auto_ack(QName, #basic_message{persistent_key = PKey}) -> + %% auto-acks are always non-transactional + rabbit_persister:dirty_work([{ack, {QName, PKey}}]). + +persist_work(_Txn,_QName, []) -> + ok; +persist_work(none, _QName, WorkList) -> + rabbit_persister:dirty_work(WorkList); +persist_work(Txn, QName, WorkList) -> + mark_tx_persistent(Txn), + rabbit_persister:extend_transaction({Txn, QName}, WorkList). + +commit_work(Txn, QName) -> + do_if_persistent(fun rabbit_persister:commit_transaction/1, + Txn, QName). + +rollback_work(Txn, QName) -> + do_if_persistent(fun rabbit_persister:rollback_transaction/1, + Txn, QName). + +%% optimisation: don't do unnecessary work +%% it would be nice if this was handled by the persister +do_if_persistent(F, Txn, QName) -> + case is_tx_persistent(Txn) of + false -> ok; + true -> ok = F({Txn, QName}) + end. + +lookup_tx(Txn) -> + case get({txn, Txn}) of + undefined -> #tx{ch_pid = none, + is_persistent = false, + pending_messages = [], + pending_acks = []}; + V -> V + end. + +store_tx(Txn, Tx) -> + put({txn, Txn}, Tx). + +erase_tx(Txn) -> + erase({txn, Txn}). + +all_tx() -> + [Txn || {{txn, Txn}, _} <- get()]. + +mark_tx_persistent(Txn) -> + Tx = lookup_tx(Txn), + store_tx(Txn, Tx#tx{is_persistent = true}). + +is_tx_persistent(Txn) -> + #tx{is_persistent = Res} = lookup_tx(Txn), + Res. + +record_pending_message(Txn, Message) -> + Tx = #tx{pending_messages = Pending} = lookup_tx(Txn), + store_tx(Txn, Tx#tx{pending_messages = [{Message, false} | Pending]}). + +record_pending_acks(Txn, ChPid, MsgIds) -> + Tx = #tx{pending_acks = Pending} = lookup_tx(Txn), + store_tx(Txn, Tx#tx{pending_acks = [MsgIds | Pending], ch_pid = ChPid}). + +process_pending(Txn, State) -> + #tx{ch_pid = ChPid, + pending_messages = PendingMessages, + pending_acks = PendingAcks} = lookup_tx(Txn), + case lookup_ch(ChPid) of + not_found -> ok; + C = #cr{unacked_messages = UAM} -> + {_Acked, Remaining} = + collect_messages(lists:append(PendingAcks), UAM), + store_ch_record(C#cr{unacked_messages = Remaining}) + end, + deliver_or_enqueue_n(lists:reverse(PendingMessages), State). + +collect_messages(MsgIds, UAM) -> + lists:mapfoldl( + fun (MsgId, D) -> {dict:fetch(MsgId, D), dict:erase(MsgId, D)} end, + UAM, MsgIds). + +purge_message_buffer(QName, MessageBuffer) -> + Messages = + [[Message || {Message, _Delivered} <- + queue:to_list(MessageBuffer)] | + lists:map( + fun (#cr{unacked_messages = UAM}) -> + [Message || {_MessageId, Message} <- dict:to_list(UAM)] + end, + all_ch_record())], + %% the simplest, though certainly not the most obvious or + %% efficient, way to purge messages from the persister is to + %% artifically ack them. + persist_acks(none, QName, lists:append(Messages)). + +%--------------------------------------------------------------------------- + +handle_call({deliver_immediately, Txn, Message}, _From, State) -> + %% Synchronous, "immediate" delivery mode + %% + %% FIXME: Is this correct semantics? + %% + %% I'm worried in particular about the case where an exchange has + %% two queues against a particular routing key, and a message is + %% sent in immediate mode through the binding. In non-immediate + %% mode, both queues get the message, saving it for later if + %% there's noone ready to receive it just now. In immediate mode, + %% should both queues still get the message, somehow, or should + %% just all ready-to-consume queues get the message, with unready + %% queues discarding the message? + %% + {Delivered, NewState} = attempt_delivery(Txn, Message, State), + {reply, Delivered, NewState}; + +handle_call({deliver, Txn, Message}, _From, State) -> + %% Synchronous, "mandatory" delivery mode + {Delivered, NewState} = deliver_or_enqueue(Txn, Message, State), + {reply, Delivered, NewState}; + +handle_call({commit, Txn}, From, State) -> + ok = commit_work(Txn, qname(State)), + %% optimisation: we reply straight away so the sender can continue + gen_server:reply(From, ok), + NewState = process_pending(Txn, State), + erase_tx(Txn), + {noreply, NewState}; + +handle_call({notify_down, ChPid}, From, State) -> + %% optimisation: we reply straight away so the sender can continue + gen_server:reply(From, ok), + handle_ch_down(ChPid, State); + +handle_call({basic_get, ChPid, NoAck}, _From, + State = #q{q = #amqqueue{name = QName}, + next_msg_id = NextId, + message_buffer = MessageBuffer}) -> + case queue:out(MessageBuffer) of + {{value, {Message, Delivered}}, BufferTail} -> + AckRequired = not(NoAck), + case AckRequired of + true -> + persist_delivery(QName, Message, Delivered), + C = #cr{unacked_messages = UAM} = ch_record(ChPid), + NewUAM = dict:store(NextId, Message, UAM), + store_ch_record(C#cr{unacked_messages = NewUAM}); + false -> + persist_auto_ack(QName, Message) + end, + Msg = {QName, self(), NextId, Delivered, Message}, + {reply, {ok, queue:len(BufferTail), Msg}, + State#q{message_buffer = BufferTail, next_msg_id = NextId + 1}}; + {empty, _} -> + {reply, empty, State} + end; + +handle_call({basic_consume, NoAck, ReaderPid, ChPid, ConsumerTag, + ExclusiveConsume, OkMsg}, + _From, State = #q{owner = Owner, + exclusive_consumer = ExistingHolder, + round_robin = RoundRobin}) -> + case check_queue_owner(Owner, ReaderPid) of + mismatch -> + {reply, {error, queue_owned_by_another_connection}, State}; + ok -> + case check_exclusive_access(ExistingHolder, ExclusiveConsume) of + in_use -> + {reply, {error, exclusive_consume_unavailable}, State}; + ok -> + C = #cr{consumers = Consumers} = ch_record(ChPid), + Consumer = #consumer{tag = ConsumerTag, ack_required = not(NoAck)}, + C1 = C#cr{consumers = [Consumer | Consumers]}, + store_ch_record(C1), + State1 = State#q{has_had_consumers = true, + exclusive_consumer = + if + ExclusiveConsume -> {ChPid, ConsumerTag}; + true -> ExistingHolder + end, + round_robin = queue:in({ChPid, Consumer}, RoundRobin)}, + ok = maybe_send_reply(ChPid, OkMsg), + {reply, ok, run_poke_burst(State1)} + end + end; + +handle_call({basic_cancel, ChPid, ConsumerTag, OkMsg}, _From, + State = #q{exclusive_consumer = Holder, + round_robin = RoundRobin}) -> + case lookup_ch(ChPid) of + not_found -> + ok = maybe_send_reply(ChPid, OkMsg), + {reply, ok, State}; + C = #cr{consumers = Consumers} -> + NewConsumers = lists:filter + (fun (#consumer{tag = CT}) -> CT /= ConsumerTag end, + Consumers), + C1 = C#cr{consumers = NewConsumers}, + store_ch_record(C1), + ok = maybe_send_reply(ChPid, OkMsg), + case check_auto_delete( + State#q{exclusive_consumer = cancel_holder(ChPid, + ConsumerTag, + Holder), + round_robin = block_consumer(ChPid, + ConsumerTag, + RoundRobin)}) of + {continue, State1} -> + {reply, ok, State1}; + {stop, State1} -> + {stop, normal, ok, State1} + end + end; + +handle_call(stat, _From, State = #q{q = #amqqueue{name = Name}, + message_buffer = MessageBuffer, + round_robin = RoundRobin}) -> + {reply, {ok, Name, queue:len(MessageBuffer), queue:len(RoundRobin)}, State}; + +handle_call({delete, IfUnused, IfEmpty}, _From, + State = #q{message_buffer = MessageBuffer}) -> + IsEmpty = queue:is_empty(MessageBuffer), + IsUnused = is_unused(), + if + IfEmpty and not(IsEmpty) -> + {reply, {error, not_empty}, State}; + IfUnused and not(IsUnused) -> + {reply, {error, in_use}, State}; + true -> + {stop, normal, {ok, queue:len(MessageBuffer)}, State} + end; + +handle_call(purge, _From, State = #q{message_buffer = MessageBuffer}) -> + ok = purge_message_buffer(qname(State), MessageBuffer), + {reply, {ok, queue:len(MessageBuffer)}, State#q{message_buffer = queue:new()}}; + +handle_call({claim_queue, ReaderPid}, _From, State = #q{owner = Owner, + exclusive_consumer = Holder}) -> + case Owner of + none -> + case check_exclusive_access(Holder, true) of + in_use -> + %% FIXME: Is this really the right answer? What if + %% an active consumer's reader is actually the + %% claiming pid? Should that be allowed? In order + %% to check, we'd need to hold not just the ch + %% pid for each consumer, but also its reader + %% pid... + {reply, locked, State}; + ok -> + {reply, ok, State#q{owner = {ReaderPid, erlang:monitor(process, ReaderPid)}}} + end; + {ReaderPid, _MonitorRef} -> + {reply, ok, State}; + _ -> + {reply, locked, State} + end. + +handle_cast({deliver, Txn, Message}, State) -> + %% Asynchronous, non-"mandatory", non-"immediate" deliver mode. + {_Delivered, NewState} = deliver_or_enqueue(Txn, Message, State), + {noreply, NewState}; + +handle_cast({ack, Txn, MsgIds, ChPid}, State) -> + case lookup_ch(ChPid) of + not_found -> + {noreply, State}; + C = #cr{unacked_messages = UAM} -> + {Acked, Remaining} = collect_messages(MsgIds, UAM), + persist_acks(Txn, qname(State), Acked), + case Txn of + none -> + store_ch_record(C#cr{unacked_messages = Remaining}); + _ -> + record_pending_acks(Txn, ChPid, MsgIds) + end, + {noreply, State} + end; + +handle_cast({rollback, Txn}, State) -> + ok = rollback_work(Txn, qname(State)), + erase_tx(Txn), + {noreply, State}; + +handle_cast({redeliver, Messages}, State) -> + {noreply, deliver_or_enqueue_n(Messages, State)}; + +handle_cast({requeue, MsgIds, ChPid}, State) -> + case lookup_ch(ChPid) of + not_found -> + rabbit_log:warning("Ignoring requeue from unknown ch: ~p~n", + [ChPid]), + {noreply, State}; + C = #cr{unacked_messages = UAM} -> + {Messages, NewUAM} = collect_messages(MsgIds, UAM), + store_ch_record(C#cr{unacked_messages = NewUAM}), + {noreply, deliver_or_enqueue_n( + [{Message, true} || Message <- Messages], State)} + end; + +handle_cast({notify_sent, ChPid}, State) -> + case lookup_ch(ChPid) of + not_found -> {noreply, State}; + T = #cr{unsent_message_count =Count} -> + {noreply, possibly_unblock( + T#cr{unsent_message_count = Count - 1}, + State)} + end. + +handle_info({'DOWN', MonitorRef, process, DownPid, _Reason}, + State = #q{owner = {DownPid, MonitorRef}}) -> + %% We know here that there are no consumers on this queue that are + %% owned by other pids than the one that just went down, so since + %% exclusive in some sense implies autodelete, we delete the queue + %% here. The other way of implementing the "exclusive implies + %% autodelete" feature is to actually set autodelete when an + %% exclusive declaration is seen, but this has the problem that + %% the python tests rely on the queue not going away after a + %% basic.cancel when the queue was declared exclusive and + %% nonautodelete. + NewState = State#q{owner = none}, + {stop, normal, NewState}; +handle_info({'DOWN', _MonitorRef, process, DownPid, _Reason}, State) -> + handle_ch_down(DownPid, State); + +handle_info(Info, State) -> + ?LOGDEBUG("Info in queue: ~p~n", [Info]), + {stop, {unhandled_info, Info}, State}. diff --git a/src/rabbit_amqqueue_sup.erl b/src/rabbit_amqqueue_sup.erl new file mode 100644 index 00000000..c68e2b3e --- /dev/null +++ b/src/rabbit_amqqueue_sup.erl @@ -0,0 +1,42 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (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.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developers of the Original Code are LShift Ltd., +%% Cohesive Financial Technologies LLC., and Rabbit Technologies Ltd. +%% +%% Portions created by LShift Ltd., Cohesive Financial Technologies +%% LLC., and Rabbit Technologies Ltd. are Copyright (C) 2007-2008 +%% LShift Ltd., Cohesive Financial Technologies LLC., and Rabbit +%% Technologies Ltd.; +%% +%% All Rights Reserved. +%% +%% Contributor(s): ______________________________________. +%% + +-module(rabbit_amqqueue_sup). + +-behaviour(supervisor). + +-export([start_link/0]). + +-export([init/1]). + +-define(SERVER, ?MODULE). + +start_link() -> + supervisor:start_link({local, ?SERVER}, ?MODULE, []). + +init([]) -> + {ok, {{simple_one_for_one, 10, 10}, + [{rabbit_amqqueue, {rabbit_amqqueue_process, start_link, []}, + temporary, brutal_kill, worker, [rabbit_amqqueue_process]}]}}. diff --git a/src/rabbit_binary_generator.erl b/src/rabbit_binary_generator.erl new file mode 100644 index 00000000..8b16abec --- /dev/null +++ b/src/rabbit_binary_generator.erl @@ -0,0 +1,271 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (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.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developers of the Original Code are LShift Ltd., +%% Cohesive Financial Technologies LLC., and Rabbit Technologies Ltd. +%% +%% Portions created by LShift Ltd., Cohesive Financial Technologies +%% LLC., and Rabbit Technologies Ltd. are Copyright (C) 2007-2008 +%% LShift Ltd., Cohesive Financial Technologies LLC., and Rabbit +%% Technologies Ltd.; +%% +%% All Rights Reserved. +%% +%% Contributor(s): ______________________________________. +%% + +-module(rabbit_binary_generator). +-include("rabbit_framing.hrl"). +-include("rabbit.hrl"). + +% EMPTY_CONTENT_BODY_FRAME_SIZE, 8 = 1 + 2 + 4 + 1 +% - 1 byte of frame type +% - 2 bytes of channel number +% - 4 bytes of frame payload length +% - 1 byte of payload trailer FRAME_END byte +% See definition of check_empty_content_body_frame_size/0, an assertion called at startup. +-define(EMPTY_CONTENT_BODY_FRAME_SIZE, 8). + +-export([build_simple_method_frame/2, + build_simple_content_frames/3, + build_heartbeat_frame/0]). +-export([generate_table/1, encode_properties/2]). +-export([check_empty_content_body_frame_size/0]). + +-import(lists). + +%%---------------------------------------------------------------------------- + +-ifdef(use_specs). + +-type(frame() :: [binary()]). + +-spec(build_simple_method_frame/2 :: + (channel_number(), amqp_method()) -> frame()). +-spec(build_simple_content_frames/3 :: + (channel_number(), content(), non_neg_integer()) -> [frame()]). +-spec(build_heartbeat_frame/0 :: () -> frame()). +-spec(generate_table/1 :: (amqp_table()) -> binary()). +-spec(encode_properties/2 :: ([amqp_property_type()], [any()]) -> binary()). +-spec(check_empty_content_body_frame_size/0 :: () -> 'ok'). + +-endif. + +%%---------------------------------------------------------------------------- + +build_simple_method_frame(ChannelInt, MethodRecord) -> + MethodFields = rabbit_framing:encode_method_fields(MethodRecord), + MethodName = rabbit_misc:method_record_type(MethodRecord), + {ClassId, MethodId} = rabbit_framing:method_id(MethodName), + create_frame(1, ChannelInt, [<<ClassId:16, MethodId:16>>, MethodFields]). + +build_simple_content_frames(ChannelInt, + #content{class_id = ClassId, + properties = ContentProperties, + properties_bin = ContentPropertiesBin, + payload_fragments_rev = PayloadFragmentsRev}, + FrameMax) -> + {BodySize, ContentFrames} = build_content_frames(PayloadFragmentsRev, FrameMax, ChannelInt), + HeaderFrame = create_frame(2, ChannelInt, + [<<ClassId:16, 0:16, BodySize:64>>, + maybe_encode_properties(ContentProperties, ContentPropertiesBin)]), + [HeaderFrame | ContentFrames]. + +maybe_encode_properties(_ContentProperties, ContentPropertiesBin) + when is_binary(ContentPropertiesBin) -> + ContentPropertiesBin; +maybe_encode_properties(ContentProperties, none) -> + rabbit_framing:encode_properties(ContentProperties). + +build_content_frames(FragmentsRev, FrameMax, ChannelInt) -> + BodyPayloadMax = if + FrameMax == 0 -> + none; + true -> + FrameMax - ?EMPTY_CONTENT_BODY_FRAME_SIZE + end, + build_content_frames(0, [], FragmentsRev, BodyPayloadMax, ChannelInt). + +build_content_frames(SizeAcc, FragmentAcc, [], _BodyPayloadMax, _ChannelInt) -> + {SizeAcc, FragmentAcc}; +build_content_frames(SizeAcc, FragmentAcc, [Fragment | FragmentsRev], + BodyPayloadMax, ChannelInt) + when is_number(BodyPayloadMax) and (size(Fragment) > BodyPayloadMax) -> + <<Head:BodyPayloadMax/binary, Tail/binary>> = Fragment, + build_content_frames(SizeAcc, FragmentAcc, [Tail, Head | FragmentsRev], + BodyPayloadMax, ChannelInt); +build_content_frames(SizeAcc, FragmentAcc, [<<>> | FragmentsRev], + BodyPayloadMax, ChannelInt) -> + build_content_frames(SizeAcc, FragmentAcc, FragmentsRev, BodyPayloadMax, ChannelInt); +build_content_frames(SizeAcc, FragmentAcc, [Fragment | FragmentsRev], + BodyPayloadMax, ChannelInt) -> + build_content_frames(SizeAcc + size(Fragment), + [create_frame(3, ChannelInt, Fragment) | FragmentAcc], + FragmentsRev, + BodyPayloadMax, + ChannelInt). + +build_heartbeat_frame() -> + create_frame(?FRAME_HEARTBEAT, 0, <<>>). + +create_frame(TypeInt, ChannelInt, PayloadBin) when is_binary(PayloadBin) -> + [<<TypeInt:8, ChannelInt:16, (size(PayloadBin)):32>>, PayloadBin, <<?FRAME_END>>]; +create_frame(TypeInt, ChannelInt, Payload) -> + create_frame(TypeInt, ChannelInt, list_to_binary(Payload)). + +%% table_field_to_binary supports the AMQP 0-8/0-9 standard types, S, +%% I, D, T and F, as well as the QPid extensions b, d, f, l, s, t, x, +%% and V. + +table_field_to_binary({FName, longstr, Value}) -> + [short_string_to_binary(FName), "S", long_string_to_binary(Value)]; + +table_field_to_binary({FName, signedint, Value}) -> + [short_string_to_binary(FName), "I", <<Value:32/signed>>]; + +table_field_to_binary({FName, decimal, {Before, After}}) -> + [short_string_to_binary(FName), "D", Before, <<After:32>>]; + +table_field_to_binary({FName, timestamp, Value}) -> + [short_string_to_binary(FName), "T", <<Value:64>>]; + +table_field_to_binary({FName, table, Value}) -> + [short_string_to_binary(FName), "F", table_to_binary(Value)]; + +table_field_to_binary({FName, byte, Value}) -> + [short_string_to_binary(FName), "b", <<Value:8/unsigned>>]; + +table_field_to_binary({FName, double, Value}) -> + [short_string_to_binary(FName), "d", <<Value:64/float>>]; + +table_field_to_binary({FName, float, Value}) -> + [short_string_to_binary(FName), "f", <<Value:32/float>>]; + +table_field_to_binary({FName, long, Value}) -> + [short_string_to_binary(FName), "l", <<Value:64/signed>>]; + +table_field_to_binary({FName, short, Value}) -> + [short_string_to_binary(FName), "s", <<Value:16/signed>>]; + +table_field_to_binary({FName, bool, Value}) -> + [short_string_to_binary(FName), "t", if Value -> 1; true -> 0 end]; + +table_field_to_binary({FName, binary, Value}) -> + [short_string_to_binary(FName), "x", long_string_to_binary(Value)]; + +table_field_to_binary({FName, void, _Value}) -> + [short_string_to_binary(FName), "V"]. + +table_to_binary(Table) when is_list(Table) -> + BinTable = generate_table(Table), + [<<(size(BinTable)):32>>, BinTable]. + +generate_table(Table) when is_list(Table) -> + list_to_binary(lists:map(fun table_field_to_binary/1, Table)). + + +short_string_to_binary(String) when is_binary(String) and (size(String) < 256) -> + [<<(size(String)):8>>, String]; +short_string_to_binary(String) -> + StringLength = length(String), + true = (StringLength < 256), % assertion + [<<StringLength:8>>, String]. + + +long_string_to_binary(String) when is_binary(String) -> + [<<(size(String)):32>>, String]; +long_string_to_binary(String) -> + [<<(length(String)):32>>, String]. + + +encode_properties([], []) -> + <<0, 0>>; +encode_properties(TypeList, ValueList) -> + encode_properties(0, TypeList, ValueList, 0, [], []). + +encode_properties(_Bit, [], [], FirstShortAcc, FlagsAcc, PropsAcc) -> + list_to_binary([lists:reverse(FlagsAcc), <<FirstShortAcc:16>>, lists:reverse(PropsAcc)]); +encode_properties(_Bit, [], _ValueList, _FirstShortAcc, _FlagsAcc, _PropsAcc) -> + exit(content_properties_values_overflow); +encode_properties(15, TypeList, ValueList, FirstShortAcc, FlagsAcc, PropsAcc) -> + NewFlagsShort = FirstShortAcc bor 1, % set the continuation low bit + encode_properties(0, TypeList, ValueList, 0, [<<NewFlagsShort:16>> | FlagsAcc], PropsAcc); +encode_properties(Bit, [bit | TypeList], [Value | ValueList], FirstShortAcc, FlagsAcc, PropsAcc) -> + case Value of + true -> encode_properties(Bit + 1, TypeList, ValueList, + FirstShortAcc bor (1 bsl (15 - Bit)), FlagsAcc, PropsAcc); + false -> encode_properties(Bit + 1, TypeList, ValueList, + FirstShortAcc, FlagsAcc, PropsAcc); + Other -> exit({content_properties_illegal_bit_value, Other}) + end; +encode_properties(Bit, [T | TypeList], [Value | ValueList], FirstShortAcc, FlagsAcc, PropsAcc) -> + case Value of + undefined -> encode_properties(Bit + 1, TypeList, ValueList, + FirstShortAcc, FlagsAcc, PropsAcc); + _ -> encode_properties(Bit + 1, TypeList, ValueList, + FirstShortAcc bor (1 bsl (15 - Bit)), + FlagsAcc, + [encode_property(T, Value) | PropsAcc]) + end. + +encode_property(shortstr, String) -> + Len = size(String), <<Len:8/unsigned, String:Len/binary>>; +encode_property(longstr, String) -> + Len = size(String), <<Len:32/unsigned, String:Len/binary>>; +encode_property(octet, Int) -> + <<Int:8/unsigned>>; +encode_property(shortint, Int) -> + <<Int:16/unsigned>>; +encode_property(longint, Int) -> + <<Int:32/unsigned>>; +encode_property(longlongint, Int) -> + <<Int:64/unsigned>>; +encode_property(timestamp, Int) -> + <<Int:64/unsigned>>; +encode_property(table, Table) -> + encode_table(Table). + + +encode_table(Table) -> + TableBin = list_to_binary(lists:map(fun encode_table_entry/1, Table)), + Len = size(TableBin), + <<Len:32/unsigned, TableBin:Len/binary>>. + + +encode_table_entry({Name, longstr, Value}) -> + NLen = size(Name), + VLen = size(Value), + <<NLen:8/unsigned, Name:NLen/binary, "S", VLen:32/unsigned, Value:VLen/binary>>; +encode_table_entry({Name, signedint, Value}) -> + NLen = size(Name), + <<NLen:8/unsigned, Name:NLen/binary, "I", Value:32/signed>>; +encode_table_entry({Name, decimal, {Before, After}}) -> + NLen = size(Name), + <<NLen:8/unsigned, Name:NLen/binary, "D", Before:8/unsigned, After:32/unsigned>>; +encode_table_entry({Name, timestamp, Value}) -> + NLen = size(Name), + <<NLen:8/unsigned, Name:NLen/binary, "T", Value:64/unsigned>>; +encode_table_entry({Name, table, Value}) -> + NLen = size(Name), + TableBin = encode_table(Value), + <<NLen:8/unsigned, Name:NLen/binary, "F", TableBin/binary>>. + +check_empty_content_body_frame_size() -> + %% Intended to ensure that EMPTY_CONTENT_BODY_FRAME_SIZE is + %% defined correctly. + ComputedSize = size(list_to_binary(create_frame(?FRAME_BODY, 0, <<>>))), + if ComputedSize == ?EMPTY_CONTENT_BODY_FRAME_SIZE -> + ok; + true -> + exit({incorrect_empty_content_body_frame_size, + ComputedSize, ?EMPTY_CONTENT_BODY_FRAME_SIZE}) + end. diff --git a/src/rabbit_binary_parser.erl b/src/rabbit_binary_parser.erl new file mode 100644 index 00000000..e9425215 --- /dev/null +++ b/src/rabbit_binary_parser.erl @@ -0,0 +1,161 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (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.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developers of the Original Code are LShift Ltd., +%% Cohesive Financial Technologies LLC., and Rabbit Technologies Ltd. +%% +%% Portions created by LShift Ltd., Cohesive Financial Technologies +%% LLC., and Rabbit Technologies Ltd. are Copyright (C) 2007-2008 +%% LShift Ltd., Cohesive Financial Technologies LLC., and Rabbit +%% Technologies Ltd.; +%% +%% All Rights Reserved. +%% +%% Contributor(s): ______________________________________. +%% + +-module(rabbit_binary_parser). + +-include("rabbit.hrl"). + +-export([parse_table/1, parse_properties/2]). +-export([ensure_content_decoded/1, clear_decoded_content/1]). + +-import(lists). + +%%---------------------------------------------------------------------------- + +-ifdef(use_specs). + +-spec(parse_table/1 :: (binary()) -> amqp_table()). +-spec(parse_properties/2 :: ([amqp_property_type()], binary()) -> [any()]). +-spec(ensure_content_decoded/1 :: (content()) -> decoded_content()). +-spec(clear_decoded_content/1 :: (content()) -> undecoded_content()). + +-endif. + +%%---------------------------------------------------------------------------- + +%% parse_table supports the AMQP 0-8/0-9 standard types, S, I, D, T +%% and F, as well as the QPid extensions b, d, f, l, s, t, x, and V. + +parse_table(<<>>) -> + []; + +parse_table(<<NLen:8/unsigned, NameString:NLen/binary, "S", VLen:32/unsigned, ValueString:VLen/binary, Rest/binary>>) -> + [{NameString, longstr, ValueString} | parse_table(Rest)]; + +parse_table(<<NLen:8/unsigned, NameString:NLen/binary, "I", Value:32/signed, Rest/binary>>) -> + [{NameString, signedint, Value} | parse_table(Rest)]; + +parse_table(<<NLen:8/unsigned, NameString:NLen/binary, "D", Before:8/unsigned, After:32/unsigned, Rest/binary>>) -> + [{NameString, decimal, {Before, After}} | parse_table(Rest)]; + +parse_table(<<NLen:8/unsigned, NameString:NLen/binary, "T", Value:64/unsigned, Rest/binary>>) -> + [{NameString, timestamp, Value} | parse_table(Rest)]; + +parse_table(<<NLen:8/unsigned, NameString:NLen/binary, "F", VLen:32/unsigned, Table:VLen/binary, Rest/binary>>) -> + [{NameString, table, parse_table(Table)} | parse_table(Rest)]; + +parse_table(<<NLen:8/unsigned, NameString:NLen/binary, "b", Value:8/unsigned, Rest/binary>>) -> + [{NameString, byte, Value} | parse_table(Rest)]; + +parse_table(<<NLen:8/unsigned, NameString:NLen/binary, "d", Value:64/float, Rest/binary>>) -> + [{NameString, double, Value} | parse_table(Rest)]; + +parse_table(<<NLen:8/unsigned, NameString:NLen/binary, "f", Value:32/float, Rest/binary>>) -> + [{NameString, float, Value} | parse_table(Rest)]; + +parse_table(<<NLen:8/unsigned, NameString:NLen/binary, "l", Value:64/signed, Rest/binary>>) -> + [{NameString, long, Value} | parse_table(Rest)]; + +parse_table(<<NLen:8/unsigned, NameString:NLen/binary, "s", Value:16/signed, Rest/binary>>) -> + [{NameString, short, Value} | parse_table(Rest)]; + +parse_table(<<NLen:8/unsigned, NameString:NLen/binary, "t", Value:8/unsigned, Rest/binary>>) -> + [{NameString, bool, (Value /= 0)} | parse_table(Rest)]; + +parse_table(<<NLen:8/unsigned, NameString:NLen/binary, "x", VLen:32/unsigned, ValueString:VLen/binary, Rest/binary>>) -> + [{NameString, binary, ValueString} | parse_table(Rest)]; + +parse_table(<<NLen:8/unsigned, NameString:NLen/binary, "V", Rest/binary>>) -> + [{NameString, void, undefined} | parse_table(Rest)]. + + +parse_properties([], _PropBin) -> + []; +parse_properties(TypeList, PropBin) -> + FlagCount = length(TypeList), + %% round up to the nearest multiple of 15 bits, since the 16th bit + %% in each short is a "continuation" bit. + FlagsLengthBytes = trunc((FlagCount + 14) / 15) * 2, + <<Flags:FlagsLengthBytes/binary, Properties/binary>> = PropBin, + <<FirstShort:16, Remainder/binary>> = Flags, + parse_properties(0, TypeList, [], FirstShort, Remainder, Properties). + +parse_properties(_Bit, [], Acc, _FirstShort, + _Remainder, <<>>) -> + lists:reverse(Acc); +parse_properties(_Bit, [], _Acc, _FirstShort, + _Remainder, _LeftoverBin) -> + exit(content_properties_binary_overflow); +parse_properties(15, TypeList, Acc, _OldFirstShort, + <<NewFirstShort:16,Remainder/binary>>, Properties) -> + parse_properties(0, TypeList, Acc, NewFirstShort, Remainder, Properties); +parse_properties(Bit, [Type | TypeListRest], Acc, FirstShort, + Remainder, Properties) -> + {Value, Rest} = + if (FirstShort band (1 bsl (15 - Bit))) /= 0 -> + parse_property(Type, Properties); + Type == bit -> {false , Properties}; + true -> {undefined, Properties} + end, + parse_properties(Bit + 1, TypeListRest, [Value | Acc], FirstShort, + Remainder, Rest). + +parse_property(shortstr, <<Len:8/unsigned, String:Len/binary, Rest/binary>>) -> + {String, Rest}; +parse_property(longstr, <<Len:32/unsigned, String:Len/binary, Rest/binary>>) -> + {String, Rest}; +parse_property(octet, <<Int:8/unsigned, Rest/binary>>) -> + {Int, Rest}; +parse_property(shortint, <<Int:16/unsigned, Rest/binary>>) -> + {Int, Rest}; +parse_property(longint, <<Int:32/unsigned, Rest/binary>>) -> + {Int, Rest}; +parse_property(longlongint, <<Int:64/unsigned, Rest/binary>>) -> + {Int, Rest}; +parse_property(timestamp, <<Int:64/unsigned, Rest/binary>>) -> + {Int, Rest}; +parse_property(bit, Rest) -> + {true, Rest}; +parse_property(table, <<Len:32/unsigned, Table:Len/binary, Rest/binary>>) -> + {parse_table(Table), Rest}. + + +ensure_content_decoded(Content = #content{properties = Props}) + when Props =/= 'none' -> + Content; +ensure_content_decoded(Content = #content{properties_bin = PropBin}) + when is_binary(PropBin) -> + Content#content{properties = rabbit_framing:decode_properties( + Content#content.class_id, PropBin)}. + +clear_decoded_content(Content = #content{properties = none}) -> + Content; +clear_decoded_content(Content = #content{properties_bin = none}) -> + %% Only clear when we can rebuild the properties later in + %% accordance to the content record definition comment - maximum + %% one of properties and properties_bin can be 'none' + Content; +clear_decoded_content(Content = #content{}) -> + Content#content{properties = none}. diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl new file mode 100644 index 00000000..ec1d1fba --- /dev/null +++ b/src/rabbit_channel.erl @@ -0,0 +1,894 @@ +%% The contents of this file are subject to the Mozilla Public Licenses +%% Version 1.1 (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.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developers of the Original Code are LShift Ltd., +%% Cohesive Financial Technologies LLC., and Rabbit Technologies Ltd. +%% +%% Portions created by LShift Ltd., Cohesive Financial Technologies +%% LLC., and Rabbit Technologies Ltd. are Copyright (C) 2007-2008 +%% LShift Ltd., Cohesive Financial Technologies LLC., and Rabbit +%% Technologies Ltd.; +%% +%% All Rights Reserved. +%% +%% Contributor(s): ______________________________________. +%% + +-module(rabbit_channel). +-include("rabbit_framing.hrl"). +-include("rabbit.hrl"). + +-export([start_link/4, do/2, do/3, shutdown/1]). +-export([send_command/2, deliver/4]). + +%% callbacks +-export([init/2, handle_message/2]). + +-record(ch, {state, proxy_pid, reader_pid, writer_pid, + transaction_id, tx_participants, next_tag, + uncommitted_ack_q, unacked_message_q, + username, virtual_host, + most_recently_declared_queue, consumer_mapping, next_ticket}). + +%%---------------------------------------------------------------------------- + +-ifdef(use_specs). + +-spec(start_link/4 :: (pid(), pid(), username(), vhost()) -> pid()). +-spec(do/2 :: (pid(), amqp_method()) -> 'ok'). +-spec(do/3 :: (pid(), amqp_method(), maybe(content())) -> 'ok'). +-spec(shutdown/1 :: (pid()) -> 'ok'). +-spec(send_command/2 :: (pid(), amqp_method()) -> 'ok'). +-spec(deliver/4 :: (pid(), ctag(), bool(), msg()) -> 'ok'). + +-endif. + +%%---------------------------------------------------------------------------- + +start_link(ReaderPid, WriterPid, Username, VHost) -> + buffering_proxy:start_link(?MODULE, [ReaderPid, WriterPid, + Username, VHost]). + +do(Pid, Method) -> + do(Pid, Method, none). + +do(Pid, Method, Content) -> + Pid ! {method, Method, Content}, + ok. + +shutdown(Pid) -> + Pid ! terminate, + ok. + +send_command(Pid, Msg) -> + Pid ! {command, Msg}, + ok. + +deliver(Pid, ConsumerTag, AckRequired, Msg) -> + Pid ! {deliver, ConsumerTag, AckRequired, Msg}, + ok. + +%%--------------------------------------------------------------------------- + +init(ProxyPid, [ReaderPid, WriterPid, Username, VHost]) -> + process_flag(trap_exit, true), + link(WriterPid), + #ch{state = starting, + proxy_pid = ProxyPid, + reader_pid = ReaderPid, + writer_pid = WriterPid, + transaction_id = none, + tx_participants = sets:new(), + next_tag = 1, + uncommitted_ack_q = queue:new(), + unacked_message_q = queue:new(), + username = Username, + virtual_host = VHost, + most_recently_declared_queue = <<>>, + consumer_mapping = dict:new(), + next_ticket = 101}. + +handle_message({method, Method, Content}, State) -> + case (catch handle_method(Method, Content, State)) of + {reply, Reply, NewState} -> + ok = rabbit_writer:send_command(NewState#ch.writer_pid, Reply), + NewState; + {noreply, NewState} -> + NewState; + stop -> + exit(normal); + {'EXIT', {amqp, Error, Explanation, none}} -> + terminate({amqp, Error, Explanation, + rabbit_misc:method_record_type(Method)}, + State); + {'EXIT', Reason} -> + terminate(Reason, State) + end; + +handle_message(terminate, State) -> + terminate(normal, State); + +handle_message({command, Msg}, State = #ch{writer_pid = WriterPid}) -> + ok = rabbit_writer:send_command(WriterPid, Msg), + State; + +handle_message({deliver, ConsumerTag, AckRequired, Msg}, + State = #ch{proxy_pid = ProxyPid, + writer_pid = WriterPid, + next_tag = DeliveryTag}) -> + State1 = lock_message(AckRequired, {DeliveryTag, ConsumerTag, Msg}, State), + ok = internal_deliver(WriterPid, ProxyPid, + true, ConsumerTag, DeliveryTag, Msg), + State1#ch{next_tag = DeliveryTag + 1}; + +handle_message({'EXIT', _Pid, Reason}, State) -> + terminate(Reason, State); + +handle_message(Other, State) -> + terminate({unexpected_channel_message, Other}, State). + +%%--------------------------------------------------------------------------- + +terminate(Reason, State = #ch{writer_pid = WriterPid}) -> + Res = notify_queues(internal_rollback(State)), + ok = rabbit_realm:leave_realms(self()), + case Reason of + normal -> ok = Res; + _ -> ok + end, + rabbit_writer:shutdown(WriterPid), + exit(Reason). + +return_ok(State, true, _Msg) -> {noreply, State}; +return_ok(State, false, Msg) -> {reply, Msg, State}. + +ok_msg(true, _Msg) -> undefined; +ok_msg(false, Msg) -> Msg. + +return_queue_declare_ok(State, NoWait, Q) -> + NewState = State#ch{most_recently_declared_queue = + (Q#amqqueue.name)#resource.name}, + case NoWait of + true -> {noreply, NewState}; + false -> + {ok, ActualName, MessageCount, ConsumerCount} = + rabbit_misc:with_exit_handler( + fun () -> {ok, Q#amqqueue.name, 0, 0} end, + fun () -> rabbit_amqqueue:stat(Q) end), + Reply = #'queue.declare_ok'{queue = ActualName#resource.name, + message_count = MessageCount, + consumer_count = ConsumerCount}, + {reply, Reply, NewState} + end. + +expand_queue_name_shortcut(<<>>, #ch{ most_recently_declared_queue = <<>> }) -> + rabbit_misc:protocol_error( + not_allowed, "no previously declared queue", []); +expand_queue_name_shortcut(<<>>, #ch{ virtual_host = VHostPath, + most_recently_declared_queue = MRDQ }) -> + rabbit_misc:r(VHostPath, queue, MRDQ); +expand_queue_name_shortcut(QueueNameBin, #ch{ virtual_host = VHostPath }) -> + rabbit_misc:r(VHostPath, queue, QueueNameBin). + +expand_routing_key_shortcut(<<>>, <<>>, + #ch{ most_recently_declared_queue = <<>> }) -> + rabbit_misc:protocol_error( + not_allowed, "no previously declared queue", []); +expand_routing_key_shortcut(<<>>, <<>>, + #ch{ most_recently_declared_queue = MRDQ }) -> + MRDQ; +expand_routing_key_shortcut(_QueueNameBin, RoutingKey, _State) -> + RoutingKey. + +die_precondition_failed(Fmt, Params) -> + %% FIXME: 406 should be replaced with precondition_failed when we + %% move to AMQP spec >=8.1 + rabbit_misc:protocol_error({false, 406, <<"PRECONDITION_FAILED">>}, + Fmt, Params). + +check_ticket(TicketNumber, FieldIndex, Name, #ch{ username = Username}) -> + rabbit_ticket:check_ticket(TicketNumber, FieldIndex, Name, Username). + +lookup_ticket(TicketNumber, FieldIndex, + #ch{ username = Username, virtual_host = VHostPath }) -> + rabbit_ticket:lookup_ticket(TicketNumber, FieldIndex, + Username, VHostPath). + +%% check that an exchange/queue name does not contain the reserved +%% "amq." prefix. +%% +%% One, quite reasonable, interpretation of the spec, taken by the +%% QPid M1 Java client, is that the exclusion of "amq." prefixed names +%% only applies on actual creation, and not in the cases where the +%% entity already exists. This is how we use this function in the code +%% below. However, AMQP JIRA 123 changes that in 0-10, and possibly +%% 0-9SP1, making it illegal to attempt to declare an exchange/queue +%% with an amq.* name when passive=false. So this will need +%% revisiting. +%% +%% TODO: enforce other constraints on name. See AMQP JIRA 69. +check_name(Kind, NameBin = <<"amq.", _/binary>>) -> + rabbit_misc:protocol_error( + access_refused, + "~s name '~s' contains reserved prefix 'amq.*'",[Kind, NameBin]); +check_name(_Kind, NameBin) -> + NameBin. + +handle_method(#'channel.open'{}, _, State = #ch{state = starting}) -> + {reply, #'channel.open_ok'{}, State#ch{state = running}}; + +handle_method(#'channel.open'{}, _, _State) -> + rabbit_misc:protocol_error( + command_invalid, "second 'channel.open' seen", []); + +handle_method(_Method, _, #ch{state = starting}) -> + rabbit_misc:protocol_error(channel_error, "expected 'channel.open'", []); + +handle_method(#'channel.close'{}, _, State = #ch{writer_pid = WriterPid}) -> + ok = notify_queues(internal_rollback(State)), + ok = rabbit_realm:leave_realms(self()), + ok = rabbit_writer:send_command(WriterPid, #'channel.close_ok'{}), + ok = rabbit_writer:shutdown(WriterPid), + stop; + +handle_method(#'access.request'{realm = RealmNameBin, + exclusive = Exclusive, + passive = Passive, + active = Active, + write = Write, + read = Read}, + _, State = #ch{username = Username, + virtual_host = VHostPath, + next_ticket = NextTicket}) -> + RealmName = rabbit_misc:r(VHostPath, realm, RealmNameBin), + Ticket = #ticket{realm_name = RealmName, + passive_flag = Passive, + active_flag = Active, + write_flag = Write, + read_flag = Read}, + case rabbit_realm:access_request(Username, Exclusive, Ticket) of + ok -> + rabbit_ticket:record_ticket(NextTicket, Ticket), + NewState = State#ch{next_ticket = NextTicket + 1}, + {reply, #'access.request_ok'{ticket = NextTicket}, NewState}; + {error, not_found} -> + rabbit_misc:protocol_error( + invalid_path, "no ~s", [rabbit_misc:rs(RealmName)]); + {error, bad_realm_path} -> + %% FIXME: spec bug? access_refused is a soft error, spec requires it to be hard + rabbit_misc:protocol_error( + access_refused, "bad path for ~s", [rabbit_misc:rs(RealmName)]); + {error, resource_locked} -> + rabbit_misc:protocol_error( + resource_locked, "~s is locked", [rabbit_misc:rs(RealmName)]); + {error, access_refused} -> + rabbit_misc:protocol_error( + access_refused, + "~w permissions denied for user '~s' attempting to access ~s", + [rabbit_misc:permission_list(Ticket), + Username, rabbit_misc:rs(RealmName)]) + end; + +handle_method(#'basic.publish'{ticket = TicketNumber, + exchange = ExchangeNameBin, + routing_key = RoutingKey, + mandatory = Mandatory, + immediate = Immediate}, + Content, State = #ch{ virtual_host = VHostPath}) -> + ExchangeName = rabbit_misc:r(VHostPath, exchange, ExchangeNameBin), + check_ticket(TicketNumber, #ticket.write_flag, ExchangeName, State), + Exchange = rabbit_exchange:lookup_or_die(ExchangeName), + %% We decode the content's properties here because we're almost + %% certain to want to look at delivery-mode and priority. + DecodedContent = rabbit_binary_parser:ensure_content_decoded(Content), + PersistentKey = case is_message_persistent(DecodedContent) of + true -> rabbit_misc:guid(); + false -> none + end, + {noreply, publish(Mandatory, Immediate, + #basic_message{exchange_name = ExchangeName, + routing_key = RoutingKey, + content = DecodedContent, + persistent_key = PersistentKey}, + rabbit_exchange:route(Exchange, RoutingKey), State)}; + +handle_method(#'basic.ack'{delivery_tag = DeliveryTag, + multiple = Multiple}, + _, State = #ch{transaction_id = TxnKey, + next_tag = NextDeliveryTag, + unacked_message_q = UAMQ}) -> + if DeliveryTag >= NextDeliveryTag -> + rabbit_misc:protocol_error( + command_invalid, "unknown delivery tag ~w", [DeliveryTag]); + true -> ok + end, + {Acked, Remaining} = collect_acks(UAMQ, DeliveryTag, Multiple), + Participants = ack(State#ch.proxy_pid, TxnKey, Acked), + {noreply, case TxnKey of + none -> State#ch{unacked_message_q = Remaining}; + _ -> NewUAQ = queue:join(State#ch.uncommitted_ack_q, + Acked), + add_tx_participants( + Participants, + State#ch{unacked_message_q = Remaining, + uncommitted_ack_q = NewUAQ}) + end}; + +handle_method(#'basic.get'{ticket = TicketNumber, + queue = QueueNameBin, + no_ack = NoAck}, + _, State = #ch{ proxy_pid = ProxyPid, writer_pid = WriterPid, + next_tag = DeliveryTag }) -> + QueueName = expand_queue_name_shortcut(QueueNameBin, State), + check_ticket(TicketNumber, #ticket.read_flag, QueueName, State), + case rabbit_amqqueue:with_or_die( + QueueName, + fun (Q) -> rabbit_amqqueue:basic_get(Q, ProxyPid, NoAck) end) of + {ok, MessageCount, + Msg = {_QName, _QPid, _MsgId, Redelivered, + #basic_message{exchange_name = ExchangeName, + routing_key = RoutingKey, + content = Content}}} -> + State1 = lock_message(not(NoAck), {DeliveryTag, none, Msg}, State), + ok = rabbit_writer:send_command( + WriterPid, + #'basic.get_ok'{delivery_tag = DeliveryTag, + redelivered = Redelivered, + exchange = ExchangeName#resource.name, + routing_key = RoutingKey, + message_count = MessageCount}, + Content), + {noreply, State1#ch{next_tag = DeliveryTag + 1}}; + empty -> + {reply, #'basic.get_empty'{cluster_id = <<>>}, State} + end; + +handle_method(#'basic.consume'{ticket = TicketNumber, + queue = QueueNameBin, + consumer_tag = ConsumerTag, + no_local = _, % FIXME: implement + no_ack = NoAck, + exclusive = ExclusiveConsume, + nowait = NoWait}, + _, State = #ch{ proxy_pid = ProxyPid, + reader_pid = ReaderPid, + consumer_mapping = ConsumerMapping }) -> + case dict:find(ConsumerTag, ConsumerMapping) of + error -> + QueueName = expand_queue_name_shortcut(QueueNameBin, State), + check_ticket(TicketNumber, #ticket.read_flag, QueueName, State), + ActualConsumerTag = + case ConsumerTag of + <<>> -> rabbit_misc:binstring_guid("amq.ctag"); + Other -> Other + end, + + %% In order to ensure that the consume_ok gets sent before + %% any messages are sent to the consumer, we get the queue + %% process to send the consume_ok on our behalf. + case rabbit_amqqueue:with_or_die( + QueueName, + fun (Q) -> + rabbit_amqqueue:basic_consume( + Q, NoAck, ReaderPid, ProxyPid, + ActualConsumerTag, ExclusiveConsume, + ok_msg(NoWait, #'basic.consume_ok'{ + consumer_tag = ActualConsumerTag})) + end) of + ok -> + {noreply, State#ch{consumer_mapping = + dict:store(ActualConsumerTag, + QueueName, + ConsumerMapping)}}; + {error, queue_owned_by_another_connection} -> + %% The spec is silent on which exception to use + %% here. This seems reasonable? + %% FIXME: check this + + rabbit_misc:protocol_error( + resource_locked, "~s owned by another connection", + [rabbit_misc:rs(QueueName)]); + {error, exclusive_consume_unavailable} -> + rabbit_misc:protocol_error( + access_refused, "~s in exclusive use", + [rabbit_misc:rs(QueueName)]) + end; + {ok, _} -> + %% Attempted reuse of consumer tag. + rabbit_misc:protocol_error( + not_allowed, "attempt to reuse consumer tag '~s'", [ConsumerTag]) + end; + +handle_method(#'basic.cancel'{consumer_tag = ConsumerTag, + nowait = NoWait}, + _, State = #ch{ proxy_pid = ProxyPid, + consumer_mapping = ConsumerMapping }) -> + OkMsg = #'basic.cancel_ok'{consumer_tag = ConsumerTag}, + case dict:find(ConsumerTag, ConsumerMapping) of + error -> + %% Spec requires we ignore this situation. + return_ok(State, NoWait, OkMsg); + {ok, QueueName} -> + NewState = State#ch{consumer_mapping = + dict:erase(ConsumerTag, + ConsumerMapping)}, + case rabbit_amqqueue:with( + QueueName, + fun (Q) -> + %% In order to ensure that no more messages + %% are sent to the consumer after the + %% cancel_ok has been sent, we get the + %% queue process to send the cancel_ok on + %% our behalf. If we were sending the + %% cancel_ok ourselves it might overtake a + %% message sent previously by the queue. + rabbit_amqqueue:basic_cancel( + Q, ProxyPid, ConsumerTag, + ok_msg(NoWait, #'basic.cancel_ok'{ + consumer_tag = ConsumerTag})) + end) of + ok -> + {noreply, NewState}; + {error, not_found} -> + %% Spec requires we ignore this situation. + return_ok(NewState, NoWait, OkMsg) + end + end; + +handle_method(#'basic.qos'{}, _, State) -> + %% FIXME: Need to implement QOS + {reply, #'basic.qos_ok'{}, State}; + +handle_method(#'basic.recover'{requeue = true}, + _, State = #ch{ transaction_id = none, + proxy_pid = ProxyPid, + unacked_message_q = UAMQ }) -> + ok = fold_per_queue( + fun (QPid, MsgIds, ok) -> + %% The Qpid python test suite incorrectly assumes + %% that messages will be requeued in their original + %% order. To keep it happy we reverse the id list + %% since we are given them in reverse order. + rabbit_amqqueue:requeue( + QPid, lists:reverse(MsgIds), ProxyPid) + end, ok, UAMQ), + %% No answer required, apparently! + {noreply, State#ch{unacked_message_q = queue:new()}}; + +handle_method(#'basic.recover'{requeue = false}, + _, State = #ch{ transaction_id = none, + proxy_pid = ProxyPid, + writer_pid = WriterPid, + unacked_message_q = UAMQ }) -> + lists:foreach( + fun ({_DeliveryTag, none, _Msg}) -> + %% Was sent as a basic.get_ok. Don't redeliver + %% it. FIXME: appropriate? + ok; + ({DeliveryTag, ConsumerTag, + {QName, QPid, MsgId, _Redelivered, Message}}) -> + %% Was sent as a proper consumer delivery. Resend it as + %% before. + %% + %% FIXME: What should happen if the consumer's been + %% cancelled since? + %% + %% FIXME: should we allocate a fresh DeliveryTag? + ok = internal_deliver( + WriterPid, ProxyPid, + false, ConsumerTag, DeliveryTag, + {QName, QPid, MsgId, true, Message}) + end, queue:to_list(UAMQ)), + %% No answer required, apparently! + {noreply, State}; + +handle_method(#'basic.recover'{}, _, _State) -> + rabbit_misc:protocol_error( + not_allowed, "attempt to recover a transactional channel",[]); + +handle_method(#'exchange.declare'{ticket = TicketNumber, + exchange = ExchangeNameBin, + type = TypeNameBin, + passive = false, + durable = Durable, + auto_delete = AutoDelete, + internal = false, + nowait = NoWait, + arguments = Args}, + _, State = #ch{ virtual_host = VHostPath }) -> + #ticket{realm_name = RealmName} = + lookup_ticket(TicketNumber, #ticket.active_flag, State), + CheckedType = rabbit_exchange:check_type(TypeNameBin), + %% FIXME: clarify spec as per declare wrt differing realms + X = case rabbit_exchange:lookup( + rabbit_misc:r(VHostPath, exchange, ExchangeNameBin)) of + {ok, FoundX} -> FoundX; + {error, not_found} -> + ActualNameBin = check_name('exchange', ExchangeNameBin), + rabbit_exchange:declare(RealmName, + ActualNameBin, + CheckedType, + Durable, + AutoDelete, + Args) + end, + ok = rabbit_exchange:assert_type(X, CheckedType), + return_ok(State, NoWait, #'exchange.declare_ok'{}); + +handle_method(#'exchange.declare'{ticket = TicketNumber, + exchange = ExchangeNameBin, + type = TypeNameBin, + passive = true, + nowait = NoWait}, + _, State = #ch{ virtual_host = VHostPath }) -> + %% FIXME: spec issue: permit active_flag here as well as passive_flag? + #ticket{} = lookup_ticket(TicketNumber, #ticket.passive_flag, State), + ExchangeName = rabbit_misc:r(VHostPath, exchange, ExchangeNameBin), + X = rabbit_exchange:lookup_or_die(ExchangeName), + ok = rabbit_exchange:assert_type(X, rabbit_exchange:check_type(TypeNameBin)), + return_ok(State, NoWait, #'exchange.declare_ok'{}); + +handle_method(#'exchange.delete'{ticket = TicketNumber, + exchange = ExchangeNameBin, + if_unused = IfUnused, + nowait = NoWait}, + _, State = #ch { virtual_host = VHostPath }) -> + ExchangeName = rabbit_misc:r(VHostPath, exchange, ExchangeNameBin), + check_ticket(TicketNumber, #ticket.active_flag, ExchangeName, State), + case rabbit_exchange:delete(ExchangeName, IfUnused) of + {error, not_found} -> + rabbit_misc:protocol_error( + not_found, "no ~s", [rabbit_misc:rs(ExchangeName)]); + {error, in_use} -> + die_precondition_failed( + "~s in use", [rabbit_misc:rs(ExchangeName)]); + ok -> + return_ok(State, NoWait, #'exchange.delete_ok'{}) + end; + +handle_method(#'queue.declare'{ticket = TicketNumber, + queue = QueueNameBin, + passive = false, + durable = Durable, + exclusive = ExclusiveDeclare, + auto_delete = AutoDelete, + nowait = NoWait, + arguments = Args}, + _, State = #ch { virtual_host = VHostPath, + reader_pid = ReaderPid }) -> + #ticket{realm_name = RealmName} = + lookup_ticket(TicketNumber, #ticket.active_flag, State), + %% FIXME: atomic create&claim + Finish = + fun (Q) -> + if ExclusiveDeclare -> + case rabbit_amqqueue:claim_queue(Q, ReaderPid) of + locked -> + %% AMQP 0-8 doesn't say which + %% exception to use, so we mimic QPid + %% here. + rabbit_misc:protocol_error( + resource_locked, + "cannot obtain exclusive access to locked ~s", + [rabbit_misc:rs(Q#amqqueue.name)]); + ok -> ok + end; + true -> + ok + end, + Q + end, + %% FIXME: clarify spec as per declare wrt differing realms + Q = case rabbit_amqqueue:with( + rabbit_misc:r(VHostPath, queue, QueueNameBin), + Finish) of + {error, not_found} -> + ActualNameBin = + case QueueNameBin of + <<>> -> rabbit_misc:binstring_guid("amq.gen"); + Other -> check_name('queue', Other) + end, + Finish(rabbit_amqqueue:declare(RealmName, + ActualNameBin, + Durable, + AutoDelete, + Args)); + Other -> Other + end, + return_queue_declare_ok(State, NoWait, Q); + +handle_method(#'queue.declare'{ticket = TicketNumber, + queue = QueueNameBin, + passive = true, + nowait = NoWait}, + _, State = #ch{ virtual_host = VHostPath }) -> + #ticket{} = lookup_ticket(TicketNumber, #ticket.passive_flag, State), + QueueName = rabbit_misc:r(VHostPath, queue, QueueNameBin), + Q = rabbit_amqqueue:with_or_die(QueueName, fun (Q) -> Q end), + return_queue_declare_ok(State, NoWait, Q); + +handle_method(#'queue.delete'{ticket = TicketNumber, + queue = QueueNameBin, + if_unused = IfUnused, + if_empty = IfEmpty, + nowait = NoWait + }, + _, State) -> + QueueName = expand_queue_name_shortcut(QueueNameBin, State), + check_ticket(TicketNumber, #ticket.active_flag, QueueName, State), + case rabbit_amqqueue:with_or_die( + QueueName, + fun (Q) -> rabbit_amqqueue:delete(Q, IfUnused, IfEmpty) end) of + {error, in_use} -> + die_precondition_failed( + "~s in use", [rabbit_misc:rs(QueueName)]); + {error, not_empty} -> + die_precondition_failed( + "~s not empty", [rabbit_misc:rs(QueueName)]); + {ok, PurgedMessageCount} -> + return_ok(State, NoWait, + #'queue.delete_ok'{ + message_count = PurgedMessageCount}) + end; + +handle_method(#'queue.bind'{ticket = TicketNumber, + queue = QueueNameBin, + exchange = ExchangeNameBin, + routing_key = RoutingKey, + nowait = NoWait, + arguments = Arguments}, + _, State = #ch{ virtual_host = VHostPath }) -> + %% FIXME: connection exception (!) on failure?? (see rule named "failure" in spec-XML) + %% FIXME: don't allow binding to internal exchanges - including the one named "" ! + QueueName = expand_queue_name_shortcut(QueueNameBin, State), + ActualRoutingKey = expand_routing_key_shortcut(QueueNameBin, RoutingKey, + State), + check_ticket(TicketNumber, #ticket.active_flag, QueueName, State), + ExchangeName = rabbit_misc:r(VHostPath, exchange, ExchangeNameBin), + case rabbit_amqqueue:add_binding(QueueName, ExchangeName, + ActualRoutingKey, Arguments) of + {error, queue_not_found} -> + rabbit_misc:protocol_error( + not_found, "no ~s", [rabbit_misc:rs(QueueName)]); + {error, exchange_not_found} -> + rabbit_misc:protocol_error( + not_found, "no ~s", [rabbit_misc:rs(ExchangeName)]); + {error, durability_settings_incompatible} -> + rabbit_misc:protocol_error( + not_allowed, "durability settings of ~s incompatible with ~s", + [rabbit_misc:rs(QueueName), rabbit_misc:rs(ExchangeName)]); + {ok, _BindingCount} -> + return_ok(State, NoWait, #'queue.bind_ok'{}) + end; + +handle_method(#'queue.purge'{ticket = TicketNumber, + queue = QueueNameBin, + nowait = NoWait}, + _, State) -> + QueueName = expand_queue_name_shortcut(QueueNameBin, State), + check_ticket(TicketNumber, #ticket.read_flag, QueueName, State), + {ok, PurgedMessageCount} = rabbit_amqqueue:with_or_die( + QueueName, + fun (Q) -> rabbit_amqqueue:purge(Q) end), + return_ok(State, NoWait, + #'queue.purge_ok'{message_count = PurgedMessageCount}); + +handle_method(#'tx.select'{}, _, State = #ch{transaction_id = none}) -> + {reply, #'tx.select_ok'{}, new_tx(State)}; + +handle_method(#'tx.select'{}, _, State) -> + {reply, #'tx.select_ok'{}, State}; + +handle_method(#'tx.commit'{}, _, #ch{transaction_id = none}) -> + rabbit_misc:protocol_error( + not_allowed, "channel is not transactional", []); + +handle_method(#'tx.commit'{}, _, State) -> + {reply, #'tx.commit_ok'{}, internal_commit(State)}; + +handle_method(#'tx.rollback'{}, _, #ch{transaction_id = none}) -> + rabbit_misc:protocol_error( + not_allowed, "channel is not transactional", []); + +handle_method(#'tx.rollback'{}, _, State) -> + {reply, #'tx.rollback_ok'{}, internal_rollback(State)}; + +handle_method(#'channel.flow'{active = _}, _, State) -> + %% FIXME: implement + {reply, #'channel.flow_ok'{active = true}, State}; + +handle_method(_MethodRecord, _Content, _State) -> + rabbit_misc:protocol_error( + command_invalid, "unimplemented method", []). + +%%---------------------------------------------------------------------------- + +publish(Mandatory, Immediate, Message, QPids, + State = #ch{transaction_id = TxnKey, writer_pid = WriterPid}) -> + Handled = deliver(QPids, Mandatory, Immediate, TxnKey, + Message, WriterPid), + case TxnKey of + none -> State; + _ -> add_tx_participants(Handled, State) + end. + +deliver(QPids, Mandatory, Immediate, Txn, Message, WriterPid) -> + case rabbit_router:deliver(QPids, Mandatory, Immediate, Txn, Message) of + {ok, DeliveredQPids} -> DeliveredQPids; + {error, unroutable} -> + %% FIXME: 312 should be replaced by the ?NO_ROUTE + %% definition, when we move to >=0-9 + ok = basic_return(Message, WriterPid, 312, <<"unroutable">>), + []; + {error, not_delivered} -> + %% FIXME: 313 should be replaced by the ?NO_CONSUMERS + %% definition, when we move to >=0-9 + ok = basic_return(Message, WriterPid, 313, <<"not_delivered">>), + [] + end. + +basic_return(#basic_message{exchange_name = ExchangeName, + routing_key = RoutingKey, + content = Content}, + WriterPid, ReplyCode, ReplyText) -> + ok = rabbit_writer:send_command( + WriterPid, + #'basic.return'{reply_code = ReplyCode, + reply_text = ReplyText, + exchange = ExchangeName#resource.name, + routing_key = RoutingKey}, + Content). + +collect_acks(Q, 0, true) -> + {Q, queue:new()}; +collect_acks(Q, DeliveryTag, Multiple) -> + collect_acks(queue:new(), queue:new(), Q, DeliveryTag, Multiple). + +collect_acks(ToAcc, PrefixAcc, Q, DeliveryTag, Multiple) -> + case queue:out(Q) of + {{value, UnackedMsg = {CurrentDeliveryTag, _ConsumerTag, _Msg}}, + QTail} -> + if CurrentDeliveryTag == DeliveryTag -> + {queue:in(UnackedMsg, ToAcc), queue:join(PrefixAcc, QTail)}; + Multiple -> + collect_acks(queue:in(UnackedMsg, ToAcc), PrefixAcc, + QTail, DeliveryTag, Multiple); + true -> + collect_acks(ToAcc, queue:in(UnackedMsg, PrefixAcc), + QTail, DeliveryTag, Multiple) + end; + {empty, _} -> + {ToAcc, PrefixAcc} + end. + +add_tx_participants(MoreP, State = #ch{tx_participants = Participants}) -> + State#ch{tx_participants = sets:union(Participants, + sets:from_list(MoreP))}. + +ack(ProxyPid, TxnKey, UAQ) -> + fold_per_queue( + fun (QPid, MsgIds, L) -> + ok = rabbit_amqqueue:ack(QPid, TxnKey, MsgIds, ProxyPid), + [QPid | L] + end, [], UAQ). + +make_tx_id() -> rabbit_misc:guid(). + +safe_pmap_set_ok(F, S) -> + case lists:filter(fun (R) -> R =/= ok end, + rabbit_misc:upmap( + fun (V) -> + try F(V) + catch Class:Reason -> {Class, Reason} + end + end, sets:to_list(S))) of + [] -> ok; + Errors -> {error, Errors} + end. + +notify_participants(F, TxnKey, Participants) -> + safe_pmap_set_ok(fun (QPid) -> F(QPid, TxnKey) end, Participants). + +new_tx(State) -> + State#ch{transaction_id = make_tx_id(), + tx_participants = sets:new(), + uncommitted_ack_q = queue:new()}. + +internal_commit(State = #ch{transaction_id = TxnKey, + tx_participants = Participants}) -> + case notify_participants(fun rabbit_amqqueue:commit/2, + TxnKey, Participants) of + ok -> new_tx(State); + {error, Errors} -> exit({commit_failed, Errors}) + end. + +internal_rollback(State = #ch{transaction_id = TxnKey, + tx_participants = Participants, + uncommitted_ack_q = UAQ, + unacked_message_q = UAMQ}) -> + ?LOGDEBUG("rollback ~p~n - ~p acks uncommitted, ~p messages unacked~n", + [self(), + queue:len(UAQ), + queue:len(UAMQ)]), + case notify_participants(fun rabbit_amqqueue:rollback/2, + TxnKey, Participants) of + ok -> NewUAMQ = queue:join(UAQ, UAMQ), + new_tx(State#ch{unacked_message_q = NewUAMQ}); + {error, Errors} -> exit({rollback_failed, Errors}) + end. + +fold_per_queue(F, Acc0, UAQ) -> + D = lists:foldl( + fun ({_DTag, _CTag, + {_QName, QPid, MsgId, _Redelivered, _Message}}, D) -> + %% dict:append would be simpler and avoid the + %% lists:reverse in handle_message({recover, true}, + %% ...). However, it is significantly slower when + %% going beyond a few thousand elements. + dict:update(QPid, + fun (MsgIds) -> [MsgId | MsgIds] end, + [MsgId], + D) + end, dict:new(), queue:to_list(UAQ)), + dict:fold(fun (QPid, MsgIds, Acc) -> F(QPid, MsgIds, Acc) end, + Acc0, D). + +notify_queues(#ch{proxy_pid = ProxyPid, consumer_mapping = Consumers}) -> + safe_pmap_set_ok( + fun (QueueName) -> + case rabbit_amqqueue:with( + QueueName, + fun (Q) -> + rabbit_amqqueue:notify_down(Q, ProxyPid) + end) of + ok -> + ok; + {error, not_found} -> + %% queue has been deleted in the meantime + ok + end + end, + dict:fold(fun (_ConsumerTag, QueueName, S) -> + sets:add_element(QueueName, S) + end, sets:new(), Consumers)). + +is_message_persistent(#content{properties = #'P_basic'{ + delivery_mode = Mode}}) -> + case Mode of + 1 -> false; + 2 -> true; + undefined -> false; + Other -> rabbit_log:warning("Unknown delivery mode ~p - treating as 1, non-persistent~n", + [Other]), + false + end. + +lock_message(true, MsgStruct, State = #ch{unacked_message_q = UAMQ}) -> + State#ch{unacked_message_q = queue:in(MsgStruct, UAMQ)}; +lock_message(false, _MsgStruct, State) -> + State. + +internal_deliver(WriterPid, ChPid, Notify, ConsumerTag, DeliveryTag, + {_QName, QPid, _MsgId, Redelivered, + #basic_message{exchange_name = ExchangeName, + routing_key = RoutingKey, + content = Content}}) -> + M = #'basic.deliver'{consumer_tag = ConsumerTag, + delivery_tag = DeliveryTag, + redelivered = Redelivered, + exchange = ExchangeName#resource.name, + routing_key = RoutingKey}, + ok = case Notify of + true -> rabbit_writer:send_command_and_notify( + WriterPid, QPid, ChPid, M, Content); + false -> rabbit_writer:send_command(WriterPid, M, Content) + end. diff --git a/src/rabbit_control.erl b/src/rabbit_control.erl new file mode 100644 index 00000000..ad796b61 --- /dev/null +++ b/src/rabbit_control.erl @@ -0,0 +1,260 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (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.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developers of the Original Code are LShift Ltd., +%% Cohesive Financial Technologies LLC., and Rabbit Technologies Ltd. +%% +%% Portions created by LShift Ltd., Cohesive Financial Technologies +%% LLC., and Rabbit Technologies Ltd. are Copyright (C) 2007-2008 +%% LShift Ltd., Cohesive Financial Technologies LLC., and Rabbit +%% Technologies Ltd.; +%% +%% All Rights Reserved. +%% +%% Contributor(s): ______________________________________. +%% + +-module(rabbit_control). +-include("rabbit.hrl"). + +-export([start/0, stop/0, action/3]). + +-define(RPC_TIMEOUT, 30000). + +start() -> + case init:get_plain_arguments() of + [] -> + usage(); + FullCommand -> + {Node, Command, Args} = parse_args(FullCommand), + case catch action(Command, Node, Args) of + ok -> + io:format("done.~n"), + init:stop(); + {'EXIT', {function_clause, [{?MODULE, action, _} | _]}} -> + io:format("Invalid command ~p~n", [FullCommand]), + usage(); + Other -> + io:format("~nrabbit_control action ~p failed:~n~p~n", [Command, Other]), + halt(2) + end + end. + +parse_args(["-n", NodeS, Command | Args]) -> + Node = case lists:member($@, NodeS) of + true -> list_to_atom(NodeS); + false -> rabbit_misc:localnode(list_to_atom(NodeS)) + end, + {Node, list_to_atom(Command), Args}; +parse_args([Command | Args]) -> + {rabbit_misc:localnode(rabbit), list_to_atom(Command), Args}. + +stop() -> + ok. + +usage() -> + io:format("Usage: rabbitmqctl [-n <node>] <command> [<arg> ...] + +Available commands: + + stop - stops the RabbitMQ application and halts the node + stop_app - stops the RabbitMQ application, leaving the node running + start_app - starts the RabbitMQ application on an already-running node + reset - resets node to default configuration, deleting all data + force_reset + cluster <ClusterNode> ... + status + + add_user <UserName> <Password> + delete_user <UserName> + change_password <UserName> <NewPassword> + list_users + + add_vhost <VHostPath> + delete_vhost <VHostPath> + list_vhosts + + map_user_vhost <UserName> <VHostPath> + unmap_user_vhost <UserName> <VHostPath> + list_user_vhosts <UserName> + list_vhost_users <VHostPath> + + add_realm <VHostPath> <RealmName> + delete_realm <VHostPath> <RealmName> + list_realms <VHostPath> + + set_permissions <UserName> <VHostPath> <RealmName> [<Permission> ...] + Permissions management. The available permissions are 'passive', + 'active', 'write' and 'read', corresponding to the permissions + referred to in AMQP's \"access.request\" message, or 'all' as an + abbreviation for all defined permission flags. + list_permissions <UserName> <VHostPath> + +<node> should be the name of the master node of the RabbitMQ cluster. It +defaults to the node named \"rabbit\" on the local host. On a host named +\"server.example.com\", the master node will usually be rabbit@server (unless +NODENAME has been set to some non-default value at broker startup time). The +output of hostname -s is usually the correct suffix to use after the \"@\" sign. + +"), + halt(1). + +action(stop, Node, []) -> + io:format("Stopping and halting node ~p ...", [Node]), + call(Node, {rabbit, stop_and_halt, []}); + +action(stop_app, Node, []) -> + io:format("Stopping node ~p ...", [Node]), + call(Node, {rabbit, stop, []}); + +action(start_app, Node, []) -> + io:format("Starting node ~p ...", [Node]), + call(Node, {rabbit, start, []}); + +action(reset, Node, []) -> + io:format("Resetting node ~p ...", [Node]), + call(Node, {rabbit_mnesia, reset, []}); + +action(force_reset, Node, []) -> + io:format("Forcefully resetting node ~p ...", [Node]), + call(Node, {rabbit_mnesia, force_reset, []}); + +action(cluster, Node, ClusterNodeSs) -> + ClusterNodes = lists:map(fun list_to_atom/1, ClusterNodeSs), + io:format("Clustering node ~p with ~p ...", + [Node, ClusterNodes]), + rpc_call(Node, rabbit_mnesia, cluster, [ClusterNodes]); + +action(status, Node, []) -> + io:format("Status of node ~p ...", [Node]), + Res = call(Node, {rabbit, status, []}), + io:format("~n~p~n", [Res]), + ok; + +action(add_user, Node, Args = [Username, _Password]) -> + io:format("Creating user ~p ...", [Username]), + call(Node, {rabbit_access_control, add_user, Args}); + +action(delete_user, Node, Args = [_Username]) -> + io:format("Deleting user ~p ...", Args), + call(Node, {rabbit_access_control, delete_user, Args}); + +action(change_password, Node, Args = [Username, _Newpassword]) -> + io:format("Changing password for user ~p ...", [Username]), + call(Node, {rabbit_access_control, change_password, Args}); + +action(list_users, Node, []) -> + io:format("Listing users ..."), + display_list(call(Node, {rabbit_access_control, list_users, []})); + +action(add_vhost, Node, Args = [_VHostPath]) -> + io:format("Creating vhost ~p ...", Args), + call(Node, {rabbit_access_control, add_vhost, Args}); + +action(delete_vhost, Node, Args = [_VHostPath]) -> + io:format("Deleting vhost ~p ...", Args), + call(Node, {rabbit_access_control, delete_vhost, Args}); + +action(list_vhosts, Node, []) -> + io:format("Listing vhosts ..."), + display_list(call(Node, {rabbit_access_control, list_vhosts, []})); + +action(map_user_vhost, Node, Args = [_Username, _VHostPath]) -> + io:format("Mapping user ~p to vhost ~p ...", Args), + call(Node, {rabbit_access_control, map_user_vhost, Args}); + +action(unmap_user_vhost, Node, Args = [_Username, _VHostPath]) -> + io:format("Unmapping user ~p from vhost ~p ...", Args), + call(Node, {rabbit_access_control, unmap_user_vhost, Args}); + +action(list_user_vhosts, Node, Args = [_Username]) -> + io:format("Listing vhosts for user ~p...", Args), + display_list(call(Node, {rabbit_access_control, list_user_vhosts, Args})); + +action(list_vhost_users, Node, Args = [_VHostPath]) -> + io:format("Listing users for vhosts ~p...", Args), + display_list(call(Node, {rabbit_access_control, list_vhost_users, Args})); + +action(add_realm, Node, [VHostPath, RealmName]) -> + io:format("Adding realm ~p to vhost ~p ...", [RealmName, VHostPath]), + rpc_call(Node, rabbit_realm, add_realm, + [realm_rsrc(VHostPath, RealmName)]); + +action(delete_realm, Node, [VHostPath, RealmName]) -> + io:format("Deleting realm ~p from vhost ~p ...", [RealmName, VHostPath]), + rpc_call(Node, rabbit_realm, delete_realm, + [realm_rsrc(VHostPath, RealmName)]); + +action(list_realms, Node, Args = [_VHostPath]) -> + io:format("Listing realms for vhost ~p ...", Args), + display_list(call(Node, {rabbit_realm, list_vhost_realms, Args})); + +action(set_permissions, Node, + [Username, VHostPath, RealmName | Permissions]) -> + io:format("Setting permissions for user ~p, vhost ~p, realm ~p ...", + [Username, VHostPath, RealmName]), + CheckedPermissions = check_permissions(Permissions), + Ticket = #ticket{ + realm_name = realm_rsrc(VHostPath, RealmName), + passive_flag = lists:member(passive, CheckedPermissions), + active_flag = lists:member(active, CheckedPermissions), + write_flag = lists:member(write, CheckedPermissions), + read_flag = lists:member(read, CheckedPermissions)}, + rpc_call(Node, rabbit_access_control, map_user_realm, + [list_to_binary(Username), Ticket]); + +action(list_permissions, Node, Args = [_Username, _VHostPath]) -> + io:format("Listing permissions for user ~p in vhost ~p ...", Args), + Perms = call(Node, {rabbit_access_control, list_user_realms, Args}), + if is_list(Perms) -> + lists:foreach( + fun ({RealmName, Pattern}) -> + io:format("~n~s: ~p", + [binary_to_list(RealmName), + rabbit_misc:permission_list(Pattern)]) + end, + lists:sort(Perms)), + io:nl(), + ok; + true -> Perms + end. + +check_permissions([]) -> []; +check_permissions(["all" | R]) -> + [passive, active, write, read | check_permissions(R)]; +check_permissions([P | R]) when (P == "passive") or + (P == "active") or + (P == "write") or + (P == "read") -> + [list_to_atom(P) | check_permissions(R)]; +check_permissions([P | _R]) -> + io:format("~nError: invalid permission flag ~p~n", [P]), + usage(). + +realm_rsrc(VHostPath, RealmName) -> + rabbit_misc:r(list_to_binary(VHostPath), + realm, + list_to_binary(RealmName)). + +display_list(L) when is_list(L) -> + lists:foreach(fun (I) -> + io:format("~n~s", [binary_to_list(I)]) + end, + lists:sort(L)), + io:nl(); +display_list(Other) -> Other. + +call(Node, {Mod, Fun, Args}) -> + rpc_call(Node, Mod, Fun, lists:map(fun list_to_binary/1, Args)). + +rpc_call(Node, Mod, Fun, Args) -> + rpc:call(Node, Mod, Fun, Args, ?RPC_TIMEOUT). diff --git a/src/rabbit_error_logger.erl b/src/rabbit_error_logger.erl new file mode 100644 index 00000000..0ae116bb --- /dev/null +++ b/src/rabbit_error_logger.erl @@ -0,0 +1,77 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (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.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developers of the Original Code are LShift Ltd., +%% Cohesive Financial Technologies LLC., and Rabbit Technologies Ltd. +%% +%% Portions created by LShift Ltd., Cohesive Financial Technologies +%% LLC., and Rabbit Technologies Ltd. are Copyright (C) 2007-2008 +%% LShift Ltd., Cohesive Financial Technologies LLC., and Rabbit +%% Technologies Ltd.; +%% +%% All Rights Reserved. +%% +%% Contributor(s): ______________________________________. +%% + +-module(rabbit_error_logger). +-include("rabbit.hrl"). + +-define(LOG_EXCH_NAME, <<"amq.rabbitmq.log">>). + +-behaviour(gen_event). + +-export([init/1, terminate/2, code_change/3, handle_call/2, handle_event/2, handle_info/2]). + +init([DefaultVHost]) -> + #exchange{} = rabbit_exchange:declare( + #resource{virtual_host = DefaultVHost, + kind = realm, + name = <<"/admin">>}, + ?LOG_EXCH_NAME, + topic, true, false, []), + {ok, #resource{virtual_host = DefaultVHost, + kind = exchange, + name = ?LOG_EXCH_NAME}}. + +terminate(_Arg, _State) -> + terminated_ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +handle_call(_Request, State) -> + {ok, not_understood, State}. + +handle_event({Kind, _Gleader, {_Pid, Format, Data}}, State) -> + ok = publish(Kind, Format, Data, State), + {ok, State}; +handle_event(_Event, State) -> + {ok, State}. + +handle_info(_Info, State) -> + {ok, State}. + +publish(error, Format, Data, State) -> + publish1(<<"error">>, Format, Data, State); +publish(warning_msg, Format, Data, State) -> + publish1(<<"warning">>, Format, Data, State); +publish(info_msg, Format, Data, State) -> + publish1(<<"info">>, Format, Data, State); +publish(_Other, _Format, _Data, _State) -> + ok. + +publish1(RoutingKey, Format, Data, LogExch) -> + {ok, _QueueNames} = rabbit_exchange:simple_publish( + false, false, LogExch, RoutingKey, <<"text/plain">>, + list_to_binary(io_lib:format(Format, Data))), + ok. diff --git a/src/rabbit_exchange.erl b/src/rabbit_exchange.erl new file mode 100644 index 00000000..113b7878 --- /dev/null +++ b/src/rabbit_exchange.erl @@ -0,0 +1,380 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (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.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developers of the Original Code are LShift Ltd., +%% Cohesive Financial Technologies LLC., and Rabbit Technologies Ltd. +%% +%% Portions created by LShift Ltd., Cohesive Financial Technologies +%% LLC., and Rabbit Technologies Ltd. are Copyright (C) 2007-2008 +%% LShift Ltd., Cohesive Financial Technologies LLC., and Rabbit +%% Technologies Ltd.; +%% +%% All Rights Reserved. +%% +%% Contributor(s): ______________________________________. +%% + +-module(rabbit_exchange). +-include_lib("stdlib/include/qlc.hrl"). +-include("rabbit.hrl"). +-include("rabbit_framing.hrl"). + +-export([recover/0, declare/6, lookup/1, lookup_or_die/1, + list_vhost_exchanges/1, list_exchange_bindings/1, + simple_publish/6, simple_publish/3, + route/2]). +-export([add_binding/2, delete_binding/2]). +-export([delete/2]). +-export([check_type/1, assert_type/2, topic_matches/2]). + +-import(mnesia). +-import(sets). +-import(lists). +-import(qlc). +-import(regexp). + +%%---------------------------------------------------------------------------- + +-ifdef(use_specs). + +-type(publish_res() :: {'ok', [pid()]} | + not_found() | {'error', 'unroutable' | 'not_delivered'}). + +-spec(recover/0 :: () -> 'ok'). +-spec(declare/6 :: (realm_name(), name(), exchange_type(), bool(), bool(), + amqp_table()) -> exchange()). +-spec(check_type/1 :: (binary()) -> atom()). +-spec(assert_type/2 :: (exchange(), atom()) -> 'ok'). +-spec(lookup/1 :: (exchange_name()) -> {'ok', exchange()} | not_found()). +-spec(lookup_or_die/1 :: (exchange_name()) -> exchange()). +-spec(list_vhost_exchanges/1 :: (vhost()) -> [exchange()]). +-spec(list_exchange_bindings/1 :: (exchange_name()) -> + [{queue_name(), routing_key(), amqp_table()}]). +-spec(simple_publish/6 :: + (bool(), bool(), exchange_name(), routing_key(), binary(), binary()) -> + publish_res()). +-spec(simple_publish/3 :: (bool(), bool(), message()) -> publish_res()). +-spec(route/2 :: (exchange(), routing_key()) -> [pid()]). +-spec(add_binding/2 :: (binding_spec(), amqqueue()) -> + 'ok' | not_found() | + {'error', 'durability_settings_incompatible'}). +-spec(delete_binding/2 :: (binding_spec(), amqqueue()) -> + 'ok' | not_found()). +-spec(topic_matches/2 :: (binary(), binary()) -> bool()). +-spec(delete/2 :: (exchange_name(), bool()) -> + 'ok' | not_found() | {'error', 'in_use'}). + +-endif. + +%%---------------------------------------------------------------------------- + +recover() -> + ok = recover_durable_exchanges(), + ok. + +recover_durable_exchanges() -> + rabbit_misc:execute_mnesia_transaction( + fun () -> + mnesia:foldl(fun (Exchange, Acc) -> + ok = mnesia:write(Exchange), + Acc + end, ok, durable_exchanges) + end). + +declare(RealmName, NameBin, Type, Durable, AutoDelete, Args) -> + XName = rabbit_misc:r(RealmName, exchange, NameBin), + Exchange = #exchange{name = XName, + type = Type, + durable = Durable, + auto_delete = AutoDelete, + arguments = Args}, + rabbit_misc:execute_mnesia_transaction( + fun () -> + case mnesia:wread({exchange, XName}) of + [] -> ok = mnesia:write(Exchange), + if Durable -> + ok = mnesia:write( + durable_exchanges, Exchange, write); + true -> ok + end, + ok = rabbit_realm:add(RealmName, XName), + Exchange; + [ExistingX] -> ExistingX + end + end). + +check_type(<<"fanout">>) -> + fanout; +check_type(<<"direct">>) -> + direct; +check_type(<<"topic">>) -> + topic; +check_type(T) -> + rabbit_misc:protocol_error( + command_invalid, "invalid exchange type '~s'", [T]). + +assert_type(#exchange{ type = ActualType }, RequiredType) + when ActualType == RequiredType -> + ok; +assert_type(#exchange{ name = Name, type = ActualType }, RequiredType) -> + rabbit_misc:protocol_error( + not_allowed, "cannot redeclare ~s of type '~s' with type '~s'", + [rabbit_misc:rs(Name), ActualType, RequiredType]). + +lookup(Name) -> + rabbit_misc:dirty_read({exchange, Name}). + +lookup_or_die(Name) -> + case lookup(Name) of + {ok, X} -> X; + {error, not_found} -> + rabbit_misc:protocol_error( + not_found, "no ~s", [rabbit_misc:rs(Name)]) + end. + +list_vhost_exchanges(VHostPath) -> + mnesia:dirty_match_object( + #exchange{name = rabbit_misc:r(VHostPath, exchange), _ = '_'}). + +list_exchange_bindings(Name) -> + [{QueueName, RoutingKey, Arguments} || + #binding{handlers = Handlers} <- bindings_for_exchange(Name), + #handler{binding_spec = #binding_spec{routing_key = RoutingKey, + arguments = Arguments}, + queue = QueueName} <- Handlers]. + +bindings_for_exchange(Name) -> + qlc:e(qlc:q([B || + B = #binding{key = K} <- mnesia:table(binding), + element(1, K) == Name])). + +empty_handlers() -> + []. + +%% Usable by Erlang code that wants to publish messages. +simple_publish(Mandatory, Immediate, ExchangeName, RoutingKeyBin, ContentTypeBin, BodyBin) -> + {ClassId, _MethodId} = rabbit_framing:method_id('basic.publish'), + Content = #content{class_id = ClassId, + properties = #'P_basic'{content_type = ContentTypeBin}, + properties_bin = none, + payload_fragments_rev = [BodyBin]}, + Message = #basic_message{exchange_name = ExchangeName, + routing_key = RoutingKeyBin, + content = Content, + persistent_key = none}, + simple_publish(Mandatory, Immediate, Message). + +%% Usable by Erlang code that wants to publish messages. +simple_publish(Mandatory, Immediate, + Message = #basic_message{exchange_name = ExchangeName, + routing_key = RoutingKey}) -> + case lookup(ExchangeName) of + {ok, Exchange} -> + QPids = route(Exchange, RoutingKey), + rabbit_router:deliver(QPids, Mandatory, Immediate, + none, Message); + {error, Error} -> {error, Error} + end. + +%% return the list of qpids to which a message with a given routing +%% key, sent to a particular exchange, should be delivered. +%% +%% The function ensures that a qpid appears in the return list exactly +%% as many times as a message should be delivered to it. With the +%% current exchange types that is at most once. +route(#exchange{name = Name, type = topic}, RoutingKey) -> + sets:to_list( + sets:union( + mnesia:activity( + async_dirty, + fun () -> + qlc:e(qlc:q([handler_qpids(H) || + #binding{key = {Name1, PatternKey}, + handlers = H} + <- mnesia:table(binding), + Name == Name1, + topic_matches(PatternKey, RoutingKey)])) + end))); + +route(#exchange{name = Name, type = Type}, RoutingKey) -> + BindingKey = delivery_key_for_type(Type, Name, RoutingKey), + case rabbit_misc:dirty_read({binding, BindingKey}) of + {ok, #binding{handlers = H}} -> sets:to_list(handler_qpids(H)); + {error, not_found} -> [] + end. + +delivery_key_for_type(fanout, Name, _RoutingKey) -> + {Name, fanout}; +delivery_key_for_type(_Type, Name, RoutingKey) -> + {Name, RoutingKey}. + +call_with_exchange(Name, Fun) -> + case mnesia:wread({exchange, Name}) of + [] -> {error, not_found}; + [X] -> Fun(X) + end. + +make_handler(BindingSpec, #amqqueue{name = QueueName, pid = QPid}) -> + #handler{binding_spec = BindingSpec, queue = QueueName, qpid = QPid}. + +add_binding(BindingSpec = #binding_spec{exchange_name = ExchangeName, + routing_key = RoutingKey}, Q) -> + call_with_exchange( + ExchangeName, + fun (X) -> if Q#amqqueue.durable and not(X#exchange.durable) -> + {error, durability_settings_incompatible}; + true -> + internal_add_binding( + X, RoutingKey, make_handler(BindingSpec, Q)) + end + end). + +delete_binding(BindingSpec = #binding_spec{exchange_name = ExchangeName, + routing_key = RoutingKey}, Q) -> + call_with_exchange( + ExchangeName, + fun (X) -> ok = internal_delete_binding( + X, RoutingKey, make_handler(BindingSpec, Q)), + maybe_auto_delete(X) + end). + +%% Must run within a transaction. +maybe_auto_delete(#exchange{auto_delete = false}) -> + ok; +maybe_auto_delete(#exchange{name = ExchangeName, auto_delete = true}) -> + case internal_delete(ExchangeName, true) of + {error, in_use} -> ok; + ok -> ok + end. + +handlers_isempty([]) -> true; +handlers_isempty([_|_]) -> false. + +extend_handlers(Handlers, Handler) -> [Handler | Handlers]. + +delete_handler(Handlers, Handler) -> lists:delete(Handler, Handlers). + +handler_qpids(Handlers) -> + sets:from_list([QPid || #handler{qpid = QPid} <- Handlers]). + +%% Must run within a transaction. +internal_add_binding(#exchange{name = ExchangeName, type = Type}, + RoutingKey, Handler) -> + BindingKey = delivery_key_for_type(Type, ExchangeName, RoutingKey), + ok = add_handler_to_binding(BindingKey, Handler). + +%% Must run within a transaction. +internal_delete_binding(#exchange{name = ExchangeName, type = Type}, RoutingKey, Handler) -> + BindingKey = delivery_key_for_type(Type, ExchangeName, RoutingKey), + remove_handler_from_binding(BindingKey, Handler), + ok. + +%% Must run within a transaction. +add_handler_to_binding(BindingKey, Handler) -> + ok = case mnesia:wread({binding, BindingKey}) of + [] -> + ok = mnesia:write( + #binding{key = BindingKey, + handlers = extend_handlers( + empty_handlers(), Handler)}); + [B = #binding{handlers = H}] -> + ok = mnesia:write( + B#binding{handlers = extend_handlers(H, Handler)}) + end. + +%% Must run within a transaction. +remove_handler_from_binding(BindingKey, Handler) -> + case mnesia:wread({binding, BindingKey}) of + [] -> empty; + [B = #binding{handlers = H}] -> + H1 = delete_handler(H, Handler), + case handlers_isempty(H1) of + true -> + ok = mnesia:delete({binding, BindingKey}), + empty; + _ -> + ok = mnesia:write(B#binding{handlers = H1}), + not_empty + end + end. + +split_topic_key(Key) -> + {ok, KeySplit} = regexp:split(binary_to_list(Key), "\\."), + KeySplit. + +topic_matches(PatternKey, RoutingKey) -> + P = split_topic_key(PatternKey), + R = split_topic_key(RoutingKey), + topic_matches1(P, R). + +topic_matches1(["#"], _R) -> + true; +topic_matches1(["#" | PTail], R) -> + last_topic_match(PTail, [], lists:reverse(R)); +topic_matches1([], []) -> + true; +topic_matches1(["*" | PatRest], [_ | ValRest]) -> + topic_matches1(PatRest, ValRest); +topic_matches1([PatElement | PatRest], [ValElement | ValRest]) when PatElement == ValElement -> + topic_matches1(PatRest, ValRest); +topic_matches1(_, _) -> + false. + +last_topic_match(P, R, []) -> + topic_matches1(P, R); +last_topic_match(P, R, [BacktrackNext | BacktrackList]) -> + topic_matches1(P, R) or last_topic_match(P, [BacktrackNext | R], BacktrackList). + +delete(ExchangeName, IfUnused) -> + rabbit_misc:execute_mnesia_transaction( + fun () -> internal_delete(ExchangeName, IfUnused) end). + +internal_delete(ExchangeName, _IfUnused = true) -> + Bindings = bindings_for_exchange(ExchangeName), + case Bindings of + [] -> do_internal_delete(ExchangeName, Bindings); + _ -> + case lists:all(fun (#binding{handlers = H}) -> handlers_isempty(H) end, + Bindings) of + true -> + %% There are no handlers anywhere in any of the + %% bindings for this exchange. + do_internal_delete(ExchangeName, Bindings); + false -> + %% There was at least one real handler + %% present. It's still in use. + {error, in_use} + end + end; +internal_delete(ExchangeName, false) -> + do_internal_delete(ExchangeName, bindings_for_exchange(ExchangeName)). + +forcibly_remove_handlers(Handlers) -> + lists:foreach( + fun (#handler{binding_spec = BindingSpec, queue = QueueName}) -> + ok = rabbit_amqqueue:binding_forcibly_removed( + BindingSpec, QueueName) + end, Handlers), + ok. + +do_internal_delete(ExchangeName, Bindings) -> + case mnesia:wread({exchange, ExchangeName}) of + [] -> {error, not_found}; + _ -> + lists:foreach(fun (#binding{key = K, handlers = H}) -> + ok = forcibly_remove_handlers(H), + ok = mnesia:delete({binding, K}) + end, Bindings), + ok = mnesia:delete({durable_exchanges, ExchangeName}), + ok = mnesia:delete({exchange, ExchangeName}), + ok = rabbit_realm:delete_from_all(ExchangeName) + end. diff --git a/src/rabbit_framing_channel.erl b/src/rabbit_framing_channel.erl new file mode 100644 index 00000000..76c04613 --- /dev/null +++ b/src/rabbit_framing_channel.erl @@ -0,0 +1,115 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (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.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developers of the Original Code are LShift Ltd., +%% Cohesive Financial Technologies LLC., and Rabbit Technologies Ltd. +%% +%% Portions created by LShift Ltd., Cohesive Financial Technologies +%% LLC., and Rabbit Technologies Ltd. are Copyright (C) 2007-2008 +%% LShift Ltd., Cohesive Financial Technologies LLC., and Rabbit +%% Technologies Ltd.; +%% +%% All Rights Reserved. +%% +%% Contributor(s): ______________________________________. +%% + +-module(rabbit_framing_channel). +-include("rabbit.hrl"). + +-export([start_link/2, process/2, shutdown/1]). + +%% internal +-export([mainloop/1]). + +%%-------------------------------------------------------------------- + +start_link(StartFun, StartArgs) -> + spawn_link( + fun () -> + %% we trap exits so that a normal termination of the + %% channel or reader process terminates us too. + process_flag(trap_exit, true), + mainloop(apply(StartFun, StartArgs)) + end). + +process(Pid, Frame) -> + Pid ! {frame, Frame}, + ok. + +shutdown(Pid) -> + Pid ! terminate, + ok. + +%%-------------------------------------------------------------------- + +read_frame(ChannelPid) -> + receive + %% converting the exit signal into one of our own ensures that + %% the reader sees the right pid (i.e. ours) when a channel + %% exits. Similarly in the other direction, though it is not + %% really relevant there since the channel is not specifically + %% watching out for reader exit signals. + {'EXIT', _Pid, Reason} -> exit(Reason); + {frame, Frame} -> Frame; + terminate -> rabbit_channel:shutdown(ChannelPid), + read_frame(ChannelPid); + Msg -> exit({unexpected_message, Msg}) + end. + +mainloop(ChannelPid) -> + {method, MethodName, FieldsBin} = read_frame(ChannelPid), + Method = rabbit_framing:decode_method_fields(MethodName, FieldsBin), + case rabbit_framing:method_has_content(MethodName) of + true -> rabbit_channel:do(ChannelPid, Method, + collect_content(ChannelPid, MethodName)); + false -> rabbit_channel:do(ChannelPid, Method) + end, + ?MODULE:mainloop(ChannelPid). + +collect_content(ChannelPid, MethodName) -> + {ClassId, _MethodId} = rabbit_framing:method_id(MethodName), + case read_frame(ChannelPid) of + {content_header, HeaderClassId, 0, BodySize, PropertiesBin} -> + if HeaderClassId == ClassId -> + Payload = collect_content_payload(ChannelPid, BodySize, []), + #content{class_id = ClassId, + properties = none, + properties_bin = PropertiesBin, + payload_fragments_rev = Payload}; + true -> + rabbit_misc:protocol_error( + command_invalid, + "expected content header for class ~w, got one for class ~w instead", + [ClassId, HeaderClassId]) + end; + _ -> + rabbit_misc:protocol_error( + command_invalid, + "expected content header for class ~w, got non content header frame instead", + [ClassId]) + end. + +collect_content_payload(_ChannelPid, 0, Acc) -> + Acc; +collect_content_payload(ChannelPid, RemainingByteCount, Acc) -> + case read_frame(ChannelPid) of + {content_body, FragmentBin} -> + collect_content_payload(ChannelPid, + RemainingByteCount - size(FragmentBin), + [FragmentBin | Acc]); + _ -> + rabbit_misc:protocol_error( + command_invalid, + "expected content body, got non content body frame instead", + []) + end. diff --git a/src/rabbit_heartbeat.erl b/src/rabbit_heartbeat.erl new file mode 100644 index 00000000..6e825578 --- /dev/null +++ b/src/rabbit_heartbeat.erl @@ -0,0 +1,94 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (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.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developers of the Original Code are LShift Ltd., +%% Cohesive Financial Technologies LLC., and Rabbit Technologies Ltd. +%% +%% Portions created by LShift Ltd., Cohesive Financial Technologies +%% LLC., and Rabbit Technologies Ltd. are Copyright (C) 2007-2008 +%% LShift Ltd., Cohesive Financial Technologies LLC., and Rabbit +%% Technologies Ltd.; +%% +%% All Rights Reserved. +%% +%% Contributor(s): ______________________________________. +%% + +-module(rabbit_heartbeat). + +-export([start_heartbeat/2]). + +start_heartbeat(_Sock, 0) -> + none; +start_heartbeat(Sock, TimeoutSec) -> + Parent = self(), + %% we check for incoming data every interval, and time out after + %% two checks with no change. As a result we will time out between + %% 2 and 3 intervals after the last data has been received. + spawn_link(fun () -> heartbeater(Sock, TimeoutSec * 1000, + recv_oct, 1, + fun () -> + Parent ! timeout, + stop + end, + erlang:monitor(process, Parent)) end), + %% the 'div 2' is there so that we don't end up waiting for nearly + %% 2 * TimeoutSec before sending a heartbeat in the boundary case + %% where the last message was sent just after a heartbeat. + spawn_link(fun () -> heartbeater(Sock, TimeoutSec * 1000 div 2, + send_oct, 0, + fun () -> + catch gen_tcp:send(Sock, rabbit_binary_generator:build_heartbeat_frame()), + continue + end, + erlang:monitor(process, Parent)) end), + ok. + +%% Y-combinator, posted by Vladimir Sekissov to the Erlang mailing list +%% http://www.erlang.org/ml-archive/erlang-questions/200301/msg00053.html +y(X) -> + F = fun (P) -> X(fun (A) -> (P(P))(A) end) end, + F(F). + +heartbeater(Sock, TimeoutMillisec, StatName, Threshold, Handler, MonitorRef) -> + Heartbeat = + fun (F) -> + fun ({StatVal, SameCount}) -> + receive + {'DOWN', MonitorRef, process, _Object, _Info} -> ok; + Other -> exit({unexpected_message, Other}) + after TimeoutMillisec -> + case prim_inet:getstat(Sock, [StatName]) of + {ok, [{StatName, NewStatVal}]} -> + if NewStatVal =/= StatVal -> + F({NewStatVal, 0}); + SameCount < Threshold -> + F({NewStatVal, SameCount + 1}); + true -> + case Handler() of + stop -> ok; + continue -> F({NewStatVal, 0}) + end + end; + {error, einval} -> + %% the socket is dead, most + %% likely because the + %% connection is being shut + %% down -> terminate + ok; + {error, Reason} -> + exit({cannot_get_socket_stats, Reason}) + end + end + end + end, + (y(Heartbeat))({0, 0}). diff --git a/src/rabbit_load.erl b/src/rabbit_load.erl new file mode 100644 index 00000000..8deec8eb --- /dev/null +++ b/src/rabbit_load.erl @@ -0,0 +1,66 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (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.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developers of the Original Code are LShift Ltd., +%% Cohesive Financial Technologies LLC., and Rabbit Technologies Ltd. +%% +%% Portions created by LShift Ltd., Cohesive Financial Technologies +%% LLC., and Rabbit Technologies Ltd. are Copyright (C) 2007-2008 +%% LShift Ltd., Cohesive Financial Technologies LLC., and Rabbit +%% Technologies Ltd.; +%% +%% All Rights Reserved. +%% +%% Contributor(s): ______________________________________. +%% + +-module(rabbit_load). + +-export([local_load/0, remote_loads/0, pick/0]). + +-define(FUDGE_FACTOR, 0.98). +-define(TIMEOUT, 100). + +%%---------------------------------------------------------------------------- + +-ifdef(use_specs). + +-type(node() :: atom()). +-type(load() :: {{non_neg_integer(), float()}, node()}). +-spec(local_load/0 :: () -> load()). +-spec(remote_loads/0 :: () -> [load()]). +-spec(pick/0 :: () -> node()). + +-endif. + +%%---------------------------------------------------------------------------- + +local_load() -> + LoadAvg = case whereis(cpu_sup) of + undefined -> 0.0; + _Other -> cpu_sup:avg1() + end, + {{statistics(run_queue), LoadAvg}, node()}. + +remote_loads() -> + {ResL, _BadNodes} = + rpc:multicall(nodes(), ?MODULE, local_load, [], ?TIMEOUT), + ResL. + +pick() -> + RemoteLoads = remote_loads(), + {{RunQ, LoadAvg}, Node} = local_load(), + %% add bias towards current node + AdjustedLoadAvg = LoadAvg * ?FUDGE_FACTOR, + Loads = [{{RunQ, AdjustedLoadAvg}, Node} | RemoteLoads], + {_, SelectedNode} = lists:min(Loads), + SelectedNode. diff --git a/src/rabbit_log.erl b/src/rabbit_log.erl new file mode 100644 index 00000000..a8f839f0 --- /dev/null +++ b/src/rabbit_log.erl @@ -0,0 +1,132 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (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.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developers of the Original Code are LShift Ltd., +%% Cohesive Financial Technologies LLC., and Rabbit Technologies Ltd. +%% +%% Portions created by LShift Ltd., Cohesive Financial Technologies +%% LLC., and Rabbit Technologies Ltd. are Copyright (C) 2007-2008 +%% LShift Ltd., Cohesive Financial Technologies LLC., and Rabbit +%% Technologies Ltd.; +%% +%% All Rights Reserved. +%% +%% Contributor(s): ______________________________________. +%% + +-module(rabbit_log). + +-behaviour(gen_server). + +-export([start_link/0]). + +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +-export([debug/1, debug/2, info/1, info/2, + warning/1, warning/2, error/1, error/2]). + +-import(io). +-import(error_logger). + +-define(SERVER, ?MODULE). + +%%---------------------------------------------------------------------------- + +-ifdef(use_specs). + +-spec(start_link/0 :: () -> {'ok', pid()} | 'ignore' | {'error', any()}). +-spec(debug/1 :: (string()) -> 'ok'). +-spec(debug/2 :: (string(), [any()]) -> 'ok'). +-spec(info/1 :: (string()) -> 'ok'). +-spec(info/2 :: (string(), [any()]) -> 'ok'). +-spec(warning/1 :: (string()) -> 'ok'). +-spec(warning/2 :: (string(), [any()]) -> 'ok'). +-spec(error/1 :: (string()) -> 'ok'). +-spec(error/2 :: (string(), [any()]) -> 'ok'). + +-endif. + +%%---------------------------------------------------------------------------- + +start_link() -> + gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). + +debug(Fmt) -> + gen_server:cast(?SERVER, {debug, Fmt}). + +debug(Fmt, Args) when is_list(Args) -> + gen_server:cast(?SERVER, {debug, Fmt, Args}). + +info(Fmt) -> + gen_server:cast(?SERVER, {info, Fmt}). + +info(Fmt, Args) when is_list(Args) -> + gen_server:cast(?SERVER, {info, Fmt, Args}). + +warning(Fmt) -> + gen_server:cast(?SERVER, {warning, Fmt}). + +warning(Fmt, Args) when is_list(Args) -> + gen_server:cast(?SERVER, {warning, Fmt, Args}). + +error(Fmt) -> + gen_server:cast(?SERVER, {error, Fmt}). + +error(Fmt, Args) when is_list(Args) -> + gen_server:cast(?SERVER, {error, Fmt, Args}). + +%%-------------------------------------------------------------------- + +init([]) -> {ok, none}. + +handle_call(_Request, _From, State) -> + {noreply, State}. + +handle_cast({debug, Fmt}, State) -> + io:format("debug:: "), io:format(Fmt), + error_logger:info_msg("debug:: " ++ Fmt), + {noreply, State}; +handle_cast({debug, Fmt, Args}, State) -> + io:format("debug:: "), io:format(Fmt, Args), + error_logger:info_msg("debug:: " ++ Fmt, Args), + {noreply, State}; +handle_cast({info, Fmt}, State) -> + error_logger:info_msg(Fmt), + {noreply, State}; +handle_cast({info, Fmt, Args}, State) -> + error_logger:info_msg(Fmt, Args), + {noreply, State}; +handle_cast({warning, Fmt}, State) -> + error_logger:warning_msg(Fmt), + {noreply, State}; +handle_cast({warning, Fmt, Args}, State) -> + error_logger:warning_msg(Fmt, Args), + {noreply, State}; +handle_cast({error, Fmt}, State) -> + error_logger:error_msg(Fmt), + {noreply, State}; +handle_cast({error, Fmt, Args}, State) -> + error_logger:error_msg(Fmt, Args), + {noreply, State}; +handle_cast(_Msg, State) -> + {noreply, State}. + +handle_info(_Info, State) -> + {noreply, State}. + +terminate(_Reason, _State) -> + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + diff --git a/src/rabbit_misc.erl b/src/rabbit_misc.erl new file mode 100644 index 00000000..927d7712 --- /dev/null +++ b/src/rabbit_misc.erl @@ -0,0 +1,391 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (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.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developers of the Original Code are LShift Ltd., +%% Cohesive Financial Technologies LLC., and Rabbit Technologies Ltd. +%% +%% Portions created by LShift Ltd., Cohesive Financial Technologies +%% LLC., and Rabbit Technologies Ltd. are Copyright (C) 2007-2008 +%% LShift Ltd., Cohesive Financial Technologies LLC., and Rabbit +%% Technologies Ltd.; +%% +%% All Rights Reserved. +%% +%% Contributor(s): ______________________________________. +%% + +-module(rabbit_misc). +-include("rabbit.hrl"). +-include("rabbit_framing.hrl"). + +-export([method_record_type/1, polite_pause/0, polite_pause/1]). +-export([die/1, frame_error/2, protocol_error/3, protocol_error/4]). +-export([strict_ticket_checking/0]). +-export([get_config/1, get_config/2, set_config/2]). +-export([dirty_read/1]). +-export([r/3, r/2, rs/1]). +-export([permission_list/1]). +-export([enable_cover/0, report_cover/0]). +-export([with_exit_handler/2]). +-export([with_user/2, with_vhost/2, with_realm/2, with_user_and_vhost/3]). +-export([execute_mnesia_transaction/1]). +-export([ensure_ok/2]). +-export([localnode/1, tcp_name/3]). +-export([intersperse/2, upmap/2, map_in_order/2]). +-export([guid/0, string_guid/1, binstring_guid/1]). +-export([dirty_read_all/1, dirty_foreach_key/2, dirty_dump_log/1]). + +-import(mnesia). +-import(lists). +-import(cover). +-import(disk_log). + +%%---------------------------------------------------------------------------- + +-ifdef(use_specs). + +-include_lib("kernel/include/inet.hrl"). + +-spec(method_record_type/1 :: (tuple()) -> atom()). +-spec(polite_pause/0 :: () -> 'done'). +-spec(polite_pause/1 :: (non_neg_integer()) -> 'done'). +-spec(die/1 :: (atom()) -> no_return()). +-spec(frame_error/2 :: (atom(), binary()) -> no_return()). +-spec(protocol_error/3 :: + (atom() | amqp_error(), string(), [any()]) -> no_return()). +-spec(protocol_error/4 :: + (atom() | amqp_error(), string(), [any()], atom()) -> no_return()). +-spec(strict_ticket_checking/0 :: () -> bool()). +-spec(get_config/1 :: (atom()) -> {'ok', any()} | not_found()). +-spec(get_config/2 :: (atom(), A) -> A). +-spec(set_config/2 :: (atom(), any()) -> 'ok'). +-spec(dirty_read/1 :: ({atom(), any()}) -> {'ok', any()} | not_found()). +-spec(r/3 :: (realm_name() | vhost(), K, name()) -> + r(K) when is_subtype(K, atom())). +-spec(r/2 :: (vhost(), K) -> #resource{virtual_host :: vhost(), + kind :: K, + name :: '_'} + when is_subtype(K, atom())). +-spec(rs/1 :: (r(atom())) -> string()). +-spec(permission_list/1 :: (ticket()) -> [permission()]). +-spec(enable_cover/0 :: () -> 'ok' | {'error', any()}). +-spec(report_cover/0 :: () -> 'ok'). +-spec(with_exit_handler/2 :: (thunk(A), thunk(A)) -> A). +-spec(with_user/2 :: (username(), thunk(A)) -> A). +-spec(with_vhost/2 :: (vhost(), thunk(A)) -> A). +-spec(with_realm/2 :: (realm_name(), thunk(A)) -> A). +-spec(with_user_and_vhost/3 :: (username(), vhost(), thunk(A)) -> A). +-spec(execute_mnesia_transaction/1 :: (thunk(A)) -> A). +-spec(ensure_ok/2 :: ('ok' | {'error', any()}, atom()) -> 'ok'). +-spec(localnode/1 :: (atom()) -> node()). +-spec(tcp_name/3 :: (atom(), ip_address(), ip_port()) -> atom()). +-spec(intersperse/2 :: (A, [A]) -> [A]). +-spec(upmap/2 :: (fun ((A) -> B), [A]) -> [B]). +-spec(map_in_order/2 :: (fun ((A) -> B), [A]) -> [B]). +-spec(guid/0 :: () -> guid()). +-spec(string_guid/1 :: (any()) -> string()). +-spec(binstring_guid/1 :: (any()) -> binary()). +-spec(dirty_read_all/1 :: (atom()) -> [any()]). +-spec(dirty_foreach_key/2 :: (fun ((any()) -> any()), atom()) -> + 'ok' | 'aborted'). +-spec(dirty_dump_log/1 :: (string()) -> 'ok' | {'error', any()}). + +-endif. + +%%---------------------------------------------------------------------------- + +method_record_type(Record) -> + element(1, Record). + +polite_pause() -> + polite_pause(3000). + +polite_pause(N) -> + receive + after N -> done + end. + +die(Error) -> + protocol_error(Error, "~w", [Error]). + +frame_error(MethodName, BinaryFields) -> + protocol_error(frame_error, "cannot decode ~w", + [BinaryFields], MethodName). + +protocol_error(Error, Explanation, Params) -> + protocol_error(Error, Explanation, Params, none). + +protocol_error(Error, Explanation, Params, Method) -> + CompleteExplanation = lists:flatten(io_lib:format(Explanation, Params)), + exit({amqp, Error, CompleteExplanation, Method}). + +boolean_config_param(Name, TrueValue, FalseValue, DefaultValue) -> + ActualValue = get_config(Name, DefaultValue), + if + ActualValue == TrueValue -> + true; + ActualValue == FalseValue -> + false; + true -> + rabbit_log:error( + "Bad setting for config param '~w': ~p~n" ++ + "legal values are '~w', '~w'; using default value '~w'", + [Name, ActualValue, TrueValue, FalseValue, DefaultValue]), + DefaultValue == TrueValue + end. + +strict_ticket_checking() -> + boolean_config_param(strict_ticket_checking, enabled, disabled, disabled). + +get_config(Key) -> + case dirty_read({rabbit_config, Key}) of + {ok, {rabbit_config, Key, V}} -> {ok, V}; + Other -> Other + end. + +get_config(Key, DefaultValue) -> + case get_config(Key) of + {ok, V} -> V; + {error, not_found} -> DefaultValue + end. + +set_config(Key, Value) -> + ok = mnesia:dirty_write({rabbit_config, Key, Value}). + +dirty_read(ReadSpec) -> + case mnesia:dirty_read(ReadSpec) of + [Result] -> {ok, Result}; + [] -> {error, not_found} + end. + +r(#resource{virtual_host = VHostPath}, Kind, Name) + when is_binary(Name) -> + #resource{virtual_host = VHostPath, kind = Kind, name = Name}; +r(VHostPath, Kind, Name) when is_binary(Name) andalso is_binary(VHostPath) -> + #resource{virtual_host = VHostPath, kind = Kind, name = Name}. + +r(VHostPath, Kind) when is_binary(VHostPath) -> + #resource{virtual_host = VHostPath, kind = Kind, name = '_'}. + +rs(#resource{virtual_host = VHostPath, kind = Kind, name = Name}) -> + lists:flatten(io_lib:format("~s '~s' in vhost '~s'", + [Kind, Name, VHostPath])). + +permission_list(Ticket = #ticket{}) -> + lists:foldr(fun ({Field, Label}, L) -> + case element(Field, Ticket) of + true -> [Label | L]; + false -> L + end + end, + [], + [{#ticket.passive_flag, passive}, + {#ticket.active_flag, active}, + {#ticket.write_flag, write}, + {#ticket.read_flag, read}]). + +enable_cover() -> + case cover:compile_beam_directory("ebin") of + {error,Reason} -> {error,Reason}; + _ -> ok + end. + +report_cover() -> + Dir = "cover/", + ok = filelib:ensure_dir(Dir), + lists:foreach(fun(F) -> file:delete(F) end, + filelib:wildcard(Dir ++ "*.html")), + {ok, SummaryFile} = file:open(Dir ++ "summary.txt", [write]), + {CT, NCT} = + lists:foldl( + fun(M,{CovTot, NotCovTot}) -> + {ok, {M, {Cov, NotCov}}} = cover:analyze(M, module), + ok = report_coverage_percentage(SummaryFile, + Cov, NotCov, M), + {ok,_} = cover:analyze_to_file( + M, + Dir ++ atom_to_list(M) ++ ".html", + [html]), + {CovTot+Cov, NotCovTot+NotCov} + end, + {0, 0}, + lists:sort(cover:modules())), + ok = report_coverage_percentage(SummaryFile, CT, NCT, 'TOTAL'), + ok = file:close(SummaryFile), + ok. + +report_coverage_percentage(File, Cov, NotCov, Mod) -> + io:fwrite(File, "~6.2f ~p~n", + [if + Cov+NotCov > 0 -> 100.0*Cov/(Cov+NotCov); + true -> 100.0 + end, + Mod]). + +with_exit_handler(Handler, Thunk) -> + try + Thunk() + catch + exit:{R, _} when R =:= noproc; R =:= normal -> Handler() + end. + +with_user(Username, Thunk) -> + fun () -> + case mnesia:read({user, Username}) of + [] -> + mnesia:abort({no_such_user, Username}); + [_U] -> + Thunk() + end + end. + +with_vhost(VHostPath, Thunk) -> + fun () -> + case mnesia:read({vhost, VHostPath}) of + [] -> + mnesia:abort({no_such_vhost, VHostPath}); + [_V] -> + Thunk() + end + end. + +with_realm(Name = #resource{virtual_host = VHostPath, kind = realm}, + Thunk) -> + fun () -> + case mnesia:read({realm, Name}) of + [] -> + mnesia:abort({no_such_realm, Name}); + [_R] -> + case mnesia:match_object( + #vhost_realm{virtual_host = VHostPath, + realm = Name}) of + [] -> + %% This should never happen + mnesia:abort({no_such_realm, Name}); + [_VR] -> + Thunk() + end + end + end. + +with_user_and_vhost(Username, VHostPath, Thunk) -> + with_user(Username, with_vhost(VHostPath, Thunk)). + +execute_mnesia_transaction(TxFun) -> + %% Making this a sync_transaction allows us to use dirty_read + %% elsewhere and get a consistent result even when that read + %% executes on a different node. + case mnesia:sync_transaction(TxFun) of + {atomic, Result} -> Result; + {aborted, Reason} -> throw({error, Reason}) + end. + +ensure_ok(ok, _) -> ok; +ensure_ok({error, Reason}, ErrorTag) -> throw({error, {ErrorTag, Reason}}). + +localnode(Name) -> + %% This is horrible, but there doesn't seem to be a way to split a + %% nodename into its constituent parts. + list_to_atom(lists:append(atom_to_list(Name), + lists:dropwhile(fun (E) -> E =/= $@ end, + atom_to_list(node())))). + +tcp_name(Prefix, IPAddress, Port) + when is_atom(Prefix) andalso is_number(Port) -> + list_to_atom( + lists:flatten( + io_lib:format("~w_~s:~w", + [Prefix, inet_parse:ntoa(IPAddress), Port]))). + +intersperse(_, []) -> []; +intersperse(_, [E]) -> [E]; +intersperse(Sep, [E|T]) -> [E, Sep | intersperse(Sep, T)]. + +%% This is a modified version of Luke Gorrie's pmap - +%% http://lukego.livejournal.com/6753.html - that doesn't care about +%% the order in which results are received. +upmap(F, L) -> + Parent = self(), + Ref = make_ref(), + [receive {Ref, Result} -> Result end + || _ <- [spawn(fun() -> Parent ! {Ref, F(X)} end) || X <- L]]. + +map_in_order(F, L) -> + lists:reverse( + lists:foldl(fun (E, Acc) -> [F(E) | Acc] end, [], L)). + +%% generate a guid that is monotonically increasing per process. +%% +%% The id is only unique within a single cluster and as the persistent +%% message store hasn't been deleted. +guid() -> + %% We don't use erlang:now() here because a) it may return + %% duplicates when the system clock has been rewound prior to a + %% restart, or ids were generated at a high rate (which causes + %% now() to move ahead of the system time), and b) it is really + %% slow since it takes a global lock and makes a system call. + %% + %% rabbit_persister:serial/0, in combination with self/0 (which + %% includes the node name) uniquely identifies a process in space + %% and time. We combine that with a process-local counter to give + %% us a GUID that is monotonically increasing per process. + G = case get(guid) of + undefined -> {{rabbit_persister:serial(), self()}, 0}; + {S, I} -> {S, I+1} + end, + put(guid, G), + G. + +%% generate a readable string representation of a guid. Note that any +%% monotonicity of the guid is not preserved in the encoding. +string_guid(Prefix) -> + %% we use the (undocumented) ssl_base64 module here because it is + %% present throughout OTP R11 and R12 whereas base64 only becomes + %% available in R11B-4. + %% + %% TODO: once debian stable and EPEL have moved from R11B-2 to + %% R11B-4 or later we should change this to use base64. + Prefix ++ "-" ++ ssl_base64:encode(erlang:md5(term_to_binary(guid()))). + +binstring_guid(Prefix) -> + list_to_binary(string_guid(Prefix)). + +dirty_read_all(TableName) -> + mnesia:dirty_select(TableName, [{'$1',[],['$1']}]). + +dirty_foreach_key(F, TableName) -> + dirty_foreach_key1(F, TableName, mnesia:dirty_first(TableName)). + +dirty_foreach_key1(_F, _TableName, '$end_of_table') -> + ok; +dirty_foreach_key1(F, TableName, K) -> + case catch mnesia:dirty_next(TableName, K) of + {'EXIT', _} -> + aborted; + NextKey -> + F(K), + dirty_foreach_key1(F, TableName, NextKey) + end. + +dirty_dump_log(FileName) -> + {ok, LH} = disk_log:open([{name, dirty_dump_log}, {mode, read_only}, {file, FileName}]), + dirty_dump_log1(LH, disk_log:chunk(LH, start)), + disk_log:close(LH). + +dirty_dump_log1(_LH, eof) -> + io:format("Done.~n"); +dirty_dump_log1(LH, {K, Terms}) -> + io:format("Chunk: ~p~n", [Terms]), + dirty_dump_log1(LH, disk_log:chunk(LH, K)); +dirty_dump_log1(LH, {K, Terms, BadBytes}) -> + io:format("Bad Chunk, ~p: ~p~n", [BadBytes, Terms]), + dirty_dump_log1(LH, disk_log:chunk(LH, K)). diff --git a/src/rabbit_mnesia.erl b/src/rabbit_mnesia.erl new file mode 100644 index 00000000..82b80cb4 --- /dev/null +++ b/src/rabbit_mnesia.erl @@ -0,0 +1,399 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (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.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developers of the Original Code are LShift Ltd., +%% Cohesive Financial Technologies LLC., and Rabbit Technologies Ltd. +%% +%% Portions created by LShift Ltd., Cohesive Financial Technologies +%% LLC., and Rabbit Technologies Ltd. are Copyright (C) 2007-2008 +%% LShift Ltd., Cohesive Financial Technologies LLC., and Rabbit +%% Technologies Ltd.; +%% +%% All Rights Reserved. +%% +%% Contributor(s): ______________________________________. +%% + +-module(rabbit_mnesia). + +-export([ensure_mnesia_dir/0, status/0, init/0, is_db_empty/0, + cluster/1, reset/0, force_reset/0]). + +-export([table_names/0]). + +%% create_tables/0 exported for helping embed RabbitMQ in or alongside +%% other mnesia-using Erlang applications, such as ejabberd +-export([create_tables/0]). + +-include("rabbit.hrl"). + +%%---------------------------------------------------------------------------- + +-ifdef(use_specs). + +-spec(status/0 :: () -> [{'nodes' | 'running_nodes', [node()]}]). +-spec(ensure_mnesia_dir/0 :: () -> 'ok'). +-spec(init/0 :: () -> 'ok'). +-spec(is_db_empty/0 :: () -> bool()). +-spec(cluster/1 :: ([node()]) -> 'ok'). +-spec(reset/0 :: () -> 'ok'). +-spec(force_reset/0 :: () -> 'ok'). +-spec(create_tables/0 :: () -> 'ok'). + +-endif. + +%%---------------------------------------------------------------------------- + +status() -> + [{nodes, mnesia:system_info(db_nodes)}, + {running_nodes, mnesia:system_info(running_db_nodes)}]. + +init() -> + ok = ensure_mnesia_running(), + ok = ensure_mnesia_dir(), + ok = init_db(read_cluster_nodes_config()), + ok = wait_for_tables(), + ok. + +is_db_empty() -> + lists:all(fun (Tab) -> mnesia:dirty_first(Tab) == '$end_of_table' end, + table_names()). + +%% Alter which disk nodes this node is clustered with. This can be a +%% subset of all the disk nodes in the cluster but can (and should) +%% include the node itself if it is to be a disk rather than a ram +%% node. +cluster(ClusterNodes) -> + ok = ensure_mnesia_not_running(), + ok = ensure_mnesia_dir(), + rabbit_misc:ensure_ok(mnesia:start(), cannot_start_mnesia), + try + ok = init_db(ClusterNodes), + ok = wait_for_tables(), + ok = create_cluster_nodes_config(ClusterNodes) + after + mnesia:stop() + end, + ok. + +%% return node to its virgin state, where it is not member of any +%% cluster, has no cluster configuration, no local database, and no +%% persisted messages +reset() -> reset(false). +force_reset() -> reset(true). + +%%-------------------------------------------------------------------- + +table_definitions() -> + [{user, [{disc_copies, [node()]}, + {attributes, record_info(fields, user)}]}, + {user_vhost, [{type, bag}, + {disc_copies, [node()]}, + {attributes, record_info(fields, user_vhost)}, + {index, [virtual_host]}]}, + {vhost, [{disc_copies, [node()]}, + {attributes, record_info(fields, vhost)}]}, + {vhost_realm, [{type, bag}, + {disc_copies, [node()]}, + {attributes, record_info(fields, vhost_realm)}, + {index, [realm]}]}, + {realm, [{disc_copies, [node()]}, + {attributes, record_info(fields, realm)}]}, + {user_realm, [{type, bag}, + {disc_copies, [node()]}, + {attributes, record_info(fields, user_realm)}, + {index, [realm]}]}, + {exclusive_realm_visitor, + [{record_name, realm_visitor}, + {attributes, record_info(fields, realm_visitor)}, + {index, [pid]}]}, + {realm_visitor, [{type, bag}, + {attributes, record_info(fields, realm_visitor)}, + {index, [pid]}]}, + {rabbit_config, [{disc_copies, [node()]}]}, + {listener, [{type, bag}, + {attributes, record_info(fields, listener)}]}, + {binding, [{attributes, record_info(fields, binding)}]}, + {durable_exchanges, [{disc_copies, [node()]}, + {record_name, exchange}, + {attributes, record_info(fields, exchange)}]}, + {exchange, [{attributes, record_info(fields, exchange)}]}, + {durable_queues, [{disc_copies, [node()]}, + {record_name, amqqueue}, + {attributes, record_info(fields, amqqueue)}]}, + {amqqueue, [{attributes, record_info(fields, amqqueue)}, + {index, [pid]}]}]. + +table_names() -> + [Tab || {Tab, _} <- table_definitions()]. + +ensure_mnesia_dir() -> + MnesiaDir = mnesia:system_info(directory) ++ "/", + case filelib:ensure_dir(MnesiaDir) of + {error, Reason} -> + throw({error, {cannot_create_mnesia_dir, MnesiaDir, Reason}}); + ok -> ok + end. + +ensure_mnesia_running() -> + case mnesia:system_info(is_running) of + yes -> ok; + no -> throw({error, mnesia_not_running}) + end. + +ensure_mnesia_not_running() -> + case mnesia:system_info(is_running) of + no -> ok; + yes -> throw({error, mnesia_unexpectedly_running}) + end. + +ensure_schema_integrity() -> + case catch lists:foreach(fun (Tab) -> + mnesia:table_info(Tab, version) + end, + table_names()) of + {'EXIT', Reason} -> throw({error, {schema_integrity_check_failed, + Reason}}); + _ -> ok + end, + %%TODO: more thorough checks + ok. + +%% The cluster node config file contains some or all of the disk nodes +%% that are members of the cluster this node is / should be a part of. +%% +%% If the file is absent, the list is empty, or only contains the +%% current node, then the current node is a standalone (disk) +%% node. Otherwise it is a node that is part of a cluster as either a +%% disk node, if it appears in the cluster node config, or ram node if +%% it doesn't. + +cluster_nodes_config_filename() -> + mnesia:system_info(directory) ++ "/cluster_nodes.config". + +create_cluster_nodes_config(ClusterNodes) -> + FileName = cluster_nodes_config_filename(), + Handle = case file:open(FileName, [write]) of + {ok, Device} -> Device; + {error, Reason} -> + throw({error, {cannot_create_cluster_nodes_config, + FileName, Reason}}) + end, + try + ok = io:write(Handle, ClusterNodes), + ok = io:put_chars(Handle, [$.]) + after + case file:close(Handle) of + ok -> ok; + {error, Reason1} -> + throw({error, {cannot_close_cluster_nodes_config, + FileName, Reason1}}) + end + end, + ok. + +read_cluster_nodes_config() -> + FileName = cluster_nodes_config_filename(), + case file:consult(FileName) of + {ok, [ClusterNodes]} -> ClusterNodes; + {error, enoent} -> + case application:get_env(cluster_config) of + undefined -> []; + {ok, DefaultFileName} -> + case file:consult(DefaultFileName) of + {ok, [ClusterNodes]} -> ClusterNodes; + {error, enoent} -> + error_logger:warning_msg( + "default cluster config file ~p does not exist~n", + [DefaultFileName]), + []; + {error, Reason} -> + throw({error, {cannot_read_cluster_nodes_config, + DefaultFileName, Reason}}) + end + end; + {error, Reason} -> + throw({error, {cannot_read_cluster_nodes_config, + FileName, Reason}}) + end. + +delete_cluster_nodes_config() -> + FileName = cluster_nodes_config_filename(), + case file:delete(FileName) of + ok -> ok; + {error, enoent} -> ok; + {error, Reason} -> + throw({error, {cannot_delete_cluster_nodes_config, + FileName, Reason}}) + end. + +%% Take a cluster node config and create the right kind of node - a +%% standalone disk node, or disk or ram node connected to the +%% specified cluster nodes. +init_db(ClusterNodes) -> + WasDiskNode = mnesia:system_info(use_dir), + IsDiskNode = ClusterNodes == [] orelse + lists:member(node(), ClusterNodes), + case mnesia:change_config(extra_db_nodes, ClusterNodes -- [node()]) of + {ok, []} -> + if WasDiskNode and IsDiskNode -> + ok; + WasDiskNode -> + throw({error, {cannot_convert_disk_node_to_ram_node, + ClusterNodes}}); + IsDiskNode -> + ok = create_schema(); + true -> + throw({error, {unable_to_contact_cluster_nodes, + ClusterNodes}}) + end; + {ok, [_|_]} -> + ok = ensure_schema_integrity(), + ok = wait_for_tables(), + ok = create_local_table_copies( + case IsDiskNode of + true -> disc; + false -> ram + end); + {error, Reason} -> + %% one reason we may end up here is if we try to join + %% nodes together that are currently running standalone or + %% are members of a different cluster + throw({error, {unable_to_join_cluster, + ClusterNodes, Reason}}) + end. + +create_schema() -> + mnesia:stop(), + rabbit_misc:ensure_ok(mnesia:create_schema([node()]), + cannot_create_schema), + rabbit_misc:ensure_ok(mnesia:start(), + cannot_start_mnesia), + create_tables(). + +create_tables() -> + lists:foreach(fun ({Tab, TabArgs}) -> + case mnesia:create_table(Tab, TabArgs) of + {atomic, ok} -> ok; + {aborted, Reason} -> + throw({error, {table_creation_failed, + Tab, TabArgs, Reason}}) + end + end, + table_definitions()), + ok. + +create_local_table_copies(Type) -> + ok = if Type /= ram -> create_local_table_copy(schema, disc_copies); + true -> ok + end, + lists:foreach( + fun({Tab, TabDef}) -> + HasDiscCopies = + lists:keymember(disc_copies, 1, TabDef), + HasDiscOnlyCopies = + lists:keymember(disc_only_copies, 1, TabDef), + StorageType = + case Type of + disc -> + if + HasDiscCopies -> disc_copies; + HasDiscOnlyCopies -> disc_only_copies; + true -> ram_copies + end; +%% unused code - commented out to keep dialyzer happy +%% disc_only -> +%% if +%% HasDiscCopies or HasDiscOnlyCopies -> +%% disc_only_copies; +%% true -> ram_copies +%% end; + ram -> + ram_copies + end, + ok = create_local_table_copy(Tab, StorageType) + end, + table_definitions()), + ok = if Type == ram -> create_local_table_copy(schema, ram_copies); + true -> ok + end, + ok. + +create_local_table_copy(Tab, Type) -> + StorageType = mnesia:table_info(Tab, storage_type), + {atomic, ok} = + if + StorageType == unknown -> + mnesia:add_table_copy(Tab, node(), Type); + StorageType /= Type -> + mnesia:change_table_copy_type(Tab, node(), Type); + true -> {atomic, ok} + end, + ok. + +wait_for_tables() -> + case mnesia:wait_for_tables(table_names(), 30000) of + ok -> ok; + {timeout, BadTabs} -> + throw({error, {timeout_waiting_for_tables, BadTabs}}); + {error, Reason} -> + throw({error, {failed_waiting_for_tables, Reason}}) + end. + +reset(Force) -> + ok = ensure_mnesia_not_running(), + Node = node(), + case Force of + true -> ok; + false -> + ok = ensure_mnesia_dir(), + rabbit_misc:ensure_ok(mnesia:start(), cannot_start_mnesia), + {Nodes, RunningNodes} = + try + ok = init(), + {mnesia:system_info(db_nodes) -- [Node], + mnesia:system_info(running_db_nodes) -- [Node]} + after + mnesia:stop() + end, + leave_cluster(Nodes, RunningNodes), + rabbit_misc:ensure_ok(mnesia:delete_schema([Node]), + cannot_delete_schema) + end, + ok = delete_cluster_nodes_config(), + %% remove persistet messages and any other garbage we find + lists:foreach(fun file:delete/1, + filelib:wildcard(mnesia:system_info(directory) ++ "/*")), + ok. + +leave_cluster([], _) -> ok; +leave_cluster(Nodes, RunningNodes) -> + %% find at least one running cluster node and instruct it to + %% remove our schema copy which will in turn result in our node + %% being removed as a cluster node from the schema, with that + %% change being propagated to all nodes + case lists:any( + fun (Node) -> + case rpc:call(Node, mnesia, del_table_copy, + [schema, node()]) of + {atomic, ok} -> true; + {badrpc, nodedown} -> false; + {aborted, Reason} -> + throw({error, {failed_to_leave_cluster, + Nodes, RunningNodes, Reason}}) + end + end, + RunningNodes) of + true -> ok; + false -> throw({error, {no_running_cluster_nodes, + Nodes, RunningNodes}}) + end. diff --git a/src/rabbit_multi.erl b/src/rabbit_multi.erl new file mode 100644 index 00000000..cd92f1ac --- /dev/null +++ b/src/rabbit_multi.erl @@ -0,0 +1,279 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (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.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developers of the Original Code are LShift Ltd., +%% Cohesive Financial Technologies LLC., and Rabbit Technologies Ltd. +%% +%% Portions created by LShift Ltd., Cohesive Financial Technologies +%% LLC., and Rabbit Technologies Ltd. are Copyright (C) 2007-2008 +%% LShift Ltd., Cohesive Financial Technologies LLC., and Rabbit +%% Technologies Ltd.; +%% +%% All Rights Reserved. +%% +%% Contributor(s): ______________________________________. +%% + +-module(rabbit_multi). +-include("rabbit.hrl"). + +-export([start/0, stop/0]). + +-define(RPC_SLEEP, 500). + +start() -> + RpcTimeout = + case init:get_argument(maxwait) of + {ok,[[N1]]} -> 1000 * list_to_integer(N1); + _ -> 30000 + end, + case init:get_plain_arguments() of + [] -> + usage(); + FullCommand -> + {Command, Args} = parse_args(FullCommand), + case catch action(Command, Args, RpcTimeout) of + ok -> + io:format("done.~n"), + init:stop(); + {'EXIT', {function_clause, [{?MODULE, action, _} | _]}} -> + io:format("Invalid command ~p~n", [FullCommand]), + usage(); + timeout -> + io:format("timeout starting some nodes.~n"), + halt(1); + Other -> + io:format("~nrabbit_multi action ~p failed:~n~p~n", + [Command, Other]), + halt(2) + end + end. + +parse_args([Command | Args]) -> + {list_to_atom(Command), Args}. + +stop() -> + ok. + +usage() -> + io:format("Usage: rabbitmq-multi <command> + +Available commands: + + start_all <NodeCount> - start a local cluster of RabbitMQ nodes. + stop_all - stops all local RabbitMQ nodes. +"), + halt(3). + +action(start_all, [NodeCount], RpcTimeout) -> + io:format("Starting all nodes...~n", []), + N = list_to_integer(NodeCount), + {NodePids, Running} = start_nodes(N, N, [], true, + getenv("NODENAME"), + getenv("NODE_PORT"), + RpcTimeout), + write_pids_file(NodePids), + case Running of + true -> ok; + false -> timeout + end; + +action(stop_all, [], RpcTimeout) -> + io:format("Stopping all nodes...~n", []), + case read_pids_file() of + [] -> throw(no_nodes_running); + NodePids -> stop_nodes(NodePids, RpcTimeout), + delete_pids_file() + end. + +%% PNodePid is the list of PIDs +%% Running is a boolean exhibiting success at some moment +start_nodes(0, _, PNodePid, Running, _, _, _) -> {PNodePid, Running}; + +start_nodes(N, Total, PNodePid, Running, + NodeNameBase, NodePortBase, RpcTimeout) -> + NodeNumber = Total - N, + NodeName = if NodeNumber == 0 -> + %% For compatibility with running a single node + NodeNameBase; + true -> + NodeNameBase ++ "_" ++ integer_to_list(NodeNumber) + end, + {NodePid, Started} = start_node(NodeName, + list_to_integer(NodePortBase) + NodeNumber, + RpcTimeout), + start_nodes(N - 1, Total, [NodePid | PNodePid], + Started and Running, + NodeNameBase, NodePortBase, RpcTimeout). + +start_node(NodeName, NodePort, RpcTimeout) -> + os:putenv("NODENAME", NodeName), + os:putenv("NODE_PORT", integer_to_list(NodePort)), + Node = rabbit_misc:localnode(list_to_atom(NodeName)), + io:format("Starting node ~s...~n", [Node]), + case rpc:call(Node, os, getpid, []) of + {badrpc, _} -> + Port = run_cmd(script_filename()), + Started = wait_for_rabbit_to_start(Node, RpcTimeout, Port), + Pid = case rpc:call(Node, os, getpid, []) of + {badrpc, _} -> throw(cannot_get_pid); + PidS -> list_to_integer(PidS) + end, + io:format("~s~n", [case Started of + true -> "OK"; + false -> "timeout" + end]), + {{Node, Pid}, Started}; + PidS -> + Pid = list_to_integer(PidS), + throw({node_already_running, Node, Pid}) + end. + +wait_for_rabbit_to_start(_ , RpcTimeout, _) when RpcTimeout < 0 -> + false; +wait_for_rabbit_to_start(Node, RpcTimeout, Port) -> + case parse_status(rpc:call(Node, rabbit, status, [])) of + true -> true; + false -> receive + {'EXIT', Port, PosixCode} -> + throw({node_start_failed, PosixCode}) + after ?RPC_SLEEP -> + wait_for_rabbit_to_start( + Node, RpcTimeout - ?RPC_SLEEP, Port) + end + end. + +run_cmd(FullPath) -> + erlang:open_port({spawn, FullPath}, [nouse_stdio]). + +parse_status({badrpc, _}) -> + false; + +parse_status(Status) -> + case lists:keysearch(running_applications, 1, Status) of + {value, {running_applications, Apps}} -> + lists:keymember(rabbit, 1, Apps); + _ -> + false + end. + +with_os(Handlers) -> + {OsFamily, _} = os:type(), + case lists:keysearch(OsFamily, 1, Handlers) of + {value, {_, Handler}} -> Handler(); + false -> throw({unsupported_os, OsFamily}) + end. + +script_filename() -> + ScriptHome = getenv("SCRIPT_HOME"), + ScriptName = with_os( + [{unix , fun () -> "rabbitmq-server" end}, + {win32, fun () -> "rabbitmq-server.bat" end}]), + ScriptHome ++ "/" ++ ScriptName ++ " -noinput". + +pids_file() -> getenv("PIDS_FILE"). + +write_pids_file(Pids) -> + FileName = pids_file(), + Handle = case file:open(FileName, [write]) of + {ok, Device} -> + Device; + {error, Reason} -> + throw({error, {cannot_create_pids_file, + FileName, Reason}}) + end, + try + ok = io:write(Handle, Pids), + ok = io:put_chars(Handle, [$.]) + after + case file:close(Handle) of + ok -> ok; + {error, Reason1} -> + throw({error, {cannot_create_pids_file, + FileName, Reason1}}) + end + end, + ok. + +delete_pids_file() -> + FileName = pids_file(), + case file:delete(FileName) of + ok -> ok; + {error, enoent} -> ok; + {error, Reason} -> throw({error, {cannot_delete_pids_file, + FileName, Reason}}) + end. + +read_pids_file() -> + FileName = pids_file(), + case file:consult(FileName) of + {ok, [Pids]} -> Pids; + {error, enoent} -> []; + {error, Reason} -> throw({error, {cannot_read_pids_file, + FileName, Reason}}) + end. + +stop_nodes([],_) -> ok; + +stop_nodes([NodePid | Rest], RpcTimeout) -> + stop_node(NodePid, RpcTimeout), + stop_nodes(Rest, RpcTimeout). + +stop_node({Node, Pid}, RpcTimeout) -> + io:format("Stopping node ~p~n", [Node]), + rpc:call(Node, rabbit, stop_and_halt, []), + case kill_wait(Pid, RpcTimeout, false) of + false -> kill_wait(Pid, RpcTimeout, true); + true -> ok + end, + io:format("OK~n", []). + +kill_wait(Pid, TimeLeft, Forceful) when TimeLeft < 0 -> + Cmd = with_os([{unix, fun () -> if Forceful -> "kill -9"; + true -> "kill" + end + end}, + %% Kill forcefully always on Windows, since erl.exe + %% seems to completely ignore non-forceful killing + %% even when everything is working + {win32, fun () -> "taskkill /f /pid" end}]), + os:cmd(Cmd ++ " " ++ integer_to_list(Pid)), + false; % Don't assume what we did just worked! + +% Returns true if the process is dead, false otherwise. +kill_wait(Pid, TimeLeft, Forceful) -> + timer:sleep(?RPC_SLEEP), + io:format(".", []), + is_dead(Pid) orelse kill_wait(Pid, TimeLeft - ?RPC_SLEEP, Forceful). + +% Test using some OS clunkiness since we shouldn't trust +% rpc:call(os, getpid, []) at this point +is_dead(Pid) -> + PidS = integer_to_list(Pid), + with_os([{unix, fun () -> + Res = os:cmd("ps --no-headers --pid " ++ PidS), + Res == "" + end}, + {win32, fun () -> + Res = os:cmd("tasklist /nh /fi \"pid eq " ++ + PidS ++ "\""), + case regexp:first_match(Res, "erl.exe") of + {match, _, _} -> false; + _ -> true + end + end}]). + +getenv(Var) -> + case os:getenv(Var) of + false -> throw({missing_env_var, Var}); + Value -> Value + end. diff --git a/src/rabbit_networking.erl b/src/rabbit_networking.erl new file mode 100644 index 00000000..79c927cb --- /dev/null +++ b/src/rabbit_networking.erl @@ -0,0 +1,151 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (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.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developers of the Original Code are LShift Ltd., +%% Cohesive Financial Technologies LLC., and Rabbit Technologies Ltd. +%% +%% Portions created by LShift Ltd., Cohesive Financial Technologies +%% LLC., and Rabbit Technologies Ltd. are Copyright (C) 2007-2008 +%% LShift Ltd., Cohesive Financial Technologies LLC., and Rabbit +%% Technologies Ltd.; +%% +%% All Rights Reserved. +%% +%% Contributor(s): ______________________________________. +%% + +-module(rabbit_networking). + +-export([start/0, start_tcp_listener/2, stop_tcp_listener/2, + on_node_down/1, active_listeners/0, node_listeners/1]). +%%used by TCP-based transports, e.g. STOMP adapter +-export([check_tcp_listener_address/3]). + +-export([tcp_listener_started/2, tcp_listener_stopped/2, start_client/1]). + +-include("rabbit.hrl"). +-include_lib("kernel/include/inet.hrl"). + +%%---------------------------------------------------------------------------- + +-ifdef(use_specs). + +-type(host() :: ip_address() | string() | atom()). + +-spec(start/0 :: () -> 'ok'). +-spec(start_tcp_listener/2 :: (host(), ip_port()) -> 'ok'). +-spec(stop_tcp_listener/2 :: (host(), ip_port()) -> 'ok'). +-spec(active_listeners/0 :: () -> [listener()]). +-spec(node_listeners/1 :: (node()) -> [listener()]). +-spec(on_node_down/1 :: (node()) -> 'ok'). +-spec(check_tcp_listener_address/3 :: (atom(), host(), ip_port()) -> + {ip_address(), atom()}). + +-endif. + +%%---------------------------------------------------------------------------- + +start() -> + {ok,_} = supervisor:start_child( + rabbit_sup, + {rabbit_tcp_client_sup, + {tcp_client_sup, start_link, + [{local, rabbit_tcp_client_sup}, + {rabbit_reader,start_link,[]}]}, + transient, infinity, supervisor, [tcp_client_sup]}), + ok. + +check_tcp_listener_address(NamePrefix, Host, Port) -> + IPAddress = + case inet:getaddr(Host, inet) of + {ok, IPAddress1} -> IPAddress1; + {error, Reason} -> + error_logger:error_msg("invalid host ~p - ~p~n", + [Host, Reason]), + throw({error, {invalid_host, Host, Reason}}) + end, + if is_integer(Port) andalso (Port >= 0) andalso (Port =< 65535) -> ok; + true -> error_logger:error_msg("invalid port ~p - not 0..65535~n", + [Port]), + throw({error, invalid_port, Port}) + end, + Name = rabbit_misc:tcp_name(NamePrefix, IPAddress, Port), + {IPAddress, Name}. + +start_tcp_listener(Host, Port) -> + {IPAddress, Name} = check_tcp_listener_address(rabbit_tcp_listener_sup, Host, Port), + {ok,_} = supervisor:start_child( + rabbit_sup, + {Name, + {tcp_listener_sup, start_link, + [IPAddress, Port, + [binary, + {packet, raw}, % no packaging + {reuseaddr, true}, % allow rebind without waiting + %% {nodelay, true}, % TCP_NODELAY - disable Nagle's alg. + %% {delay_send, true}, + {exit_on_close, false}], + {?MODULE, tcp_listener_started, []}, + {?MODULE, tcp_listener_stopped, []}, + {?MODULE, start_client, []}]}, + transient, infinity, supervisor, [tcp_listener_sup]}), + ok. + +stop_tcp_listener(Host, Port) -> + {ok, IPAddress} = inet:getaddr(Host, inet), + Name = rabbit_misc:tcp_name(rabbit_tcp_listener_sup, IPAddress, Port), + ok = supervisor:terminate_child(rabbit_sup, Name), + ok = supervisor:delete_child(rabbit_sup, Name), + ok. + +tcp_listener_started(IPAddress, Port) -> + ok = mnesia:dirty_write( + #listener{node = node(), + protocol = tcp, + host = tcp_host(IPAddress), + port = Port}). + +tcp_listener_stopped(IPAddress, Port) -> + ok = mnesia:dirty_delete_object( + #listener{node = node(), + protocol = tcp, + host = tcp_host(IPAddress), + port = Port}). + +active_listeners() -> + rabbit_misc:dirty_read_all(listener). + +node_listeners(Node) -> + mnesia:dirty_read(listener, Node). + +on_node_down(Node) -> + ok = mnesia:dirty_delete(listener, Node). + +start_client(Sock) -> + {ok, Child} = supervisor:start_child(rabbit_tcp_client_sup, []), + ok = gen_tcp:controlling_process(Sock, Child), + Child ! {go, Sock}, + Child. + +%%-------------------------------------------------------------------- + +tcp_host({0,0,0,0}) -> + {ok, Hostname} = inet:gethostname(), + case inet:gethostbyname(Hostname) of + {ok, #hostent{h_name = Name}} -> Name; + {error, _Reason} -> Hostname + end; +tcp_host(IPAddress) -> + case inet:gethostbyaddr(IPAddress) of + {ok, #hostent{h_name = Name}} -> Name; + {error, _Reason} -> inet_parse:ntoa(IPAddress) + end. diff --git a/src/rabbit_node_monitor.erl b/src/rabbit_node_monitor.erl new file mode 100644 index 00000000..beef5285 --- /dev/null +++ b/src/rabbit_node_monitor.erl @@ -0,0 +1,76 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (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.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developers of the Original Code are LShift Ltd., +%% Cohesive Financial Technologies LLC., and Rabbit Technologies Ltd. +%% +%% Portions created by LShift Ltd., Cohesive Financial Technologies +%% LLC., and Rabbit Technologies Ltd. are Copyright (C) 2007-2008 +%% LShift Ltd., Cohesive Financial Technologies LLC., and Rabbit +%% Technologies Ltd.; +%% +%% All Rights Reserved. +%% +%% Contributor(s): ______________________________________. +%% + +-module(rabbit_node_monitor). + +-behaviour(gen_server). + +-export([start_link/0]). + +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +-define(SERVER, ?MODULE). + +%%-------------------------------------------------------------------- + +start_link() -> + gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). + +%%-------------------------------------------------------------------- + +init([]) -> + ok = net_kernel:monitor_nodes(true), + {ok, no_state}. + +handle_call(_Request, _From, State) -> + {noreply, State}. + +handle_cast(_Msg, State) -> + {noreply, State}. + +handle_info({nodeup, Node}, State) -> + rabbit_log:info("node ~p up", [Node]), + {noreply, State}; +handle_info({nodedown, Node}, State) -> + rabbit_log:info("node ~p down", [Node]), + %% TODO: This may turn out to be a performance hog when there are + %% lots of nodes. We really only need to execute this code on + %% *one* node, rather than all of them. + ok = rabbit_networking:on_node_down(Node), + ok = rabbit_realm:on_node_down(Node), + ok = rabbit_amqqueue:on_node_down(Node), + {noreply, State}; +handle_info(_Info, State) -> + {noreply, State}. + +terminate(_Reason, _State) -> + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%%-------------------------------------------------------------------- + diff --git a/src/rabbit_persister.erl b/src/rabbit_persister.erl new file mode 100644 index 00000000..91016d9d --- /dev/null +++ b/src/rabbit_persister.erl @@ -0,0 +1,511 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (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.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developers of the Original Code are LShift Ltd., +%% Cohesive Financial Technologies LLC., and Rabbit Technologies Ltd. +%% +%% Portions created by LShift Ltd., Cohesive Financial Technologies +%% LLC., and Rabbit Technologies Ltd. are Copyright (C) 2007-2008 +%% LShift Ltd., Cohesive Financial Technologies LLC., and Rabbit +%% Technologies Ltd.; +%% +%% All Rights Reserved. +%% +%% Contributor(s): ______________________________________. +%% + +-module(rabbit_persister). + +-behaviour(gen_server). + +-export([start_link/0]). + +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +-export([transaction/1, extend_transaction/2, dirty_work/1, + commit_transaction/1, rollback_transaction/1, + force_snapshot/0, serial/0]). + +-include("rabbit.hrl"). + +-define(SERVER, ?MODULE). + +-define(LOG_BUNDLE_DELAY, 5). +-define(COMPLETE_BUNDLE_DELAY, 2). + +-define(MAX_WRAP_ENTRIES, 500). + +-define(PERSISTER_LOG_FORMAT_VERSION, {2, 4}). + +-record(pstate, {log_handle, entry_count, deadline, + pending_logs, pending_replies, + snapshot}). + +%% two tables for efficient persistency +%% one maps a key to a message +%% the other maps a key to one or more queues. +%% The aim is to reduce the overload of storing a message multiple times +%% when it appears in several queues. +-record(psnapshot, {serial, transactions, messages, queues}). + +%%---------------------------------------------------------------------------- + +-ifdef(use_specs). + +-type(qmsg() :: {amqqueue(), pkey()}). +-type(work_item() :: + {publish, message(), qmsg()} | + {deliver, qmsg()} | + {ack, qmsg()}). + +-spec(start_link/0 :: () -> {'ok', pid()} | 'ignore' | {'error', any()}). +-spec(transaction/1 :: ([work_item()]) -> 'ok'). +-spec(extend_transaction/2 :: (txn(), [work_item()]) -> 'ok'). +-spec(dirty_work/1 :: ([work_item()]) -> 'ok'). +-spec(commit_transaction/1 :: (txn()) -> 'ok'). +-spec(rollback_transaction/1 :: (txn()) -> 'ok'). +-spec(force_snapshot/0 :: () -> 'ok'). +-spec(serial/0 :: () -> non_neg_integer()). + +-endif. + +%%---------------------------------------------------------------------------- + +start_link() -> + gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). + +transaction(MessageList) -> + ?LOGDEBUG("transaction ~p~n", [MessageList]), + TxnKey = rabbit_misc:guid(), + gen_server:call(?SERVER, {transaction, TxnKey, MessageList}). + +extend_transaction(TxnKey, MessageList) -> + ?LOGDEBUG("extend_transaction ~p ~p~n", [TxnKey, MessageList]), + gen_server:cast(?SERVER, {extend_transaction, TxnKey, MessageList}). + +dirty_work(MessageList) -> + ?LOGDEBUG("dirty_work ~p~n", [MessageList]), + gen_server:cast(?SERVER, {dirty_work, MessageList}). + +commit_transaction(TxnKey) -> + ?LOGDEBUG("commit_transaction ~p~n", [TxnKey]), + gen_server:call(?SERVER, {commit_transaction, TxnKey}). + +rollback_transaction(TxnKey) -> + ?LOGDEBUG("rollback_transaction ~p~n", [TxnKey]), + gen_server:cast(?SERVER, {rollback_transaction, TxnKey}). + +force_snapshot() -> + gen_server:call(?SERVER, force_snapshot). + +serial() -> + gen_server:call(?SERVER, serial). + +%%-------------------------------------------------------------------- + +init(_Args) -> + process_flag(trap_exit, true), + FileName = base_filename(), + ok = filelib:ensure_dir(FileName), + Snapshot = #psnapshot{serial = 0, + transactions = dict:new(), + messages = ets:new(messages, []), + queues = ets:new(queues, [])}, + LogHandle = + case disk_log:open([{name, rabbit_persister}, + {head, current_snapshot(Snapshot)}, + {file, FileName}]) of + {ok, LH} -> LH; + {repaired, LH, {recovered, Recovered}, {badbytes, Bad}} -> + WarningFun = if + Bad > 0 -> fun rabbit_log:warning/2; + true -> fun rabbit_log:info/2 + end, + WarningFun("Repaired persister log - ~p recovered, ~p bad~n", + [Recovered, Bad]), + LH + end, + {Res, LoadedSnapshot} = internal_load_snapshot(LogHandle, Snapshot), + NewSnapshot = LoadedSnapshot#psnapshot{ + serial = LoadedSnapshot#psnapshot.serial + 1}, + case Res of + ok -> + ok = take_snapshot(LogHandle, NewSnapshot); + {error, Reason} -> + rabbit_log:error("Failed to load persister log: ~p~n", [Reason]), + ok = take_snapshot_and_save_old(LogHandle, NewSnapshot) + end, + State = #pstate{log_handle = LogHandle, + entry_count = 0, + deadline = infinity, + pending_logs = [], + pending_replies = [], + snapshot = NewSnapshot}, + {ok, State}. + +handle_call({transaction, Key, MessageList}, From, State) -> + NewState = internal_extend(Key, MessageList, State), + do_noreply(internal_commit(From, Key, NewState)); +handle_call({commit_transaction, TxnKey}, From, State) -> + do_noreply(internal_commit(From, TxnKey, State)); +handle_call(force_snapshot, _From, State = #pstate{log_handle = LH, + snapshot = Snapshot}) -> + ok = take_snapshot(LH, Snapshot), + do_reply(ok, State); +handle_call(serial, _From, + State = #pstate{snapshot = #psnapshot{serial = Serial}}) -> + do_reply(Serial, State); +handle_call(_Request, _From, State) -> + {noreply, State}. + +handle_cast({rollback_transaction, TxnKey}, State) -> + do_noreply(internal_rollback(TxnKey, State)); +handle_cast({dirty_work, MessageList}, State) -> + do_noreply(internal_dirty_work(MessageList, State)); +handle_cast({extend_transaction, TxnKey, MessageList}, State) -> + do_noreply(internal_extend(TxnKey, MessageList, State)); +handle_cast(_Msg, State) -> + {noreply, State}. + +handle_info(timeout, State) -> + {noreply, flush(State)}; +handle_info(_Info, State) -> + {noreply, State}. + +terminate(_Reason, State = #pstate{log_handle = LogHandle}) -> + flush(State), + disk_log:close(LogHandle), + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, flush(State)}. + +%%-------------------------------------------------------------------- + +internal_extend(Key, MessageList, State) -> + log_work(fun (ML) -> {extend_transaction, Key, ML} end, + MessageList, State). + +internal_dirty_work(MessageList, State) -> + log_work(fun (ML) -> {dirty_work, ML} end, + MessageList, State). + +internal_commit(From, Key, State = #pstate{snapshot = Snapshot}) -> + Unit = {commit_transaction, Key}, + NewSnapshot = internal_integrate1(Unit, Snapshot), + complete(From, Unit, State#pstate{snapshot = NewSnapshot}). + +internal_rollback(Key, State = #pstate{snapshot = Snapshot}) -> + Unit = {rollback_transaction, Key}, + NewSnapshot = internal_integrate1(Unit, Snapshot), + log(State#pstate{snapshot = NewSnapshot}, Unit). + +complete(From, Item, State = #pstate{deadline = ExistingDeadline, + pending_logs = Logs, + pending_replies = Waiting}) -> + State#pstate{deadline = compute_deadline( + ?COMPLETE_BUNDLE_DELAY, ExistingDeadline), + pending_logs = [Item | Logs], + pending_replies = [From | Waiting]}. + +%% This is made to limit disk usage by writing messages only once onto +%% disk. We keep a table associating pkeys to messages, and provided +%% the list of messages to output is left to right, we can guarantee +%% that pkeys will be a backreference to a message in memory when a +%% "tied" is met. +log_work(CreateWorkUnit, MessageList, + State = #pstate{ + snapshot = Snapshot = #psnapshot{ + messages = Messages}}) -> + Unit = CreateWorkUnit( + rabbit_misc:map_in_order( + fun(M = {publish, Message, QK = {_QName, PKey}}) -> + case ets:lookup(Messages, PKey) of + [_] -> {tied, QK}; + [] -> ets:insert(Messages, {PKey, Message}), + M + end; + (M) -> M + end, + MessageList)), + NewSnapshot = internal_integrate1(Unit, Snapshot), + log(State#pstate{snapshot = NewSnapshot}, Unit). + +log(State = #pstate{deadline = ExistingDeadline, pending_logs = Logs}, + Message) -> + State#pstate{deadline = compute_deadline(?LOG_BUNDLE_DELAY, + ExistingDeadline), + pending_logs = [Message | Logs]}. + +base_filename() -> + mnesia:system_info(directory) ++ "/rabbit_persister.LOG". + +take_snapshot(LogHandle, OldFileName, Snapshot) -> + ok = disk_log:sync(LogHandle), + %% current_snapshot is the Head (ie. first thing logged) + ok = disk_log:reopen(LogHandle, OldFileName, current_snapshot(Snapshot)). + +take_snapshot(LogHandle, Snapshot) -> + OldFileName = lists:flatten(base_filename() ++ ".previous"), + file:delete(OldFileName), + rabbit_log:info("Rolling persister log to ~p~n", [OldFileName]), + ok = take_snapshot(LogHandle, OldFileName, Snapshot). + +take_snapshot_and_save_old(LogHandle, Snapshot) -> + {MegaSecs, Secs, MicroSecs} = erlang:now(), + Timestamp = MegaSecs * 1000000 + Secs * 1000 + MicroSecs, + OldFileName = lists:flatten(io_lib:format("~s.saved.~p", + [base_filename(), Timestamp])), + rabbit_log:info("Saving persister log in ~p~n", [OldFileName]), + ok = take_snapshot(LogHandle, OldFileName, Snapshot). + +maybe_take_snapshot(State = #pstate{entry_count = EntryCount, log_handle = LH, + snapshot = Snapshot}) + when EntryCount >= ?MAX_WRAP_ENTRIES -> + ok = take_snapshot(LH, Snapshot), + State#pstate{entry_count = 0}; +maybe_take_snapshot(State) -> + State. + +later_ms(DeltaMilliSec) -> + {MegaSec, Sec, MicroSec} = now(), + %% Note: not normalised. Unimportant for this application. + {MegaSec, Sec, MicroSec + (DeltaMilliSec * 1000)}. + +%% Result = B - A, more or less +time_diff({B1, B2, B3}, {A1, A2, A3}) -> + (B1 - A1) * 1000000 + (B2 - A2) + (B3 - A3) / 1000000.0 . + +compute_deadline(TimerDelay, infinity) -> + later_ms(TimerDelay); +compute_deadline(_TimerDelay, ExistingDeadline) -> + ExistingDeadline. + +compute_timeout(infinity) -> + infinity; +compute_timeout(Deadline) -> + DeltaMilliSec = time_diff(Deadline, now()) * 1000.0, + if + DeltaMilliSec =< 1 -> + 0; + true -> + round(DeltaMilliSec) + end. + +do_noreply(State = #pstate{deadline = Deadline}) -> + {noreply, State, compute_timeout(Deadline)}. + +do_reply(Reply, State = #pstate{deadline = Deadline}) -> + {reply, Reply, State, compute_timeout(Deadline)}. + +flush(State = #pstate{pending_logs = PendingLogs, + pending_replies = Waiting, + log_handle = LogHandle}) -> + State1 = if + PendingLogs /= [] -> + disk_log:alog(LogHandle, lists:reverse(PendingLogs)), + maybe_take_snapshot( + State#pstate{ + entry_count = State#pstate.entry_count + 1}); + true -> + State + end, + if Waiting /= [] -> + ok = disk_log:sync(LogHandle), + lists:foreach(fun (From) -> gen_server:reply(From, ok) end, + Waiting); + true -> + ok + end, + State1#pstate{deadline = infinity, + pending_logs = [], + pending_replies = []}. + +current_snapshot(_Snapshot = #psnapshot{serial = Serial, + transactions= Ts, + messages = Messages, + queues = Queues}) -> + %% Avoid infinite growth of the table by removing messages not + %% bound to a queue anymore + prune_table(Messages, ets:foldl( + fun ({{_QName, PKey}, _Delivered}, S) -> + sets:add_element(PKey, S) + end, sets:new(), Queues)), + InnerSnapshot = {{serial, Serial}, + {txns, Ts}, + {messages, ets:tab2list(Messages)}, + {queues, ets:tab2list(Queues)}}, + ?LOGDEBUG("Inner snapshot: ~p~n", [InnerSnapshot]), + {persist_snapshot, {vsn, ?PERSISTER_LOG_FORMAT_VERSION}, + term_to_binary(InnerSnapshot)}. + +prune_table(Tab, Keys) -> + true = ets:safe_fixtable(Tab, true), + ok = prune_table(Tab, Keys, ets:first(Tab)), + true = ets:safe_fixtable(Tab, false). + +prune_table(_Tab, _Keys, '$end_of_table') -> ok; +prune_table(Tab, Keys, Key) -> + case sets:is_element(Key, Keys) of + true -> ok; + false -> ets:delete(Tab, Key) + end, + prune_table(Tab, Keys, ets:next(Tab, Key)). + +internal_load_snapshot(LogHandle, + Snapshot = #psnapshot{messages = Messages, + queues = Queues}) -> + {K, [Loaded_Snapshot | Items]} = disk_log:chunk(LogHandle, start), + case check_version(Loaded_Snapshot) of + {ok, StateBin} -> + {{serial, Serial}, {txns, Ts}, {messages, Ms}, {queues, Qs}} = + binary_to_term(StateBin), + true = ets:insert(Messages, Ms), + true = ets:insert(Queues, Qs), + Snapshot1 = replay(Items, LogHandle, K, + Snapshot#psnapshot{ + serial = Serial, + transactions = Ts}), + Snapshot2 = requeue_messages(Snapshot1), + %% uncompleted transactions are discarded - this is TRTTD + %% since we only get into this code on node restart, so + %% any uncompleted transactions will have been aborted. + {ok, Snapshot2#psnapshot{transactions = dict:new()}}; + {error, Reason} -> {{error, Reason}, Snapshot} + end. + +check_version({persist_snapshot, {vsn, ?PERSISTER_LOG_FORMAT_VERSION}, + StateBin}) -> + {ok, StateBin}; +check_version({persist_snapshot, {vsn, Vsn}, _StateBin}) -> + {error, {unsupported_persister_log_format, Vsn}}; +check_version(_Other) -> + {error, unrecognised_persister_log_format}. + +requeue_messages(Snapshot = #psnapshot{messages = Messages, + queues = Queues}) -> + Work = ets:foldl(fun accumulate_requeues/2, dict:new(), Queues), + %% unstable parallel map, because order doesn't matter + L = lists:append( + rabbit_misc:upmap( + %% we do as much work as possible in spawned worker + %% processes, but we need to make sure the ets:inserts are + %% performed in self() + fun ({QName, Requeues}) -> + requeue(QName, Requeues, Messages) + end, dict:to_list(Work))), + NewMessages = [{K, M} || {{_Q, K}, M, _D} <- L], + NewQueues = [{QK, D} || {QK, _M, D} <- L], + ets:delete_all_objects(Messages), + ets:delete_all_objects(Queues), + true = ets:insert(Messages, NewMessages), + true = ets:insert(Queues, NewQueues), + %% contains the mutated messages and queues tables + Snapshot. + +accumulate_requeues({{QName, PKey}, Delivered}, Acc) -> + Requeue = {PKey, Delivered}, + dict:update(QName, + fun (Requeues) -> [Requeue | Requeues] end, + [Requeue], + Acc). + +requeue(QName, Requeues, Messages) -> + case rabbit_amqqueue:lookup(QName) of + {ok, #amqqueue{pid = QPid}} -> + RequeueMessages = + [{{QName, PKey}, Message, Delivered} || + {PKey, Delivered} <- Requeues, + {_, Message} <- ets:lookup(Messages, PKey)], + rabbit_amqqueue:redeliver( + QPid, + %% Messages published by the same process receive + %% persistence keys that are monotonically + %% increasing. Since message ordering is defined on a + %% per-channel basis, and channels are bound to specific + %% processes, sorting the list does provide the correct + %% ordering properties. + [{Message, Delivered} || {_, Message, Delivered} <- + lists:sort(RequeueMessages)]), + RequeueMessages; + {error, not_found} -> + [] + end. + +replay([], LogHandle, K, Snapshot) -> + case disk_log:chunk(LogHandle, K) of + {K1, Items} -> + replay(Items, LogHandle, K1, Snapshot); + {K1, Items, Badbytes} -> + rabbit_log:warning("~p bad bytes recovering persister log~n", + [Badbytes]), + replay(Items, LogHandle, K1, Snapshot); + eof -> Snapshot + end; +replay([Item | Items], LogHandle, K, Snapshot) -> + NewSnapshot = internal_integrate_messages(Item, Snapshot), + replay(Items, LogHandle, K, NewSnapshot). + +internal_integrate_messages(Items, Snapshot) -> + lists:foldl(fun (Item, Snap) -> internal_integrate1(Item, Snap) end, + Snapshot, Items). + +internal_integrate1({extend_transaction, Key, MessageList}, + Snapshot = #psnapshot {transactions = Transactions}) -> + NewTransactions = + dict:update(Key, + fun (MessageLists) -> [MessageList | MessageLists] end, + [MessageList], + Transactions), + Snapshot#psnapshot{transactions = NewTransactions}; +internal_integrate1({rollback_transaction, Key}, + Snapshot = #psnapshot{transactions = Transactions}) -> + Snapshot#psnapshot{transactions = dict:erase(Key, Transactions)}; +internal_integrate1({commit_transaction, Key}, + Snapshot = #psnapshot{transactions = Transactions, + messages = Messages, + queues = Queues}) -> + case dict:find(Key, Transactions) of + {ok, MessageLists} -> + ?LOGDEBUG("persist committing txn ~p~n", [Key]), + lists:foreach(fun (ML) -> perform_work(ML, Messages, Queues) end, + lists:reverse(MessageLists)), + Snapshot#psnapshot{transactions = dict:erase(Key, Transactions)}; + error -> + Snapshot + end; +internal_integrate1({dirty_work, MessageList}, + Snapshot = #psnapshot {messages = Messages, + queues = Queues}) -> + perform_work(MessageList, Messages, Queues), + Snapshot. + +perform_work(MessageList, Messages, Queues) -> + lists:foreach( + fun (Item) -> perform_work_item(Item, Messages, Queues) end, + MessageList). + +perform_work_item({publish, Message, QK = {_QName, PKey}}, Messages, Queues) -> + ets:insert(Messages, {PKey, Message}), + ets:insert(Queues, {QK, false}); + +perform_work_item({tied, QK}, _Messages, Queues) -> + ets:insert(Queues, {QK, false}); + +perform_work_item({deliver, QK}, _Messages, Queues) -> + %% from R12B-2 onward we could use ets:update_element/3 here + ets:delete(Queues, QK), + ets:insert(Queues, {QK, true}); + +perform_work_item({ack, QK}, _Messages, Queues) -> + ets:delete(Queues, QK). diff --git a/src/rabbit_reader.erl b/src/rabbit_reader.erl new file mode 100644 index 00000000..1d11cbaa --- /dev/null +++ b/src/rabbit_reader.erl @@ -0,0 +1,693 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (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.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developers of the Original Code are LShift Ltd., +%% Cohesive Financial Technologies LLC., and Rabbit Technologies Ltd. +%% +%% Portions created by LShift Ltd., Cohesive Financial Technologies +%% LLC., and Rabbit Technologies Ltd. are Copyright (C) 2007-2008 +%% LShift Ltd., Cohesive Financial Technologies LLC., and Rabbit +%% Technologies Ltd.; +%% +%% All Rights Reserved. +%% +%% Contributor(s): ______________________________________. +%% + +-module(rabbit_reader). +-include("rabbit_framing.hrl"). +-include("rabbit.hrl"). + +-export([start_link/0]). + +-export([system_continue/3, system_terminate/4, system_code_change/4]). + +-export([init/1, mainloop/3]). + +-export([analyze_frame/2]). + +-import(gen_tcp). +-import(fprof). +-import(inet). +-import(prim_inet). + +-define(HANDSHAKE_TIMEOUT, 10). +-define(NORMAL_TIMEOUT, 3). +-define(CLOSING_TIMEOUT, 1). +-define(CHANNEL_CLOSING_TIMEOUT, 1). +-define(CHANNEL_TERMINATION_TIMEOUT, 3). + +%--------------------------------------------------------------------------- + +-record(v1, {sock, connection, callback, recv_ref, connection_state}). + +%% connection lifecycle +%% +%% all state transitions and terminations are marked with *...* +%% +%% The lifecycle begins with: start handshake_timeout timer, *pre-init* +%% +%% all states, unless specified otherwise: +%% socket error -> *exit* +%% socket close -> *throw* +%% forced termination -> *exit* +%% handshake_timeout -> *throw* +%% pre-init: +%% receive protocol header -> send connection.start, *starting* +%% starting: +%% receive connection.start_ok -> send connection.tune, *tuning* +%% tuning: +%% receive connection.tune_ok -> start heartbeats, *opening* +%% opening: +%% receive connection.open -> send connection.open_ok, *running* +%% running: +%% receive connection.close -> +%% tell channels to terminate gracefully +%% if no channels then send connection.close_ok, start +%% terminate_connection timer, *closed* +%% else *closing* +%% forced termination +%% -> wait for channels to terminate forcefully, start +%% terminate_connection timer, send close, *exit* +%% channel exit with hard error +%% -> log error, wait for channels to terminate forcefully, start +%% terminate_connection timer, send close, *closed* +%% channel exit with soft error +%% -> log error, start terminate_channel timer, mark channel as +%% closing, *running* +%% terminate_channel timeout -> remove 'closing' mark, *running* +%% handshake_timeout -> ignore, *running* +%% heartbeat timeout -> *throw* +%% closing: +%% socket close -> *terminate* +%% receive frame -> ignore, *closing* +%% terminate_channel timeout -> remove 'closing' mark, *closing* +%% handshake_timeout -> ignore, *closing* +%% heartbeat timeout -> *throw* +%% channel exit -> +%% if abnormal exit then log error +%% if last channel to exit then send connection.close_ok, start +%% terminate_connection timer, *closing* +%% closed: +%% socket close -> *terminate* +%% receive connection.close_ok -> self() ! terminate_connection, +%% *closed* +%% receive frame -> ignore, *closed* +%% terminate_connection timeout -> *terminate* +%% terminate_channel timeout -> remove 'closing' mark, *closed* +%% handshake_timeout -> ignore, *closed* +%% heartbeat timeout -> *throw* +%% channel exit -> log error, *closed* +%% +%% +%% TODO: refactor the code so that the above is obvious + +%%-------------------------------------------------------------------------- + +start_link() -> + {ok, proc_lib:spawn_link(?MODULE, init, [self()])}. + +init(Parent) -> + Deb = sys:debug_options([]), + receive + {go, Sock} -> start_connection(Parent, Deb, Sock) + end. + +system_continue(Parent, Deb, State) -> + ?MODULE:mainloop(Parent, Deb, State). + +system_terminate(Reason, _Parent, _Deb, _State) -> + exit(Reason). + +system_code_change(Misc, _Module, _OldVsn, _Extra) -> + {ok, Misc}. + +setup_profiling() -> + Value = rabbit_misc:get_config(profiling_enabled, false), + case Value of + once -> + rabbit_log:info("Enabling profiling for this connection, and disabling for subsequent.~n"), + rabbit_misc:set_config(profiling_enabled, false), + fprof:trace(start); + true -> + rabbit_log:info("Enabling profiling for this connection.~n"), + fprof:trace(start); + false -> + ok + end, + Value. + +teardown_profiling(Value) -> + case Value of + false -> + ok; + _ -> + rabbit_log:info("Completing profiling for this connection.~n"), + fprof:trace(stop), + fprof:profile(), + fprof:analyse([{dest, []}, {cols, 100}]) + end. + +start_connection(Parent, Deb, ClientSock) -> + ProfilingValue = setup_profiling(), + process_flag(trap_exit, true), + {ok, {PeerAddress, PeerPort}} = inet:peername(ClientSock), + PeerAddressS = inet_parse:ntoa(PeerAddress), + rabbit_log:info("starting TCP connection ~p from ~s:~p~n", + [self(), PeerAddressS, PeerPort]), + try + erlang:send_after(?HANDSHAKE_TIMEOUT * 1000, self(), + handshake_timeout), + mainloop(Parent, Deb, switch_callback( + #v1{sock = ClientSock, + connection = #connection{ + user = none, + timeout_sec = ?HANDSHAKE_TIMEOUT, + frame_max = ?FRAME_MIN_SIZE, + vhost = none}, + callback = uninitialized_callback, + recv_ref = none, + connection_state = pre_init}, + handshake, 8)) + catch + Ex -> rabbit_log:error("error on TCP connection ~p from ~s:~p~n~p~n", + [self(), PeerAddressS, PeerPort, Ex]) + after + rabbit_log:info("closing TCP connection ~p from ~s:~p~n", + [self(), PeerAddressS, PeerPort]), + %% We don't close the socket explicitly. The reader is the + %% controlling process and hence its termination will close + %% the socket. Furthermore, gen_tcp:close/1 waits for pending + %% output to be sent, which results in unnecessary delays. + %% + %% gen_tcp:close(ClientSock), + teardown_profiling(ProfilingValue) + end, + done. + +mainloop(Parent, Deb, State = #v1{sock= Sock, recv_ref = Ref}) -> + %%?LOGDEBUG("Reader mainloop: ~p bytes available, need ~p~n", [HaveBytes, WaitUntilNBytes]), + receive + {inet_async, Sock, Ref, {ok, Data}} -> + {State1, Callback1, Length1} = + handle_input(State#v1.callback, Data, + State#v1{recv_ref = none}), + mainloop(Parent, Deb, + switch_callback(State1, Callback1, Length1)); + {inet_async, Sock, Ref, {error, closed}} -> + if State#v1.connection_state =:= closed -> + State; + true -> + throw(connection_closed_abruptly) + end; + {inet_async, Sock, Ref, {error, Reason}} -> + throw({inet_error, Reason}); + {'EXIT', Parent, Reason} -> + if State#v1.connection_state =:= running -> + send_exception( + State, 0, + {amqp, connection_forced, + io_lib:format( + "broker forced connection closure with reason '~w'", + [Reason]), none}); + true -> ok + end, + %% this is what we are expected to do according to + %% http://www.erlang.org/doc/man/sys.html + %% + %% If we wanted to be *really* nice we should wait for a + %% while for clients to close the socket at their end, + %% just as we do in the ordinary error case. However, + %% since this termination is initiated by our parent it is + %% probably more important to exit quickly. + exit(Reason); + {'EXIT', Pid, Reason} -> + mainloop(Parent, Deb, handle_dependent_exit(Pid, Reason, State)); + {terminate_channel, Channel, Ref1} -> + mainloop(Parent, Deb, terminate_channel(Channel, Ref1, State)); + terminate_connection -> + State; + handshake_timeout -> + if State#v1.connection_state =:= running orelse + State#v1.connection_state =:= closing orelse + State#v1.connection_state =:= closed -> + mainloop(Parent, Deb, State); + true -> + throw({handshake_timeout, State#v1.callback}) + end; + timeout -> + throw({timeout, State#v1.connection_state}); + {system, From, Request} -> + sys:handle_system_msg(Request, From, + Parent, ?MODULE, Deb, State); + Other -> + %% internal error -> something worth dying for + exit({unexpected_message, Other}) + end. + +switch_callback(OldState, NewCallback, Length) -> + {ok, Ref} = prim_inet:async_recv(OldState#v1.sock, Length, -1), + OldState#v1{callback = NewCallback, + recv_ref = Ref}. + +close_connection(State = #v1{connection = #connection{ + timeout_sec = TimeoutSec}}) -> + %% We terminate the connection after the specified interval, but + %% no later than ?CLOSING_TIMEOUT seconds. + TimeoutMillisec = + 1000 * if TimeoutSec > 0 andalso + TimeoutSec < ?CLOSING_TIMEOUT -> TimeoutSec; + true -> ?CLOSING_TIMEOUT + end, + erlang:send_after(TimeoutMillisec, self(), terminate_connection), + State#v1{connection_state = closed}. + +close_channel(Channel, State) -> + Ref = make_ref(), + TRef = erlang:send_after(1000 * ?CHANNEL_CLOSING_TIMEOUT, + self(), + {terminate_channel, Channel, Ref}), + put({closing_channel, Channel}, {Ref, TRef}), + State. + +terminate_channel(Channel, Ref, State) -> + case get({closing_channel, Channel}) of + undefined -> ok; %% got close_ok in the meantime + {Ref, _} -> erase({closing_channel, Channel}), + ok; + {_Ref, _} -> ok %% got close_ok, and have new closing channel + end, + State. + +handle_dependent_exit(Pid, Reason, + State = #v1{connection_state = closing}) -> + case channel_cleanup(Pid) of + undefined -> exit({abnormal_dependent_exit, Pid, Reason}); + Channel -> + case Reason of + normal -> ok; + _ -> log_channel_error(closing, Channel, Reason) + end, + maybe_close(State) + end; +handle_dependent_exit(Pid, normal, State) -> + channel_cleanup(Pid), + State; +handle_dependent_exit(Pid, Reason, State) -> + case channel_cleanup(Pid) of + undefined -> exit({abnormal_dependent_exit, Pid, Reason}); + Channel -> handle_exception(State, Channel, Reason) + end. + +channel_cleanup(Pid) -> + case get({chpid, Pid}) of + undefined -> + case get({closing_chpid, Pid}) of + undefined -> undefined; + {channel, Channel} -> + erase({closing_chpid, Pid}), + Channel + end; + {channel, Channel} -> + erase({channel, Channel}), + erase({chpid, Pid}), + Channel + end. + +all_channels() -> [Pid || {{chpid, Pid},_} <- get()]. + +terminate_channels() -> + NChannels = length([exit(Pid, normal) || Pid <- all_channels()]), + if NChannels > 0 -> + Timeout = 1000 * ?CHANNEL_TERMINATION_TIMEOUT * NChannels, + TimerRef = erlang:send_after(Timeout, self(), cancel_wait), + wait_for_channel_termination(NChannels, TimerRef); + true -> ok + end. + +wait_for_channel_termination(0, TimerRef) -> + case erlang:cancel_timer(TimerRef) of + false -> receive + cancel_wait -> ok + end; + _ -> ok + end; + +wait_for_channel_termination(N, TimerRef) -> + receive + {'EXIT', Pid, Reason} -> + case channel_cleanup(Pid) of + undefined -> + exit({abnormal_dependent_exit, Pid, Reason}); + Channel -> + case Reason of + normal -> ok; + _ -> + rabbit_log:error( + "connection ~p, channel ~p - error while terminating:~n~p~n", + [self(), Channel, Reason]) + end, + wait_for_channel_termination(N-1, TimerRef) + end; + cancel_wait -> + exit(channel_termination_timeout) + end. + +maybe_close(State) -> + case all_channels() of + [] -> ok = send_on_channel0( + State#v1.sock, #'connection.close_ok'{}), + close_connection(State); + _ -> State + end. + +handle_frame(Type, 0, Payload, State = #v1{connection_state = CS}) + when CS =:= closing; CS =:= closed -> + case analyze_frame(Type, Payload) of + {method, MethodName, FieldsBin} -> + handle_method0(MethodName, FieldsBin, State); + _Other -> State + end; +handle_frame(_Type, _Channel, _Payload, State = #v1{connection_state = CS}) + when CS =:= closing; CS =:= closed -> + State; +handle_frame(Type, 0, Payload, State) -> + case analyze_frame(Type, Payload) of + error -> throw({unknown_frame, Type, Payload}); + heartbeat -> State; + trace -> State; + {method, MethodName, FieldsBin} -> + handle_method0(MethodName, FieldsBin, State); + Other -> throw({unexpected_frame_on_channel0, Other}) + end; +handle_frame(Type, Channel, Payload, State) -> + case analyze_frame(Type, Payload) of + error -> throw({unknown_frame, Type, Payload}); + heartbeat -> throw({unexpected_heartbeat_frame, Channel}); + trace -> throw({unexpected_trace_frame, Channel}); + AnalyzedFrame -> + %%?LOGDEBUG("Ch ~p Frame ~p~n", [Channel, AnalyzedFrame]), + case get({channel, Channel}) of + {chpid, ChPid} -> + ok = check_for_close(Channel, ChPid, AnalyzedFrame), + ok = rabbit_framing_channel:process(ChPid, AnalyzedFrame), + State; + undefined -> + case State#v1.connection_state of + running -> send_to_new_channel( + Channel, AnalyzedFrame, State), + State; + Other -> throw({channel_frame_while_starting, + Channel, Other, AnalyzedFrame}) + end + end + end. + +analyze_frame(?FRAME_METHOD, <<ClassId:16, MethodId:16, MethodFields/binary>>) -> + {method, rabbit_framing:lookup_method_name({ClassId, MethodId}), MethodFields}; +analyze_frame(?FRAME_HEADER, <<ClassId:16, Weight:16, BodySize:64, Properties/binary>>) -> + {content_header, ClassId, Weight, BodySize, Properties}; +analyze_frame(?FRAME_BODY, Body) -> + {content_body, Body}; +analyze_frame(?FRAME_TRACE, _Body) -> + trace; +analyze_frame(?FRAME_HEARTBEAT, <<>>) -> + heartbeat; +analyze_frame(_Type, _Body) -> + error. + +handle_input(frame_header, <<Type:8,Channel:16,PayloadSize:32>>, State) -> + %%?LOGDEBUG("Got frame header: ~p/~p/~p~n", [Type, Channel, PayloadSize]), + {State, {frame_payload, Type, Channel, PayloadSize}, PayloadSize + 1}; + +handle_input({frame_payload, Type, Channel, PayloadSize}, PayloadAndMarker, State) -> + case PayloadAndMarker of + <<Payload:PayloadSize/binary, ?FRAME_END>> -> + %%?LOGDEBUG("Frame completed: ~p/~p/~p~n", [Type, Channel, Payload]), + NewState = handle_frame(Type, Channel, Payload, State), + {NewState, frame_header, 7}; + _ -> + throw({bad_payload, PayloadAndMarker}) + end; + +handle_input(handshake, <<"AMQP",1,1,ProtocolMajor,ProtocolMinor>>, + State = #v1{sock = Sock, connection = Connection}) -> + case check_version({ProtocolMajor, ProtocolMinor}, + {?PROTOCOL_VERSION_MAJOR, ?PROTOCOL_VERSION_MINOR}) of + true -> + {ok, Product} = application:get_key(id), + {ok, Version} = application:get_key(vsn), + ok = send_on_channel0( + Sock, + #'connection.start'{ + version_major = ?PROTOCOL_VERSION_MAJOR, + version_minor = ?PROTOCOL_VERSION_MINOR, + server_properties = + [{list_to_binary(K), longstr, list_to_binary(V)} || + {K, V} <- + [{"product", Product}, + {"version", Version}, + {"platform", "Erlang/OTP"}, + {"copyright", ?COPYRIGHT_MESSAGE}, + {"information", ?INFORMATION_MESSAGE}]], + mechanisms = <<"PLAIN AMQPLAIN">>, + locales = <<"en_US">> }), + {State#v1{connection = Connection#connection{ + timeout_sec = ?NORMAL_TIMEOUT}, + connection_state = starting}, + frame_header, 7}; + false -> + throw({bad_version, ProtocolMajor, ProtocolMinor}) + end; + +handle_input(handshake, Other, #v1{sock = Sock}) -> + ok = gen_tcp:send(Sock, <<"AMQP",1,1,?PROTOCOL_VERSION_MAJOR,?PROTOCOL_VERSION_MINOR>>), + throw({bad_header, Other}); + +handle_input(Callback, Data, _State) -> + throw({bad_input, Callback, Data}). + +%% the 0-8 spec, confusingly, defines the version as 8-0 +adjust_version({8,0}) -> {0,8}; +adjust_version(Version) -> Version. +check_version(ClientVersion, ServerVersion) -> + {ClientMajor, ClientMinor} = adjust_version(ClientVersion), + {ServerMajor, ServerMinor} = adjust_version(ServerVersion), + ClientMajor > ServerMajor + orelse + (ClientMajor == ServerMajor andalso + ClientMinor >= ServerMinor). + +%%-------------------------------------------------------------------------- + +handle_method0(MethodName, FieldsBin, State) -> + try + handle_method0(rabbit_framing:decode_method_fields( + MethodName, FieldsBin), + State) + catch exit:Reason -> + CompleteReason = + case Reason of + {amqp, Error, Explanation, none} -> + {amqp, Error, Explanation, MethodName}; + OtherReason -> OtherReason + end, + case State#v1.connection_state of + running -> send_exception(State, 0, CompleteReason); + Other -> throw({channel0_error, Other, CompleteReason}) + end + end. +handle_method0(#'connection.start_ok'{mechanism = Mechanism, + response = Response}, + State = #v1{connection_state = starting, + connection = Connection, + sock = Sock}) -> + User = rabbit_access_control:check_login(Mechanism, Response), + ok = send_on_channel0( + Sock, + #'connection.tune'{channel_max = 0, + %% set to zero once QPid fix their negotiation + frame_max = 131072, + heartbeat = 0}), + State#v1{connection_state = tuning, + connection = Connection#connection{user = User}}; +handle_method0(#'connection.tune_ok'{channel_max = _ChannelMax, + frame_max = FrameMax, + heartbeat = ClientHeartbeat}, + State = #v1{connection_state = tuning, + connection = Connection, + sock = Sock}) -> + %% if we have a channel_max limit that the client wishes to + %% exceed, die as per spec. Not currently a problem, so we ignore + %% the client's channel_max parameter. + rabbit_heartbeat:start_heartbeat(Sock, ClientHeartbeat), + State#v1{connection_state = opening, + connection = Connection#connection{ + timeout_sec = ClientHeartbeat, + frame_max = FrameMax}}; +handle_method0(#'connection.open'{virtual_host = VHostPath, + insist = Insist}, + State = #v1{connection_state = opening, + connection = Connection = #connection{ + user = User}, + sock = Sock}) -> + ok = rabbit_access_control:check_vhost_access(User, VHostPath), + NewConnection = Connection#connection{vhost = VHostPath}, + KnownHosts = format_listeners(rabbit_networking:active_listeners()), + Redirects = compute_redirects(Insist), + if Redirects == [] -> + ok = send_on_channel0( + Sock, + #'connection.open_ok'{known_hosts = KnownHosts}), + State#v1{connection_state = running, + connection = NewConnection}; + true -> + %% FIXME: 'host' is supposed to only contain one + %% address; but which one do we pick? This is + %% really a problem with the spec. + Host = format_listeners(Redirects), + rabbit_log:info("connection ~p redirecting to ~p~n", + [self(), Host]), + ok = send_on_channel0( + Sock, + #'connection.redirect'{host = Host, + known_hosts = KnownHosts}), + close_connection(State#v1{connection = NewConnection}) + end; +handle_method0(#'connection.close'{}, + State = #v1{connection_state = running}) -> + lists:foreach(fun rabbit_framing_channel:shutdown/1, all_channels()), + maybe_close(State#v1{connection_state = closing}); +handle_method0(#'connection.close_ok'{}, + State = #v1{connection_state = closed}) -> + self() ! terminate_connection, + State; +handle_method0(_Method, State = #v1{connection_state = CS}) + when CS =:= closing; CS =:= closed -> + State; +handle_method0(_Method, #v1{connection_state = S}) -> + rabbit_misc:protocol_error( + channel_error, "unexpected method in connection state ~w", [S]). + +send_on_channel0(Sock, Method) -> + ok = rabbit_writer:internal_send_command(Sock, 0, Method). + +format_listeners(Listeners) -> + list_to_binary( + rabbit_misc:intersperse( + $,, + [io_lib:format("~s:~w", [Host, Port]) || + #listener{host = Host, port = Port} <- Listeners])). + +compute_redirects(true) -> []; +compute_redirects(false) -> + Node = node(), + LNode = rabbit_load:pick(), + if Node == LNode -> []; + true -> rabbit_networking:node_listeners(LNode) + end. + +%%-------------------------------------------------------------------------- + +send_to_new_channel(Channel, AnalyzedFrame, State) -> + case get({closing_channel, Channel}) of + undefined -> + #v1{sock = Sock, + connection = #connection{ + frame_max = FrameMax, + user = #user{username = Username}, + vhost = VHost}} = State, + WriterPid = rabbit_writer:start(Sock, Channel, FrameMax), + ChPid = rabbit_framing_channel:start_link( + fun rabbit_channel:start_link/4, + [self(), WriterPid, Username, VHost]), + put({channel, Channel}, {chpid, ChPid}), + put({chpid, ChPid}, {channel, Channel}), + ok = rabbit_framing_channel:process(ChPid, AnalyzedFrame); + {_, TRef} -> + %% According to the spec, after sending a channel.close we + %% must ignore all frames except channel.close_ok. + case AnalyzedFrame of + {method, 'channel.close_ok', _} -> + erlang:cancel_timer(TRef), + erase({closing_channel, Channel}), + ok; + _Other -> ok + end + end. + +check_for_close(Channel, ChPid, {method, 'channel.close', _}) -> + channel_cleanup(ChPid), + put({closing_chpid, ChPid}, {channel, Channel}), + ok; +check_for_close(_Channel, _ChPid, _Frame) -> + ok. + +log_channel_error(ConnectionState, Channel, Reason) -> + rabbit_log:error("connection ~p (~p), channel ~p - error:~n~p~n", + [self(), ConnectionState, Channel, Reason]). + +handle_exception(State = #v1{connection_state = closed}, Channel, Reason) -> + log_channel_error(closed, Channel, Reason), + State; +handle_exception(State = #v1{connection_state = CS}, Channel, Reason) -> + log_channel_error(CS, Channel, Reason), + send_exception(State, Channel, Reason). + +send_exception(State, Channel, Reason) -> + {ShouldClose, CloseChannel, CloseMethod} = map_exception(Channel, Reason), + NewState = case ShouldClose of + true -> terminate_channels(), + close_connection(State); + false -> close_channel(Channel, State) + end, + ok = rabbit_writer:internal_send_command( + NewState#v1.sock, CloseChannel, CloseMethod), + NewState. + +map_exception(Channel, Reason) -> + {SuggestedClose, ReplyCode, ReplyText, FailedMethod} = + lookup_amqp_exception(Reason), + ShouldClose = SuggestedClose or (Channel == 0), + {ClassId, MethodId} = case FailedMethod of + {_, _} -> FailedMethod; + none -> {0, 0}; + _ -> rabbit_framing:method_id(FailedMethod) + end, + {CloseChannel, CloseMethod} = + case ShouldClose of + true -> {0, #'connection.close'{reply_code = ReplyCode, + reply_text = ReplyText, + class_id = ClassId, + method_id = MethodId}}; + false -> {Channel, #'channel.close'{reply_code = ReplyCode, + reply_text = ReplyText, + class_id = ClassId, + method_id = MethodId}} + end, + {ShouldClose, CloseChannel, CloseMethod}. + +lookup_amqp_exception({amqp, {ShouldClose, Code, Text}, Expl, Method}) -> + ExplBin = list_to_binary(Expl), + CompleteTextBin = <<Text/binary, " - ", ExplBin/binary>>, + SafeTextBin = if size(CompleteTextBin) > 255 -> + <<CompleteTextBin:252/binary, "...">>; + true -> + CompleteTextBin + end, + {ShouldClose, Code, SafeTextBin, Method}; +lookup_amqp_exception({amqp, ErrorName, Expl, Method}) -> + Details = rabbit_framing:lookup_amqp_exception(ErrorName), + lookup_amqp_exception({amqp, Details, Expl, Method}); +lookup_amqp_exception(Other) -> + rabbit_log:warning("Non-AMQP exit reason '~p'~n", [Other]), + {true, ?INTERNAL_ERROR, <<"INTERNAL_ERROR">>, none}. diff --git a/src/rabbit_realm.erl b/src/rabbit_realm.erl new file mode 100644 index 00000000..4463954d --- /dev/null +++ b/src/rabbit_realm.erl @@ -0,0 +1,316 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (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.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developers of the Original Code are LShift Ltd., +%% Cohesive Financial Technologies LLC., and Rabbit Technologies Ltd. +%% +%% Portions created by LShift Ltd., Cohesive Financial Technologies +%% LLC., and Rabbit Technologies Ltd. are Copyright (C) 2007-2008 +%% LShift Ltd., Cohesive Financial Technologies LLC., and Rabbit +%% Technologies Ltd.; +%% +%% All Rights Reserved. +%% +%% Contributor(s): ______________________________________. +%% + +-module(rabbit_realm). + +-export([recover/0]). +-export([add_realm/1, delete_realm/1, list_vhost_realms/1]). +-export([add/2, delete/2, check/2, delete_from_all/1]). +-export([access_request/3, enter_realm/3, leave_realms/1]). +-export([on_node_down/1]). + +-include("rabbit.hrl"). +-include_lib("stdlib/include/qlc.hrl"). + +%%---------------------------------------------------------------------------- + +-ifdef(use_specs). + +-type(e_or_q() :: 'exchange' | 'queue'). + +-spec(recover/0 :: () -> 'ok'). +-spec(add_realm/1 :: (realm_name()) -> 'ok'). +-spec(delete_realm/1 :: (realm_name()) -> 'ok'). +-spec(list_vhost_realms/1 :: (vhost()) -> [name()]). +-spec(add/2 :: (realm_name(), r(e_or_q())) -> 'ok'). +-spec(delete/2 :: (realm_name(), r(e_or_q())) -> 'ok'). +-spec(check/2 :: (realm_name(), r(e_or_q())) -> bool() | not_found()). +-spec(delete_from_all/1 :: (r(e_or_q())) -> 'ok'). +-spec(access_request/3 :: (username(), bool(), ticket()) -> + 'ok' | not_found() | {'error', 'bad_realm_path' | + 'access_refused' | + 'resource_locked'}). +-spec(enter_realm/3 :: (realm_name(), bool(), pid()) -> + 'ok' | {'error', 'resource_locked'}). +-spec(leave_realms/1 :: (pid()) -> 'ok'). +-spec(on_node_down/1 :: (node()) -> 'ok'). + +-endif. + +%%-------------------------------------------------------------------- + +recover() -> + %% preens resource lists, limiting them to currently-extant resources + rabbit_misc:execute_mnesia_transaction( + fun () -> + Realms = mnesia:foldl(fun preen_realm/2, [], realm), + lists:foreach(fun mnesia:write/1, Realms), + ok + end). + +add_realm(Name = #resource{virtual_host = VHostPath, kind = realm}) -> + rabbit_misc:execute_mnesia_transaction( + rabbit_misc:with_vhost( + VHostPath, + fun () -> + case mnesia:read({realm, Name}) of + [] -> + NewRealm = #realm{name = Name, + exchanges = ordsets:new(), + queues = ordsets:new()}, + ok = mnesia:write(NewRealm), + ok = mnesia:write( + #vhost_realm{virtual_host = VHostPath, + realm = Name}), + ok; + [_R] -> + mnesia:abort({realm_already_exists, Name}) + end + end)). + +delete_realm(Name = #resource{virtual_host = VHostPath, kind = realm}) -> + rabbit_misc:execute_mnesia_transaction( + rabbit_misc:with_vhost( + VHostPath, + rabbit_misc:with_realm( + Name, + fun () -> + ok = mnesia:delete({realm, Name}), + ok = mnesia:delete_object( + #vhost_realm{virtual_host = VHostPath, + realm = Name}), + lists:foreach(fun mnesia:delete_object/1, + mnesia:index_read(user_realm, Name, + #user_realm.realm)), + ok + end))). + +list_vhost_realms(VHostPath) -> + [Name || + #vhost_realm{realm = #resource{name = Name}} <- + %% TODO: use dirty ops instead + rabbit_misc:execute_mnesia_transaction( + rabbit_misc:with_vhost( + VHostPath, + fun () -> mnesia:read({vhost_realm, VHostPath}) end))]. + +add(Name = #resource{kind = realm}, Resource) -> + internal_update_realm_byname(Name, Resource, fun ordsets:add_element/2). + +delete(Name = #resource{kind = realm}, Resource) -> + internal_update_realm_byname(Name, Resource, fun ordsets:del_element/2). + +check(Name = #resource{kind = realm}, Resource = #resource{kind = Kind}) -> + case rabbit_misc:dirty_read({realm, Name}) of + {ok, R} -> + case Kind of + exchange -> ordsets:is_element(Resource, R#realm.exchanges); + queue -> ordsets:is_element(Resource, R#realm.queues) + end; + Other -> Other + end. + +% Requires a mnesia transaction. +delete_from_all(Resource = #resource{kind = Kind}) -> + Realms = mnesia:foldl + (fun (Realm = #realm{exchanges = E0, + queues = Q0}, + Acc) -> + IsMember = lists:member(Resource, + case Kind of + exchange -> E0; + queue -> Q0 + end), + if + IsMember -> + [internal_update_realm_record( + Realm, Resource, + fun ordsets:del_element/2) + | Acc]; + true -> + Acc + end + end, [], realm), + lists:foreach(fun mnesia:write/1, Realms), + ok. + +access_request(Username, Exclusive, Ticket = #ticket{realm_name = RealmName}) + when is_binary(Username) -> + %% FIXME: We should do this all in a single tx. Otherwise we may + %% a) get weird answers, b) create inconsistencies in the db + %% (e.g. realm_visitor records referring to non-existing realms). + case check_and_lookup(RealmName) of + {error, Reason} -> + {error, Reason}; + {ok, _Realm} -> + {ok, U} = rabbit_access_control:lookup_user(Username), + case rabbit_access_control:lookup_realm_access(U, RealmName) of + none -> + {error, access_refused}; + TicketPattern -> + case match_ticket(TicketPattern, Ticket) of + no_match -> + {error, access_refused}; + match -> + enter_realm(RealmName, Exclusive, self()) + end + end + end. + +enter_realm(Name = #resource{kind = realm}, IsExclusive, Pid) -> + RealmVisitor = #realm_visitor{realm = Name, pid = Pid}, + rabbit_misc:execute_mnesia_transaction( + fun () -> + case mnesia:read({exclusive_realm_visitor, Name}) of + [] when IsExclusive -> + ok = mnesia:delete_object(RealmVisitor), + %% TODO: find a more efficient way of checking + %% for "no machting results" that doesn't + %% involve retrieving all the records + case mnesia:read({realm_visitor, Name}) of + [] -> + mnesia:write( + exclusive_realm_visitor, RealmVisitor, write), + ok; + [_|_] -> + {error, resource_locked} + end; + [] -> + ok = mnesia:write(RealmVisitor), + ok; + [RealmVisitor] when IsExclusive -> ok; + [RealmVisitor] -> + ok = mnesia:delete({exclusive_realm_visitor, Name}), + ok = mnesia:write(RealmVisitor), + ok; + [_] -> + {error, resource_locked} + end + end). + +leave_realms(Pid) -> + rabbit_misc:execute_mnesia_transaction( + fun () -> + case mnesia:index_read(exclusive_realm_visitor, Pid, + #realm_visitor.pid) of + [] -> ok; + [R] -> + ok = mnesia:delete_object( + exclusive_realm_visitor, R, write) + end, + lists:foreach(fun mnesia:delete_object/1, + mnesia:index_read(realm_visitor, Pid, + #realm_visitor.pid)), + ok + end). + +on_node_down(Node) -> + rabbit_misc:execute_mnesia_transaction( + fun () -> + lists:foreach( + fun (T) -> ok = remove_visitors(Node, T) end, + [exclusive_realm_visitor, realm_visitor]), + ok + end). + +%%-------------------------------------------------------------------- + +preen_realm(Realm = #realm{name = #resource{kind = realm}, + exchanges = E0, + queues = Q0}, + Realms) -> + [Realm#realm{exchanges = filter_out_missing(E0, exchange), + queues = filter_out_missing(Q0, amqqueue)} + | Realms]. + +filter_out_missing(Items, TableName) -> + ordsets:filter(fun (Item) -> + case mnesia:read({TableName, Item}) of + [] -> false; + _ -> true + end + end, Items). + +internal_update_realm_byname(Name, Resource, SetUpdater) -> + rabbit_misc:execute_mnesia_transaction( + fun () -> + case mnesia:read({realm, Name}) of + [] -> + mnesia:abort(not_found); + [R] -> + ok = mnesia:write(internal_update_realm_record + (R, Resource, SetUpdater)) + end + end). + +internal_update_realm_record(R = #realm{exchanges = E0, queues = Q0}, + Resource = #resource{kind = Kind}, + SetUpdater) -> + case Kind of + exchange -> R#realm{exchanges = SetUpdater(Resource, E0)}; + queue -> R#realm{queues = SetUpdater(Resource, Q0)} + end. + +check_and_lookup(RealmName = #resource{kind = realm, + name = <<"/data", _/binary>>}) -> + lookup(RealmName); +check_and_lookup(RealmName = #resource{kind = realm, + name = <<"/admin", _/binary>>}) -> + lookup(RealmName); +check_and_lookup(_) -> + {error, bad_realm_path}. + +lookup(Name = #resource{kind = realm}) -> + rabbit_misc:dirty_read({realm, Name}). + +match_ticket(#ticket{passive_flag = PP, + active_flag = PA, + write_flag = PW, + read_flag = PR}, + #ticket{passive_flag = TP, + active_flag = TA, + write_flag = TW, + read_flag = TR}) -> + if + %% Matches if either we're not requesting passive access, or + %% passive access is permitted, and ... + (not(TP) orelse PP) andalso + (not(TA) orelse PA) andalso + (not(TW) orelse PW) andalso + (not(TR) orelse PR) -> + match; + true -> + no_match + end. + +remove_visitors(Node, T) -> + qlc:fold( + fun (R, Acc) -> + ok = mnesia:delete_object(T, R, write), + Acc + end, + ok, + qlc:q([R || R = #realm_visitor{pid = Pid} <- mnesia:table(T), + node(Pid) == Node])). diff --git a/src/rabbit_router.erl b/src/rabbit_router.erl new file mode 100644 index 00000000..41a8d64c --- /dev/null +++ b/src/rabbit_router.erl @@ -0,0 +1,166 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (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.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developers of the Original Code are LShift Ltd., +%% Cohesive Financial Technologies LLC., and Rabbit Technologies Ltd. +%% +%% Portions created by LShift Ltd., Cohesive Financial Technologies +%% LLC., and Rabbit Technologies Ltd. are Copyright (C) 2007-2008 +%% LShift Ltd., Cohesive Financial Technologies LLC., and Rabbit +%% Technologies Ltd.; +%% +%% All Rights Reserved. +%% +%% Contributor(s): ______________________________________. +%% + +-module(rabbit_router). +-include("rabbit.hrl"). + +-behaviour(gen_server). + +-export([start_link/0, + deliver/5]). + +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +-define(SERVER, ?MODULE). + +%%---------------------------------------------------------------------------- + +-ifdef(use_specs). + +-spec(start_link/0 :: () -> {'ok', pid()} | 'ignore' | {'error', any()}). +-spec(deliver/5 :: ([pid()], bool(), bool(), maybe(txn()), message()) -> + {'ok', [pid()]} | {'error', 'unroutable' | 'not_delivered'}). + +-endif. + +%%---------------------------------------------------------------------------- + +start_link() -> + gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). + +deliver(QPids, Mandatory, Immediate, Txn, Message) -> + %% we reduce inter-node traffic by grouping the qpids by node and + %% only delivering one copy of the message to each node involved, + %% which then in turn delivers it to its queues. + deliver_per_node( + dict:to_list( + lists:foldl( + fun (QPid, D) -> + dict:update(node(QPid), + fun (QPids1) -> [QPid | QPids1] end, + [QPid], D) + end, + dict:new(), QPids)), + Mandatory, Immediate, Txn, Message). + +deliver_per_node([{Node, QPids}], Mandatory, Immediate, + Txn, Message) + when Node == node() -> + %% optimisation + check_delivery(Mandatory, Immediate, + run_bindings(QPids, Mandatory, Immediate, Txn, Message)); +deliver_per_node(NodeQPids, Mandatory = false, Immediate = false, + Txn, Message) -> + %% optimisation: when Mandatory = false and Immediate = false, + %% rabbit_amqqueue:deliver in run_bindings below will deliver the + %% message to the queue process asynchronously, and return true, + %% which means all the QPids will always be returned. It is + %% therefore safe to use a fire-and-forget cast here and return + %% the QPids - the semantics is preserved. This scales much better + %% than the non-immediate case below. + {ok, lists:flatmap( + fun ({Node, QPids}) -> + gen_server:cast( + {?SERVER, Node}, + {deliver, QPids, Mandatory, Immediate, Txn, Message}), + QPids + end, + NodeQPids)}; +deliver_per_node(NodeQPids, Mandatory, Immediate, + Txn, Message) -> + R = rabbit_misc:upmap( + fun ({Node, QPids}) -> + try gen_server:call( + {?SERVER, Node}, + {deliver, QPids, Mandatory, Immediate, Txn, Message}) + catch + _Class:_Reason -> + %% TODO: figure out what to log (and do!) here + {false, []} + end + end, + NodeQPids), + {Routed, Handled} = + lists:foldl(fun ({Routed, Handled}, {RoutedAcc, HandledAcc}) -> + {Routed or RoutedAcc, + %% we do the concatenation below, which + %% should be faster + [Handled | HandledAcc]} + end, + {false, []}, + R), + check_delivery(Mandatory, Immediate, {Routed, lists:append(Handled)}). + +%%-------------------------------------------------------------------- + +init([]) -> + {ok, no_state}. + +handle_call({deliver, QPids, Mandatory, Immediate, Txn, Message}, + From, State) -> + spawn( + fun () -> + R = run_bindings(QPids, Mandatory, Immediate, Txn, Message), + gen_server:reply(From, R) + end), + {noreply, State}. + +handle_cast({deliver, QPids, Mandatory, Immediate, Txn, Message}, + State) -> + %% in order to preserve message ordering we must not spawn here + run_bindings(QPids, Mandatory, Immediate, Txn, Message), + {noreply, State}. + +handle_info(_Info, State) -> + {noreply, State}. + +terminate(_Reason, _State) -> + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%%-------------------------------------------------------------------- + +run_bindings(QPids, IsMandatory, IsImmediate, Txn, Message) -> + lists:foldl( + fun (QPid, {Routed, Handled}) -> + case catch rabbit_amqqueue:deliver(IsMandatory, IsImmediate, + Txn, Message, QPid) of + true -> {true, [QPid | Handled]}; + false -> {true, Handled}; + {'EXIT', Reason} -> rabbit_log:warning("delivery to ~p failed:~n~p~n", + [QPid, Reason]), + {Routed, Handled} + end + end, + {false, []}, + QPids). + +%% check_delivery(Mandatory, Immediate, {WasRouted, QPids}) +check_delivery(true, _ , {false, []}) -> {error, unroutable}; +check_delivery(_ , true, {_ , []}) -> {error, not_delivered}; +check_delivery(_ , _ , {_ , Qs}) -> {ok, Qs}. diff --git a/src/rabbit_sup.erl b/src/rabbit_sup.erl new file mode 100644 index 00000000..ea558690 --- /dev/null +++ b/src/rabbit_sup.erl @@ -0,0 +1,40 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (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.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developers of the Original Code are LShift Ltd., +%% Cohesive Financial Technologies LLC., and Rabbit Technologies Ltd. +%% +%% Portions created by LShift Ltd., Cohesive Financial Technologies +%% LLC., and Rabbit Technologies Ltd. are Copyright (C) 2007-2008 +%% LShift Ltd., Cohesive Financial Technologies LLC., and Rabbit +%% Technologies Ltd.; +%% +%% All Rights Reserved. +%% +%% Contributor(s): ______________________________________. +%% + +-module(rabbit_sup). + +-behaviour(supervisor). + +-export([start_link/0]). + +-export([init/1]). + +-define(SERVER, ?MODULE). + +start_link() -> + supervisor:start_link({local, ?SERVER}, ?MODULE, []). + +init([]) -> + {ok, {{one_for_one, 10, 10}, []}}. diff --git a/src/rabbit_tests.erl b/src/rabbit_tests.erl new file mode 100644 index 00000000..beeb3508 --- /dev/null +++ b/src/rabbit_tests.erl @@ -0,0 +1,393 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (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.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developers of the Original Code are LShift Ltd., +%% Cohesive Financial Technologies LLC., and Rabbit Technologies Ltd. +%% +%% Portions created by LShift Ltd., Cohesive Financial Technologies +%% LLC., and Rabbit Technologies Ltd. are Copyright (C) 2007-2008 +%% LShift Ltd., Cohesive Financial Technologies LLC., and Rabbit +%% Technologies Ltd.; +%% +%% All Rights Reserved. +%% +%% Contributor(s): ______________________________________. +%% + +-module(rabbit_tests). + +-export([all_tests/0, test_parsing/0]). + +-import(lists). + +test_content_prop_roundtrip(Datum, Binary) -> + Types = [element(1, E) || E <- Datum], + Values = [element(2, E) || E <- Datum], + Values = rabbit_binary_parser:parse_properties(Types, Binary), %% assertion + Binary = rabbit_binary_generator:encode_properties(Types, Values). %% assertion + +all_tests() -> + passed = test_parsing(), + passed = test_topic_matching(), + passed = test_app_management(), + passed = test_cluster_management(), + passed = test_user_management(), + passed. + +test_parsing() -> + passed = test_content_properties(), + passed. + +test_content_properties() -> + test_content_prop_roundtrip([], <<0, 0>>), + test_content_prop_roundtrip([{bit, true}, {bit, false}, {bit, true}, {bit, false}], + <<16#A0, 0>>), + test_content_prop_roundtrip([{bit, true}, {octet, 123}, {bit, true}, {octet, undefined}, + {bit, true}], + <<16#E8,0,123>>), + test_content_prop_roundtrip([{bit, true}, {octet, 123}, {octet, 123}, {bit, true}], + <<16#F0,0,123,123>>), + test_content_prop_roundtrip([{bit, true}, {shortstr, <<"hi">>}, {bit, true}, + {shortint, 54321}, {bit, true}], + <<16#F8,0,2,"hi",16#D4,16#31>>), + test_content_prop_roundtrip([{bit, true}, {shortstr, undefined}, {bit, true}, + {shortint, 54321}, {bit, true}], + <<16#B8,0,16#D4,16#31>>), + test_content_prop_roundtrip([{table, [{<<"a signedint">>, signedint, 12345678}, + {<<"a longstr">>, longstr, <<"yes please">>}, + {<<"a decimal">>, decimal, {123, 12345678}}, + {<<"a timestamp">>, timestamp, 123456789012345}, + {<<"a nested table">>, table, + [{<<"one">>, signedint, 1}, + {<<"two">>, signedint, 2}]}]}], + << + 16#8000:16, % flags + % properties: + + 117:32, % table length in bytes + + 11,"a signedint", % name + "I",12345678:32, % type and value + + 9,"a longstr", + "S",10:32,"yes please", + + 9,"a decimal", + "D",123,12345678:32, + + 11,"a timestamp", + "T", 123456789012345:64, + + 14,"a nested table", + "F", + 18:32, + + 3,"one", + "I",1:32, + + 3,"two", + "I",2:32 >>), + case catch rabbit_binary_parser:parse_properties([bit, bit, bit, bit], <<16#A0,0,1>>) of + {'EXIT', content_properties_binary_overflow} -> passed; + V -> exit({got_success_but_expected_failure, V}) + end. + +test_topic_match(P, R) -> + test_topic_match(P, R, true). + +test_topic_match(P, R, Expected) -> + case rabbit_exchange:topic_matches(list_to_binary(P), list_to_binary(R)) of + Expected -> + passed; + _ -> + {topic_match_failure, P, R} + end. + +test_topic_matching() -> + passed = test_topic_match("#", "test.test"), + passed = test_topic_match("#", ""), + passed = test_topic_match("#.T.R", "T.T.R"), + passed = test_topic_match("#.T.R", "T.R.T.R"), + passed = test_topic_match("#.Y.Z", "X.Y.Z.X.Y.Z"), + passed = test_topic_match("#.test", "test"), + passed = test_topic_match("#.test", "test.test"), + passed = test_topic_match("#.test", "ignored.test"), + passed = test_topic_match("#.test", "more.ignored.test"), + passed = test_topic_match("#.test", "notmatched", false), + passed = test_topic_match("#.z", "one.two.three.four", false), + passed. + +test_app_management() -> + %% starting, stopping, status + ok = control_action(stop_app, []), + ok = control_action(stop_app, []), + ok = control_action(status, []), + ok = control_action(start_app, []), + ok = control_action(start_app, []), + ok = control_action(status, []), + passed. + +test_cluster_management() -> + + %% 'cluster' and 'reset' should only work if the app is stopped + {error, _} = control_action(cluster, []), + {error, _} = control_action(reset, []), + {error, _} = control_action(force_reset, []), + + ok = control_action(stop_app, []), + + %% various ways of creating a standalone node + NodeS = atom_to_list(node()), + ClusteringSequence = [[], + [NodeS], + ["invalid@invalid", NodeS], + [NodeS, "invalid@invalid"]], + + ok = control_action(reset, []), + lists:foreach(fun (Arg) -> + ok = control_action(cluster, Arg), + ok + end, + ClusteringSequence), + lists:foreach(fun (Arg) -> + ok = control_action(reset, []), + ok = control_action(cluster, Arg), + ok + end, + ClusteringSequence), + ok = control_action(reset, []), + lists:foreach(fun (Arg) -> + ok = control_action(cluster, Arg), + ok = control_action(start_app, []), + ok = control_action(stop_app, []), + ok + end, + ClusteringSequence), + lists:foreach(fun (Arg) -> + ok = control_action(reset, []), + ok = control_action(cluster, Arg), + ok = control_action(start_app, []), + ok = control_action(stop_app, []), + ok + end, + ClusteringSequence), + + %% attempt to convert a disk node into a ram node + ok = control_action(reset, []), + ok = control_action(start_app, []), + ok = control_action(stop_app, []), + {error, {cannot_convert_disk_node_to_ram_node, _}} = + control_action(cluster, ["invalid1@invalid", + "invalid2@invalid"]), + + %% attempt to join a non-existing cluster as a ram node + ok = control_action(reset, []), + {error, {unable_to_contact_cluster_nodes, _}} = + control_action(cluster, ["invalid1@invalid", + "invalid2@invalid"]), + + SecondaryNode = rabbit_misc:localnode(hare), + case net_adm:ping(SecondaryNode) of + pong -> passed = test_cluster_management2(SecondaryNode); + pang -> io:format("Skipping clustering tests with node ~p~n", + [SecondaryNode]) + end, + + ok = control_action(start_app, []), + + passed. + +test_cluster_management2(SecondaryNode) -> + NodeS = atom_to_list(node()), + SecondaryNodeS = atom_to_list(SecondaryNode), + + %% attempt to convert a disk node into a ram node + ok = control_action(reset, []), + ok = control_action(cluster, [NodeS]), + {error, {unable_to_join_cluster, _, _}} = + control_action(cluster, [SecondaryNodeS]), + + %% join cluster as a ram node + ok = control_action(reset, []), + ok = control_action(cluster, [SecondaryNodeS, "invalid1@invalid"]), + ok = control_action(start_app, []), + ok = control_action(stop_app, []), + + %% change cluster config while remaining in same cluster + ok = control_action(cluster, ["invalid2@invalid", SecondaryNodeS]), + ok = control_action(start_app, []), + ok = control_action(stop_app, []), + + %% attempt to join non-existing cluster as a ram node + {error, _} = control_action(cluster, ["invalid1@invalid", + "invalid2@invalid"]), + + %% turn ram node into disk node + ok = control_action(cluster, [SecondaryNodeS, NodeS]), + ok = control_action(start_app, []), + ok = control_action(stop_app, []), + + %% attempt to convert a disk node into a ram node + {error, {cannot_convert_disk_node_to_ram_node, _}} = + control_action(cluster, ["invalid1@invalid", + "invalid2@invalid"]), + + %% turn a disk node into a ram node + ok = control_action(cluster, [SecondaryNodeS]), + ok = control_action(start_app, []), + ok = control_action(stop_app, []), + + %% NB: this will log an inconsistent_database error, which is harmless + true = disconnect_node(SecondaryNode), + pong = net_adm:ping(SecondaryNode), + + %% leaving a cluster as a ram node + ok = control_action(reset, []), + %% ...and as a disk node + ok = control_action(cluster, [SecondaryNodeS, NodeS]), + ok = control_action(start_app, []), + ok = control_action(stop_app, []), + ok = control_action(reset, []), + + %% attempt to leave cluster when no other node is alive + ok = control_action(cluster, [SecondaryNodeS, NodeS]), + ok = control_action(start_app, []), + ok = control_action(stop_app, SecondaryNode, []), + ok = control_action(stop_app, []), + {error, {no_running_cluster_nodes, _, _}} = + control_action(reset, []), + ok = control_action(force_reset, []), + + passed. + +test_user_management() -> + + %% lots if stuff that should fail + {error, {no_such_user, _}} = + control_action(delete_user, ["foo"]), + {error, {no_such_user, _}} = + control_action(change_password, ["foo", "baz"]), + {error, {no_such_vhost, _}} = + control_action(delete_vhost, ["/testhost"]), + {error, {no_such_user, _}} = + control_action(map_user_vhost, ["foo", "/"]), + {error, {no_such_user, _}} = + control_action(unmap_user_vhost, ["foo", "/"]), + {error, {no_such_user, _}} = + control_action(list_user_vhosts, ["foo"]), + {error, {no_such_user, _}} = + control_action(set_permissions, ["foo", "/", "/data"]), + {error, {no_such_user, _}} = + control_action(list_permissions, ["foo", "/"]), + {error, {no_such_vhost, _}} = + control_action(map_user_vhost, ["guest", "/testhost"]), + {error, {no_such_vhost, _}} = + control_action(unmap_user_vhost, ["guest", "/testhost"]), + {error, {no_such_vhost, _}} = + control_action(list_vhost_users, ["/testhost"]), + {error, {no_such_vhost, _}} = + control_action(set_permissions, ["guest", "/testhost", "/data"]), + {error, {no_such_vhost, _}} = + control_action(list_permissions, ["guest", "/testhost"]), + {error, {no_such_vhost, _}} = + control_action(add_realm, ["/testhost", "/data/test"]), + {error, {no_such_vhost, _}} = + control_action(delete_realm, ["/testhost", "/data/test"]), + {error, {no_such_vhost, _}} = + control_action(list_realms, ["/testhost"]), + {error, {no_such_realm, _}} = + control_action(set_permissions, ["guest", "/", "/data/test"]), + {error, {no_such_realm, _}} = + control_action(delete_realm, ["/", "/data/test"]), + + %% user creation + ok = control_action(add_user, ["foo", "bar"]), + {error, {user_already_exists, _}} = + control_action(add_user, ["foo", "bar"]), + ok = control_action(change_password, ["foo", "baz"]), + ok = control_action(list_users, []), + + %% vhost creation + ok = control_action(add_vhost, ["/testhost"]), + {error, {vhost_already_exists, _}} = + control_action(add_vhost, ["/testhost"]), + ok = control_action(list_vhosts, []), + + %% user/vhost mapping + ok = control_action(map_user_vhost, ["foo", "/testhost"]), + ok = control_action(map_user_vhost, ["foo", "/testhost"]), + ok = control_action(list_user_vhosts, ["foo"]), + + %% realm creation + ok = control_action(add_realm, ["/testhost", "/data/test"]), + {error, {realm_already_exists, _}} = + control_action(add_realm, ["/testhost", "/data/test"]), + ok = control_action(list_realms, ["/testhost"]), + + %% user permissions + ok = control_action(set_permissions, + ["foo", "/testhost", "/data/test", + "passive", "active", "write", "read"]), + ok = control_action(list_permissions, ["foo", "/testhost"]), + ok = control_action(set_permissions, + ["foo", "/testhost", "/data/test", "all"]), + ok = control_action(set_permissions, + ["foo", "/testhost", "/data/test"]), + {error, not_mapped_to_vhost} = + control_action(set_permissions, + ["guest", "/testhost", "/data/test"]), + {error, not_mapped_to_vhost} = + control_action(list_permissions, ["guest", "/testhost"]), + + %% realm deletion + ok = control_action(delete_realm, ["/testhost", "/data/test"]), + {error, {no_such_realm, _}} = + control_action(delete_realm, ["/testhost", "/data/test"]), + + %% user/vhost unmapping + ok = control_action(unmap_user_vhost, ["foo", "/testhost"]), + ok = control_action(unmap_user_vhost, ["foo", "/testhost"]), + + %% vhost deletion + ok = control_action(delete_vhost, ["/testhost"]), + {error, {no_such_vhost, _}} = + control_action(delete_vhost, ["/testhost"]), + + %% deleting a populated vhost + ok = control_action(add_vhost, ["/testhost"]), + ok = control_action(add_realm, ["/testhost", "/data/test"]), + ok = control_action(map_user_vhost, ["foo", "/testhost"]), + ok = control_action(set_permissions, + ["foo", "/testhost", "/data/test", "all"]), + _ = rabbit_amqqueue:declare( + rabbit_misc:r(<<"/testhost">>, realm, <<"/data/test">>), + <<"bar">>, true, false, []), + ok = control_action(delete_vhost, ["/testhost"]), + + %% user deletion + ok = control_action(delete_user, ["foo"]), + {error, {no_such_user, _}} = + control_action(delete_user, ["foo"]), + + passed. + +control_action(Command, Args) -> control_action(Command, node(), Args). + +control_action(Command, Node, Args) -> + case catch rabbit_control:action(Command, Node, Args) of + ok -> + io:format("done.~n"), + ok; + Other -> + io:format("failed.~n"), + Other + end. diff --git a/src/rabbit_ticket.erl b/src/rabbit_ticket.erl new file mode 100644 index 00000000..3a608faa --- /dev/null +++ b/src/rabbit_ticket.erl @@ -0,0 +1,131 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (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.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developers of the Original Code are LShift Ltd., +%% Cohesive Financial Technologies LLC., and Rabbit Technologies Ltd. +%% +%% Portions created by LShift Ltd., Cohesive Financial Technologies +%% LLC., and Rabbit Technologies Ltd. are Copyright (C) 2007-2008 +%% LShift Ltd., Cohesive Financial Technologies LLC., and Rabbit +%% Technologies Ltd.; +%% +%% All Rights Reserved. +%% +%% Contributor(s): ______________________________________. +%% + +-module(rabbit_ticket). +-include("rabbit.hrl"). + +-export([record_ticket/2, lookup_ticket/4, check_ticket/4]). + +-import(application). + +%%---------------------------------------------------------------------------- + +-ifdef(use_specs). + +-type(ticket_number() :: non_neg_integer()). +%% we'd like to write #ticket.passive_flag | #ticket.active_flag | ... +%% but dialyzer doesn't support that. +-type(ticket_field() :: 3..6). + +-spec(record_ticket/2 :: (ticket_number(), ticket()) -> 'ok'). +-spec(lookup_ticket/4 :: + (ticket_number(), ticket_field(), username(), vhost()) -> + ticket()). +-spec(check_ticket/4 :: + (ticket_number(), ticket_field(), r('exchange' | 'queue'), username()) -> + 'ok'). + +-endif. + +%%---------------------------------------------------------------------------- + +record_ticket(TicketNumber, Ticket) -> + put({ticket, TicketNumber}, Ticket), + ok. + +lookup_ticket(TicketNumber, FieldIndex, Username, VHostPath) -> + case get({ticket, TicketNumber}) of + undefined -> + %% Spec: "The server MUST isolate access tickets per + %% channel and treat an attempt by a client to mix these + %% as a connection exception." + rabbit_log:warning("Attempt by client to use invalid ticket ~p~n", [TicketNumber]), + maybe_relax_checks(TicketNumber, Username, VHostPath); + Ticket = #ticket{} -> + case element(FieldIndex, Ticket) of + false -> rabbit_misc:protocol_error( + access_refused, + "ticket ~w has insufficient permissions", + [TicketNumber]); + true -> Ticket + end + end. + +maybe_relax_checks(TicketNumber, Username, VHostPath) -> + case rabbit_misc:strict_ticket_checking() of + true -> + rabbit_misc:protocol_error( + access_refused, "invalid ticket ~w", [TicketNumber]); + false -> + rabbit_log:warning("Lax ticket check mode: fabricating full ticket ~p for user ~p, vhost ~p~n", + [TicketNumber, Username, VHostPath]), + Ticket = rabbit_access_control:full_ticket( + rabbit_misc:r(VHostPath, realm, <<"/data">>)), + case rabbit_realm:access_request(Username, false, Ticket) of + ok -> record_ticket(TicketNumber, Ticket), + Ticket; + {error, Reason} -> + rabbit_misc:protocol_error( + Reason, + "fabrication of ticket ~w for user '~s' in vhost '~s' failed", + [TicketNumber, Username, VHostPath]) + end + end. + +check_ticket(TicketNumber, FieldIndex, + Name = #resource{virtual_host = VHostPath}, Username) -> + #ticket{realm_name = RealmName} = + lookup_ticket(TicketNumber, FieldIndex, Username, VHostPath), + case resource_in_realm(RealmName, Name) of + false -> + case rabbit_misc:strict_ticket_checking() of + true -> + rabbit_misc:protocol_error( + access_refused, + "insufficient permissions in ticket ~w to access ~s in ~s", + [TicketNumber, rabbit_misc:rs(Name), + rabbit_misc:rs(RealmName)]); + false -> + rabbit_log:warning("Lax ticket check mode: ignoring cross-realm access for ticket ~p~n", [TicketNumber]), + ok + end; + true -> + ok + end. + +resource_in_realm(RealmName, ResourceName = #resource{kind = Kind}) -> + CacheKey = {resource_cache, RealmName, Kind}, + case get(CacheKey) of + Name when Name == ResourceName -> + true; + _ -> + case rabbit_realm:check(RealmName, ResourceName) of + true -> + put(CacheKey, ResourceName), + true; + _ -> + false + end + end. diff --git a/src/rabbit_tracer.erl b/src/rabbit_tracer.erl new file mode 100644 index 00000000..5365b873 --- /dev/null +++ b/src/rabbit_tracer.erl @@ -0,0 +1,44 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (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.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developers of the Original Code are LShift Ltd., +%% Cohesive Financial Technologies LLC., and Rabbit Technologies Ltd. +%% +%% Portions created by LShift Ltd., Cohesive Financial Technologies +%% LLC., and Rabbit Technologies Ltd. are Copyright (C) 2007-2008 +%% LShift Ltd., Cohesive Financial Technologies LLC., and Rabbit +%% Technologies Ltd.; +%% +%% All Rights Reserved. +%% +%% Contributor(s): ______________________________________. +%% + +-module(rabbit_tracer). +-export([start/0]). + +-import(erlang). + +start() -> + spawn(fun mainloop/0), + ok. + +mainloop() -> + erlang:trace(new, true, [all]), + mainloop1(). + +mainloop1() -> + receive + Msg -> + rabbit_log:info("TRACE: ~p~n", [Msg]) + end, + mainloop1(). diff --git a/src/rabbit_writer.erl b/src/rabbit_writer.erl new file mode 100644 index 00000000..eda871ec --- /dev/null +++ b/src/rabbit_writer.erl @@ -0,0 +1,162 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (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.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developers of the Original Code are LShift Ltd., +%% Cohesive Financial Technologies LLC., and Rabbit Technologies Ltd. +%% +%% Portions created by LShift Ltd., Cohesive Financial Technologies +%% LLC., and Rabbit Technologies Ltd. are Copyright (C) 2007-2008 +%% LShift Ltd., Cohesive Financial Technologies LLC., and Rabbit +%% Technologies Ltd.; +%% +%% All Rights Reserved. +%% +%% Contributor(s): ______________________________________. +%% + +-module(rabbit_writer). +-include("rabbit.hrl"). +-include("rabbit_framing.hrl"). + +-export([start/3, shutdown/1, mainloop/1]). +-export([send_command/2, send_command/3, + send_command_and_notify/5]). +-export([internal_send_command/3, internal_send_command/5]). + +-import(gen_tcp). + +-record(wstate, {sock, channel, frame_max}). + +%%---------------------------------------------------------------------------- + +-ifdef(use_specs). + +-spec(start/3 :: (socket(), channel_number(), non_neg_integer()) -> pid()). +-spec(send_command/2 :: (pid(), amqp_method()) -> 'ok'). +-spec(send_command/3 :: (pid(), amqp_method(), content()) -> 'ok'). +-spec(send_command_and_notify/5 :: + (pid(), pid(), pid(), amqp_method(), content()) -> 'ok'). +-spec(internal_send_command/3 :: + (socket(), channel_number(), amqp_method()) -> 'ok'). +-spec(internal_send_command/5 :: + (socket(), channel_number(), amqp_method(), + content(), non_neg_integer()) -> 'ok'). + +-endif. + +%%---------------------------------------------------------------------------- + +start(Sock, Channel, FrameMax) -> + spawn(?MODULE, mainloop, [#wstate{sock = Sock, + channel = Channel, + frame_max = FrameMax}]). + +mainloop(State) -> + receive + Message -> ?MODULE:mainloop(handle_message(Message, State)) + end. + +handle_message({send_command, MethodRecord}, + State = #wstate{sock = Sock, channel = Channel}) -> + ok = internal_send_command_async(Sock, Channel, MethodRecord), + State; +handle_message({send_command, MethodRecord, Content}, + State = #wstate{sock = Sock, + channel = Channel, + frame_max = FrameMax}) -> + ok = internal_send_command_async(Sock, Channel, MethodRecord, + Content, FrameMax), + State; +handle_message({send_command_and_notify, QPid, ChPid, MethodRecord, Content}, + State = #wstate{sock = Sock, + channel = Channel, + frame_max = FrameMax}) -> + ok = internal_send_command_async(Sock, Channel, MethodRecord, + Content, FrameMax), + rabbit_amqqueue:notify_sent(QPid, ChPid), + State; +handle_message({inet_reply, _, ok}, State) -> + State; +handle_message({inet_reply, _, Status}, _State) -> + exit({writer, send_failed, Status}); +handle_message(shutdown, _State) -> + exit(normal); +handle_message(Message, _State) -> + exit({writer, message_not_understood, Message}). + +%--------------------------------------------------------------------------- + +send_command(W, MethodRecord) -> + W ! {send_command, MethodRecord}, + ok. + +send_command(W, MethodRecord, Content) -> + W ! {send_command, MethodRecord, Content}, + ok. + +send_command_and_notify(W, Q, ChPid, MethodRecord, Content) -> + W ! {send_command_and_notify, Q, ChPid, MethodRecord, Content}, + ok. + +shutdown(W) -> + W ! shutdown, + ok. + +%--------------------------------------------------------------------------- + +assemble_frames(Channel, MethodRecord) -> + ?LOGMESSAGE(out, Channel, MethodRecord, none), + rabbit_binary_generator:build_simple_method_frame(Channel, MethodRecord). + +assemble_frames(Channel, MethodRecord, Content, FrameMax) -> + ?LOGMESSAGE(out, Channel, MethodRecord, Content), + MethodName = rabbit_misc:method_record_type(MethodRecord), + true = rabbit_framing:method_has_content(MethodName), % assertion + MethodFrame = rabbit_binary_generator:build_simple_method_frame( + Channel, MethodRecord), + ContentFrames = rabbit_binary_generator:build_simple_content_frames( + Channel, Content, FrameMax), + [MethodFrame | ContentFrames]. + +internal_send_command(Sock, Channel, MethodRecord) -> + ok = gen_tcp:send(Sock, assemble_frames(Channel, MethodRecord)). + +internal_send_command(Sock, Channel, MethodRecord, Content, FrameMax) -> + ok = gen_tcp:send(Sock, assemble_frames(Channel, MethodRecord, + Content, FrameMax)). + +%% gen_tcp:send/2 does a selective receive of {inet_reply, Sock, +%% Status} to obtain the result. That is bad when it is called from +%% the writer since it requires scanning of the writers possibly quite +%% large message queue. +%% +%% So instead we lift the code from prim_inet:send/2, which is what +%% gen_tcp:send/2 calls, do the first half here and then just process +%% the result code in handle_message/2 as and when it arrives. +%% +%% This means we may end up happily sending data down a closed/broken +%% socket, but that's ok since a) data in the buffers will be lost in +%% any case (so qualitatively we are no worse off than if we used +%% gen_tcp:send/2), and b) we do detect the changed socket status +%% eventually, i.e. when we get round to handling the result code. +%% +%% Also note that the port has bounded buffers and port_command blocks +%% when these are full. So the fact that we process the result +%% asynchronously does not impact flow control. +internal_send_command_async(Sock, Channel, MethodRecord) -> + true = erlang:port_command(Sock, assemble_frames(Channel, MethodRecord)), + ok. + +internal_send_command_async(Sock, Channel, MethodRecord, Content, FrameMax) -> + true = erlang:port_command(Sock, assemble_frames(Channel, MethodRecord, + Content, FrameMax)), + ok. diff --git a/src/tcp_acceptor.erl b/src/tcp_acceptor.erl new file mode 100644 index 00000000..3e4c5ee4 --- /dev/null +++ b/src/tcp_acceptor.erl @@ -0,0 +1,91 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (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.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developers of the Original Code are LShift Ltd., +%% Cohesive Financial Technologies LLC., and Rabbit Technologies Ltd. +%% +%% Portions created by LShift Ltd., Cohesive Financial Technologies +%% LLC., and Rabbit Technologies Ltd. are Copyright (C) 2007-2008 +%% LShift Ltd., Cohesive Financial Technologies LLC., and Rabbit +%% Technologies Ltd.; +%% +%% All Rights Reserved. +%% +%% Contributor(s): ______________________________________. +%% + +-module(tcp_acceptor). + +-behaviour(gen_server). + +-export([start_link/2]). + +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +-record(state, {callback, sock, ref}). + +%%-------------------------------------------------------------------- + +start_link(Callback, LSock) -> + gen_server:start_link(?MODULE, {Callback, LSock}, []). + +%%-------------------------------------------------------------------- + +init({Callback, LSock}) -> + case prim_inet:async_accept(LSock, -1) of + {ok, Ref} -> {ok, #state{callback=Callback, sock=LSock, ref=Ref}}; + Error -> {stop, {cannot_accept, Error}} + end. + +handle_call(_Request, _From, State) -> + {noreply, State}. + +handle_cast(_Msg, State) -> + {noreply, State}. + +handle_info({inet_async, LSock, Ref, {ok, Sock}}, + State = #state{callback={M,F,A}, sock=LSock, ref=Ref}) -> + + %% patch up the socket so it looks like one we got from + %% gen_tcp:accept/1 + {ok, Mod} = inet_db:lookup_socket(LSock), + inet_db:register_socket(Sock, Mod), + + %% report + {ok, {Address, Port}} = inet:sockname(LSock), + {ok, {PeerAddress, PeerPort}} = inet:peername(Sock), + error_logger:info_msg("accepted TCP connection on ~s:~p from ~s:~p~n", + [inet_parse:ntoa(Address), Port, + inet_parse:ntoa(PeerAddress), PeerPort]), + + %% handle + apply(M, F, A ++ [Sock]), + + %% accept more + case prim_inet:async_accept(LSock, -1) of + {ok, NRef} -> {noreply, State#state{ref=NRef}}; + Error -> {stop, {cannot_accept, Error}, none} + end; +handle_info({inet_async, LSock, Ref, {error, closed}}, + State=#state{sock=LSock, ref=Ref}) -> + %% It would be wrong to attempt to restart the acceptor when we + %% know this will fail. + {stop, normal, State}; +handle_info(_Info, State) -> + {noreply, State}. + +terminate(_Reason, _State) -> + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. diff --git a/src/tcp_acceptor_sup.erl b/src/tcp_acceptor_sup.erl new file mode 100644 index 00000000..a3144e41 --- /dev/null +++ b/src/tcp_acceptor_sup.erl @@ -0,0 +1,40 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (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.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developers of the Original Code are LShift Ltd., +%% Cohesive Financial Technologies LLC., and Rabbit Technologies Ltd. +%% +%% Portions created by LShift Ltd., Cohesive Financial Technologies +%% LLC., and Rabbit Technologies Ltd. are Copyright (C) 2007-2008 +%% LShift Ltd., Cohesive Financial Technologies LLC., and Rabbit +%% Technologies Ltd.; +%% +%% All Rights Reserved. +%% +%% Contributor(s): ______________________________________. +%% + +-module(tcp_acceptor_sup). + +-behaviour(supervisor). + +-export([start_link/2]). + +-export([init/1]). + +start_link(Name, Callback) -> + supervisor:start_link({local,Name}, ?MODULE, Callback). + +init(Callback) -> + {ok, {{simple_one_for_one, 10, 10}, + [{tcp_acceptor, {tcp_acceptor, start_link, [Callback]}, + transient, brutal_kill, worker, [tcp_acceptor]}]}}. diff --git a/src/tcp_client_sup.erl b/src/tcp_client_sup.erl new file mode 100644 index 00000000..4e1b9c6b --- /dev/null +++ b/src/tcp_client_sup.erl @@ -0,0 +1,43 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (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.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developers of the Original Code are LShift Ltd., +%% Cohesive Financial Technologies LLC., and Rabbit Technologies Ltd. +%% +%% Portions created by LShift Ltd., Cohesive Financial Technologies +%% LLC., and Rabbit Technologies Ltd. are Copyright (C) 2007-2008 +%% LShift Ltd., Cohesive Financial Technologies LLC., and Rabbit +%% Technologies Ltd.; +%% +%% All Rights Reserved. +%% +%% Contributor(s): ______________________________________. +%% + +-module(tcp_client_sup). + +-behaviour(supervisor). + +-export([start_link/1, start_link/2]). + +-export([init/1]). + +start_link(Callback) -> + supervisor:start_link(?MODULE, Callback). + +start_link(SupName, Callback) -> + supervisor:start_link(SupName, ?MODULE, Callback). + +init({M,F,A}) -> + {ok, {{simple_one_for_one, 10, 10}, + [{tcp_client, {M,F,A}, + temporary, brutal_kill, worker, [M]}]}}. diff --git a/src/tcp_listener.erl b/src/tcp_listener.erl new file mode 100644 index 00000000..3943161a --- /dev/null +++ b/src/tcp_listener.erl @@ -0,0 +1,91 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (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.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developers of the Original Code are LShift Ltd., +%% Cohesive Financial Technologies LLC., and Rabbit Technologies Ltd. +%% +%% Portions created by LShift Ltd., Cohesive Financial Technologies +%% LLC., and Rabbit Technologies Ltd. are Copyright (C) 2007-2008 +%% LShift Ltd., Cohesive Financial Technologies LLC., and Rabbit +%% Technologies Ltd.; +%% +%% All Rights Reserved. +%% +%% Contributor(s): ______________________________________. +%% + +-module(tcp_listener). + +-behaviour(gen_server). + +-export([start_link/7]). + +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +-record(state, {sock, on_startup, on_shutdown}). + +%%-------------------------------------------------------------------- + +start_link(IPAddress, Port, SocketOpts, + ConcurrentAcceptorCount, AcceptorSup, + OnStartup, OnShutdown) -> + gen_server:start_link( + ?MODULE, {IPAddress, Port, SocketOpts, + ConcurrentAcceptorCount, AcceptorSup, + OnStartup, OnShutdown}, []). + +%%-------------------------------------------------------------------- + +init({IPAddress, Port, SocketOpts, + ConcurrentAcceptorCount, AcceptorSup, + {M,F,A} = OnStartup, OnShutdown}) -> + process_flag(trap_exit, true), + case gen_tcp:listen(Port, SocketOpts ++ [{ip, IPAddress}, + {active, false}]) of + {ok, LSock} -> + lists:foreach(fun (_) -> + {ok, _APid} = supervisor:start_child( + AcceptorSup, [LSock]) + end, + lists:duplicate(ConcurrentAcceptorCount, dummy)), + error_logger:info_msg( + "started TCP listener on ~s:~p~n", + [inet_parse:ntoa(IPAddress), Port]), + apply(M, F, A ++ [IPAddress, Port]), + {ok, #state{sock=LSock, + on_startup = OnStartup, on_shutdown = OnShutdown}}; + {error, Reason} -> + error_logger:error_msg( + "failed to start TCP listener on ~s:~p - ~p~n", + [inet_parse:ntoa(IPAddress), Port, Reason]), + {stop, {cannot_listen, IPAddress, Port, Reason}} + end. + +handle_call(_Request, _From, State) -> + {noreply, State}. + +handle_cast(_Msg, State) -> + {noreply, State}. + +handle_info(_Info, State) -> + {noreply, State}. + +terminate(_Reason, #state{sock=LSock, on_shutdown = {M,F,A}}) -> + {ok, {IPAddress, Port}} = inet:sockname(LSock), + gen_tcp:close(LSock), + error_logger:info_msg("stopped TCP listener on ~s:~p~n", + [inet_parse:ntoa(IPAddress), Port]), + apply(M, F, A ++ [IPAddress, Port]). + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. diff --git a/src/tcp_listener_sup.erl b/src/tcp_listener_sup.erl new file mode 100644 index 00000000..93472406 --- /dev/null +++ b/src/tcp_listener_sup.erl @@ -0,0 +1,60 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (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.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ. +%% +%% The Initial Developers of the Original Code are LShift Ltd., +%% Cohesive Financial Technologies LLC., and Rabbit Technologies Ltd. +%% +%% Portions created by LShift Ltd., Cohesive Financial Technologies +%% LLC., and Rabbit Technologies Ltd. are Copyright (C) 2007-2008 +%% LShift Ltd., Cohesive Financial Technologies LLC., and Rabbit +%% Technologies Ltd.; +%% +%% All Rights Reserved. +%% +%% Contributor(s): ______________________________________. +%% + +-module(tcp_listener_sup). + +-behaviour(supervisor). + +-export([start_link/6, start_link/7]). + +-export([init/1]). + +start_link(IPAddress, Port, SocketOpts, OnStartup, OnShutdown, + AcceptCallback) -> + start_link(IPAddress, Port, SocketOpts, OnStartup, OnShutdown, + AcceptCallback, 1). + +start_link(IPAddress, Port, SocketOpts, OnStartup, OnShutdown, + AcceptCallback, ConcurrentAcceptorCount) -> + supervisor:start_link( + ?MODULE, {IPAddress, Port, SocketOpts, OnStartup, OnShutdown, + AcceptCallback, ConcurrentAcceptorCount}). + +init({IPAddress, Port, SocketOpts, OnStartup, OnShutdown, + AcceptCallback, ConcurrentAcceptorCount}) -> + %% This is gross. The tcp_listener needs to know about the + %% tcp_acceptor_sup, and the only way I can think of accomplishing + %% that without jumping through hoops is to register the + %% tcp_acceptor_sup. + Name = rabbit_misc:tcp_name(tcp_acceptor_sup, IPAddress, Port), + {ok, {{one_for_all, 10, 10}, + [{tcp_acceptor_sup, {tcp_acceptor_sup, start_link, + [Name, AcceptCallback]}, + transient, infinity, supervisor, [tcp_acceptor_sup]}, + {tcp_listener, {tcp_listener, start_link, + [IPAddress, Port, SocketOpts, + ConcurrentAcceptorCount, Name, + OnStartup, OnShutdown]}, + transient, 100, worker, [tcp_listener]}]}}. |