summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--.travis.yml2
-rw-r--r--LICENSE-MPL-RabbitMQ2
-rw-r--r--Makefile20
-rw-r--r--README.md5
-rw-r--r--docs/advanced.config.example109
-rw-r--r--docs/rabbitmq.conf.example736
-rw-r--r--docs/rabbitmq.config.example84
-rw-r--r--docs/rabbitmqctl.1.xml84
-rw-r--r--include/rabbit_cli.hrl1
-rw-r--r--include/rabbit_log.hrl1
-rwxr-xr-x[-rw-r--r--]packaging/RPMS/Fedora/rabbitmq-server.init3
-rw-r--r--packaging/RPMS/Fedora/rabbitmq-server.logrotate5
-rw-r--r--packaging/RPMS/Fedora/rabbitmq-server.spec3
-rw-r--r--packaging/debs/Debian/debian/postinst4
-rwxr-xr-x[-rw-r--r--]packaging/debs/Debian/debian/rabbitmq-server.init3
-rw-r--r--packaging/debs/Debian/debian/rabbitmq-server.logrotate5
-rw-r--r--packaging/windows-exe/rabbitmq_nsi.in14
-rw-r--r--priv/schema/.gitignore4
-rw-r--r--priv/schema/rabbitmq.schema980
-rw-r--r--rabbitmq.conf.d/ldap.conf138
-rw-r--r--rabbitmq.conf.d/rabbitmq.conf726
-rwxr-xr-xscripts/cuttlefishbin0 -> 430306 bytes
-rwxr-xr-xscripts/rabbitmq-defaults3
-rw-r--r--scripts/rabbitmq-defaults.bat3
-rwxr-xr-xscripts/rabbitmq-env15
-rw-r--r--scripts/rabbitmq-env.bat53
-rwxr-xr-xscripts/rabbitmq-server68
-rw-r--r--scripts/rabbitmq-server.bat78
-rw-r--r--scripts/rabbitmq-service.bat76
-rw-r--r--src/file_handle_cache.erl16
-rw-r--r--src/file_handle_cache_stats.erl6
-rw-r--r--src/gm.erl2
-rw-r--r--src/lqueue.erl4
-rw-r--r--src/pg2_fixed.erl4
-rw-r--r--src/rabbit.app.src7
-rw-r--r--src/rabbit.erl289
-rw-r--r--src/rabbit_amqqueue_process.erl133
-rw-r--r--src/rabbit_binding.erl2
-rw-r--r--src/rabbit_cli.erl2
-rw-r--r--src/rabbit_config.erl179
-rw-r--r--src/rabbit_connection_tracking.erl337
-rw-r--r--src/rabbit_connection_tracking_handler.erl108
-rw-r--r--src/rabbit_control_main.erl195
-rw-r--r--src/rabbit_dead_letter.erl2
-rw-r--r--src/rabbit_error_logger.erl20
-rw-r--r--src/rabbit_error_logger_file_h.erl180
-rw-r--r--src/rabbit_exchange.erl12
-rw-r--r--src/rabbit_exchange_type_direct.erl4
-rw-r--r--src/rabbit_exchange_type_fanout.erl4
-rw-r--r--src/rabbit_exchange_type_headers.erl4
-rw-r--r--src/rabbit_exchange_type_invalid.erl4
-rw-r--r--src/rabbit_exchange_type_topic.erl4
-rw-r--r--src/rabbit_file.erl1
-rw-r--r--src/rabbit_hipe.erl6
-rw-r--r--src/rabbit_lager.erl267
-rw-r--r--src/rabbit_limiter.erl2
-rw-r--r--src/rabbit_log.erl165
-rw-r--r--src/rabbit_mirror_queue_master.erl40
-rw-r--r--src/rabbit_mirror_queue_misc.erl17
-rw-r--r--src/rabbit_mirror_queue_mode.erl7
-rw-r--r--src/rabbit_mirror_queue_mode_exactly.erl2
-rw-r--r--src/rabbit_mirror_queue_slave.erl26
-rw-r--r--src/rabbit_mirror_queue_sync.erl8
-rw-r--r--src/rabbit_mnesia.erl13
-rw-r--r--src/rabbit_mnesia_rename.erl8
-rw-r--r--src/rabbit_msg_store.erl4
-rw-r--r--src/rabbit_parameter_validation.erl8
-rw-r--r--src/rabbit_password.erl2
-rw-r--r--src/rabbit_plugins.erl217
-rw-r--r--src/rabbit_plugins_main.erl22
-rw-r--r--src/rabbit_policy.erl4
-rw-r--r--src/rabbit_prelaunch.erl44
-rw-r--r--src/rabbit_queue_consumers.erl10
-rw-r--r--src/rabbit_queue_index.erl12
-rw-r--r--src/rabbit_queue_location_random.erl2
-rw-r--r--src/rabbit_registry.erl162
-rw-r--r--src/rabbit_sasl_report_file_h.erl102
-rw-r--r--src/rabbit_upgrade_functions.erl62
-rw-r--r--src/rabbit_variable_queue.erl14
-rw-r--r--src/rabbit_vhost.erl31
-rw-r--r--src/rabbit_vhost_limit.erl99
-rw-r--r--test/channel_interceptor_SUITE.erl113
-rw-r--r--test/channel_operation_timeout_test_queue.erl14
-rw-r--r--test/cluster_rename_SUITE.erl12
-rw-r--r--test/config_schema_SUITE.erl143
-rw-r--r--test/config_schema_SUITE_data/certs/cacert.pem1
-rw-r--r--test/config_schema_SUITE_data/certs/cert.pem1
-rw-r--r--test/config_schema_SUITE_data/certs/key.pem1
-rw-r--r--test/config_schema_SUITE_data/rabbit-mgmt/access.log0
-rw-r--r--test/config_schema_SUITE_data/schema/rabbitmq.schema980
-rw-r--r--test/config_schema_SUITE_data/schema/rabbitmq_amqp1_0.schema31
-rw-r--r--test/config_schema_SUITE_data/schema/rabbitmq_auth_backend_amqp.schema27
-rw-r--r--test/config_schema_SUITE_data/schema/rabbitmq_auth_backend_http.schema15
-rw-r--r--test/config_schema_SUITE_data/schema/rabbitmq_auth_backend_ldap.schema183
-rw-r--r--test/config_schema_SUITE_data/schema/rabbitmq_clusterer.schema58
-rw-r--r--test/config_schema_SUITE_data/schema/rabbitmq_management.schema203
-rw-r--r--test/config_schema_SUITE_data/schema/rabbitmq_metronome.schema9
-rw-r--r--test/config_schema_SUITE_data/schema/rabbitmq_mqtt.schema248
-rw-r--r--test/config_schema_SUITE_data/schema/rabbitmq_stomp.schema110
-rw-r--r--test/config_schema_SUITE_data/schema/rabbitmq_web_mqtt.schema44
-rw-r--r--test/config_schema_SUITE_data/schema/rabbitmq_web_stomp.schema64
-rw-r--r--test/config_schema_SUITE_data/snippets.config732
-rw-r--r--test/dummy_interceptor.erl26
-rw-r--r--test/dynamic_ha_SUITE.erl154
-rw-r--r--test/per_vhost_connection_limit_SUITE.erl795
-rw-r--r--test/per_vhost_connection_limit_partitions_SUITE.erl170
-rw-r--r--test/plugin_versioning_SUITE.erl177
-rw-r--r--test/priority_queue_SUITE.erl35
-rw-r--r--test/unit_SUITE.erl3
-rw-r--r--test/unit_inbroker_SUITE.erl274
111 files changed, 9528 insertions, 1198 deletions
diff --git a/.gitignore b/.gitignore
index 7582deb64e..1a0e609c88 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,6 +7,7 @@
/cover/
/debug/
/deps/
+/debug/
/doc/
/ebin/
/etc/
@@ -48,3 +49,5 @@ rabbitmq-server-*.zip
# Tracing tools
*-ttb
*.ti
+
+PACKAGES/*
diff --git a/.travis.yml b/.travis.yml
index 22e67aea5f..e6bba5c81c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -10,8 +10,6 @@ addons:
packages:
- xsltproc
otp_release:
- - "R16B03-1"
- - "17.5"
- "18.3"
- "19.0"
env:
diff --git a/LICENSE-MPL-RabbitMQ b/LICENSE-MPL-RabbitMQ
index 82c7cf5419..f1ba9a5ca1 100644
--- a/LICENSE-MPL-RabbitMQ
+++ b/LICENSE-MPL-RabbitMQ
@@ -447,7 +447,7 @@ EXHIBIT A -Mozilla Public License.
The Original Code is RabbitMQ.
The Initial Developer of the Original Code is Pivotal Software, Inc.
- Copyright (c) 2007-2015 Pivotal Software, Inc. All rights reserved.''
+ Copyright (c) 2007-2016 Pivotal Software, Inc. All rights reserved.''
[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
diff --git a/Makefile b/Makefile
index e211234bf6..f30faaab74 100644
--- a/Makefile
+++ b/Makefile
@@ -4,7 +4,7 @@ VERSION ?= $(call get_app_version,src/$(PROJECT).app.src)
# Release artifacts are put in $(PACKAGES_DIR).
PACKAGES_DIR ?= $(abspath PACKAGES)
-DEPS = ranch $(PLUGINS)
+DEPS = ranch lager $(PLUGINS)
define usage_xml_to_erl
$(subst __,_,$(patsubst $(DOCS_DIR)/rabbitmq%.1.xml, src/rabbit_%_usage.erl, $(subst -,_,$(1))))
@@ -69,7 +69,7 @@ endif
endif
# FIXME: Remove rabbitmq_test as TEST_DEPS from here for now.
-TEST_DEPS := amqp_client meck $(filter-out rabbitmq_test,$(TEST_DEPS))
+TEST_DEPS := amqp_client meck proper $(filter-out rabbitmq_test,$(TEST_DEPS))
include erlang.mk
@@ -258,7 +258,10 @@ $(SOURCE_DIST): $(ERLANG_MK_RECURSIVE_DEPS_LIST)
$(verbose) cat packaging/common/LICENSE.tail >> $@/LICENSE
$(verbose) find $@/deps/licensing -name 'LICENSE-*' -exec cp '{}' $@ \;
$(verbose) for file in $$(find $@ -name '*.app.src'); do \
- sed -E -i.bak -e 's/[{]vsn[[:blank:]]*,[[:blank:]]*""[[:blank:]]*}/{vsn, "$(VERSION)"}/' $$file; \
+ sed -E -i.bak \
+ -e 's/[{]vsn[[:blank:]]*,[[:blank:]]*""[[:blank:]]*}/{vsn, "$(VERSION)"}/' \
+ -e 's/[{]broker_version_requirements[[:blank:]]*,[[:blank:]]*\[\][[:blank:]]*}/{broker_version_requirements, ["$(VERSION)"]}/' \
+ $$file; \
rm $$file.bak; \
done
$(verbose) echo "$(PROJECT) $$(git rev-parse HEAD) $$(git describe --tags --exact-match 2>/dev/null || git symbolic-ref -q --short HEAD)" > $@/git-revisions.txt
@@ -320,7 +323,8 @@ SCRIPTS = rabbitmq-defaults \
rabbitmq-env \
rabbitmq-server \
rabbitmqctl \
- rabbitmq-plugins
+ rabbitmq-plugins \
+ cuttlefish
WINDOWS_SCRIPTS = rabbitmq-defaults.bat \
rabbitmq-echopid.bat \
@@ -328,7 +332,8 @@ WINDOWS_SCRIPTS = rabbitmq-defaults.bat \
rabbitmq-plugins.bat \
rabbitmq-server.bat \
rabbitmq-service.bat \
- rabbitmqctl.bat
+ rabbitmqctl.bat \
+ cuttlefish
UNIX_TO_DOS ?= todos
@@ -339,7 +344,7 @@ install: install-erlapp install-scripts
install-erlapp: dist
$(verbose) mkdir -p $(DESTDIR)$(RMQ_ERLAPP_DIR)
- $(inst_verbose) cp -r include ebin plugins LICENSE* INSTALL \
+ $(inst_verbose) cp -r include ebin plugins priv LICENSE* INSTALL \
$(DESTDIR)$(RMQ_ERLAPP_DIR)
$(verbose) echo "Put your EZs here and use rabbitmq-plugins to enable them." \
> $(DESTDIR)$(RMQ_ERLAPP_DIR)/plugins/README
@@ -380,7 +385,7 @@ install-windows: install-windows-erlapp install-windows-scripts install-windows-
install-windows-erlapp: dist
$(verbose) mkdir -p $(DESTDIR)$(WINDOWS_PREFIX)
- $(inst_verbose) cp -r include ebin plugins LICENSE* INSTALL \
+ $(inst_verbose) cp -r include ebin plugins priv LICENSE* INSTALL \
$(DESTDIR)$(WINDOWS_PREFIX)
$(verbose) echo "Put your EZs here and use rabbitmq-plugins.bat to enable them." \
> $(DESTDIR)$(WINDOWS_PREFIX)/plugins/README.txt
@@ -433,3 +438,4 @@ package-rpm-suse package-windows package-standalone-macosx \
package-generic-unix: $(PACKAGES_SOURCE_DIST_FILE)
$(verbose) $(MAKE) -C packaging $@ \
SOURCE_DIST_FILE=$(abspath $(PACKAGES_SOURCE_DIST_FILE))
+
diff --git a/README.md b/README.md
index ae6f9b2d01..9e2a046dff 100644
--- a/README.md
+++ b/README.md
@@ -19,6 +19,11 @@
* [Documentation guides](http://www.rabbitmq.com/documentation.html)
* [Client libraries and tools](http://www.rabbitmq.com/devtools.html)
+## Building From Source and Packaging
+
+ * [Building RabbitMQ Server From Source](http://www.rabbitmq.com/build-server.html)
+ * [Building RabbitMQ Server Packages](http://www.rabbitmq.com/build-server.html)
+
## Getting Help
diff --git a/docs/advanced.config.example b/docs/advanced.config.example
new file mode 100644
index 0000000000..82a1c000e1
--- /dev/null
+++ b/docs/advanced.config.example
@@ -0,0 +1,109 @@
+[
+
+
+ %% ----------------------------------------------------------------------------
+ %% Advanced Erlang Networking/Clustering Options.
+ %%
+ %% See http://www.rabbitmq.com/clustering.html for details
+ %% ----------------------------------------------------------------------------
+ %% Sets the net_kernel tick time.
+ %% Please see http://erlang.org/doc/man/kernel_app.html and
+ %% http://www.rabbitmq.com/nettick.html for further details.
+ %%
+ %% {kernel, [{net_ticktime, 60}]},
+ %% ----------------------------------------------------------------------------
+ %% RabbitMQ Shovel Plugin
+ %%
+ %% See http://www.rabbitmq.com/shovel.html for details
+ %% ----------------------------------------------------------------------------
+
+ {rabbitmq_shovel,
+ [{shovels,
+ [%% A named shovel worker.
+ %% {my_first_shovel,
+ %% [
+
+ %% List the source broker(s) from which to consume.
+ %%
+ %% {sources,
+ %% [%% URI(s) and pre-declarations for all source broker(s).
+ %% {brokers, ["amqp://user:password@host.domain/my_vhost"]},
+ %% {declarations, []}
+ %% ]},
+
+ %% List the destination broker(s) to publish to.
+ %% {destinations,
+ %% [%% A singular version of the 'brokers' element.
+ %% {broker, "amqp://"},
+ %% {declarations, []}
+ %% ]},
+
+ %% Name of the queue to shovel messages from.
+ %%
+ %% {queue, <<"your-queue-name-goes-here">>},
+
+ %% Optional prefetch count.
+ %%
+ %% {prefetch_count, 10},
+
+ %% when to acknowledge messages:
+ %% - no_ack: never (auto)
+ %% - on_publish: after each message is republished
+ %% - on_confirm: when the destination broker confirms receipt
+ %%
+ %% {ack_mode, on_confirm},
+
+ %% Overwrite fields of the outbound basic.publish.
+ %%
+ %% {publish_fields, [{exchange, <<"my_exchange">>},
+ %% {routing_key, <<"from_shovel">>}]},
+
+ %% Static list of basic.properties to set on re-publication.
+ %%
+ %% {publish_properties, [{delivery_mode, 2}]},
+
+ %% The number of seconds to wait before attempting to
+ %% reconnect in the event of a connection failure.
+ %%
+ %% {reconnect_delay, 2.5}
+
+ %% ]} %% End of my_first_shovel
+ ]}
+ %% Rather than specifying some values per-shovel, you can specify
+ %% them for all shovels here.
+ %%
+ %% {defaults, [{prefetch_count, 0},
+ %% {ack_mode, on_confirm},
+ %% {publish_fields, []},
+ %% {publish_properties, [{delivery_mode, 2}]},
+ %% {reconnect_delay, 2.5}]}
+ ]},
+
+ {rabbitmq_auth_backend_ldap, [
+ %%
+ %% Authorisation
+ %% =============
+ %%
+
+ %% The LDAP plugin can perform a variety of queries against your
+ %% LDAP server to determine questions of authorisation. See
+ %% http://www.rabbitmq.com/ldap.html#authorisation for more
+ %% information.
+
+ %% Set the query to use when determining vhost access
+ %%
+ %% {vhost_access_query, {in_group,
+ %% "ou=${vhost}-users,ou=vhosts,dc=example,dc=com"}},
+
+ %% Set the query to use when determining resource (e.g., queue) access
+ %%
+ %% {resource_access_query, {constant, true}},
+
+ %% Set queries to determine which tags a user has
+ %%
+ %% {tag_queries, []}
+ ]}
+].
+
+
+
diff --git a/docs/rabbitmq.conf.example b/docs/rabbitmq.conf.example
new file mode 100644
index 0000000000..7e60c53da4
--- /dev/null
+++ b/docs/rabbitmq.conf.example
@@ -0,0 +1,736 @@
+# ======================================
+# RabbbitMQ broker section
+# ======================================
+
+## Network Connectivity
+## ====================
+##
+## By default, RabbitMQ will listen on all interfaces, using
+## the standard (reserved) AMQP port.
+##
+# listeners.tcp.default = 5672
+
+
+## To listen on a specific interface, provide an IP address with port.
+## For example, to listen only on localhost for both IPv4 and IPv6:
+##
+# IPv4
+# listeners.tcp.local = 127.0.0.1:5672
+# IPv6
+# listeners.tcp.local_v6 = ::1:5672
+
+## You can define multiple listeners using listener names
+# listeners.tcp.other_port = 5673
+# listeners.tcp.other_ip = 10.10.10.10:5672
+
+
+## SSL listeners are configured in the same fashion as TCP listeners,
+## including the option to control the choice of interface.
+##
+# listeners.ssl.default = 5671
+
+## Number of Erlang processes that will accept connections for the TCP
+## and SSL listeners.
+##
+# num_acceptors.tcp = 10
+# num_acceptors.ssl = 1
+
+
+## Maximum time for AMQP 0-8/0-9/0-9-1 handshake (after socket connection
+## and SSL handshake), in milliseconds.
+##
+# handshake_timeout = 10000
+
+## Set to 'true' to perform reverse DNS lookups when accepting a
+## connection. Hostnames will then be shown instead of IP addresses
+## in rabbitmqctl and the management plugin.
+##
+# reverse_dns_lookups = true
+
+##
+## Security / AAA
+## ==============
+##
+
+## The default "guest" user is only permitted to access the server
+## via a loopback interface (e.g. localhost).
+## {loopback_users, [<<"guest">>]},
+##
+# loopback_users.guest = true
+
+## Uncomment the following line if you want to allow access to the
+## guest user from anywhere on the network.
+# loopback_users.guest = false
+
+## Configuring SSL.
+## See http://www.rabbitmq.com/ssl.html for full documentation.
+##
+# ssl_options.verify = verify_peer
+# ssl_options.fail_if_no_peer_cert = false
+# ssl_options.cacertfile = /path/to/cacert.pem
+# ssl_options.certfile = /path/to/cert.pem
+# ssl_options.keyfile = /path/to/key.pem
+
+## Select an authentication/authorisation backend to use.
+##
+## Alternative backends are provided by plugins, such as rabbitmq-auth-backend-ldap.
+##
+## NB: These settings require certain plugins to be enabled.
+## See http://www.rabbitmq.com/plugins.html and http://rabbitmq.com/access-control.html
+## for details.
+
+# auth_backends.1 = rabbit_auth_backend_internal
+
+## uses separate backends for authentication and authorisation,
+## see below.
+# auth_backends.1.authn = rabbit_auth_backend_ldap
+# auth_backends.1.authz = rabbit_auth_backend_internal
+
+## The rabbitmq_auth_backend_ldap plugin allows the broker to
+## perform authentication and authorisation by deferring to an
+## external LDAP server.
+##
+## For more information about configuring the LDAP backend, see
+## http://www.rabbitmq.com/ldap.html and http://rabbitmq.com/access-control.html.
+##
+## uses LDAP for both authentication and authorisation
+# auth_backends.1 = rabbit_auth_backend_ldap
+
+## uses HTTP service for both authentication and
+## authorisation
+# auth_backends.1 = rabbit_auth_backend_http
+
+## uses two backends in a chain: HTTP first, then internal
+# auth_backends.1 = rabbit_auth_backend_http
+# auth_backends.2 = rabbit_auth_backend_internal
+
+## Authentication
+## The built-in mechanisms are 'PLAIN',
+## 'AMQPLAIN', and 'EXTERNAL' Additional mechanisms can be added via
+## plugins.
+##
+## See http://www.rabbitmq.com/authentication.html for more details.
+##
+# auth_mechanisms.1 = PLAIN
+# auth_mechanisms.2 = AMQPLAIN
+
+## The rabbitmq-auth-mechanism-ssl plugin makes it possible to
+## authenticate a user based on the client's x509 (TLS) certificate.
+## See http://www.rabbitmq.com/authentication.html for more info.
+##
+## To use auth-mechanism-ssl, the EXTERNAL mechanism should
+## be enabled:
+##
+# auth_mechanisms.1 = PLAIN
+# auth_mechanisms.2 = AMQPLAIN
+# auth_mechanisms.3 = EXTERNAL
+
+## To force x509 certificate-based authentication on all clients,
+## exclude all other mechanisms (note: this will disable password-based
+## authentication even for the management UI!):
+##
+# auth_mechanisms.1 = EXTERNAL
+
+## This pertains to both the rabbitmq-auth-mechanism-ssl plugin and
+## STOMP ssl_cert_login configurations. See the rabbitmq_stomp
+## configuration section later in this file and the README in
+## https://github.com/rabbitmq/rabbitmq-auth-mechanism-ssl for further
+## details.
+##
+## To use the SSL cert's CN instead of its DN as the username
+##
+# ssl_cert_login_from = common_name
+
+## SSL handshake timeout, in milliseconds.
+##
+# ssl_handshake_timeout = 5000
+
+
+## Password hashing implementation. Will only affect newly
+## created users. To recalculate hash for an existing user
+## it's necessary to update her password.
+##
+## To use SHA-512, set to rabbit_password_hashing_sha512.
+##
+# password_hashing_module = rabbit_password_hashing_sha256
+
+## When importing definitions exported from versions earlier
+## than 3.6.0, it is possible to go back to MD5 (only do this
+## as a temporary measure!) by setting this to rabbit_password_hashing_md5.
+##
+# password_hashing_module = rabbit_password_hashing_md5
+
+##
+## Default User / VHost
+## ====================
+##
+
+## On first start RabbitMQ will create a vhost and a user. These
+## config items control what gets created. See
+## http://www.rabbitmq.com/access-control.html for further
+## information about vhosts and access control.
+##
+# default_vhost = /
+# default_user = guest
+# default_pass = guest
+
+# default_permissions.configure = .*
+# default_permissions.read = .*
+# default_permissions.write = .*
+
+## Tags for default user
+##
+## For more details about tags, see the documentation for the
+## Management Plugin at http://www.rabbitmq.com/management.html.
+##
+# default_user_tags.administrator = true
+
+## Define other tags like this:
+# default_user_tags.management = true
+# default_user_tags.custom_tag = true
+
+##
+## Additional network and protocol related configuration
+## =====================================================
+##
+
+## Set the default AMQP 0-9-1 heartbeat interval (in seconds).
+## See http://rabbitmq.com/heartbeats.html for more details.
+##
+# heartbeat = 600
+
+## Set the max permissible size of an AMQP frame (in bytes).
+##
+# frame_max = 131072
+
+## Set the max frame size the server will accept before connection
+## tuning occurs
+##
+# initial_frame_max = 4096
+
+## Set the max permissible number of channels per connection.
+## 0 means "no limit".
+##
+# channel_max = 128
+
+## Customising Socket Options.
+##
+## See (http://www.erlang.org/doc/man/inet.html#setopts-2) for
+## further documentation.
+##
+
+# tcp_listen_options.backlog = 128
+# tcp_listen_options.nodelay = true
+# tcp_listen_options.exit_on_close = false
+
+##
+## Resource Limits & Flow Control
+## ==============================
+##
+## See http://www.rabbitmq.com/memory.html for full details.
+
+## Memory-based Flow Control threshold.
+##
+# vm_memory_high_watermark.relative = 0.4
+
+## Alternatively, we can set a limit (in bytes) of RAM used by the node.
+##
+# vm_memory_high_watermark.absolute = 1073741824
+
+## Or you can set absolute value using memory units (with RabbitMQ 3.6.0+).
+## Absolute watermark will be ignored if relative is defined!
+##
+# vm_memory_high_watermark.absolute = 2GB
+##
+## Supported units suffixes:
+##
+## kb, KB: kibibytes (2^10 bytes)
+## mb, MB: mebibytes (2^20)
+## gb, GB: gibibytes (2^30)
+
+
+
+## Fraction of the high watermark limit at which queues start to
+## page message out to disc in order to free up memory.
+##
+## Values greater than 0.9 can be dangerous and should be used carefully.
+##
+# vm_memory_high_watermark_paging_ratio = 0.5
+
+## Interval (in milliseconds) at which we perform the check of the memory
+## levels against the watermarks.
+##
+# memory_monitor_interval = 2500
+
+## Set disk free limit (in bytes). Once free disk space reaches this
+## lower bound, a disk alarm will be set - see the documentation
+## listed above for more details.
+##
+## Absolute watermark will be ignored if relative is defined!
+# disk_free_limit.absolute = 50000
+
+## Or you can set it using memory units (same as in vm_memory_high_watermark)
+## with RabbitMQ 3.6.0+.
+# disk_free_limit.absolute = 500KB
+# disk_free_limit.absolute = 50mb
+# disk_free_limit.absolute = 5GB
+
+## Alternatively, we can set a limit relative to total available RAM.
+##
+## Values lower than 1.0 can be dangerous and should be used carefully.
+# disk_free_limit.relative = 2.0
+
+##
+## Clustering
+## =====================
+##
+# cluster_partition_handling = ignore
+
+## pause_if_all_down strategy require additional configuration
+# cluster_partition_handling = pause_if_all_down
+
+## Recover strategy. Can be either 'autoheal' or 'ignore'
+# cluster_partition_handling.pause_if_all_down.recover = ignore
+
+## Node names to check
+# cluster_partition_handling.pause_if_all_down.nodes.1 = rabbit@localhost
+# cluster_partition_handling.pause_if_all_down.nodes.2 = hare@localhost
+
+## Mirror sync batch size, in messages. Increasing this will speed
+## up syncing but total batch size in bytes must not exceed 2 GiB.
+## Available in RabbitMQ 3.6.0 or later.
+##
+# mirroring_sync_batch_size = 4096
+
+## Make clustering happen *automatically* at startup - only applied
+## to nodes that have just been reset or started for the first time.
+## See http://www.rabbitmq.com/clustering.html#auto-config for
+## further details.
+##
+# cluster_nodes.disc.rabbit = rabbit@my.host.com
+
+## You can define multiple nodes
+# cluster_nodes.disc.hare = hare@my.host.com
+
+## There can be also ram nodes.
+## Ram nodes should not be defined together with disk nodes
+# cluster_nodes.ram.rabbit = rabbit@my.host.com
+
+## Interval (in milliseconds) at which we send keepalive messages
+## to other cluster members. Note that this is not the same thing
+## as net_ticktime; missed keepalive messages will not cause nodes
+## to be considered down.
+##
+# cluster_keepalive_interval = 10000
+
+##
+## Statistics Collection
+## =====================
+##
+
+## Set (internal) statistics collection granularity.
+##
+## Can be none, coarse or fine
+# collect_statistics = none
+
+# collect_statistics = coarse
+
+## Statistics collection interval (in milliseconds). Increasing
+## this will reduce the load on management database.
+##
+# collect_statistics_interval = 5000
+
+##
+## Misc/Advanced Options
+## =====================
+##
+## NB: Change these only if you understand what you are doing!
+##
+
+## Explicitly enable/disable hipe compilation.
+##
+# hipe_compile = false
+
+## Timeout used when waiting for Mnesia tables in a cluster to
+## become available.
+##
+# mnesia_table_loading_timeout = 30000
+
+## Size in bytes below which to embed messages in the queue index. See
+## http://www.rabbitmq.com/persistence-conf.html
+##
+# queue_index_embed_msgs_below = 4096
+
+## You can also set this size in memory units
+##
+# queue_index_embed_msgs_below = 4kb
+
+## ----------------------------------------------------------------------------
+## Advanced Erlang Networking/Clustering Options.
+##
+## See http://www.rabbitmq.com/clustering.html for details
+## ----------------------------------------------------------------------------
+
+# ======================================
+# Kernel section
+# ======================================
+
+# kernel.net_ticktime = 60
+
+## ----------------------------------------------------------------------------
+## RabbitMQ Management Plugin
+##
+## See http://www.rabbitmq.com/management.html for details
+## ----------------------------------------------------------------------------
+
+# =======================================
+# Management section
+# =======================================
+
+## Pre-Load schema definitions from the following JSON file. See
+## http://www.rabbitmq.com/management.html#load-definitions
+##
+# management.load_definitions = /path/to/schema.json
+
+## Log all requests to the management HTTP API to a file.
+##
+# management.http_log_dir = /path/to/access.log
+
+## Change the port on which the HTTP listener listens,
+## specifying an interface for the web server to bind to.
+## Also set the listener to use SSL and provide SSL options.
+##
+
+# QA: Maybe use IP type like in tcp_listener?
+# management.listener.port = 12345
+# management.listener.ip = 127.0.0.1
+# management.listener.ssl = true
+
+# management.listener.ssl_opts.cacertfile = /path/to/cacert.pem
+# management.listener.ssl_opts.certfile = /path/to/cert.pem
+# management.listener.ssl_opts.keyfile = /path/to/key.pem
+
+## One of 'basic', 'detailed' or 'none'. See
+## http://www.rabbitmq.com/management.html#fine-stats for more details.
+# management.rates_mode = basic
+
+## Configure how long aggregated data (such as message rates and queue
+## lengths) is retained. Please read the plugin's documentation in
+## http://www.rabbitmq.com/management.html#configuration for more
+## details.
+## Your can use 'minute', 'hour' and '24hours' keys or integer key (in seconds)
+# management.sample_retention_policies.global.minute = 5
+# management.sample_retention_policies.global.hour = 60
+# management.sample_retention_policies.global.24hours = 1200
+
+# management.sample_retention_policies.basic.minute = 5
+# management.sample_retention_policies.basic.hour = 60
+
+# management.sample_retention_policies.detailed.10 = 5
+
+## ----------------------------------------------------------------------------
+## RabbitMQ Shovel Plugin
+##
+## See http://www.rabbitmq.com/shovel.html for details
+## ----------------------------------------------------------------------------
+
+## Shovel plugin config example is defined in additional.config file
+
+
+## ----------------------------------------------------------------------------
+## RabbitMQ Stomp Adapter
+##
+## See http://www.rabbitmq.com/stomp.html for details
+## ----------------------------------------------------------------------------
+
+# =======================================
+# STOMP section
+# =======================================
+
+## Network Configuration - the format is generally the same as for the broker
+##
+# stomp.listeners.tcp.default = 61613
+
+## Same for ssl listeners
+##
+# stomp.listeners.ssl.default = 61614
+
+## Number of Erlang processes that will accept connections for the TCP
+## and SSL listeners.
+##
+# stomp.num_acceptors.tcp = 10
+# stomp.num_acceptors.ssl = 1
+
+## Additional SSL options
+
+## Extract a name from the client's certificate when using SSL.
+##
+# stomp.ssl_cert_login = true
+
+## Set a default user name and password. This is used as the default login
+## whenever a CONNECT frame omits the login and passcode headers.
+##
+## Please note that setting this will allow clients to connect without
+## authenticating!
+##
+# stomp.default_user = guest
+# stomp.default_pass = guest
+
+## If a default user is configured, or you have configured use SSL client
+## certificate based authentication, you can choose to allow clients to
+## omit the CONNECT frame entirely. If set to true, the client is
+## automatically connected as the default user or user supplied in the
+## SSL certificate whenever the first frame sent on a session is not a
+## CONNECT frame.
+##
+# stomp.implicit_connect = true
+
+## ----------------------------------------------------------------------------
+## RabbitMQ MQTT Adapter
+##
+## See https://github.com/rabbitmq/rabbitmq-mqtt/blob/stable/README.md
+## for details
+## ----------------------------------------------------------------------------
+
+# =======================================
+# MQTT section
+# =======================================
+
+## Set the default user name and password. Will be used as the default login
+## if a connecting client provides no other login details.
+##
+## Please note that setting this will allow clients to connect without
+## authenticating!
+##
+# mqtt.default_user = guest
+# mqtt.default_pass = guest
+
+## Enable anonymous access. If this is set to false, clients MUST provide
+## login information in order to connect. See the default_user/default_pass
+## configuration elements for managing logins without authentication.
+##
+# mqtt.allow_anonymous = true
+
+## If you have multiple chosts, specify the one to which the
+## adapter connects.
+##
+# mqtt.vhost = /
+
+## Specify the exchange to which messages from MQTT clients are published.
+##
+# mqtt.exchange = amq.topic
+
+## Specify TTL (time to live) to control the lifetime of non-clean sessions.
+##
+# mqtt.subscription_ttl = 1800000
+
+## Set the prefetch count (governing the maximum number of unacknowledged
+## messages that will be delivered).
+##
+# mqtt.prefetch = 10
+
+## TCP/SSL Configuration (as per the broker configuration).
+##
+# mqtt.listeners.tcp.default = 1883
+
+## Same for ssl listener
+##
+# mqtt.listeners.ssl.default = 1884
+
+## Number of Erlang processes that will accept connections for the TCP
+## and SSL listeners.
+##
+# mqtt.num_acceptors.tcp = 10
+# mqtt.num_acceptors.ssl = 1
+
+## TCP/Socket options (as per the broker configuration).
+##
+# mqtt.tcp_listen_options.backlog = 128
+# mqtt.tcp_listen_options.nodelay = true
+
+## ----------------------------------------------------------------------------
+## RabbitMQ AMQP 1.0 Support
+##
+## See https://github.com/rabbitmq/rabbitmq-amqp1.0/blob/stable/README.md
+## for details
+## ----------------------------------------------------------------------------
+
+# =======================================
+# AMQP_1 section
+# =======================================
+
+
+## Connections that are not authenticated with SASL will connect as this
+## account. See the README for more information.
+##
+## Please note that setting this will allow clients to connect without
+## authenticating!
+##
+# amqp1_0.default_user = guest
+
+## Enable protocol strict mode. See the README for more information.
+##
+# amqp1_0.protocol_strict_mode = false
+
+## Lager controls logging.
+## See https://github.com/basho/lager for more documentation
+##
+## Log direcrory, taken from the RABBITMQ_LOG_BASE env variable by default.
+##
+# log.dir = /var/log/rabbitmq
+
+## Logging to console (can be true or false)
+##
+# log.console = false
+
+## Loglevel to log to console
+##
+# log.console.level = info
+
+## Logging to file. Can be false or filename.
+## Default:
+# log.file = rabbit.log
+
+## To turn off:
+# log.file = false
+
+## Loglevel to log to file
+##
+# log.file.level = info
+
+## File rotation config. No rotation by defualt.
+## DO NOT SET rotation date to ''. Leave unset if require "" value
+# log.file.rotation.date = $D0
+# log.file.rotation.size = 0
+
+
+## QA: Config for syslog logging
+# log.syslog = false
+# log.syslog.identity = rabbitmq
+# log.syslog.level = info
+# log.syslog.facility = daemon
+
+
+## ----------------------------------------------------------------------------
+## RabbitMQ LDAP Plugin
+##
+## See http://www.rabbitmq.com/ldap.html for details.
+##
+## ----------------------------------------------------------------------------
+
+# =======================================
+# LDAP section
+# =======================================
+
+##
+## Connecting to the LDAP server(s)
+## ================================
+##
+
+## Specify servers to bind to. You *must* set this in order for the plugin
+## to work properly.
+##
+# ldap.servers.1 = your-server-name-goes-here
+
+## You can define multiple servers
+# ldap.servers.2 = your-other-server
+
+## Connect to the LDAP server using SSL
+##
+# ldap.use_ssl = false
+
+## Specify the LDAP port to connect to
+##
+# ldap.port = 389
+
+## LDAP connection timeout, in milliseconds or 'infinity'
+##
+# ldap.timeout = infinity
+
+## Or number
+# ldap.timeout = 500
+
+## Enable logging of LDAP queries.
+## One of
+## - false (no logging is performed)
+## - true (verbose logging of the logic used by the plugin)
+## - network (as true, but additionally logs LDAP network traffic)
+##
+## Defaults to false.
+##
+# ldap.log = false
+
+## Also can be true or network
+# ldap.log = true
+# ldap.log = network
+
+##
+## Authentication
+## ==============
+##
+
+## Pattern to convert the username given through AMQP to a DN before
+## binding
+##
+# ldap.user_dn_pattern = cn=${username},ou=People,dc=example,dc=com
+
+## Alternatively, you can convert a username to a Distinguished
+## Name via an LDAP lookup after binding. See the documentation for
+## full details.
+
+## When converting a username to a dn via a lookup, set these to
+## the name of the attribute that represents the user name, and the
+## base DN for the lookup query.
+##
+# ldap.dn_lookup_attribute = userPrincipalName
+# ldap.dn_lookup_base = DC=gopivotal,DC=com
+
+## Controls how to bind for authorisation queries and also to
+## retrieve the details of users logging in without presenting a
+## password (e.g., SASL EXTERNAL).
+## One of
+## - as_user (to bind as the authenticated user - requires a password)
+## - anon (to bind anonymously)
+## - {UserDN, Password} (to bind with a specified user name and password)
+##
+## Defaults to 'as_user'.
+##
+# ldap.other_bind = as_user
+
+## Or can be more complex:
+# ldap.other_bind.user_dn = User
+# ldap.other_bind.password = Password
+
+## If user_dn and password defined - other options is ignored.
+
+# -----------------------------
+# Too complex section of LDAP
+# -----------------------------
+
+##
+## Authorisation
+## =============
+##
+
+## The LDAP plugin can perform a variety of queries against your
+## LDAP server to determine questions of authorisation. See
+## http://www.rabbitmq.com/ldap.html#authorisation for more
+## information.
+
+## Following configuration should be defined in additional.config file
+## DO NOT UNCOMMENT THIS LINES!
+
+## Set the query to use when determining vhost access
+##
+## {vhost_access_query, {in_group,
+## "ou=${vhost}-users,ou=vhosts,dc=example,dc=com"}},
+
+## Set the query to use when determining resource (e.g., queue) access
+##
+## {resource_access_query, {constant, true}},
+
+## Set queries to determine which tags a user has
+##
+## {tag_queries, []}
+# ]},
+# -----------------------------
diff --git a/docs/rabbitmq.config.example b/docs/rabbitmq.config.example
index 4d376d953a..bea743d679 100644
--- a/docs/rabbitmq.config.example
+++ b/docs/rabbitmq.config.example
@@ -38,12 +38,6 @@
%%
%% {handshake_timeout, 10000},
- %% Log levels (currently just used for connection logging).
- %% One of 'debug', 'info', 'warning', 'error' or 'none', in decreasing
- %% order of verbosity. Defaults to 'info'.
- %%
- %% {log_levels, [{connection, info}, {channel, info}]},
-
%% Set to 'true' to perform reverse DNS lookups when accepting a
%% connection. Hostnames will then be shown instead of IP addresses
%% in rabbitmqctl and the management plugin.
@@ -130,6 +124,12 @@
%% created users. To recalculate hash for an existing user
%% it's necessary to update her password.
%%
+ %% When importing definitions exported from versions earlier
+ %% than 3.6.0, it is possible to go back to MD5 (only do this
+ %% as a temporary measure!) by setting this to rabbit_password_hashing_md5.
+ %%
+ %% To use SHA-512, set to rabbit_password_hashing_sha512.
+ %%
%% {password_hashing_module, rabbit_password_hashing_sha256},
%%
@@ -200,7 +200,7 @@
%%
%% {vm_memory_high_watermark, {absolute, 1073741824}},
%%
- %% Or you can set absolute value using memory units.
+ %% Or you can set absolute value using memory units (with RabbitMQ 3.6.0+).
%%
%% {vm_memory_high_watermark, {absolute, "1024M"}},
%%
@@ -232,6 +232,7 @@
%% {disk_free_limit, 50000000},
%%
%% Or you can set it using memory units (same as in vm_memory_high_watermark)
+ %% with RabbitMQ 3.6.0+.
%% {disk_free_limit, "50MB"},
%% {disk_free_limit, "50000kB"},
%% {disk_free_limit, "2GB"},
@@ -242,20 +243,20 @@
%% {disk_free_limit, {mem_relative, 2.0}},
%%
- %% Misc/Advanced Options
+ %% Clustering
%% =====================
%%
- %% NB: Change these only if you understand what you are doing!
- %%
-
- %% To announce custom properties to clients on connection:
- %%
- %% {server_properties, []},
%% How to respond to cluster partitions.
%% See http://www.rabbitmq.com/partitions.html for further details.
%%
%% {cluster_partition_handling, ignore},
+
+ %% Mirror sync batch size, in messages. Increasing this will speed
+ %% up syncing but total batch size in bytes must not exceed 2 GiB.
+ %% Available in RabbitMQ 3.6.0 or later.
+ %%
+ %% {mirroring_sync_batch_size, 4096},
%% Make clustering happen *automatically* at startup - only applied
%% to nodes that have just been reset or started for the first time.
@@ -271,14 +272,27 @@
%%
%% {cluster_keepalive_interval, 10000},
+ %%
+ %% Statistics Collection
+ %% =====================
+ %%
+
%% Set (internal) statistics collection granularity.
%%
%% {collect_statistics, none},
- %% Statistics collection interval (in milliseconds).
+ %% Statistics collection interval (in milliseconds). Increasing
+ %% this will reduce the load on management database.
%%
%% {collect_statistics_interval, 5000},
+ %%
+ %% Misc/Advanced Options
+ %% =====================
+ %%
+ %% NB: Change these only if you understand what you are doing!
+ %%
+
%% Explicitly enable/disable hipe compilation.
%%
%% {hipe_compile, true},
@@ -639,5 +653,45 @@
%% Set queries to determine which tags a user has
%%
%% {tag_queries, []}
+ ]},
+
+ %% Lager controls logging.
+ %% See https://github.com/basho/lager for more documentation
+ {lager, [
+ %%
+ %% Log directory, taken from the RABBITMQ_LOG_BASE env variable by default.
+ %% {log_root, "/var/log/rabbitmq"},
+ %%
+ %% All log messages go to the default "sink" configured with
+ %% the `handlers` parameter. By default, it has a single
+ %% lager_file_backend handler writing messages to "$nodename.log"
+ %% (ie. the value of $RABBIT_LOGS).
+ %% {handlers, [
+ %% {lager_file_backend, [{file, "rabbit.log"},
+ %% {level, info},
+ %% {date, ""},
+ %% {size, 0}]}
+ %% ]},
+ %%
+ %% Extra sinks are used in RabbitMQ to categorize messages. By
+ %% default, those extra sinks are configured to forward messages
+ %% to the default sink (see above). "rabbit_log_lager_event"
+ %% is the default category where all RabbitMQ messages without
+ %% a category go. Messages in the "channel" category go to the
+ %% "rabbit_channel_lager_event" Lager extra sink, and so on.
+ %% {extra_sinks, [
+ %% {rabbit_log_lager_event, [{handlers, [
+ %% {lager_forwarder_backend,
+ %% [lager_event, info]}]}]},
+ %% {rabbit_channel_lager_event, [{handlers, [
+ %% {lager_forwarder_backend,
+ %% [lager_event, info]}]}]},
+ %% {rabbit_conection_lager_event, [{handlers, [
+ %% {lager_forwarder_backend,
+ %% [lager_event, info]}]}]},
+ %% {rabbit_mirroring_lager_event, [{handlers, [
+ %% {lager_forwarder_backend,
+ %% [lager_event, info]}]}]}
+ %% ]}
]}
].
diff --git a/docs/rabbitmqctl.1.xml b/docs/rabbitmqctl.1.xml
index d9a9991ea3..a2ebc0b817 100644
--- a/docs/rabbitmqctl.1.xml
+++ b/docs/rabbitmqctl.1.xml
@@ -261,32 +261,27 @@
</varlistentry>
<varlistentry>
- <term><cmdsynopsis><command>rotate_logs</command> <arg choice="req"><replaceable>suffix</replaceable></arg></cmdsynopsis></term>
+ <term><cmdsynopsis><command>rotate_logs</command></cmdsynopsis></term>
<listitem>
<para>
- Instruct the RabbitMQ node to rotate the log files.
+ Instruct the RabbitMQ node to perform internal log rotation.
</para>
<para>
- The RabbitMQ broker appends the contents of its log
- files to files with names composed of the original name
- and the suffix, and then resumes logging to freshly
- created files at the original location. I.e. effectively
- the current log contents are moved to the end of the
- suffixed files.
+ Log rotation is performed according to lager settings
+ specified in configuration file.
</para>
<para>
- When the target files do not exist they are created. When
- no <option>suffix</option> is specified, no rotation takes
- place - log files are just re-opened.
+ Note that there is no need to call this command in case of
+ external log rotation (e.g. from logrotate), because lager
+ detects renames and automatically reopens log files.
</para>
<para role="example-prefix">For example:</para>
- <screen role="example">rabbitmqctl rotate_logs .1</screen>
+ <screen role="example">rabbitmqctl rotate_logs</screen>
<para role="example">
- This command instructs the RabbitMQ node to append the contents
- of the log files to files with names consisting of the original logs'
- names and ".1" suffix, e.g. rabbit@mymachine.log.1 and
- rabbit@mymachine-sasl.log.1. Finally, logging resumes to
- fresh files at the old locations.
+ This command starts internal log rotation
+ process. Rotation is performed asynchronously, so there is
+ no guarantee that it will be completed when this command
+ returns.
</para>
</listitem>
</varlistentry>
@@ -1218,6 +1213,55 @@
</refsect2>
<refsect2>
+ <title>Virtual Host Limits</title>
+ <para>
+ It is possible to enforce certain limits on virtual hosts.
+ </para>
+ <variablelist>
+ <varlistentry>
+ <term><cmdsynopsis><command>set_vhost_limits</command> <arg choice="opt">-p <replaceable>vhostpath</replaceable></arg> <arg choice="req"><replaceable>definition</replaceable></arg></cmdsynopsis></term>
+ <listitem>
+ <para>
+ Sets virtual host limits
+ </para>
+ <variablelist>
+ <varlistentry>
+ <term>definition</term>
+ <listitem><para>
+ The definition of the limits, as a
+ JSON term. In most shells you are very likely to
+ need to quote this.
+
+ Recognised limits: max-connections (0 means "no limit").
+ </para></listitem>
+ </varlistentry>
+ </variablelist>
+ <para role="example-prefix">For example:</para>
+ <screen role="example">rabbitmqctl set_vhost_limits -p qa_env '{"max-connections": 1024}'</screen>
+ <para role="example">
+ This command limits the max number of concurrent connections in vhost <command>qa_env</command>
+ to 1024.
+ </para>
+ </listitem>
+ </varlistentry>
+
+<varlistentry>
+ <term><cmdsynopsis><command>clear_vhost_limits</command> <arg choice="opt">-p <replaceable>vhostpath</replaceable></arg></cmdsynopsis></term>
+ <listitem>
+ <para>
+ Clears virtual host limits
+ </para>
+ <para role="example-prefix">For example:</para>
+ <screen role="example">rabbitmqctl clear_vhost_limits -p qa_env</screen>
+ <para role="example">
+ This command clears vhost limits in vhost <command>qa_env</command>.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect2>
+
+ <refsect2>
<title>Server Status</title>
<para>
The server status queries interrogate the server and return a list of
@@ -2074,9 +2118,9 @@
<varlistentry>
<term>fraction</term>
<listitem><para>
- Limit relative to the total amount available RAM
- as a non-negative floating point number.
- Values lower than 1.0 can be dangerous and
+ Limit relative to the total amount available RAM
+ as a non-negative floating point number.
+ Values lower than 1.0 can be dangerous and
should be used carefully.
</para></listitem>
</varlistentry>
diff --git a/include/rabbit_cli.hrl b/include/rabbit_cli.hrl
index b1cf41261f..19cae2fc0a 100644
--- a/include/rabbit_cli.hrl
+++ b/include/rabbit_cli.hrl
@@ -31,7 +31,6 @@
-define(ONLINE_OPT, "--online").
-define(LOCAL_OPT, "--local").
-
-define(NODE_DEF(Node), {?NODE_OPT, {option, Node}}).
-define(QUIET_DEF, {?QUIET_OPT, flag}).
-define(VHOST_DEF, {?VHOST_OPT, {option, "/"}}).
diff --git a/include/rabbit_log.hrl b/include/rabbit_log.hrl
new file mode 100644
index 0000000000..fcb72e2e31
--- /dev/null
+++ b/include/rabbit_log.hrl
@@ -0,0 +1 @@
+-define(LAGER_SINK, rabbit_log_lager_event).
diff --git a/packaging/RPMS/Fedora/rabbitmq-server.init b/packaging/RPMS/Fedora/rabbitmq-server.init
index 15929108b5..90b48499fc 100644..100755
--- a/packaging/RPMS/Fedora/rabbitmq-server.init
+++ b/packaging/RPMS/Fedora/rabbitmq-server.init
@@ -24,7 +24,6 @@ DAEMON=/usr/sbin/${NAME}
CONTROL=/usr/sbin/rabbitmqctl
DESC=rabbitmq-server
USER=rabbitmq
-ROTATE_SUFFIX=
PID_FILE=/var/run/rabbitmq/pid
RABBITMQ_ENV=/usr/lib/rabbitmq/bin/rabbitmq-env
@@ -125,7 +124,7 @@ status_rabbitmq() {
rotate_logs_rabbitmq() {
set +e
- $CONTROL rotate_logs ${ROTATE_SUFFIX}
+ $CONTROL rotate_logs
if [ $? != 0 ] ; then
RETVAL=1
fi
diff --git a/packaging/RPMS/Fedora/rabbitmq-server.logrotate b/packaging/RPMS/Fedora/rabbitmq-server.logrotate
index f902ed185f..21a9fc0b84 100644
--- a/packaging/RPMS/Fedora/rabbitmq-server.logrotate
+++ b/packaging/RPMS/Fedora/rabbitmq-server.logrotate
@@ -3,10 +3,5 @@
missingok
rotate 20
compress
- delaycompress
notifempty
- sharedscripts
- postrotate
- /usr/sbin/rabbitmqctl rotate_logs > /dev/null
- endscript
}
diff --git a/packaging/RPMS/Fedora/rabbitmq-server.spec b/packaging/RPMS/Fedora/rabbitmq-server.spec
index 83b9678ec8..811cc58526 100644
--- a/packaging/RPMS/Fedora/rabbitmq-server.spec
+++ b/packaging/RPMS/Fedora/rabbitmq-server.spec
@@ -91,9 +91,6 @@ fi
%post
/sbin/chkconfig --add %{name}
-if [ -f %{_sysconfdir}/rabbitmq/rabbitmq.conf ] && [ ! -f %{_sysconfdir}/rabbitmq/rabbitmq-env.conf ]; then
- mv %{_sysconfdir}/rabbitmq/rabbitmq.conf %{_sysconfdir}/rabbitmq/rabbitmq-env.conf
-fi
chmod -R o-rwx,g-w %{_localstatedir}/lib/rabbitmq/mnesia
%preun
diff --git a/packaging/debs/Debian/debian/postinst b/packaging/debs/Debian/debian/postinst
index c83881e6ba..f49f2aa40f 100644
--- a/packaging/debs/Debian/debian/postinst
+++ b/packaging/debs/Debian/debian/postinst
@@ -37,10 +37,6 @@ chmod -R o-rwx,g-w /var/lib/rabbitmq/mnesia
case "$1" in
configure)
- if [ -f /etc/rabbitmq/rabbitmq.conf ] && \
- [ ! -f /etc/rabbitmq/rabbitmq-env.conf ]; then
- mv /etc/rabbitmq/rabbitmq.conf /etc/rabbitmq/rabbitmq-env.conf
- fi
;;
abort-upgrade|abort-remove|abort-deconfigure)
diff --git a/packaging/debs/Debian/debian/rabbitmq-server.init b/packaging/debs/Debian/debian/rabbitmq-server.init
index fce2d16401..f8801d9864 100644..100755
--- a/packaging/debs/Debian/debian/rabbitmq-server.init
+++ b/packaging/debs/Debian/debian/rabbitmq-server.init
@@ -22,7 +22,6 @@ DAEMON=/usr/sbin/${NAME}
CONTROL=/usr/sbin/rabbitmqctl
DESC="message broker"
USER=rabbitmq
-ROTATE_SUFFIX=
PID_FILE=/var/run/rabbitmq/pid
RABBITMQ_ENV=/usr/lib/rabbitmq/bin/rabbitmq-env
@@ -106,7 +105,7 @@ status_rabbitmq() {
rotate_logs_rabbitmq() {
set +e
- $CONTROL -q rotate_logs ${ROTATE_SUFFIX}
+ $CONTROL -q rotate_logs
if [ $? != 0 ] ; then
RETVAL=1
fi
diff --git a/packaging/debs/Debian/debian/rabbitmq-server.logrotate b/packaging/debs/Debian/debian/rabbitmq-server.logrotate
index c786df77b2..21a9fc0b84 100644
--- a/packaging/debs/Debian/debian/rabbitmq-server.logrotate
+++ b/packaging/debs/Debian/debian/rabbitmq-server.logrotate
@@ -3,10 +3,5 @@
missingok
rotate 20
compress
- delaycompress
notifempty
- sharedscripts
- postrotate
- /etc/init.d/rabbitmq-server rotate-logs > /dev/null
- endscript
}
diff --git a/packaging/windows-exe/rabbitmq_nsi.in b/packaging/windows-exe/rabbitmq_nsi.in
index 4c47e86628..5e1c0cb592 100644
--- a/packaging/windows-exe/rabbitmq_nsi.in
+++ b/packaging/windows-exe/rabbitmq_nsi.in
@@ -86,6 +86,20 @@ Section "RabbitMQ Server (required)" Rabbit
; Set output path to the user's data directory
SetOutPath $APPDATA\RabbitMQ
+ IfFileExists advanced.config 0 +2
+ Goto config_written
+ IfFileExists rabbitmq.config 0 +3
+ Rename rabbitmq.config advanced.config
+ Goto config_written
+ ClearErrors
+ FileOpen $0 advanced.config w
+ IfErrors config_written
+ FileWrite $0 "[]."
+ FileClose $0
+ config_written:
+
+
+
; ...And put the example config file there
File "rabbitmq_server-%%VERSION%%\etc\rabbitmq.config.example"
diff --git a/priv/schema/.gitignore b/priv/schema/.gitignore
new file mode 100644
index 0000000000..68e5b59a44
--- /dev/null
+++ b/priv/schema/.gitignore
@@ -0,0 +1,4 @@
+# plugin schemas are extracted
+# into this directory: this is a Cuttlefish
+# requirement. So we ignore them.
+rabbitmq_*.schema
diff --git a/priv/schema/rabbitmq.schema b/priv/schema/rabbitmq.schema
new file mode 100644
index 0000000000..bf9cccbcb8
--- /dev/null
+++ b/priv/schema/rabbitmq.schema
@@ -0,0 +1,980 @@
+% ==============================
+% Rabbit app section
+% ==============================
+
+%%
+%% Network Connectivity
+%% ====================
+%%
+
+%% By default, RabbitMQ will listen on all interfaces, using
+%% the standard (reserved) AMQP port.
+%%
+%% {tcp_listeners, [5672]},
+%% To listen on a specific interface, provide a tuple of {IpAddress, Port}.
+%% For example, to listen only on localhost for both IPv4 and IPv6:
+%%
+%% {tcp_listeners, [{"127.0.0.1", 5672},
+%% {"[::1]", 5672}]},
+
+{mapping, "listeners.tcp", "rabbit.tcp_listeners",[
+ {datatype, {enum, [none]}}
+]}.
+
+{mapping, "listeners.tcp.$name", "rabbit.tcp_listeners",[
+ {datatype, [integer, ip]}
+]}.
+
+{translation, "rabbit.tcp_listeners",
+fun(Conf) ->
+ case cuttlefish:conf_get("listeners.tcp", Conf, undefined) of
+ none -> [];
+ _ ->
+ Settings = cuttlefish_variable:filter_by_prefix("listeners.tcp", Conf),
+ [ V || {_, V} <- Settings ]
+ end
+end}.
+
+%% SSL listeners are configured in the same fashion as TCP listeners,
+%% including the option to control the choice of interface.
+%%
+%% {ssl_listeners, [5671]},
+
+{mapping, "listeners.ssl", "rabbit.ssl_listeners",[
+ {datatype, {enum, [none]}}
+]}.
+
+{mapping, "listeners.ssl.$name", "rabbit.ssl_listeners",[
+ {datatype, [integer, ip]}
+]}.
+
+{translation, "rabbit.ssl_listeners",
+fun(Conf) ->
+ case cuttlefish:conf_get("listeners.ssl", Conf, undefined) of
+ none -> [];
+ _ ->
+ Settings = cuttlefish_variable:filter_by_prefix("listeners.ssl", Conf),
+ [ V || {_, V} <- Settings ]
+ end
+end}.
+
+%% Number of Erlang processes that will accept connections for the TCP
+%% and SSL listeners.
+%%
+%% {num_tcp_acceptors, 10},
+%% {num_ssl_acceptors, 1},
+
+{mapping, "num_acceptors.ssl", "rabbit.num_ssl_acceptors", [
+ {datatype, integer}
+]}.
+
+{mapping, "num_acceptors.tcp", "rabbit.num_tcp_acceptors", [
+ {datatype, integer}
+]}.
+
+
+%% Maximum time for AMQP 0-8/0-9/0-9-1 handshake (after socket connection
+%% and SSL handshake), in milliseconds.
+%%
+%% {handshake_timeout, 10000},
+
+{mapping, "handshake_timeout", "rabbit.handshake_timeout", [
+ {datatype, integer}
+]}.
+
+%% Set to 'true' to perform reverse DNS lookups when accepting a
+%% connection. Hostnames will then be shown instead of IP addresses
+%% in rabbitmqctl and the management plugin.
+%%
+%% {reverse_dns_lookups, true},
+
+{mapping, "reverse_dns_lookups", "rabbit.reverse_dns_lookups", [
+ {datatype, {enum, [true, false]}}
+]}.
+
+{mapping, "erlang.K", "vm_args.+K", [
+ {default, "true"},
+ {level, advanced}
+]}.
+
+%%
+%% Security / AAA
+%% ==============
+%%
+
+%% The default "guest" user is only permitted to access the server
+%% via a loopback interface (e.g. localhost).
+%% {loopback_users, [<<"guest">>]},
+%%
+%% Uncomment the following line if you want to allow access to the
+%% guest user from anywhere on the network.
+%% {loopback_users, []},
+
+{mapping, "loopback_users", "rabbit.loopback_users", [
+ {datatype, {enum, [none]}}
+]}.
+
+{mapping, "loopback_users.$user", "rabbit.loopback_users", [
+ {datatype, atom}
+]}.
+
+{translation, "rabbit.loopback_users",
+fun(Conf) ->
+ None = cuttlefish:conf_get("loopback_users", Conf, undefined),
+ case None of
+ none -> [];
+ _ ->
+ Settings = cuttlefish_variable:filter_by_prefix("loopback_users", Conf),
+ [ list_to_binary(U) || {["loopback_users", U], V} <- Settings, V == true ]
+ end
+end}.
+
+%% Configuring SSL.
+%% See http://www.rabbitmq.com/ssl.html for full documentation.
+%%
+%% {ssl_options, [{cacertfile, "/path/to/testca/cacert.pem"},
+%% {certfile, "/path/to/server/cert.pem"},
+%% {keyfile, "/path/to/server/key.pem"},
+%% {verify, verify_peer},
+%% {fail_if_no_peer_cert, false}]},
+
+%% SSL options section ========================================================
+
+{mapping, "ssl_allow_poodle_attack", "rabbit.ssl_allow_poodle_attack",
+[{datatype, {enum, [true, false]}}]}.
+
+{mapping, "ssl_options", "rabbit.ssl_options", [
+ {datatype, {enum, [none]}}
+]}.
+
+{translation, "rabbit.ssl_options",
+fun(Conf) ->
+ case cuttlefish:conf_get("ssl_options", Conf, undefined) of
+ none -> [];
+ _ -> cuttlefish:invalid("Invalid ssl_options")
+ end
+end}.
+
+{mapping, "ssl_options.verify", "rabbit.ssl_options.verify", [
+ {datatype, {enum, [verify_peer, verify_none]}}]}.
+
+{mapping, "ssl_options.fail_if_no_peer_cert", "rabbit.ssl_options.fail_if_no_peer_cert", [
+ {datatype, {enum, [true, false]}}]}.
+
+{mapping, "ssl_options.cacertfile", "rabbit.ssl_options.cacertfile",
+ [{datatype, string}, {validators, ["file_accessible"]}]}.
+
+{mapping, "ssl_options.certfile", "rabbit.ssl_options.certfile",
+ [{datatype, string}, {validators, ["file_accessible"]}]}.
+
+{mapping, "ssl_options.cacerts.$name", "rabbit.ssl_options.cacerts",
+ [{datatype, string}]}.
+
+{translation, "rabbit.ssl_options.cacerts",
+fun(Conf) ->
+ Settings = cuttlefish_variable:filter_by_prefix("ssl_options.cacerts", Conf),
+ [ list_to_binary(V) || {_, V} <- Settings ]
+end}.
+
+{mapping, "ssl_options.cert", "rabbit.ssl_options.cert",
+ [{datatype, string}]}.
+
+{translation, "rabbit.ssl_options.cert",
+fun(Conf) ->
+ list_to_binary(cuttlefish:conf_get("ssl_options.cert", Conf))
+end}.
+
+{mapping, "ssl_options.client_renegotiation", "rabbit.ssl_options.client_renegotiation",
+ [{datatype, {enum, [true, false]}}]}.
+
+{mapping, "ssl_options.crl_check", "rabbit.ssl_options.crl_check",
+ [{datatype, [{enum, [true, false, peer, best_effort]}]}]}.
+
+{mapping, "ssl_options.depth", "rabbit.ssl_options.depth",
+ [{datatype, integer}, {validators, ["byte"]}]}.
+
+{mapping, "ssl_options.dh", "rabbit.ssl_options.dh",
+ [{datatype, string}]}.
+
+{translation, "rabbit.ssl_options.dh",
+fun(Conf) ->
+ list_to_binary(cuttlefish:conf_get("ssl_options.dh", Conf))
+end}.
+
+{mapping, "ssl_options.dhfile", "rabbit.ssl_options.dhfile",
+ [{datatype, string}, {validators, ["file_accessible"]}]}.
+
+{mapping, "ssl_options.honor_cipher_order", "rabbit.ssl_options.honor_cipher_order",
+ [{datatype, {enum, [true, false]}}]}.
+
+{mapping, "ssl_options.key.RSAPrivateKey", "rabbit.ssl_options.key",
+ [{datatype, string}]}.
+
+{mapping, "ssl_options.key.DSAPrivateKey", "rabbit.ssl_options.key",
+ [{datatype, string}]}.
+
+{mapping, "ssl_options.key.PrivateKeyInfo", "rabbit.ssl_options.key",
+ [{datatype, string}]}.
+
+{translation, "rabbit.ssl_options.key",
+fun(Conf) ->
+ case cuttlefish_variable:filter_by_prefix("ssl_options.key", Conf) of
+ [{[_,_,Key], Val}|_] -> {list_to_atom(Key), list_to_binary(Val)};
+ _ -> undefined
+ end
+end}.
+
+{mapping, "ssl_options.keyfile", "rabbit.ssl_options.keyfile",
+ [{datatype, string}, {validators, ["file_accessible"]}]}.
+
+{mapping, "ssl_options.log_alert", "rabbit.ssl_options.log_alert",
+ [{datatype, {enum, [true, false]}}]}.
+
+{mapping, "ssl_options.password", "rabbit.ssl_options.password",
+ [{datatype, string}]}.
+
+{mapping, "ssl_options.psk_identity", "rabbit.ssl_options.psk_identity",
+ [{datatype, string}]}.
+
+{mapping, "ssl_options.reuse_sessions", "rabbit.ssl_options.reuse_sessions",
+ [{datatype, {enum, [true, false]}}]}.
+
+{mapping, "ssl_options.secure_renegotiate", "rabbit.ssl_options.secure_renegotiate",
+ [{datatype, {enum, [true, false]}}]}.
+
+{mapping, "ssl_options.versions.$version", "rabbit.ssl_options.versions",
+ [{datatype, atom}]}.
+
+{translation, "rabbit.ssl_options.versions",
+fun(Conf) ->
+ Settings = cuttlefish_variable:filter_by_prefix("ssl_options.versions", Conf),
+ [ V || {_, V} <- Settings ]
+end}.
+
+%% ===========================================================================
+
+%% Choose the available SASL mechanism(s) to expose.
+%% The two default (built in) mechanisms are 'PLAIN' and
+%% 'AMQPLAIN'. Additional mechanisms can be added via
+%% plugins.
+%%
+%% See http://www.rabbitmq.com/authentication.html for more details.
+%%
+%% {auth_mechanisms, ['PLAIN', 'AMQPLAIN']},
+
+{mapping, "auth_mechanisms.$name", "rabbit.auth_mechanisms", [
+ {datatype, atom}]}.
+
+{translation, "rabbit.auth_mechanisms",
+fun(Conf) ->
+ Settings = cuttlefish_variable:filter_by_prefix("auth_mechanisms", Conf),
+ [ V || {_, V} <- Settings ]
+end}.
+
+
+%% Select an authentication backend to use. RabbitMQ provides an
+%% internal backend in the core.
+%%
+%% {auth_backends, [rabbit_auth_backend_internal]},
+
+{translation, "rabbit.auth_backends",
+fun(Conf) ->
+ Settings = cuttlefish_variable:filter_by_prefix("auth_backends", Conf),
+ BackendModule = fun
+ (internal) -> rabbit_auth_backend_internal;
+ (ldap) -> rabbit_auth_backend_ldap;
+ (http) -> rabbit_auth_backend_http;
+ (amqp) -> rabbit_auth_backend_amqp;
+ (dummy) -> rabbit_auth_backend_dummy;
+ (Other) when is_atom(Other) -> Other;
+ (_) -> cuttlefish:invalid("Unknown/unsupported auth backend")
+ end,
+ AuthBackends = [{Num, {default, BackendModule(V)}} || {["auth_backends", Num], V} <- Settings],
+ AuthNBackends = [{Num, {authn, BackendModule(V)}} || {["auth_backends", Num, "authn"], V} <- Settings],
+ AuthZBackends = [{Num, {authz, BackendModule(V)}} || {["auth_backends", Num, "authz"], V} <- Settings],
+ Backends = lists:foldl(
+ fun({NumStr, {Type, V}}, Acc) ->
+ Num = case catch list_to_integer(NumStr) of
+ N when is_integer(N) -> N;
+ Err ->
+ cuttlefish:invalid(
+ iolist_to_binary(io_lib:format(
+ "Auth backend position in the chain should be an integer ~p", [Err])))
+ end,
+ NewVal = case dict:find(Num, Acc) of
+ {ok, {AuthN, AuthZ}} ->
+ case {Type, AuthN, AuthZ} of
+ {authn, undefined, _} ->
+ {V, AuthZ};
+ {authz, _, undefined} ->
+ {AuthN, V};
+ _ ->
+ cuttlefish:invalid(
+ iolist_to_binary(
+ io_lib:format(
+ "Auth backend already defined for the ~pth ~p backend",
+ [Num, Type])))
+ end;
+ error ->
+ case Type of
+ authn -> {V, undefined};
+ authz -> {undefined, V};
+ default -> {V, V}
+ end
+ end,
+ dict:store(Num, NewVal, Acc)
+ end,
+ dict:new(),
+ AuthBackends ++ AuthNBackends ++ AuthZBackends),
+ lists:map(
+ fun
+ ({Num, {undefined, AuthZ}}) ->
+ cuttlefish:warn(
+ io_lib:format(
+ "Auth backend undefined for the ~pth authz backend. Using ~p",
+ [Num, AuthZ])),
+ {AuthZ, AuthZ};
+ ({Num, {AuthN, undefined}}) ->
+ cuttlefish:warn(
+ io_lib:format(
+ "Authz backend undefined for the ~pth authn backend. Using ~p",
+ [Num, AuthN])),
+ {AuthN, AuthN};
+ ({_Num, {Auth, Auth}}) -> Auth;
+ ({_Num, {AuthN, AuthZ}}) -> {AuthN, AuthZ}
+ end,
+ lists:keysort(1, dict:to_list(Backends)))
+end}.
+
+{mapping, "auth_backends.$num", "rabbit.auth_backends", [
+ {datatype, atom}
+]}.
+
+{mapping, "auth_backends.$num.authn", "rabbit.auth_backends",[
+ {datatype, atom}
+]}.
+
+{mapping, "auth_backends.$num.authz", "rabbit.auth_backends",[
+ {datatype, atom}
+]}.
+
+%% This pertains to both the rabbitmq_auth_mechanism_ssl plugin and
+%% STOMP ssl_cert_login configurations. See the rabbitmq_stomp
+%% configuration section later in this file and the README in
+%% https://github.com/rabbitmq/rabbitmq-auth-mechanism-ssl for further
+%% details.
+%%
+%% To use the SSL cert's CN instead of its DN as the username
+%%
+%% {ssl_cert_login_from, common_name},
+
+{mapping, "ssl_cert_login_from", "rabbit.ssl_cert_login_from", [
+ {datatype, {enum, [distinguished_name, common_name]}}
+]}.
+
+%% SSL handshake timeout, in milliseconds.
+%%
+%% {ssl_handshake_timeout, 5000},
+
+{mapping, "ssl_handshake_timeout", "rabbit.ssl_handshake_timeout", [
+ {datatype, integer}
+]}.
+
+%% Password hashing implementation. Will only affect newly
+%% created users. To recalculate hash for an existing user
+%% it's necessary to update her password.
+%%
+%% When importing definitions exported from versions earlier
+%% than 3.6.0, it is possible to go back to MD5 (only do this
+%% as a temporary measure!) by setting this to rabbit_password_hashing_md5.
+%%
+%% To use SHA-512, set to rabbit_password_hashing_sha512.
+%%
+%% {password_hashing_module, rabbit_password_hashing_sha256},
+
+{mapping, "password_hashing_module", "rabbit.password_hashing_module", [
+ {datatype, atom}
+]}.
+
+%%
+%% Default User / VHost
+%% ====================
+%%
+
+%% On first start RabbitMQ will create a vhost and a user. These
+%% config items control what gets created. See
+%% http://www.rabbitmq.com/access-control.html for further
+%% information about vhosts and access control.
+%%
+%% {default_vhost, <<"/">>},
+%% {default_user, <<"guest">>},
+%% {default_pass, <<"guest">>},
+%% {default_permissions, [<<".*">>, <<".*">>, <<".*">>]},
+
+{mapping, "default_vhost", "rabbit.default_vhost", [
+ {datatype, string}
+]}.
+
+{translation, "rabbit.default_vhost",
+fun(Conf) ->
+ list_to_binary(cuttlefish:conf_get("default_vhost", Conf))
+end}.
+
+{mapping, "default_user", "rabbit.default_user", [
+ {datatype, string}
+]}.
+
+{translation, "rabbit.default_user",
+fun(Conf) ->
+ list_to_binary(cuttlefish:conf_get("default_user", Conf))
+end}.
+
+{mapping, "default_pass", "rabbit.default_pass", [
+ {datatype, string}
+]}.
+
+{translation, "rabbit.default_pass",
+fun(Conf) ->
+ list_to_binary(cuttlefish:conf_get("default_pass", Conf))
+end}.
+
+{mapping, "default_permissions.configure", "rabbit.default_permissions", [
+ {datatype, string}
+]}.
+
+{mapping, "default_permissions.read", "rabbit.default_permissions", [
+ {datatype, string}
+]}.
+
+{mapping, "default_permissions.write", "rabbit.default_permissions", [
+ {datatype, string}
+]}.
+
+{translation, "rabbit.default_permissions",
+fun(Conf) ->
+ Settings = cuttlefish_variable:filter_by_prefix("default_permissions", Conf),
+ Configure = proplists:get_value(["default_permissions", "configure"], Settings),
+ Read = proplists:get_value(["default_permissions", "read"], Settings),
+ Write = proplists:get_value(["default_permissions", "write"], Settings),
+ [list_to_binary(Configure), list_to_binary(Read), list_to_binary(Write)]
+end}.
+
+%% Tags for default user
+%%
+%% For more details about tags, see the documentation for the
+%% Management Plugin at http://www.rabbitmq.com/management.html.
+%%
+%% {default_user_tags, [administrator]},
+
+{mapping, "default_user_tags.$tag", "rabbit.default_user_tags",
+ [{datatype, {enum, [true, false]}}]}.
+
+{translation, "rabbit.default_user_tags",
+fun(Conf) ->
+ Settings = cuttlefish_variable:filter_by_prefix("default_user_tags", Conf),
+ [ list_to_atom(Key) || {[_,Key], Val} <- Settings, Val == true ]
+end}.
+
+%%
+%% Additional network and protocol related configuration
+%% =====================================================
+%%
+
+%% Set the default AMQP heartbeat delay (in seconds).
+%%
+%% {heartbeat, 600},
+
+{mapping, "heartbeat", "rabbit.heartbeat", [{datatype, integer}]}.
+
+%% Set the max permissible size of an AMQP frame (in bytes).
+%%
+%% {frame_max, 131072},
+
+{mapping, "frame_max", "rabbit.frame_max", [{datatype, bytesize}]}.
+
+%% Set the max frame size the server will accept before connection
+%% tuning occurs
+%%
+%% {initial_frame_max, 4096},
+
+{mapping, "initial_frame_max", "rabbit.initial_frame_max", [{datatype, bytesize}]}.
+
+%% Set the max permissible number of channels per connection.
+%% 0 means "no limit".
+%%
+%% {channel_max, 128},
+
+{mapping, "channel_max", "rabbit.channel_max", [{datatype, integer}]}.
+
+%% Customising Socket Options.
+%%
+%% See (http://www.erlang.org/doc/man/inet.html#setopts-2) for
+%% further documentation.
+%%
+%% {tcp_listen_options, [{backlog, 128},
+%% {nodelay, true},
+%% {exit_on_close, false}]},
+
+%% TCP listener section ======================================================
+
+{mapping, "tcp_listen_options", "rabbit.tcp_listen_options", [
+ {datatype, {enum, [none]}}]}.
+
+{translation, "rabbit.tcp_listen_options",
+fun(Conf) ->
+ case cuttlefish:conf_get("tcp_listen_options", Conf, undefined) of
+ none -> [];
+ _ -> cuttlefish:invalid("Invalid tcp_listen_options")
+ end
+end}.
+
+{mapping, "tcp_listen_options.backlog", "rabbit.tcp_listen_options.backlog", [
+ {datatype, integer}
+]}.
+
+{mapping, "tcp_listen_options.nodelay", "rabbit.tcp_listen_options.nodelay", [
+ {datatype, {enum, [true, false]}}
+]}.
+
+{mapping, "tcp_listen_options.buffer", "rabbit.tcp_listen_options.buffer",
+ [{datatype, integer}]}.
+
+{mapping, "tcp_listen_options.delay_send", "rabbit.tcp_listen_options.delay_send",
+ [{datatype, {enum, [true, false]}}]}.
+
+{mapping, "tcp_listen_options.dontroute", "rabbit.tcp_listen_options.dontroute",
+ [{datatype, {enum, [true, false]}}]}.
+
+{mapping, "tcp_listen_options.exit_on_close", "rabbit.tcp_listen_options.exit_on_close",
+ [{datatype, {enum, [true, false]}}]}.
+
+{mapping, "tcp_listen_options.fd", "rabbit.tcp_listen_options.fd",
+ [{datatype, integer}]}.
+
+{mapping, "tcp_listen_options.high_msgq_watermark", "rabbit.tcp_listen_options.high_msgq_watermark",
+ [{datatype, integer}]}.
+
+{mapping, "tcp_listen_options.high_watermark", "rabbit.tcp_listen_options.high_watermark",
+ [{datatype, integer}]}.
+
+{mapping, "tcp_listen_options.keepalive", "rabbit.tcp_listen_options.keepalive",
+ [{datatype, {enum, [true, false]}}]}.
+
+{mapping, "tcp_listen_options.low_msgq_watermark", "rabbit.tcp_listen_options.low_msgq_watermark",
+ [{datatype, integer}]}.
+
+{mapping, "tcp_listen_options.low_watermark", "rabbit.tcp_listen_options.low_watermark",
+ [{datatype, integer}]}.
+
+{mapping, "tcp_listen_options.port", "rabbit.tcp_listen_options.port",
+ [{datatype, integer}, {validators, ["port"]}]}.
+
+{mapping, "tcp_listen_options.priority", "rabbit.tcp_listen_options.priority",
+ [{datatype, integer}]}.
+
+{mapping, "tcp_listen_options.recbuf", "rabbit.tcp_listen_options.recbuf",
+ [{datatype, integer}]}.
+
+{mapping, "tcp_listen_options.send_timeout", "rabbit.tcp_listen_options.send_timeout",
+ [{datatype, integer}]}.
+
+{mapping, "tcp_listen_options.send_timeout_close", "rabbit.tcp_listen_options.send_timeout_close",
+ [{datatype, {enum, [true, false]}}]}.
+
+{mapping, "tcp_listen_options.sndbuf", "rabbit.tcp_listen_options.sndbuf",
+ [{datatype, integer}]}.
+
+{mapping, "tcp_listen_options.tos", "rabbit.tcp_listen_options.tos",
+ [{datatype, integer}]}.
+
+{mapping, "tcp_listen_options.linger.on", "rabbit.tcp_listen_options.linger",
+ [{datatype, {enum, [true, false]}}]}.
+
+{mapping, "tcp_listen_options.linger.timeout", "rabbit.tcp_listen_options.linger",
+ [{datatype, integer}, {validators, ["non_negative_integer"]}]}.
+
+{translation, "rabbit.tcp_listen_options.linger",
+fun(Conf) ->
+ LingerOn = cuttlefish:conf_get("tcp_listen_options.linger.on", Conf, false),
+ LingerTimeout = cuttlefish:conf_get("tcp_listen_options.linger.timeout", Conf, 0),
+ {LingerOn, LingerTimeout}
+end}.
+
+
+%% ==========================================================================
+
+%%
+%% Resource Limits & Flow Control
+%% ==============================
+%%
+%% See http://www.rabbitmq.com/memory.html for full details.
+
+%% Memory-based Flow Control threshold.
+%%
+%% {vm_memory_high_watermark, 0.4},
+
+%% Alternatively, we can set a limit (in bytes) of RAM used by the node.
+%%
+%% {vm_memory_high_watermark, {absolute, 1073741824}},
+%%
+%% Or you can set absolute value using memory units (with RabbitMQ 3.6.0+).
+%%
+%% {vm_memory_high_watermark, {absolute, "1024M"}},
+%%
+%% Supported units suffixes:
+%%
+%% kb, KB: kibibytes (2^10 bytes)
+%% mb, MB: mebibytes (2^20)
+%% gb, GB: gibibytes (2^30)
+
+{mapping, "vm_memory_high_watermark.relative", "rabbit.vm_memory_high_watermark", [
+ {datatype, float}]}.
+
+{mapping, "vm_memory_high_watermark.absolute", "rabbit.vm_memory_high_watermark", [
+ {datatype, [integer, string]}]}.
+
+
+{translation, "rabbit.vm_memory_high_watermark",
+fun(Conf) ->
+ Settings = cuttlefish_variable:filter_by_prefix("vm_memory_high_watermark", Conf),
+ Absolute = proplists:get_value(["vm_memory_high_watermark", "absolute"], Settings),
+ Relative = proplists:get_value(["vm_memory_high_watermark", "relative"], Settings),
+ case {Absolute, Relative} of
+ {undefined, undefined} -> cuttlefish:invalid("No vm watermark defined");
+ {_, undefined} -> {absolute, Absolute};
+ _ -> Relative
+ end
+end}.
+
+%% Fraction of the high watermark limit at which queues start to
+%% page message out to disc in order to free up memory.
+%%
+%% Values greater than 0.9 can be dangerous and should be used carefully.
+%%
+%% {vm_memory_high_watermark_paging_ratio, 0.5},
+
+{mapping, "vm_memory_high_watermark_paging_ratio",
+ "rabbit.vm_memory_high_watermark_paging_ratio",
+ [{datatype, float}, {validators, ["less_than_1"]}]}.
+
+%% Interval (in milliseconds) at which we perform the check of the memory
+%% levels against the watermarks.
+%%
+%% {memory_monitor_interval, 2500},
+
+{mapping, "memory_monitor_interval", "rabbit.memory_monitor_interval",
+ [{datatype, integer}]}.
+
+%% Set disk free limit (in bytes). Once free disk space reaches this
+%% lower bound, a disk alarm will be set - see the documentation
+%% listed above for more details.
+%%
+%% {disk_free_limit, 50000000},
+%%
+%% Or you can set it using memory units (same as in vm_memory_high_watermark)
+%% with RabbitMQ 3.6.0+.
+%% {disk_free_limit, "50MB"},
+%% {disk_free_limit, "50000kB"},
+%% {disk_free_limit, "2GB"},
+
+%% Alternatively, we can set a limit relative to total available RAM.
+%%
+%% Values lower than 1.0 can be dangerous and should be used carefully.
+%% {disk_free_limit, {mem_relative, 2.0}},
+
+{mapping, "disk_free_limit.relative", "rabbit.disk_free_limit", [
+ {datatype, float}]}.
+
+{mapping, "disk_free_limit.absolute", "rabbit.disk_free_limit", [
+ {datatype, [integer, string]}]}.
+
+
+{translation, "rabbit.disk_free_limit",
+fun(Conf) ->
+ Settings = cuttlefish_variable:filter_by_prefix("disk_free_limit", Conf),
+ Absolute = proplists:get_value(["disk_free_limit", "absolute"], Settings),
+ Relative = proplists:get_value(["disk_free_limit", "relative"], Settings),
+ case {Absolute, Relative} of
+ {undefined, undefined} -> cuttlefish:invalid("No disk limit defined");
+ {_, undefined} -> Absolute;
+ _ -> {mem_relative, Relative}
+ end
+end}.
+
+%%
+%% Clustering
+%% =====================
+%%
+
+%% How to respond to cluster partitions.
+%% See http://www.rabbitmq.com/partitions.html for further details.
+%%
+%% {cluster_partition_handling, ignore},
+
+{mapping, "cluster_partition_handling", "rabbit.cluster_partition_handling",
+ [{datatype, {enum, [ignore, pause_minority, autoheal, pause_if_all_down]}}]}.
+
+{mapping, "cluster_partition_handling.pause_if_all_down.recover",
+ "rabbit.cluster_partition_handling",
+ [{datatype, {enum, [ignore, autoheal]}}]}.
+
+{mapping, "cluster_partition_handling.pause_if_all_down.nodes.$name",
+ "rabbit.cluster_partition_handling",
+ [{datatype, atom}]}.
+
+{translation, "rabbit.cluster_partition_handling",
+fun(Conf) ->
+ case cuttlefish:conf_get("cluster_partition_handling", Conf) of
+ pause_if_all_down ->
+ PauseIfAllDownNodes = cuttlefish_variable:filter_by_prefix(
+ "cluster_partition_handling.pause_if_all_down.nodes",
+ Conf),
+ case PauseIfAllDownNodes of
+ [] ->
+ cuttlefish:invalid("Nodes required for pause_if_all_down");
+ _ ->
+ Nodes = [ V || {K,V} <- PauseIfAllDownNodes ],
+ PauseIfAllDownRecover = cuttlefish:conf_get(
+ "cluster_partition_handling.pause_if_all_down.recover",
+ Conf),
+ case PauseIfAllDownRecover of
+ Recover when Recover == ignore; Recover == autoheal ->
+ {pause_if_all_down, Nodes, Recover};
+ Invalid ->
+ cuttlefish:invalid("Recover strategy required for pause_if_all_down")
+ end
+ end;
+ Other -> Other
+ end
+end}.
+
+%% Mirror sync batch size, in messages. Increasing this will speed
+%% up syncing but total batch size in bytes must not exceed 2 GiB.
+%% Available in RabbitMQ 3.6.0 or later.
+%%
+%% {mirroring_sync_batch_size, 4096},
+
+{mapping, "mirroring_sync_batch_size", "rabbit.mirroring_sync_batch_size",
+ [{datatype, bytesize}, {validators, ["size_less_than_2G"]}]}.
+
+%% Make clustering happen *automatically* at startup - only applied
+%% to nodes that have just been reset or started for the first time.
+%% See http://www.rabbitmq.com/clustering.html#auto-config for
+%% further details.
+%%
+%% {cluster_nodes, {['rabbit@my.host.com'], disc}},
+
+{mapping, "cluster_nodes.disc.$node", "rabbit.cluster_nodes",
+ [{datatype, atom}]}.
+
+{mapping, "cluster_nodes.ram.$node", "rabbit.cluster_nodes",
+ [{datatype, atom}]}.
+
+{translation, "rabbit.cluster_nodes",
+fun(Conf) ->
+ DiskNodes = [ V || {_, V} <- cuttlefish_variable:filter_by_prefix("cluster_nodes.disc", Conf)],
+ RamNodes = [ V || {_, V} <- cuttlefish_variable:filter_by_prefix("cluster_nodes.ram", Conf)],
+
+ case {DiskNodes, RamNodes} of
+ {_, []} -> {DiskNodes, disc};
+ {[], _} -> {RamNodes, ram}
+ end
+end}.
+
+
+%% Interval (in milliseconds) at which we send keepalive messages
+%% to other cluster members. Note that this is not the same thing
+%% as net_ticktime; missed keepalive messages will not cause nodes
+%% to be considered down.
+%%
+%% {cluster_keepalive_interval, 10000},
+
+{mapping, "cluster_keepalive_interval", "rabbit.cluster_keepalive_interval",
+ [{datatype, integer}]}.
+
+
+{mapping, "queue_master_locator", "rabbit.queue_master_locator",
+ [{datatype, string}]}.
+
+{translation, "rabbit.queue_master_locator",
+fun(Conf) ->
+ list_to_binary(cuttlefish:conf_get("queue_master_locator", Conf))
+end}.
+
+%%
+%% Statistics Collection
+%% =====================
+%%
+
+%% Set (internal) statistics collection granularity.
+%%
+%% {collect_statistics, none},
+
+{mapping, "collect_statistics", "rabbit.collect_statistics",
+ [{datatype, {enum, [none, coarse, fine]}}]}.
+
+%% Statistics collection interval (in milliseconds). Increasing
+%% this will reduce the load on management database.
+%%
+%% {collect_statistics_interval, 5000},
+
+{mapping, "collect_statistics_interval", "rabbit.collect_statistics_interval",
+ [{datatype, integer}]}.
+
+%%
+%% Misc/Advanced Options
+%% =====================
+%%
+%% NB: Change these only if you understand what you are doing!
+%%
+
+%% Explicitly enable/disable hipe compilation.
+%%
+%% {hipe_compile, true},
+
+{mapping, "hipe_compile", "rabbit.hipe_compile",
+ [{datatype, {enum, [true, false]}}]}.
+
+%% Timeout used when waiting for Mnesia tables in a cluster to
+%% become available.
+%%
+%% {mnesia_table_loading_timeout, 30000},
+
+{mapping, "mnesia_table_loading_timeout", "rabbit.mnesia_table_loading_timeout",
+ [{datatype, integer}]}.
+
+%% Size in bytes below which to embed messages in the queue index. See
+%% http://www.rabbitmq.com/persistence-conf.html
+%%
+%% {queue_index_embed_msgs_below, 4096}
+
+{mapping, "queue_index_embed_msgs_below", "rabbit.queue_index_embed_msgs_below",
+ [{datatype, bytesize}]}.
+
+% ==========================
+% Lager section
+% ==========================
+
+{mapping, "log.dir", "lager.log_root", [
+ {datatype, string},
+ {validators, ["dir_writable"]}]}.
+
+{mapping, "log.console", "lager.handlers", [
+ {datatype, {enum, [true, false]}}
+]}.
+
+{mapping, "log.syslog", "lager.handlers", [
+ {datatype, {enum, [true, false]}}
+]}.
+{mapping, "log.file", "lager.handlers", [
+ {datatype, [{enum, [false]}, string]}
+]}.
+
+{mapping, "log.file.level", "lager.handlers", [
+ {datatype, {enum, [debug, info, warning, error]}}
+]}.
+{mapping, "log.$handler.level", "lager.handlers", [
+ {datatype, {enum, [debug, info, warning, error]}}
+]}.
+{mapping, "log.file.rotation.date", "lager.handlers", [
+ {datatype, string}
+]}.
+{mapping, "log.file.rotation.size", "lager.handlers", [
+ {datatype, integer}
+]}.
+{mapping, "log.file.rotation.count", "lager.handlers", [
+ {datatype, integer}
+]}.
+
+{mapping, "log.syslog.identity", "lager.handlers", [
+ {datatype, string}
+]}.
+{mapping, "log.syslog.facility", "lager.handlers", [
+ {datatype, atom}
+]}.
+
+{translation, "lager.handlers",
+fun(Conf) ->
+ ConsoleHandler = case cuttlefish:conf_get("log.console", Conf, false) of
+ true ->
+ ConsoleLevel = cuttlefish:conf_get("log.console.level", Conf, info),
+ [{lager_console_backend, ConsoleLevel}];
+ false -> []
+ end,
+ FileHandler = case cuttlefish:conf_get("log.file", Conf, false) of
+ false -> [];
+ File ->
+ FileLevel = cuttlefish:conf_get("log.file.level", Conf, info),
+ RotationDate = cuttlefish:conf_get("log.file.rotation.date", Conf, ""),
+ RotationSize = cuttlefish:conf_get("log.file.rotation.size", Conf, 0),
+ RotationCount = cuttlefish:conf_get("log.file.rotation.count", Conf, 10),
+ [{lager_file_backend, [{file, File},
+ {level, FileLevel},
+ {date, RotationDate},
+ {size, RotationSize},
+ {count, RotationCount}]}]
+ end,
+ SyslogHandler = case cuttlefish:conf_get("log.syslog", Conf, false) of
+ false -> [];
+ true ->
+ SyslogLevel = cuttlefish:conf_get("log.syslog.level", Conf, info),
+ Identity = cuttlefish:conf_get("log.syslog.identity", Conf),
+ Facility = cuttlefish:conf_get("log.syslog.facility", Conf),
+ [{lager_syslog_backend, [Identity, Facility, SyslogLevel]}]
+ end,
+ case ConsoleHandler ++ FileHandler ++ SyslogHandler of
+ [] -> undefined;
+ Other -> Other
+ end
+end}.
+
+
+% ===============================
+% Validators
+% ===============================
+
+{validator, "size_less_than_2G", "Byte size should be less than 2G and greater than 0",
+fun(Size) when is_integer(Size) ->
+ Size > 0 andalso Size < 2147483648
+end}.
+
+{validator, "less_than_1", "Flooat is not beetween 0 and 1",
+fun(Float) when is_float(Float) ->
+ Float > 0 andalso Float < 1
+end}.
+
+{validator, "port", "Invalid port number",
+fun(Port) when is_integer(Port) ->
+ Port > 0 andalso Port < 65535
+end}.
+
+{validator, "byte", "Integer is not 0<i<255",
+fun(Int) when is_integer(Int) ->
+ Int > 0 andalso Int < 255
+end}.
+
+{validator, "dir_writable", "Cannot create file in dir",
+fun(Dir) ->
+ TestFile = filename:join(Dir, "test_file"),
+ file:delete(TestFile),
+ Res = ok == file:write_file(TestFile, <<"test">>),
+ file:delete(TestFile),
+ Res
+end}.
+
+{validator, "file_accessible", "file doesnt exist or unaccessible",
+fun(File) ->
+ ReadFile = file:read_file_info(File),
+ element(1, ReadFile) == ok
+end}.
+
+{validator, "is_ip", "string is a valid IP address",
+fun(IpStr) ->
+ Res = inet:parse_address(IpStr),
+ element(1, Res) == ok
+end}.
+
+{validator, "non_negative_integer", "number should be greater or equal to zero",
+fun(Int) when is_integer(Int) ->
+ Int >= 0
+end}.
diff --git a/rabbitmq.conf.d/ldap.conf b/rabbitmq.conf.d/ldap.conf
new file mode 100644
index 0000000000..2f51cbb409
--- /dev/null
+++ b/rabbitmq.conf.d/ldap.conf
@@ -0,0 +1,138 @@
+# ## ----------------------------------------------------------------------------
+# ## RabbitMQ LDAP Plugin
+# ##
+# ## See http://www.rabbitmq.com/ldap.html for details.
+# ##
+# ## ----------------------------------------------------------------------------
+
+
+# =======================================
+# LDAP section
+# =======================================
+
+# Should be defined in additional.conf maybe?
+
+# {rabbitmq_auth_backend_ldap,
+# [##
+# ## Connecting to the LDAP server(s)
+# ## ================================
+# ##
+
+# ## Specify servers to bind to. You *must* set this in order for the plugin
+# ## to work properly.
+# ##
+# ## {servers, ["your-server-name-goes-here"]},
+
+ldap.servers.myserver = your-server-name-goes-here
+
+# ## Connect to the LDAP server using SSL
+# ##
+# ## {use_ssl, false},
+
+ldap.use_ssl = false
+
+# ## Specify the LDAP port to connect to
+# ##
+# ## {port, 389},
+
+ldap.port = 389
+
+# ## LDAP connection timeout, in milliseconds or 'infinity'
+# ##
+# ## {timeout, infinity},
+
+ldap.timeout = infinity
+
+# Or number
+# ldap.timeout = 500
+
+# ## Enable logging of LDAP queries.
+# ## One of
+# ## - false (no logging is performed)
+# ## - true (verbose logging of the logic used by the plugin)
+# ## - network (as true, but additionally logs LDAP network traffic)
+# ##
+# ## Defaults to false.
+# ##
+# ## {log, false},
+
+ldap.log = false
+
+# Also can be true or network
+# ldap.log = true
+# ldap.log = network
+
+# ##
+# ## Authentication
+# ## ==============
+# ##
+
+# ## Pattern to convert the username given through AMQP to a DN before
+# ## binding
+# ##
+# ## {user_dn_pattern, "cn=${username},ou=People,dc=example,dc=com"},
+
+ldap.user_dn_pattern = cn=${username},ou=People,dc=example,dc=com
+
+# ## Alternatively, you can convert a username to a Distinguished
+# ## Name via an LDAP lookup after binding. See the documentation for
+# ## full details.
+
+# ## When converting a username to a dn via a lookup, set these to
+# ## the name of the attribute that represents the user name, and the
+# ## base DN for the lookup query.
+# ##
+# ## {dn_lookup_attribute, "userPrincipalName"},
+# ## {dn_lookup_base, "DC=gopivotal,DC=com"},
+
+ldap.dn_lookup_attribute = userPrincipalName
+ldap.dn_lookup_base = DC=gopivotal,DC=com
+
+# ## Controls how to bind for authorisation queries and also to
+# ## retrieve the details of users logging in without presenting a
+# ## password (e.g., SASL EXTERNAL).
+# ## One of
+# ## - as_user (to bind as the authenticated user - requires a password)
+# ## - anon (to bind anonymously)
+# ## - {UserDN, Password} (to bind with a specified user name and password)
+# ##
+# ## Defaults to 'as_user'.
+# ##
+# ## {other_bind, as_user},
+
+ldap.other_bind = as_user
+
+# Or can be more complex:
+# ldap.other_bind.user_dn = User
+# ldap.other_bind.password = Password
+# If user_dn and password defined - other options is ignored.
+
+# -----------------------------
+# Too complex section of LDAP
+# -----------------------------
+
+# ##
+# ## Authorisation
+# ## =============
+# ##
+
+# ## The LDAP plugin can perform a variety of queries against your
+# ## LDAP server to determine questions of authorisation. See
+# ## http://www.rabbitmq.com/ldap.html#authorisation for more
+# ## information.
+
+# ## Set the query to use when determining vhost access
+# ##
+# ## {vhost_access_query, {in_group,
+# ## "ou=${vhost}-users,ou=vhosts,dc=example,dc=com"}},
+
+# ## Set the query to use when determining resource (e.g., queue) access
+# ##
+# ## {resource_access_query, {constant, true}},
+
+# ## Set queries to determine which tags a user has
+# ##
+# ## {tag_queries, []}
+# ]},
+# -----------------------------
+
diff --git a/rabbitmq.conf.d/rabbitmq.conf b/rabbitmq.conf.d/rabbitmq.conf
new file mode 100644
index 0000000000..e702ec08b4
--- /dev/null
+++ b/rabbitmq.conf.d/rabbitmq.conf
@@ -0,0 +1,726 @@
+# ======================================
+# RabbbitMQ broker section
+# ======================================
+
+## Network Connectivity
+## ====================
+##
+## By default, RabbitMQ will listen on all interfaces, using
+## the standard (reserved) AMQP port.
+##
+listener.tcp.default = 5672
+
+
+## To listen on a specific interface, provide an IP address with port.
+## For example, to listen only on localhost for both IPv4 and IPv6:
+##
+# IPv4
+# listener.tcp.local = 127.0.0.1:5672
+# IPv6
+# listener.tcp.local_v6 = ::1:5672
+
+## You can define multiple listeners using listener names
+# listener.tcp.other_port = 5673
+# listener.tcp.other_ip = 10.10.10.10:5672
+
+
+## SSL listeners are configured in the same fashion as TCP listeners,
+## including the option to control the choice of interface.
+##
+# listener.ssl.default = 5671
+
+## Number of Erlang processes that will accept connections for the TCP
+## and SSL listeners.
+##
+num_acceptors.tcp = 10
+num_acceptors.ssl = 1
+
+
+## Maximum time for AMQP 0-8/0-9/0-9-1 handshake (after socket connection
+## and SSL handshake), in milliseconds.
+##
+handshake_timeout = 10000
+
+## Set to 'true' to perform reverse DNS lookups when accepting a
+## connection. Hostnames will then be shown instead of IP addresses
+## in rabbitmqctl and the management plugin.
+##
+reverse_dns_lookups = true
+
+##
+## Security / AAA
+## ==============
+##
+
+## The default "guest" user is only permitted to access the server
+## via a loopback interface (e.g. localhost).
+## {loopback_users, [<<"guest">>]},
+##
+loopback_user.guest = true
+
+## Uncomment the following line if you want to allow access to the
+## guest user from anywhere on the network.
+# loopback_user.guest = false
+
+## Configuring SSL.
+## See http://www.rabbitmq.com/ssl.html for full documentation.
+##
+ssl_option.verify = verify_peer
+ssl_option.fail_if_no_peer_cert = false
+# ssl_option.cacertfile = /path/to/rabbitmq.crt
+# ssl_option.certfile = /path/to/rabbitmq.crt
+# ssl_option.keyfile = /path/to/rabbitmq.key
+
+## Choose the available SASL mechanism(s) to expose.
+## The two default (built in) mechanisms are 'PLAIN' and
+## 'AMQPLAIN'. Additional mechanisms can be added via
+## plugins.
+##
+## See http://www.rabbitmq.com/authentication.html for more details.
+##
+auth_mechanism.plain = PLAIN
+auth_mechanism.amqplain = AMQPLAIN
+
+## Select an authentication database to use. RabbitMQ comes bundled
+## with a built-in auth-database, based on mnesia.
+##
+auth_backends.1 = internal
+
+auth_backends.2.authn = ldap
+auth_backends.2.authz = internal
+
+auth_backends.3.authz = rabbit_auth_backend_uaa
+
+## Configurations supporting the rabbitmq_auth_mechanism_ssl and
+## rabbitmq_auth_backend_ldap plugins.
+##
+## NB: These options require that the relevant plugin is enabled.
+## See http://www.rabbitmq.com/plugins.html for further details.
+
+
+## The RabbitMQ-auth-mechanism-ssl plugin makes it possible to
+## authenticate a user based on the client's SSL certificate.
+##
+## To use auth-mechanism-ssl, add to or replace the auth_mechanisms
+## with EXTERNAL value.
+##
+#auth_mechanism.external = EXTERNAL
+
+## The rabbitmq_auth_backend_ldap plugin allows the broker to
+## perform authentication and authorisation by deferring to an
+## external LDAP server.
+##
+## For more information about configuring the LDAP backend, see
+## http://www.rabbitmq.com/ldap.html.
+##
+## Enable the LDAP auth backend by adding to or replacing the
+## auth_backends entry:
+##
+# auth_backends.2 = rabbit_auth_backend_ldap
+
+## Add another backend
+# auth_backend.3 = rabbit_auth_backend_http
+
+
+## This pertains to both the rabbitmq_auth_mechanism_ssl plugin and
+## STOMP ssl_cert_login configurations. See the rabbitmq_stomp
+## configuration section later in this file and the README in
+## https://github.com/rabbitmq/rabbitmq-auth-mechanism-ssl for further
+## details.
+##
+## To use the SSL cert's CN instead of its DN as the username
+##
+# ssl_cert_login_from = common_name
+
+## SSL handshake timeout, in milliseconds.
+##
+# ssl_handshake_timeout = 5000
+
+
+## Password hashing implementation. Will only affect newly
+## created users. To recalculate hash for an existing user
+## it's necessary to update her password.
+##
+## To use SHA-512, set to rabbit_password_hashing_sha512.
+##
+password_hashing_module = rabbit_password_hashing_sha256
+
+## When importing definitions exported from versions earlier
+## than 3.6.0, it is possible to go back to MD5 (only do this
+## as a temporary measure!) by setting this to rabbit_password_hashing_md5.
+##
+# password_hashing_module = rabbit_password_hashing_md5
+
+##
+## Default User / VHost
+## ====================
+##
+
+## On first start RabbitMQ will create a vhost and a user. These
+## config items control what gets created. See
+## http://www.rabbitmq.com/access-control.html for further
+## information about vhosts and access control.
+##
+default_vhost = /
+default_user = guest
+default_pass = guest
+
+default_permissions.configure = .*
+default_permissions.read = .*
+default_permissions.write = .*
+
+## Tags for default user
+##
+## For more details about tags, see the documentation for the
+## Management Plugin at http://www.rabbitmq.com/management.html.
+##
+default_user_tags.administrator = true
+
+## Define other tags like this:
+# default_user_tags.management = true
+# default_user_tags.custom_tag = true
+
+##
+## Additional network and protocol related configuration
+## =====================================================
+##
+
+## Set the default AMQP heartbeat delay (in seconds).
+##
+heartbeat = 600
+
+## Set the max permissible size of an AMQP frame (in bytes).
+##
+frame_max = 131072
+
+## Set the max frame size the server will accept before connection
+## tuning occurs
+##
+initial_frame_max = 4096
+
+## Set the max permissible number of channels per connection.
+## 0 means "no limit".
+##
+channel_max = 128
+
+## Customising Socket Options.
+##
+## See (http://www.erlang.org/doc/man/inet.html#setopts-2) for
+## further documentation.
+##
+
+tcp_listen_option.backlog = 128
+tcp_listen_option.nodelay = true
+tcp_listen_option.exit_on_close = false
+
+##
+## Resource Limits & Flow Control
+## ==============================
+##
+## See http://www.rabbitmq.com/memory.html for full details.
+
+## Memory-based Flow Control threshold.
+##
+vm_memory_high_watermark.relative = 0.4
+
+## Alternatively, we can set a limit (in bytes) of RAM used by the node.
+##
+# vm_memory_high_watermark.absolute = 1073741824
+
+## Or you can set absolute value using memory units (with RabbitMQ 3.6.0+).
+## Absolute watermark will be ignored if relative is defined!
+##
+# vm_memory_high_watermark.absolute = 2GB
+##
+## Supported units suffixes:
+##
+## kb, KB: kibibytes (2^10 bytes)
+## mb, MB: mebibytes (2^20)
+## gb, GB: gibibytes (2^30)
+
+
+
+## Fraction of the high watermark limit at which queues start to
+## page message out to disc in order to free up memory.
+##
+## Values greater than 0.9 can be dangerous and should be used carefully.
+##
+vm_memory_high_watermark_paging_ratio = 0.5
+
+## Interval (in milliseconds) at which we perform the check of the memory
+## levels against the watermarks.
+##
+memory_monitor_interval = 2500
+
+## Set disk free limit (in bytes). Once free disk space reaches this
+## lower bound, a disk alarm will be set - see the documentation
+## listed above for more details.
+##
+## Absolute watermark will be ignored if relative is defined!
+disk_free_limit.absolute = 50000
+
+## Or you can set it using memory units (same as in vm_memory_high_watermark)
+## with RabbitMQ 3.6.0+.
+# disk_free_limit.absolute = 500KB
+# disk_free_limit.absolute = 50mb
+# disk_free_limit.absolute = 5GB
+
+## Alternatively, we can set a limit relative to total available RAM.
+##
+## Values lower than 1.0 can be dangerous and should be used carefully.
+disk_free_limit.relative = 2.0
+
+##
+## Clustering
+## =====================
+##
+cluster_partition_handling = ignore
+
+## pause_if_all_down strategy require additional configuration
+# cluster_partition_handling = pause_if_all_down
+
+## Recover strategy. Can be either 'autoheal' or 'ignore'
+# cluster_partition_handling.pause_if_all_down.recover = ignore
+
+## Node names to check
+# cluster_partition_handling.pause_if_all_down.node.rabbit = rabbit@localhost
+# cluster_partition_handling.pause_if_all_down.node.hare = hare@localhost
+
+## Mirror sync batch size, in messages. Increasing this will speed
+## up syncing but total batch size in bytes must not exceed 2 GiB.
+## Available in RabbitMQ 3.6.0 or later.
+##
+mirroring_sync_batch_size = 4096
+
+## Make clustering happen *automatically* at startup - only applied
+## to nodes that have just been reset or started for the first time.
+## See http://www.rabbitmq.com/clustering.html#auto-config for
+## further details.
+##
+# cluster_nodes.disc.1 = rabbit@my.host.com
+
+## You can define multiple nodes
+# cluster_nodes.disc.2 = hare@my.host.com
+
+## There can be also ram nodes.
+## Ram nodes should not be defined together with disk nodes
+# cluster_nodes.ram.1 = rabbit@my.host.com
+
+## Interval (in milliseconds) at which we send keepalive messages
+## to other cluster members. Note that this is not the same thing
+## as net_ticktime; missed keepalive messages will not cause nodes
+## to be considered down.
+##
+# cluster_keepalive_interval = 10000
+
+##
+## Statistics Collection
+## =====================
+##
+
+## Set (internal) statistics collection granularity.
+##
+## Can be none, coarse or fine
+collect_statistics = none
+
+# collect_statistics = coarse
+
+## Statistics collection interval (in milliseconds). Increasing
+## this will reduce the load on management database.
+##
+collect_statistics_interval = 5000
+
+##
+## Misc/Advanced Options
+## =====================
+##
+## NB: Change these only if you understand what you are doing!
+##
+
+## Explicitly enable/disable hipe compilation.
+##
+hipe_compile = false
+
+## Timeout used when waiting for Mnesia tables in a cluster to
+## become available.
+##
+mnesia_table_loading_timeout = 30000
+
+## Size in bytes below which to embed messages in the queue index. See
+## http://www.rabbitmq.com/persistence-conf.html
+##
+queue_index_embed_msgs_below = 4096
+
+## You can also set this size in memory units
+##
+queue_index_embed_msgs_below = 4kb
+
+## ----------------------------------------------------------------------------
+## Advanced Erlang Networking/Clustering Options.
+##
+## See http://www.rabbitmq.com/clustering.html for details
+## ----------------------------------------------------------------------------
+
+# ======================================
+# Kernel section
+# ======================================
+
+# kernel.net_ticktime = 60
+
+## ----------------------------------------------------------------------------
+## RabbitMQ Management Plugin
+##
+## See http://www.rabbitmq.com/management.html for details
+## ----------------------------------------------------------------------------
+
+# =======================================
+# Management section
+# =======================================
+
+## Pre-Load schema definitions from the following JSON file. See
+## http://www.rabbitmq.com/management.html#load-definitions
+##
+# management.load_definitions = /path/to/schema.json
+
+## Log all requests to the management HTTP API to a file.
+##
+# management.http_log_dir = /path/to/access.log
+
+## Change the port on which the HTTP listener listens,
+## specifying an interface for the web server to bind to.
+## Also set the listener to use SSL and provide SSL options.
+##
+
+# QA: Maybe use IP type like in tcp_listener?
+management.listener.port = 12345
+management.listener.ip = 127.0.0.1
+# management.listener.ssl = true
+
+# management.listener.ssl_opts.cacertfile = /path/to/cacert.pem
+# management.listener.ssl_opts.certfile = /path/to/cert.pem
+# management.listener.ssl_opts.keyfile = /path/to/key.pem
+
+## One of 'basic', 'detailed' or 'none'. See
+## http://www.rabbitmq.com/management.html#fine-stats for more details.
+management.rates_mode = basic
+
+## Configure how long aggregated data (such as message rates and queue
+## lengths) is retained. Please read the plugin's documentation in
+## http://www.rabbitmq.com/management.html#configuration for more
+## details.
+## Your can use 'minute', 'hour' and '24hours' keys or integer key (in seconds)
+management.sample_retention_policies.global.minute = 5
+management.sample_retention_policies.global.hour = 60
+management.sample_retention_policies.global.day = 1200
+
+management.sample_retention_policies.basic.minute = 5
+management.sample_retention_policies.basic.hour = 60
+
+management.sample_retention_policies.detailed.10 = 5
+
+## ----------------------------------------------------------------------------
+## RabbitMQ Shovel Plugin
+##
+## See http://www.rabbitmq.com/shovel.html for details
+## ----------------------------------------------------------------------------
+
+## Shovel plugin config example is defined in additional.config file
+
+
+## ----------------------------------------------------------------------------
+## RabbitMQ Stomp Adapter
+##
+## See http://www.rabbitmq.com/stomp.html for details
+## ----------------------------------------------------------------------------
+
+# =======================================
+# STOMP section
+# =======================================
+
+## Network Configuration - the format is generally the same as for the broker
+##
+stomp.listener.tcp.default = 61613
+
+## Same for ssl listeners
+##
+# stomp.listener.ssl.default = 61614
+
+## Number of Erlang processes that will accept connections for the TCP
+## and SSL listeners.
+##
+stomp.num_acceptors.tcp = 10
+stomp.num_acceptors.ssl = 1
+
+## Additional SSL options
+
+## Extract a name from the client's certificate when using SSL.
+##
+stomp.ssl_cert_login = true
+
+## Set a default user name and password. This is used as the default login
+## whenever a CONNECT frame omits the login and passcode headers.
+##
+## Please note that setting this will allow clients to connect without
+## authenticating!
+##
+# stomp.default_user = guest
+# stomp.default_pass = guest
+
+## If a default user is configured, or you have configured use SSL client
+## certificate based authentication, you can choose to allow clients to
+## omit the CONNECT frame entirely. If set to true, the client is
+## automatically connected as the default user or user supplied in the
+## SSL certificate whenever the first frame sent on a session is not a
+## CONNECT frame.
+##
+# stomp.implicit_connect = true
+
+## ----------------------------------------------------------------------------
+## RabbitMQ MQTT Adapter
+##
+## See https://github.com/rabbitmq/rabbitmq-mqtt/blob/stable/README.md
+## for details
+## ----------------------------------------------------------------------------
+
+# =======================================
+# MQTT section
+# =======================================
+
+## Set the default user name and password. Will be used as the default login
+## if a connecting client provides no other login details.
+##
+## Please note that setting this will allow clients to connect without
+## authenticating!
+##
+# mqtt.default_user = guest
+# mqtt.default_pass = guest
+
+## Enable anonymous access. If this is set to false, clients MUST provide
+## login information in order to connect. See the default_user/default_pass
+## configuration elements for managing logins without authentication.
+##
+# mqtt.allow_anonymous = true
+
+## If you have multiple chosts, specify the one to which the
+## adapter connects.
+##
+mqtt.vhost = /
+
+## Specify the exchange to which messages from MQTT clients are published.
+##
+mqtt.exchange = amq.topic
+
+## Specify TTL (time to live) to control the lifetime of non-clean sessions.
+##
+# mqtt.subscription_ttl = 1800000
+
+## Set the prefetch count (governing the maximum number of unacknowledged
+## messages that will be delivered).
+##
+mqtt.prefetch = 10
+
+## TCP/SSL Configuration (as per the broker configuration).
+##
+mqtt.listener.tcp.default = 1883
+
+## Same for ssl listener
+##
+# mqtt.listener.ssl.default = 1884
+
+## Number of Erlang processes that will accept connections for the TCP
+## and SSL listeners.
+##
+mqtt.num_acceptors.tcp = 10
+mqtt.num_acceptors.ssl = 1
+
+## TCP/Socket options (as per the broker configuration).
+##
+# mqtt.tcp_listen_option.backlog = 128
+# mqtt.tcp_listen_option.nodelay = true
+
+## ----------------------------------------------------------------------------
+## RabbitMQ AMQP 1.0 Support
+##
+## See https://github.com/rabbitmq/rabbitmq-amqp1.0/blob/stable/README.md
+## for details
+## ----------------------------------------------------------------------------
+
+# =======================================
+# AMQP_1 section
+# =======================================
+
+
+## Connections that are not authenticated with SASL will connect as this
+## account. See the README for more information.
+##
+## Please note that setting this will allow clients to connect without
+## authenticating!
+##
+amqp1_0.default_user = guest
+
+## Enable protocol strict mode. See the README for more information.
+##
+amqp1_0.protocol_strict_mode = false
+
+## Lager controls logging.
+## See https://github.com/basho/lager for more documentation
+##
+## Log direcrory, taken from the RABBITMQ_LOG_BASE env variable by default.
+##
+# log.dir = /var/log/rabbitmq
+
+## Logging to console (can be true or false)
+##
+# log.console = false
+
+## Loglevel to log to console
+##
+# log.console.level = info
+
+## Logging to file. Can be false or filename.
+## Default:
+# log.file = rabbit.log
+
+## To turn off:
+# log.file = false
+
+## Loglevel to log to file
+##
+# log.file.level = info
+
+## File rotation config. No rotation by defualt.
+## DO NOT SET rotation date to ''. Leave unset if require "" value
+# log.file.rotation.date = $D0
+# log.file.rotation.size = 0
+
+
+## QA: Config for syslog logging
+# log.syslog = false
+# log.syslog.identity = rabbitmq
+# log.syslog.level = info
+# log.syslog.facility = daemon
+
+
+## ----------------------------------------------------------------------------
+## RabbitMQ LDAP Plugin
+##
+## See http://www.rabbitmq.com/ldap.html for details.
+##
+## ----------------------------------------------------------------------------
+
+# =======================================
+# LDAP section
+# =======================================
+
+##
+## Connecting to the LDAP server(s)
+## ================================
+##
+
+## Specify servers to bind to. You *must* set this in order for the plugin
+## to work properly.
+##
+# ldap.servers.1 = your-server-name-goes-here
+
+## You can define multiple servers
+# ldap.servers.2 = your-other-server
+
+## Connect to the LDAP server using SSL
+##
+# ldap.use_ssl = false
+
+## Specify the LDAP port to connect to
+##
+# ldap.port = 389
+
+## LDAP connection timeout, in milliseconds or 'infinity'
+##
+# ldap.timeout = infinity
+
+## Or number
+# ldap.timeout = 500
+
+## Enable logging of LDAP queries.
+## One of
+## - false (no logging is performed)
+## - true (verbose logging of the logic used by the plugin)
+## - network (as true, but additionally logs LDAP network traffic)
+##
+## Defaults to false.
+##
+# ldap.log = false
+
+## Also can be true or network
+# ldap.log = true
+# ldap.log = network
+
+##
+## Authentication
+## ==============
+##
+
+## Pattern to convert the username given through AMQP to a DN before
+## binding
+##
+# ldap.user_dn_pattern = cn=${username},ou=People,dc=example,dc=com
+
+## Alternatively, you can convert a username to a Distinguished
+## Name via an LDAP lookup after binding. See the documentation for
+## full details.
+
+## When converting a username to a dn via a lookup, set these to
+## the name of the attribute that represents the user name, and the
+## base DN for the lookup query.
+##
+# ldap.dn_lookup_attribute = userPrincipalName
+# ldap.dn_lookup_base = DC=gopivotal,DC=com
+
+## Controls how to bind for authorisation queries and also to
+## retrieve the details of users logging in without presenting a
+## password (e.g., SASL EXTERNAL).
+## One of
+## - as_user (to bind as the authenticated user - requires a password)
+## - anon (to bind anonymously)
+## - {UserDN, Password} (to bind with a specified user name and password)
+##
+## Defaults to 'as_user'.
+##
+# ldap.other_bind = as_user
+
+## Or can be more complex:
+# ldap.other_bind.user_dn = User
+# ldap.other_bind.password = Password
+
+## If user_dn and password defined - other options is ignored.
+
+# -----------------------------
+# Too complex section of LDAP
+# -----------------------------
+
+##
+## Authorisation
+## =============
+##
+
+## The LDAP plugin can perform a variety of queries against your
+## LDAP server to determine questions of authorisation. See
+## http://www.rabbitmq.com/ldap.html#authorisation for more
+## information.
+
+## Following configuration should be defined in additional.config file
+## DO NOT UNCOMMENT THIS LINES!
+
+## Set the query to use when determining vhost access
+##
+## {vhost_access_query, {in_group,
+## "ou=${vhost}-users,ou=vhosts,dc=example,dc=com"}},
+
+## Set the query to use when determining resource (e.g., queue) access
+##
+## {resource_access_query, {constant, true}},
+
+## Set queries to determine which tags a user has
+##
+## {tag_queries, []}
+# ]},
+# -----------------------------
diff --git a/scripts/cuttlefish b/scripts/cuttlefish
new file mode 100755
index 0000000000..6c1e4bbb89
--- /dev/null
+++ b/scripts/cuttlefish
Binary files differ
diff --git a/scripts/rabbitmq-defaults b/scripts/rabbitmq-defaults
index baffce80de..5342978694 100755
--- a/scripts/rabbitmq-defaults
+++ b/scripts/rabbitmq-defaults
@@ -38,6 +38,9 @@ CONFIG_FILE=${SYS_PREFIX}/etc/rabbitmq/rabbitmq
LOG_BASE=${SYS_PREFIX}/var/log/rabbitmq
MNESIA_BASE=${SYS_PREFIX}/var/lib/rabbitmq/mnesia
ENABLED_PLUGINS_FILE=${SYS_PREFIX}/etc/rabbitmq/enabled_plugins
+GENERATED_CONFIG_DIR=${SYS_PREFIX}/var/lib/rabbitmq/config
+ADVANCED_CONFIG_FILE=${SYS_PREFIX}/etc/rabbitmq/advanced
+SCHEMA_DIR=${SYS_PREFIX}/var/lib/rabbitmq/schema
PLUGINS_DIR="${RABBITMQ_HOME}/plugins"
diff --git a/scripts/rabbitmq-defaults.bat b/scripts/rabbitmq-defaults.bat
index 8fff5ea827..0246dc64fd 100644
--- a/scripts/rabbitmq-defaults.bat
+++ b/scripts/rabbitmq-defaults.bat
@@ -41,6 +41,9 @@ set CONFIG_FILE=!RABBITMQ_BASE!\rabbitmq
set LOG_BASE=!RABBITMQ_BASE!\log
set MNESIA_BASE=!RABBITMQ_BASE!\db
set ENABLED_PLUGINS_FILE=!RABBITMQ_BASE!\enabled_plugins
+set GENERATED_CONFIG_DIR=!RABBITMQ_BASE!\config
+set ADVANCED_CONFIG_FILE=!RABBITMQ_BASE!\advanced
+set SCHEMA_DIR=!RABBITMQ_BASE!\schema
REM PLUGINS_DIR="${RABBITMQ_HOME}/plugins"
for /f "delims=" %%F in ("!TDP0!..\plugins") do set PLUGINS_DIR=%%~dpsF%%~nF%%~xF
diff --git a/scripts/rabbitmq-env b/scripts/rabbitmq-env
index 0fa164cfe1..1b9c6df4b3 100755
--- a/scripts/rabbitmq-env
+++ b/scripts/rabbitmq-env
@@ -183,6 +183,9 @@ DEFAULT_NODE_PORT=5672
[ "x" = "x$RABBITMQ_SERVER_CODE_PATH" ] && RABBITMQ_SERVER_CODE_PATH=${SERVER_CODE_PATH}
[ "x" = "x$RABBITMQ_MNESIA_DIR" ] && RABBITMQ_MNESIA_DIR=${MNESIA_DIR}
[ "x" = "x$RABBITMQ_MNESIA_DIR" ] && RABBITMQ_MNESIA_DIR=${RABBITMQ_MNESIA_BASE}/${RABBITMQ_NODENAME}
+[ "x" = "x$RABBITMQ_GENERATED_CONFIG_DIR" ] && RABBITMQ_GENERATED_CONFIG_DIR=${GENERATED_CONFIG_DIR}
+[ "x" = "x$RABBITMQ_ADVANCED_CONFIG_FILE" ] && RABBITMQ_ADVANCED_CONFIG_FILE=${ADVANCED_CONFIG_FILE}
+[ "x" = "x$RABBITMQ_SCHEMA_DIR" ] && RABBITMQ_SCHEMA_DIR=${SCHEMA_DIR}
rmq_normalize_path_var \
RABBITMQ_CONFIG_FILE \
@@ -210,13 +213,11 @@ rmq_normalize_path_var RABBITMQ_PLUGINS_DIR
## Log rotation
[ "x" = "x$RABBITMQ_LOGS" ] && RABBITMQ_LOGS=${LOGS}
+[ "x" != "x$RABBITMQ_LOGS" ] && export RABBITMQ_LOGS_source=environment
[ "x" = "x$RABBITMQ_LOGS" ] && RABBITMQ_LOGS="${RABBITMQ_LOG_BASE}/${RABBITMQ_NODENAME}.log"
-[ "x" = "x$RABBITMQ_SASL_LOGS" ] && RABBITMQ_SASL_LOGS=${SASL_LOGS}
-[ "x" = "x$RABBITMQ_SASL_LOGS" ] && RABBITMQ_SASL_LOGS="${RABBITMQ_LOG_BASE}/${RABBITMQ_NODENAME}-sasl.log"
rmq_normalize_path_var \
- RABBITMQ_LOGS \
- RABBITMQ_SASL_LOGS
+ RABBITMQ_LOGS
[ "x" = "x$RABBITMQ_CTL_ERL_ARGS" ] && RABBITMQ_CTL_ERL_ARGS=${CTL_ERL_ARGS}
@@ -232,8 +233,7 @@ rmq_check_if_shared_with_mnesia \
RABBITMQ_PLUGINS_EXPAND_DIR \
RABBITMQ_ENABLED_PLUGINS_FILE \
RABBITMQ_PLUGINS_DIR \
- RABBITMQ_LOGS \
- RABBITMQ_SASL_LOGS
+ RABBITMQ_LOGS
##--- End of overridden <var_name> variables
@@ -260,7 +260,8 @@ if [ "${RABBITMQ_DEV_ENV}" ]; then
RABBITMQ_ENABLED_PLUGINS_FILE="${enabled_plugins_file}"
fi
fi
-
+
+
if [ -d "${RABBITMQ_PLUGINS_DIR}" ]; then
# RabbitMQ was started with "make run-broker" from its own
# source tree. Take rabbit_common from the plugins directory.
diff --git a/scripts/rabbitmq-env.bat b/scripts/rabbitmq-env.bat
index 5f6ae970e9..a1576e9b27 100644
--- a/scripts/rabbitmq-env.bat
+++ b/scripts/rabbitmq-env.bat
@@ -84,8 +84,8 @@ if "!RABBITMQ_NODENAME!"=="" (
if "!NODENAME!"=="" (
REM We use Erlang to query the local hostname because
REM !COMPUTERNAME! and Erlang may return different results.
- REM Start erl with -sname to make sure epmd is started.
- call "%ERLANG_HOME%\bin\erl.exe" -A0 -noinput -boot start_clean -sname rabbit-prelaunch-epmd -eval "init:stop()." >nul 2>&1
+ REM Start erl with -sname to make sure epmd is started.
+ call "%ERLANG_HOME%\bin\erl.exe" -A0 -noinput -boot start_clean -sname rabbit-prelaunch-epmd -eval "init:stop()." >nul 2>&1
for /f "delims=" %%F in ('call "%ERLANG_HOME%\bin\erl.exe" -A0 -noinput -boot start_clean -eval "net_kernel:start([list_to_atom(""rabbit-gethostname-"" ++ os:getpid()), %NAMETYPE%]), [_, H] = string:tokens(atom_to_list(node()), ""@""), io:format(""~s~n"", [H]), init:stop()."') do @set HOSTNAME=%%F
set RABBITMQ_NODENAME=rabbit@!HOSTNAME!
set HOSTNAME=
@@ -159,6 +159,8 @@ if "!RABBITMQ_SERVER_ERL_ARGS!"=="" (
)
REM [ "x" = "x$RABBITMQ_CONFIG_FILE" ] && RABBITMQ_CONFIG_FILE=${CONFIG_FILE}
+
+CALL :unquote RABBITMQ_CONFIG_FILE %RABBITMQ_CONFIG_FILE%
if "!RABBITMQ_CONFIG_FILE!"=="" (
if "!CONFIG_FILE!"=="" (
set RABBITMQ_CONFIG_FILE=!RABBITMQ_BASE!\rabbitmq
@@ -167,6 +169,32 @@ if "!RABBITMQ_CONFIG_FILE!"=="" (
)
)
+if "!RABBITMQ_GENERATED_CONFIG_DIR!"=="" (
+ if "!GENERATED_CONFIG_DIR!"=="" (
+ set RABBITMQ_GENERATED_CONFIG_DIR=!RABBITMQ_BASE!\config
+ ) else (
+ set RABBITMQ_GENERATED_CONFIG_DIR=!GENERATED_CONFIG_DIR!
+ )
+)
+
+CALL :unquote RABBITMQ_ADVANCED_CONFIG_FILE %RABBITMQ_ADVANCED_CONFIG_FILE%
+if "!RABBITMQ_ADVANCED_CONFIG_FILE!"=="" (
+ if "!ADVANCED_CONFIG_FILE!"=="" (
+ set RABBITMQ_ADVANCED_CONFIG_FILE=!RABBITMQ_BASE!\advanced
+ ) else (
+ set RABBITMQ_ADVANCED_CONFIG_FILE=!ADVANCED_CONFIG_FILE!
+ )
+)
+
+if "!RABBITMQ_SCHEMA_DIR!" == "" (
+ if "!SCHEMA_DIR!"=="" (
+ set RABBITMQ_SCHEMA_DIR=!RABBITMQ_HOME!\priv\schema
+ ) else (
+ set RABBITMQ_SCHEMA_DIR=!SCHEMA_DIR!
+ )
+)
+
+
REM [ "x" = "x$RABBITMQ_LOG_BASE" ] && RABBITMQ_LOG_BASE=${LOG_BASE}
if "!RABBITMQ_LOG_BASE!"=="" (
if "!LOG_BASE!"=="" (
@@ -289,23 +317,6 @@ if not "!RABBITMQ_LOGS" == "-" (
for /f "delims=" %%F in ("!RABBITMQ_LOGS!") do set RABBITMQ_LOGS=%%~sF
)
-REM [ "x" = "x$RABBITMQ_SASL_LOGS" ] && RABBITMQ_SASL_LOGS=${SASL_LOGS}
-REM [ "x" = "x$RABBITMQ_SASL_LOGS" ] && RABBITMQ_SASL_LOGS="${RABBITMQ_LOG_BASE}/${RABBITMQ_NODENAME}-sasl.log"
-if "!RABBITMQ_SASL_LOGS!"=="" (
- if "!SASL_LOGS!"=="" (
- set RABBITMQ_SASL_LOGS=!RABBITMQ_LOG_BASE!\!RABBITMQ_NODENAME!-sasl.log
- ) else (
- set RABBITMQ_SASL_LOGS=!SASL_LOGS!
- )
-)
-if not "!RABBITMQ_SASL_LOGS" == "-" (
- if not exist "!RABBITMQ_SASL_LOGS!" (
- for /f "delims=" %%F in ("!RABBITMQ_SASL_LOGS!") do mkdir %%~dpF 2>NUL
- copy /y NUL "!RABBITMQ_SASL_LOGS!" >NUL
- )
- for /f "delims=" %%F in ("!RABBITMQ_SASL_LOGS!") do set RABBITMQ_SASL_LOGS=%%~sF
-)
-
REM [ "x" = "x$RABBITMQ_CTL_ERL_ARGS" ] && RABBITMQ_CTL_ERL_ARGS=${CTL_ERL_ARGS}
if "!$RABBITMQ_CTL_ERL_ARGS!"=="" (
if not "!CTL_ERL_ARGS!"=="" (
@@ -418,3 +429,7 @@ REM ##--- End of overridden <var_name> variables
REM
REM # Since we source this elsewhere, don't accidentally stop execution
REM true
+
+:unquote
+set %1=%~2
+EXIT /B 0
diff --git a/scripts/rabbitmq-server b/scripts/rabbitmq-server
index c99022fcf6..a0500ebb87 100755
--- a/scripts/rabbitmq-server
+++ b/scripts/rabbitmq-server
@@ -62,6 +62,11 @@ RABBITMQ_EBIN_ROOT="${RABBITMQ_HOME}/ebin"
set +e
+RABBITMQ_ADVANCED_CONFIG_FILE_NOEX="${RABBITMQ_ADVANCED_CONFIG_FILE%.*}"
+if [ "${RABBITMQ_ADVANCED_CONFIG_FILE_NOEX}.config" = "${RABBITMQ_ADVANCED_CONFIG_FILE}" ]; then
+ RABBITMQ_ADVANCED_CONFIG_FILE="${RABBITMQ_ADVANCED_CONFIG_FILE_NOEX}"
+fi
+
# `net_kernel:start/1` will fail in `longnames` mode when erlang is
# unable to determine FQDN of a node (with a dot in it). But `erl`
# itself has some magic that still allow it to start when you
@@ -84,6 +89,9 @@ RABBITMQ_DIST_PORT=$RABBITMQ_DIST_PORT \
-hidden \
-s rabbit_prelaunch \
${RABBITMQ_NAME_TYPE} ${RABBITMQ_PRELAUNCH_NODENAME} \
+ -conf_advanced "${RABBITMQ_ADVANCED_CONFIG_FILE}" \
+ -rabbit enabled_plugins_file "\"$RABBITMQ_ENABLED_PLUGINS_FILE\"" \
+ -rabbit plugins_dir "\"$RABBITMQ_PLUGINS_DIR\"" \
-extra "${RABBITMQ_NODENAME}"
PRELAUNCH_RESULT=$?
@@ -97,30 +105,60 @@ else
exit ${PRELAUNCH_RESULT}
fi
+if [ ! -d ${RABBITMQ_SCHEMA_DIR} ]; then
+ mkdir "${RABBITMQ_SCHEMA_DIR}"
+fi
+
+if [ ! -f "${RABBITMQ_SCHEMA_DIR}/rabbitmq.schema" ]; then
+ cp "${RABBITMQ_HOME}/priv/schema/rabbitmq.schema" "${RABBITMQ_SCHEMA_DIR}"
+fi
+
set -e
-RABBITMQ_CONFIG_ARG=
-[ -f "${RABBITMQ_CONFIG_FILE}.config" ] && RABBITMQ_CONFIG_ARG="-config ${RABBITMQ_CONFIG_FILE}"
+RABBITMQ_CONFIG_FILE_NOEX="${RABBITMQ_CONFIG_FILE%.*}"
+
+if [ "${RABBITMQ_CONFIG_FILE_NOEX}.config" = "${RABBITMQ_CONFIG_FILE}" ]; then
+ if [ -f "${RABBITMQ_CONFIG_FILE}" ]; then
+ RABBITMQ_CONFIG_ARG="-config ${RABBITMQ_CONFIG_FILE_NOEX}"
+ fi
+elif [ "${RABBITMQ_CONFIG_FILE_NOEX}.conf" = "${RABBITMQ_CONFIG_FILE}" ]; then
+ RABBITMQ_CONFIG_ARG="-conf ${RABBITMQ_CONFIG_FILE_NOEX} \
+ -conf_dir ${RABBITMQ_GENERATED_CONFIG_DIR} \
+ -conf_script_dir `dirname $0` \
+ -conf_schema_dir ${RABBITMQ_SCHEMA_DIR}"
+ if [ -f "${RABBITMQ_ADVANCED_CONFIG_FILE}.config" ]; then
+ RABBITMQ_CONFIG_ARG="${RABBITMQ_CONFIG_ARG} \
+ -conf_advanced ${RABBITMQ_ADVANCED_CONFIG_FILE} \
+ -config ${RABBITMQ_ADVANCED_CONFIG_FILE}"
+ fi
+else
+ if [ -f "${RABBITMQ_CONFIG_FILE}.config" ]; then
+ RABBITMQ_CONFIG_ARG="-config ${RABBITMQ_CONFIG_FILE}"
+ elif [ -f "${RABBITMQ_CONFIG_FILE}.conf" ]; then
+ RABBITMQ_CONFIG_ARG="-conf ${RABBITMQ_CONFIG_FILE} \
+ -conf_dir ${RABBITMQ_GENERATED_CONFIG_DIR} \
+ -conf_script_dir `dirname $0` \
+ -conf_schema_dir ${RABBITMQ_SCHEMA_DIR}"
+ if [ -f "${RABBITMQ_ADVANCED_CONFIG_FILE}.config" ]; then
+ RABBITMQ_CONFIG_ARG="${RABBITMQ_CONFIG_ARG} \
+ -conf_advanced ${RABBITMQ_ADVANCED_CONFIG_FILE} \
+ -config ${RABBITMQ_ADVANCED_CONFIG_FILE}"
+ fi
+ fi
+fi
RABBITMQ_LISTEN_ARG=
[ "x" != "x$RABBITMQ_NODE_PORT" ] && [ "x" != "x$RABBITMQ_NODE_IP_ADDRESS" ] && RABBITMQ_LISTEN_ARG="-rabbit tcp_listeners [{\""${RABBITMQ_NODE_IP_ADDRESS}"\","${RABBITMQ_NODE_PORT}"}]"
-# If $RABBITMQ_LOGS is '-', send all log messages to stdout. Likewise
-# for RABBITMQ_SASL_LOGS. This is particularily useful for Docker
-# images.
+# If $RABBITMQ_LOGS is '-', send all log messages to stdout. This is
+# particularily useful for Docker images.
if [ "$RABBITMQ_LOGS" = '-' ]; then
- RABBIT_ERROR_LOGGER='tty'
-else
- RABBIT_ERROR_LOGGER='{file,"'${RABBITMQ_LOGS}'"}'
-fi
-
-if [ "$RABBITMQ_SASL_LOGS" = '-' ]; then
SASL_ERROR_LOGGER=tty
- RABBIT_SASL_ERROR_LOGGER='tty'
+ RABBIT_LAGER_HANDLER=tty
else
SASL_ERROR_LOGGER=false
- RABBIT_SASL_ERROR_LOGGER='{file,"'${RABBITMQ_SASL_LOGS}'"}'
+ RABBIT_LAGER_HANDLER='"'${RABBITMQ_LOGS}'"'
fi
# we need to turn off path expansion because some of the vars, notably
@@ -166,8 +204,8 @@ start_rabbitmq_server() {
${RABBITMQ_LISTEN_ARG} \
-sasl errlog_type error \
-sasl sasl_error_logger "$SASL_ERROR_LOGGER" \
- -rabbit error_logger "$RABBIT_ERROR_LOGGER" \
- -rabbit sasl_error_logger "$RABBIT_SASL_ERROR_LOGGER" \
+ -rabbit lager_log_root "\"$RABBITMQ_LOG_BASE\"" \
+ -rabbit lager_handler "$RABBIT_LAGER_HANDLER" \
-rabbit enabled_plugins_file "\"$RABBITMQ_ENABLED_PLUGINS_FILE\"" \
-rabbit plugins_dir "\"$RABBITMQ_PLUGINS_DIR\"" \
-rabbit plugins_expand_dir "\"$RABBITMQ_PLUGINS_EXPAND_DIR\"" \
diff --git a/scripts/rabbitmq-server.bat b/scripts/rabbitmq-server.bat
index 585a830efa..a15f24e586 100644
--- a/scripts/rabbitmq-server.bat
+++ b/scripts/rabbitmq-server.bat
@@ -21,6 +21,7 @@ rem Preserve values that might contain exclamation marks before
rem enabling delayed expansion
set TDP0=%~dp0
set STAR=%*
+set CONF_SCRIPT_DIR="%~dp0"
setlocal enabledelayedexpansion
REM Get default settings with user overrides for (RABBITMQ_)<var_name>
@@ -41,11 +42,19 @@ if not exist "!ERLANG_HOME!\bin\erl.exe" (
set RABBITMQ_EBIN_ROOT=!RABBITMQ_HOME!\ebin
+CALL :get_noex !RABBITMQ_ADVANCED_CONFIG_FILE! RABBITMQ_ADVANCED_CONFIG_FILE_NOEX
+if "!RABBITMQ_ADVANCED_CONFIG_FILE!" == "!RABBITMQ_ADVANCED_CONFIG_FILE_NOEX!.config" (
+ set RABBITMQ_ADVANCED_CONFIG_FILE=!RABBITMQ_ADVANCED_CONFIG_FILE_NOEX!
+)
+
"!ERLANG_HOME!\bin\erl.exe" ^
-pa "!RABBITMQ_EBIN_ROOT!" ^
-noinput -hidden ^
-s rabbit_prelaunch ^
!RABBITMQ_NAME_TYPE! rabbitmqprelaunch!RANDOM!!TIME:~9! ^
+ -conf_advanced "!RABBITMQ_ADVANCED_CONFIG_FILE!" ^
+ -rabbit enabled_plugins_file "!RABBITMQ_ENABLED_PLUGINS_FILE!" ^
+ -rabbit plugins_dir "!$RABBITMQ_PLUGINS_DIR!" ^
-extra "!RABBITMQ_NODENAME!"
if ERRORLEVEL 2 (
@@ -56,12 +65,46 @@ if ERRORLEVEL 2 (
set RABBITMQ_DIST_ARG=-kernel inet_dist_listen_min !RABBITMQ_DIST_PORT! -kernel inet_dist_listen_max !RABBITMQ_DIST_PORT!
)
+if not exist "!RABBITMQ_SCHEMA_DIR!" (
+ mkdir "!RABBITMQ_SCHEMA_DIR!"
+)
+
+if not exist "!RABBITMQ_SCHEMA_DIR!\rabbitmq.schema" (
+ copy "!RABBITMQ_HOME!\priv\schema\rabbitmq.schema" "!RABBITMQ_SCHEMA_DIR!\rabbitmq.schema"
+)
+
set RABBITMQ_EBIN_PATH="-pa !RABBITMQ_EBIN_ROOT!"
-if exist "!RABBITMQ_CONFIG_FILE!.config" (
- set RABBITMQ_CONFIG_ARG=-config "!RABBITMQ_CONFIG_FILE!"
+CALL :get_noex !RABBITMQ_CONFIG_FILE! RABBITMQ_CONFIG_FILE_NOEX
+
+if "!RABBITMQ_CONFIG_FILE!" == "!RABBITMQ_CONFIG_FILE_NOEX!.config" (
+ if exist "!RABBITMQ_CONFIG_FILE!" (
+ set RABBITMQ_CONFIG_ARG=-config "!RABBITMQ_CONFIG_FILE_NOEX!"
+ )
+) else if "!RABBITMQ_CONFIG_FILE!" == "!RABBITMQ_CONFIG_FILE_NOEX!.conf" (
+ set RABBITMQ_CONFIG_ARG=-conf "!RABBITMQ_CONFIG_FILE_NOEX!" ^
+ -conf_dir !RABBITMQ_GENERATED_CONFIG_DIR! ^
+ -conf_script_dir !CONF_SCRIPT_DIR:\=/! ^
+ -conf_schema_dir !RABBITMQ_SCHEMA_DIR!
+ if exist "!RABBITMQ_ADVANCED_CONFIG_FILE!.config" (
+ set RABBITMQ_CONFIG_ARG=!RABBITMQ_CONFIG_ARG! ^
+ -conf_advanced "!RABBITMQ_ADVANCED_CONFIG_FILE!" ^
+ -config "!RABBITMQ_ADVANCED_CONFIG_FILE!"
+ )
) else (
- set RABBITMQ_CONFIG_ARG=
+ if exist "!RABBITMQ_CONFIG_FILE!.config" (
+ set RABBITMQ_CONFIG_ARG=-config "!RABBITMQ_CONFIG_FILE!"
+ ) else if exist "!RABBITMQ_CONFIG_FILE!.conf" (
+ set RABBITMQ_CONFIG_ARG=-conf "!RABBITMQ_CONFIG_FILE!" ^
+ -conf_dir !RABBITMQ_GENERATED_CONFIG_DIR! ^
+ -conf_script_dir !CONF_SCRIPT_DIR:\=/! ^
+ -conf_schema_dir !RABBITMQ_SCHEMA_DIR!
+ if exist "!RABBITMQ_ADVANCED_CONFIG_FILE!.config" (
+ set RABBITMQ_CONFIG_ARG=!RABBITMQ_CONFIG_ARG! ^
+ -conf_advanced "!RABBITMQ_ADVANCED_CONFIG_FILE!" ^
+ -config "!RABBITMQ_ADVANCED_CONFIG_FILE!"
+ )
+ )
)
set RABBITMQ_LISTEN_ARG=
@@ -71,22 +114,15 @@ if not "!RABBITMQ_NODE_IP_ADDRESS!"=="" (
)
)
-REM If $RABBITMQ_LOGS is '-', send all log messages to stdout. Likewise
-REM for RABBITMQ_SASL_LOGS. This is particularily useful for Docker
-REM images.
+REM If $RABBITMQ_LOGS is '-', send all log messages to stdout. This is
+REM particularily useful for Docker images.
if "!RABBITMQ_LOGS!" == "-" (
- set RABBIT_ERROR_LOGGER=tty
-) else (
- set RABBIT_ERROR_LOGGER={file,\""!RABBITMQ_LOGS:\=/!"\"}
-)
-
-if "!RABBITMQ_SASL_LOGS!" == "-" (
set SASL_ERROR_LOGGER=tty
- set RABBIT_SASL_ERROR_LOGGER=tty
+ set RABBIT_LAGER_HANDLER=tty
) else (
set SASL_ERROR_LOGGER=false
- set RABBIT_SASL_ERROR_LOGGER={file,\""!RABBITMQ_SASL_LOGS:\=/!"\"}
+ set RABBIT_LAGER_HANDLER=\""!RABBITMQ_LOGS:\=/!"\"
)
set RABBITMQ_START_RABBIT=
@@ -99,11 +135,11 @@ if "!RABBITMQ_NODE_ONLY!"=="" (
if "!RABBITMQ_IO_THREAD_POOL_SIZE!"=="" (
set RABBITMQ_IO_THREAD_POOL_SIZE=64
-)
+)
set ENV_OK=true
-CALL :check_not_empty "RABBITMQ_BOOT_MODULE" !RABBITMQ_BOOT_MODULE!
+CALL :check_not_empty "RABBITMQ_BOOT_MODULE" !RABBITMQ_BOOT_MODULE!
CALL :check_not_empty "RABBITMQ_NAME_TYPE" !RABBITMQ_NAME_TYPE!
CALL :check_not_empty "RABBITMQ_NODENAME" !RABBITMQ_NODENAME!
@@ -126,8 +162,8 @@ if "!ENV_OK!"=="false" (
!RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS! ^
-sasl errlog_type error ^
-sasl sasl_error_logger !SASL_ERROR_LOGGER! ^
--rabbit error_logger !RABBIT_ERROR_LOGGER! ^
--rabbit sasl_error_logger !RABBIT_SASL_ERROR_LOGGER! ^
+-rabbit lager_log_root \""!RABBITMQ_LOG_BASE:\=/!"\" ^
+-rabbit lager_handler !RABBIT_LAGER_HANDLER! ^
-rabbit enabled_plugins_file \""!RABBITMQ_ENABLED_PLUGINS_FILE:\=/!"\" ^
-rabbit plugins_dir \""!RABBITMQ_PLUGINS_DIR:\=/!"\" ^
-rabbit plugins_expand_dir \""!RABBITMQ_PLUGINS_EXPAND_DIR:\=/!"\" ^
@@ -145,10 +181,14 @@ EXIT /B 0
if "%~2"=="" (
ECHO "Error: ENV variable should be defined: %1. Please check rabbitmq-env and rabbitmq-defaults, and !RABBITMQ_CONF_ENV_FILE! script files. Check also your Environment Variables settings"
set ENV_OK=false
- EXIT /B 78
+ EXIT /B 78
)
EXIT /B 0
+:get_noex
+set "%~2=%~dpn1"
+EXIT /B 0
+
endlocal
endlocal
diff --git a/scripts/rabbitmq-service.bat b/scripts/rabbitmq-service.bat
index f8a8d5a464..c9e404db46 100644
--- a/scripts/rabbitmq-service.bat
+++ b/scripts/rabbitmq-service.bat
@@ -21,6 +21,7 @@ rem Preserve values that might contain exclamation marks before
rem enabling delayed expansion
set TN0=%~n0
set TDP0=%~dp0
+set CONF_SCRIPT_DIR="%~dp0"
set P1=%1
setlocal enabledelayedexpansion
@@ -105,7 +106,7 @@ if not exist "!RABBITMQ_BASE!" (
)
set ENV_OK=true
-CALL :check_not_empty "RABBITMQ_BOOT_MODULE" !RABBITMQ_BOOT_MODULE!
+CALL :check_not_empty "RABBITMQ_BOOT_MODULE" !RABBITMQ_BOOT_MODULE!
CALL :check_not_empty "RABBITMQ_NAME_TYPE" !RABBITMQ_NAME_TYPE!
CALL :check_not_empty "RABBITMQ_NODENAME" !RABBITMQ_NODENAME!
@@ -123,10 +124,19 @@ if errorlevel 1 (
set RABBITMQ_EBIN_ROOT=!RABBITMQ_HOME!\ebin
+CALL :get_noex !RABBITMQ_ADVANCED_CONFIG_FILE! RABBITMQ_ADVANCED_CONFIG_FILE_NOEX
+
+if "!RABBITMQ_ADVANCED_CONFIG_FILE!" == "!RABBITMQ_ADVANCED_CONFIG_FILE_NOEX!.config" (
+ set RABBITMQ_ADVANCED_CONFIG_FILE=!RABBITMQ_ADVANCED_CONFIG_FILE_NOEX!
+)
+
"!ERLANG_HOME!\bin\erl.exe" ^
-pa "!RABBITMQ_EBIN_ROOT!" ^
-noinput -hidden ^
-s rabbit_prelaunch ^
+ -conf_advanced "!RABBITMQ_ADVANCED_CONFIG_FILE!" ^
+ -rabbit enabled_plugins_file "!RABBITMQ_ENABLED_PLUGINS_FILE!" ^
+ -rabbit plugins_dir "!$RABBITMQ_PLUGINS_DIR!" ^
!RABBITMQ_NAME_TYPE! rabbitmqprelaunch!RANDOM!!TIME:~9!
if ERRORLEVEL 3 (
@@ -141,18 +151,54 @@ if ERRORLEVEL 3 (
set RABBITMQ_DIST_ARG=-kernel inet_dist_listen_min !RABBITMQ_DIST_PORT! -kernel inet_dist_listen_max !RABBITMQ_DIST_PORT!
)
- REM Try to create config file, if it doesn't exist
+if not exist "!RABBITMQ_SCHEMA_DIR!" (
+ mkdir "!RABBITMQ_SCHEMA_DIR!"
+)
+
+if not exist "!RABBITMQ_SCHEMA_DIR!\rabbitmq.schema" (
+ copy "!RABBITMQ_HOME!\priv\schema\rabbitmq.schema" "!RABBITMQ_SCHEMA_DIR!\rabbitmq.schema"
+)
+ REM Try to create advanced config file, if it doesn't exist
REM It still can fail to be created, but at least not for default install
-if not exist "!RABBITMQ_CONFIG_FILE!.config" (
- echo []. > !RABBITMQ_CONFIG_FILE!.config
+if not exist "!RABBITMQ_ADVANCED_CONFIG_FILE!.config" (
+ echo []. > !RABBITMQ_ADVANCED_CONFIG_FILE!.config
)
-if exist "!RABBITMQ_CONFIG_FILE!.config" (
- set RABBITMQ_CONFIG_ARG=-config "!RABBITMQ_CONFIG_FILE!"
+CALL :get_noex !RABBITMQ_CONFIG_FILE! RABBITMQ_CONFIG_FILE_NOEX
+
+if "!RABBITMQ_CONFIG_FILE!" == "!RABBITMQ_CONFIG_FILE_NOEX!.config" (
+ if exist "!RABBITMQ_CONFIG_FILE!" (
+ set RABBITMQ_CONFIG_ARG=-config "!RABBITMQ_CONFIG_FILE_NOEX!"
+ )
+) else if "!RABBITMQ_CONFIG_FILE!" == "!RABBITMQ_CONFIG_FILE_NOEX!.conf" (
+ set RABBITMQ_CONFIG_ARG=-conf "!RABBITMQ_CONFIG_FILE_NOEX!" ^
+ -conf_dir !RABBITMQ_GENERATED_CONFIG_DIR! ^
+ -conf_script_dir !CONF_SCRIPT_DIR:\=/! ^
+ -conf_schema_dir !RABBITMQ_SCHEMA_DIR!
+ if exist "!RABBITMQ_ADVANCED_CONFIG_FILE!.config" (
+ set RABBITMQ_CONFIG_ARG=!RABBITMQ_CONFIG_ARG! ^
+ -conf_advanced "!RABBITMQ_ADVANCED_CONFIG_FILE!" ^
+ -config "!RABBITMQ_ADVANCED_CONFIG_FILE!"
+ )
) else (
- set RABBITMQ_CONFIG_ARG=
+ if exist "!RABBITMQ_CONFIG_FILE!.config" (
+ set RABBITMQ_CONFIG_ARG=-config "!RABBITMQ_CONFIG_FILE!"
+ ) else (
+ rem Always specify generated config arguments, we cannot
+ rem assume .conf file is available
+ set RABBITMQ_CONFIG_ARG=-conf "!RABBITMQ_CONFIG_FILE!" ^
+ -conf_dir !RABBITMQ_GENERATED_CONFIG_DIR! ^
+ -conf_script_dir !CONF_SCRIPT_DIR:\=/! ^
+ -conf_schema_dir !RABBITMQ_SCHEMA_DIR!
+ if exist "!RABBITMQ_ADVANCED_CONFIG_FILE!.config" (
+ set RABBITMQ_CONFIG_ARG=!RABBITMQ_CONFIG_ARG! ^
+ -conf_advanced "!RABBITMQ_ADVANCED_CONFIG_FILE!" ^
+ -config "!RABBITMQ_ADVANCED_CONFIG_FILE!"
+ )
+ )
)
+
set RABBITMQ_LISTEN_ARG=
if not "!RABBITMQ_NODE_IP_ADDRESS!"=="" (
if not "!RABBITMQ_NODE_PORT!"=="" (
@@ -160,6 +206,12 @@ if not "!RABBITMQ_NODE_IP_ADDRESS!"=="" (
)
)
+if "!RABBITMQ_LOGS!" == "-" (
+ set RABBIT_LAGER_HANDLER=tty
+) else (
+ set RABBIT_LAGER_HANDLER=\""!RABBITMQ_LOGS:\=/!"\"
+)
+
set RABBITMQ_START_RABBIT=
if "!RABBITMQ_NODE_ONLY!"=="" (
set RABBITMQ_START_RABBIT=-s "!RABBITMQ_BOOT_MODULE!" boot
@@ -186,8 +238,8 @@ set ERLANG_SERVICE_ARGUMENTS= ^
!RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS! ^
-sasl errlog_type error ^
-sasl sasl_error_logger false ^
--rabbit error_logger {file,\""!RABBITMQ_LOGS:\=/!"\"} ^
--rabbit sasl_error_logger {file,\""!RABBITMQ_SASL_LOGS:\=/!"\"} ^
+-rabbit lager_log_root \""!RABBITMQ_LOG_BASE:\=/!"\" ^
+-rabbit lager_handler !RABBIT_LAGER_HANDLER! ^
-rabbit enabled_plugins_file \""!RABBITMQ_ENABLED_PLUGINS_FILE:\=/!"\" ^
-rabbit plugins_dir \""!RABBITMQ_PLUGINS_DIR:\=/!"\" ^
-rabbit plugins_expand_dir \""!RABBITMQ_PLUGINS_EXPAND_DIR:\=/!"\" ^
@@ -234,9 +286,13 @@ EXIT /B 0
if "%~2"=="" (
ECHO "Error: ENV variable should be defined: %1. Please check rabbitmq-env, rabbitmq-default, and !RABBITMQ_CONF_ENV_FILE! script files. Check also your Environment Variables settings"
set ENV_OK=false
- EXIT /B 78
+ EXIT /B 78
)
EXIT /B 0
+:get_noex
+set "%~2=%~dpn1"
+EXIT /B 0
+
endlocal
endlocal
diff --git a/src/file_handle_cache.erl b/src/file_handle_cache.erl
index e4af1e8c1a..3f5232a657 100644
--- a/src/file_handle_cache.erl
+++ b/src/file_handle_cache.erl
@@ -540,12 +540,12 @@ clear(Ref) ->
end).
set_maximum_since_use(MaximumAge) ->
- Now = time_compat:monotonic_time(),
+ Now = erlang:monotonic_time(),
case lists:foldl(
fun ({{Ref, fhc_handle},
Handle = #handle { hdl = Hdl, last_used_at = Then }}, Rep) ->
case Hdl =/= closed andalso
- time_compat:convert_time_unit(Now - Then,
+ erlang:convert_time_unit(Now - Then,
native,
micro_seconds)
>= MaximumAge of
@@ -722,7 +722,7 @@ get_or_reopen(RefNewOrReopens) ->
{ok, [Handle || {_Ref, Handle} <- OpenHdls]};
{OpenHdls, ClosedHdls} ->
Oldest = oldest(get_age_tree(),
- fun () -> time_compat:monotonic_time() end),
+ fun () -> erlang:monotonic_time() end),
case gen_server2:call(?SERVER, {open, self(), length(ClosedHdls),
Oldest}, infinity) of
ok ->
@@ -758,7 +758,7 @@ reopen([{Ref, NewOrReopen, Handle = #handle { hdl = closed,
end,
case prim_file:open(Path, Mode) of
{ok, Hdl} ->
- Now = time_compat:monotonic_time(),
+ Now = erlang:monotonic_time(),
{{ok, _Offset}, Handle1} =
maybe_seek(Offset, reset_read_buffer(
Handle#handle{hdl = Hdl,
@@ -794,7 +794,7 @@ sort_handles([{Ref, _} | RefHdls], RefHdlsA, [{Ref, Handle} | RefHdlsB], Acc) ->
sort_handles(RefHdls, RefHdlsA, RefHdlsB, [Handle | Acc]).
put_handle(Ref, Handle = #handle { last_used_at = Then }) ->
- Now = time_compat:monotonic_time(),
+ Now = erlang:monotonic_time(),
age_tree_update(Then, Now, Ref),
put({Ref, fhc_handle}, Handle #handle { last_used_at = Now }).
@@ -1435,14 +1435,14 @@ reduce(State = #fhc_state { open_pending = OpenPending,
elders = Elders,
clients = Clients,
timer_ref = TRef }) ->
- Now = time_compat:monotonic_time(),
+ Now = erlang:monotonic_time(),
{CStates, Sum, ClientCount} =
ets:foldl(fun ({Pid, Eldest}, {CStatesAcc, SumAcc, CountAcc} = Accs) ->
[#cstate { pending_closes = PendingCloses,
opened = Opened,
blocked = Blocked } = CState] =
ets:lookup(Clients, Pid),
- TimeDiff = time_compat:convert_time_unit(
+ TimeDiff = erlang:convert_time_unit(
Now - Eldest, native, micro_seconds),
case Blocked orelse PendingCloses =:= Opened of
true -> Accs;
@@ -1481,7 +1481,7 @@ notify_age(CStates, AverageAge) ->
notify_age0(Clients, CStates, Required) ->
case [CState || CState <- CStates, CState#cstate.callback =/= undefined] of
[] -> ok;
- Notifications -> S = rand_compat:uniform(length(Notifications)),
+ Notifications -> S = rand:uniform(length(Notifications)),
{L1, L2} = lists:split(S, Notifications),
notify(Clients, Required, L2 ++ L1)
end.
diff --git a/src/file_handle_cache_stats.erl b/src/file_handle_cache_stats.erl
index 12a78f805e..f639881a6f 100644
--- a/src/file_handle_cache_stats.erl
+++ b/src/file_handle_cache_stats.erl
@@ -59,8 +59,8 @@ get() ->
lists:sort(ets:tab2list(?TABLE)).
timer_tc(Thunk) ->
- T1 = time_compat:monotonic_time(),
+ T1 = erlang:monotonic_time(),
Res = Thunk(),
- T2 = time_compat:monotonic_time(),
- Diff = time_compat:convert_time_unit(T2 - T1, native, micro_seconds),
+ T2 = erlang:monotonic_time(),
+ Diff = erlang:convert_time_unit(T2 - T1, native, micro_seconds),
{Diff, Res}.
diff --git a/src/gm.erl b/src/gm.erl
index 008a29151e..0b5c1c44c4 100644
--- a/src/gm.erl
+++ b/src/gm.erl
@@ -1072,7 +1072,7 @@ join_group(Self, GroupName, #gm_group { members = Members } = Group, TxnFun) ->
prune_or_create_group(Self, GroupName, TxnFun),
TxnFun);
Alive ->
- Left = lists:nth(rand_compat:uniform(length(Alive)), Alive),
+ Left = lists:nth(rand:uniform(length(Alive)), Alive),
Handler =
fun () ->
join_group(
diff --git a/src/lqueue.erl b/src/lqueue.erl
index fc7157dff1..0652061075 100644
--- a/src/lqueue.erl
+++ b/src/lqueue.erl
@@ -27,9 +27,7 @@
-export_type([?MODULE/0]).
--include_lib("rabbit_common/include/old_builtin_types.hrl").
-
--opaque ?MODULE() :: {non_neg_integer(), ?QUEUE_TYPE()}.
+-opaque ?MODULE() :: {non_neg_integer(), queue:queue()}.
-type value() :: any().
-type result() :: 'empty' | {'value', value()}.
diff --git a/src/pg2_fixed.erl b/src/pg2_fixed.erl
index 222a0bc849..73c05819d4 100644
--- a/src/pg2_fixed.erl
+++ b/src/pg2_fixed.erl
@@ -149,11 +149,11 @@ get_closest_pid(Name) ->
case get_members(Name) of
[] -> {error, {no_process, Name}};
Members ->
- X = time_compat:erlang_system_time(micro_seconds),
+ X = erlang:system_time(micro_seconds),
lists:nth((X rem length(Members))+1, Members)
end;
Members when is_list(Members) ->
- X = time_compat:erlang_system_time(micro_seconds),
+ X = erlang:system_time(micro_seconds),
lists:nth((X rem length(Members))+1, Members);
Else ->
Else
diff --git a/src/rabbit.app.src b/src/rabbit.app.src
index 572c1f6bc6..738a38e2bb 100644
--- a/src/rabbit.app.src
+++ b/src/rabbit.app.src
@@ -1,4 +1,5 @@
-{application, rabbit, %% -*- erlang -*-
+%% -*- erlang -*-
+{application, rabbit,
[{description, "RabbitMQ"},
{id, "RabbitMQ"},
{vsn, "0.0.0"},
@@ -9,7 +10,9 @@
rabbit_router,
rabbit_sup,
rabbit_direct_client_sup]},
- {applications, [kernel, stdlib, sasl, mnesia, rabbit_common, ranch, os_mon, xmerl]},
+ %% FIXME: Remove goldrush, once rabbit_plugins.erl knows how to ignore
+ %% indirect dependencies of rabbit.
+ {applications, [kernel, stdlib, sasl, mnesia, goldrush, lager, rabbit_common, ranch, os_mon, xmerl]},
%% we also depend on crypto, public_key and ssl but they shouldn't be
%% in here as we don't actually want to start it
{mod, {rabbit, []}},
diff --git a/src/rabbit.erl b/src/rabbit.erl
index a86fd97925..5db2c40b66 100644
--- a/src/rabbit.erl
+++ b/src/rabbit.erl
@@ -20,11 +20,17 @@
-export([start/0, boot/0, stop/0,
stop_and_halt/0, await_startup/0, status/0, is_running/0,
- is_running/1, environment/0, rotate_logs/1, force_event_refresh/1,
+ is_running/1, environment/0, rotate_logs/0, force_event_refresh/1,
start_fhc/0]).
-export([start/2, stop/1, prep_stop/1]).
-export([start_apps/1, stop_apps/1]).
--export([log_location/1, config_files/0]). %% for testing and mgmt-agent
+-export([log_locations/0, config_files/0]). %% for testing and mgmt-agent
+
+-ifdef(TEST).
+
+-export([start_logger/0]).
+
+-endif.
%%---------------------------------------------------------------------------
%% Boot steps.
@@ -172,6 +178,11 @@
{mfa, {rabbit_direct, boot, []}},
{requires, log_relay}]}).
+-rabbit_boot_step({connection_tracking,
+ [{description, "sets up internal storage for node-local connections"},
+ {mfa, {rabbit_connection_tracking, boot, []}},
+ {requires, log_relay}]}).
+
-rabbit_boot_step({networking,
[{mfa, {rabbit_networking, boot, []}},
{requires, log_relay}]}).
@@ -198,9 +209,8 @@
%%----------------------------------------------------------------------------
--type file_suffix() :: binary().
%% this really should be an abstract type
--type log_location() :: 'tty' | 'undefined' | file:filename().
+-type log_location() :: string().
-type param() :: atom().
-type app_name() :: atom().
@@ -218,10 +228,10 @@
-spec is_running() -> boolean().
-spec is_running(node()) -> boolean().
-spec environment() -> [{param(), term()}].
--spec rotate_logs(file_suffix()) -> rabbit_types:ok_or_error(any()).
+-spec rotate_logs() -> rabbit_types:ok_or_error(any()).
-spec force_event_refresh(reference()) -> 'ok'.
--spec log_location('sasl' | 'kernel') -> log_location().
+-spec log_locations() -> [log_location()].
-spec start('normal',[]) ->
{'error',
@@ -254,7 +264,7 @@ start() ->
%% restarting the app.
ok = ensure_application_loaded(),
HipeResult = rabbit_hipe:maybe_hipe_compile(),
- ok = ensure_working_log_handlers(),
+ ok = start_logger(),
rabbit_hipe:log_hipe_result(HipeResult),
rabbit_node_monitor:prepare_cluster_status_files(),
rabbit_mnesia:check_cluster_consistency(),
@@ -263,9 +273,10 @@ start() ->
boot() ->
start_it(fun() ->
+ ensure_config(),
ok = ensure_application_loaded(),
HipeResult = rabbit_hipe:maybe_hipe_compile(),
- ok = ensure_working_log_handlers(),
+ ok = start_logger(),
rabbit_hipe:log_hipe_result(HipeResult),
rabbit_node_monitor:prepare_cluster_status_files(),
ok = rabbit_upgrade:maybe_upgrade_mnesia(),
@@ -276,6 +287,20 @@ boot() ->
broker_start()
end).
+ensure_config() ->
+ case rabbit_config:prepare_and_use_config() of
+ {error, Reason} ->
+ {Format, Arg} = case Reason of
+ {generation_error, Error} -> {"~s", [Error]};
+ Other -> {"~p", [Other]}
+ end,
+ log_boot_error_and_exit(generate_config_file,
+ "~nConfig file generation failed "++Format,
+ Arg);
+ ok -> ok
+ end.
+
+
broker_start() ->
Plugins = rabbit_plugins:setup(),
ToBeLoaded = Plugins ++ ?APPS,
@@ -403,10 +428,8 @@ start_it(StartFun) ->
false -> StartFun()
end
catch
- throw:{could_not_start, _App, _Reason} = Err ->
- boot_error(Err, not_available);
- _:Reason ->
- boot_error(Reason, erlang:get_stacktrace())
+ Class:Reason ->
+ boot_error(Class, Reason)
after
unlink(Marker),
Marker ! stop,
@@ -551,27 +574,33 @@ environment(App) ->
lists:keysort(1, [P || P = {K, _} <- application:get_all_env(App),
not lists:member(K, Ignore)]).
-rotate_logs_info("") ->
- rabbit_log:info("Reopening logs", []);
-rotate_logs_info(Suffix) ->
- rabbit_log:info("Rotating logs with suffix '~s'~n", [Suffix]).
-
-rotate_logs(BinarySuffix) ->
- Suffix = binary_to_list(BinarySuffix),
- rotate_logs_info(Suffix),
- log_rotation_result(rotate_logs(log_location(kernel),
- Suffix,
- rabbit_error_logger_file_h),
- rotate_logs(log_location(sasl),
- Suffix,
- rabbit_sasl_report_file_h)).
+rotate_logs() ->
+ rabbit_lager:fold_sinks(
+ fun
+ (_, [], Acc) ->
+ Acc;
+ (SinkName, FileNames, Acc) ->
+ lager:log(SinkName, info, self(),
+ "Log file rotation forced", []),
+ %% FIXME: We use an internal message, understood by
+ %% lager_file_backend. We should use a proper API, when
+ %% it's added to Lager.
+ %%
+ %% FIXME: This message is asynchronous, therefore this
+ %% entire call is asynchronous: at the end of this
+ %% function, we can't guaranty the rotation is completed.
+ [SinkName ! {rotate, FileName} || FileName <- FileNames],
+ lager:log(SinkName, info, self(),
+ "Log file re-opened after forced rotation", []),
+ Acc
+ end, ok).
%%--------------------------------------------------------------------
start(normal, []) ->
case erts_version_check() of
ok ->
- rabbit_log:info("Starting RabbitMQ ~s on Erlang ~s~n~s~n~s~n",
+ rabbit_log:info("~n Starting RabbitMQ ~s on Erlang ~s~n ~s~n ~s~n",
[rabbit_misc:version(), rabbit_misc:otp_release(),
?COPYRIGHT_MESSAGE, ?INFORMATION_MESSAGE]),
{ok, SupPid} = rabbit_sup:start_link(),
@@ -596,10 +625,9 @@ prep_stop(_State) ->
stop(_) -> ok.
--spec boot_error(term(), not_available | [tuple()]) -> no_return().
+-spec boot_error(atom(), term()) -> no_return().
-boot_error({could_not_start, rabbit, {{timeout_waiting_for_tables, _}, _}},
- _Stacktrace) ->
+boot_error(_, {could_not_start, rabbit, {{timeout_waiting_for_tables, _}, _}}) ->
AllNodes = rabbit_mnesia:cluster_nodes(all),
Suffix = "~nBACKGROUND~n==========~n~n"
"This cluster node was shut down while other nodes were still running.~n"
@@ -618,25 +646,25 @@ boot_error({could_not_start, rabbit, {{timeout_waiting_for_tables, _}, _}},
end,
log_boot_error_and_exit(
timeout_waiting_for_tables,
- Err ++ rabbit_nodes:diagnostics(Nodes) ++ "~n~n", []);
-boot_error(Reason, Stacktrace) ->
- Fmt = "Error description:~n ~p~n~n"
- "Log files (may contain more information):~n ~s~n ~s~n~n",
- Args = [Reason, log_location(kernel), log_location(sasl)],
- boot_error(Reason, Fmt, Args, Stacktrace).
-
--spec boot_error(term(), string(), [any()], not_available | [tuple()]) ->
- no_return().
-
-boot_error(Reason, Fmt, Args, not_available) ->
- log_boot_error_and_exit(Reason, Fmt, Args);
-boot_error(Reason, Fmt, Args, Stacktrace) ->
- log_boot_error_and_exit(Reason, Fmt ++ "Stack trace:~n ~p~n~n",
- Args ++ [Stacktrace]).
+ "~n" ++ Err ++ rabbit_nodes:diagnostics(Nodes), []);
+boot_error(Class, {error, {cannot_log_to_file, _, _}} = Reason) ->
+ log_boot_error_and_exit(
+ Reason,
+ "~nError description:~s",
+ [lager:pr_stacktrace(erlang:get_stacktrace(), {Class, Reason})]);
+boot_error(Class, Reason) ->
+ LogLocations = log_locations(),
+ log_boot_error_and_exit(
+ Reason,
+ "~nError description:~s"
+ "~nLog file(s) (may contain more information):~n" ++
+ lists:flatten([" ~s~n" || _ <- lists:seq(1, length(LogLocations))]),
+ [lager:pr_stacktrace(erlang:get_stacktrace(), {Class, Reason})] ++
+ LogLocations).
log_boot_error_and_exit(Reason, Format, Args) ->
- io:format("~n~nBOOT FAILED~n===========~n~n" ++ Format, Args),
- rabbit_log:info(Format, Args),
+ rabbit_log:error(Format, Args),
+ io:format("~nBOOT FAILED~n===========~n" ++ Format ++ "~n", Args),
timer:sleep(1000),
exit(Reason).
@@ -680,75 +708,12 @@ insert_default_data() ->
%%---------------------------------------------------------------------------
%% logging
-ensure_working_log_handlers() ->
- Handlers = gen_event:which_handlers(error_logger),
- ok = ensure_working_log_handler(error_logger_tty_h,
- rabbit_error_logger_file_h,
- error_logger_tty_h,
- log_location(kernel),
- Handlers),
-
- ok = ensure_working_log_handler(sasl_report_tty_h,
- rabbit_sasl_report_file_h,
- sasl_report_tty_h,
- log_location(sasl),
- Handlers),
+start_logger() ->
+ rabbit_lager:start_logger(),
ok.
-ensure_working_log_handler(OldHandler, NewHandler, TTYHandler,
- LogLocation, Handlers) ->
- case LogLocation of
- undefined -> ok;
- tty -> case lists:member(TTYHandler, Handlers) of
- true -> ok;
- false ->
- throw({error, {cannot_log_to_tty,
- TTYHandler, not_installed}})
- end;
- _ -> case lists:member(NewHandler, Handlers) of
- true -> ok;
- false -> case rotate_logs(LogLocation, "",
- OldHandler, NewHandler) of
- ok -> ok;
- {error, Reason} ->
- throw({error, {cannot_log_to_file,
- LogLocation, Reason}})
- end
- end
- end.
-
-log_location(Type) ->
- case application:get_env(rabbit, case Type of
- kernel -> error_logger;
- sasl -> sasl_error_logger
- end) of
- {ok, {file, File}} -> File;
- {ok, false} -> undefined;
- {ok, tty} -> tty;
- {ok, silent} -> undefined;
- {ok, Bad} -> throw({error, {cannot_log_to_file, Bad}});
- _ -> undefined
- end.
-
-rotate_logs(File, Suffix, Handler) ->
- rotate_logs(File, Suffix, Handler, Handler).
-
-rotate_logs(undefined, _Suffix, _OldHandler, _NewHandler) -> ok;
-rotate_logs(tty, _Suffix, _OldHandler, _NewHandler) -> ok;
-rotate_logs(File, Suffix, OldHandler, NewHandler) ->
- gen_event:swap_handler(error_logger,
- {OldHandler, swap},
- {NewHandler, {File, Suffix}}).
-
-log_rotation_result({error, MainLogError}, {error, SaslLogError}) ->
- {error, {{cannot_rotate_main_logs, MainLogError},
- {cannot_rotate_sasl_logs, SaslLogError}}};
-log_rotation_result({error, MainLogError}, ok) ->
- {error, {cannot_rotate_main_logs, MainLogError}};
-log_rotation_result(ok, {error, SaslLogError}) ->
- {error, {cannot_rotate_sasl_logs, SaslLogError}};
-log_rotation_result(ok, ok) ->
- ok.
+log_locations() ->
+ rabbit_lager:log_locations().
force_event_refresh(Ref) ->
rabbit_direct:force_event_refresh(Ref),
@@ -760,19 +725,17 @@ force_event_refresh(Ref) ->
%% misc
log_broker_started(Plugins) ->
- rabbit_log:with_local_io(
- fun() ->
- PluginList = iolist_to_binary([rabbit_misc:format(" * ~s~n", [P])
- || P <- Plugins]),
- rabbit_log:info(
- "Server startup complete; ~b plugins started.~n~s",
- [length(Plugins), PluginList]),
- io:format(" completed with ~p plugins.~n", [length(Plugins)])
- end).
+ PluginList = iolist_to_binary([rabbit_misc:format(" * ~s~n", [P])
+ || P <- Plugins]),
+ Message = string:strip(rabbit_misc:format(
+ "Server startup complete; ~b plugins started.~n~s",
+ [length(Plugins), PluginList]), right, $\n),
+ rabbit_log:info(Message),
+ io:format(" completed with ~p plugins.~n", [length(Plugins)]).
erts_version_check() ->
ERTSVer = erlang:system_info(version),
- OTPRel = erlang:system_info(otp_release),
+ OTPRel = rabbit_misc:otp_release(),
case rabbit_misc:version_compare(?ERTS_MINIMUM, ERTSVer, lte) of
true when ?ERTS_MINIMUM =/= ERTSVer ->
ok;
@@ -793,31 +756,45 @@ erts_version_check() ->
print_banner() ->
{ok, Product} = application:get_key(id),
{ok, Version} = application:get_key(vsn),
- io:format("~n ~s ~s. ~s"
- "~n ## ## ~s"
- "~n ## ##"
- "~n ########## Logs: ~s"
- "~n ###### ## ~s"
- "~n ##########"
- "~n Starting broker..."
+ {LogFmt, LogLocations} = case log_locations() of
+ [_ | Tail] = LL ->
+ LF = lists:flatten(["~n ~s"
+ || _ <- lists:seq(1, length(Tail))]),
+ {LF, LL};
+ [] ->
+ {"", ["(none)"]}
+ end,
+ io:format("~n ## ##"
+ "~n ## ## ~s ~s. ~s"
+ "~n ########## ~s"
+ "~n ###### ##"
+ "~n ########## Logs: ~s" ++
+ LogFmt ++
+ "~n~n Starting broker..."
"~n",
- [Product, Version, ?COPYRIGHT_MESSAGE, ?INFORMATION_MESSAGE,
- log_location(kernel), log_location(sasl)]).
+ [Product, Version, ?COPYRIGHT_MESSAGE, ?INFORMATION_MESSAGE] ++
+ LogLocations).
log_banner() ->
+ {FirstLog, OtherLogs} = case log_locations() of
+ [Head | Tail] ->
+ {Head, [{"", F} || F <- Tail]};
+ [] ->
+ {"(none)", []}
+ end,
Settings = [{"node", node()},
{"home dir", home_dir()},
{"config file(s)", config_files()},
{"cookie hash", rabbit_nodes:cookie_hash()},
- {"log", log_location(kernel)},
- {"sasl log", log_location(sasl)},
- {"database dir", rabbit_mnesia:dir()}],
+ {"log(s)", FirstLog}] ++
+ OtherLogs ++
+ [{"database dir", rabbit_mnesia:dir()}],
DescrLen = 1 + lists:max([length(K) || {K, _V} <- Settings]),
Format = fun (K, V) ->
rabbit_misc:format(
- "~-" ++ integer_to_list(DescrLen) ++ "s: ~s~n", [K, V])
+ " ~-" ++ integer_to_list(DescrLen) ++ "s: ~s~n", [K, V])
end,
- Banner = iolist_to_binary(
+ Banner = string:strip(lists:flatten(
[case S of
{"config file(s)" = K, []} ->
Format(K, "(none)");
@@ -825,8 +802,8 @@ log_banner() ->
[Format(K, V0) | [Format("", V) || V <- Vs]];
{K, V} ->
Format(K, V)
- end || S <- Settings]),
- rabbit_log:info("~s", [Banner]).
+ end || S <- Settings]), right, $\n),
+ rabbit_log:info("~n~s", [Banner]).
warn_if_kernel_config_dubious() ->
case os:type() of
@@ -944,32 +921,7 @@ home_dir() ->
end.
config_files() ->
- Abs = fun (F) ->
- filename:absname(filename:rootname(F, ".config") ++ ".config")
- end,
- case init:get_argument(config) of
- {ok, Files} -> [Abs(File) || [File] <- Files];
- error -> case config_setting() of
- none -> [];
- File -> [Abs(File) ++ " (not found)"]
- end
- end.
-
-%% This is a pain. We want to know where the config file is. But we
-%% can't specify it on the command line if it is missing or the VM
-%% will fail to start, so we need to find it by some mechanism other
-%% than init:get_arguments/0. We can look at the environment variable
-%% which is responsible for setting it... but that doesn't work for a
-%% Windows service since the variable can change and the service not
-%% be reinstalled, so in that case we add a magic application env.
-config_setting() ->
- case application:get_env(rabbit, windows_service_config) of
- {ok, File1} -> File1;
- undefined -> case os:getenv("RABBITMQ_CONFIG_FILE") of
- false -> none;
- File2 -> File2
- end
- end.
+ rabbit_config:config_files().
%% We don't want this in fhc since it references rabbit stuff. And we can't put
%% this in the bootstep directly.
@@ -995,9 +947,8 @@ ensure_working_fhc() ->
{ok, true} -> "ON";
{ok, false} -> "OFF"
end,
- rabbit_log:info(
- "FHC read buffering: ~s~n"
- "FHC write buffering: ~s~n", [ReadBuf, WriteBuf]),
+ rabbit_log:info("FHC read buffering: ~s~n", [ReadBuf]),
+ rabbit_log:info("FHC write buffering: ~s~n", [WriteBuf]),
Filename = filename:join(code:lib_dir(kernel, ebin), "kernel.app"),
{ok, Fd} = file_handle_cache:open(Filename, [raw, binary, read], []),
{ok, _} = file_handle_cache:read(Fd, 1),
diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl
index 66df42987c..710ec6ab2a 100644
--- a/src/rabbit_amqqueue_process.erl
+++ b/src/rabbit_amqqueue_process.erl
@@ -85,6 +85,11 @@
%% e.g. message expiration messages from previously set up timers
%% that may or may not be still valid
args_policy_version,
+ %% used to discard outdated/superseded policy updates,
+ %% e.g. when policies are applied concurrently. See
+ %% https://github.com/rabbitmq/rabbitmq-server/issues/803 for one
+ %% example.
+ mirroring_policy_version = 0,
%% running | flow | idle
status
}).
@@ -94,7 +99,7 @@
-spec info_keys() -> rabbit_types:info_keys().
-spec init_with_backing_queue_state
(rabbit_types:amqqueue(), atom(), tuple(), any(),
- [rabbit_types:delivery()], pmon:pmon(), ?DICT_TYPE()) ->
+ [rabbit_types:delivery()], pmon:pmon(), dict:dict()) ->
#q{}.
%%----------------------------------------------------------------------------
@@ -173,6 +178,7 @@ init_it(Recover, From, State = #q{q = #amqqueue{exclusive_owner = Owner}}) ->
{_, Terms} = recovery_status(Recover),
BQS = bq_init(BQ, Q, Terms),
%% Rely on terminate to delete the queue.
+ log_delete_exclusive(Owner, State),
{stop, {shutdown, missing_owner},
State#q{backing_queue = BQ, backing_queue_state = BQS}}
end.
@@ -461,7 +467,7 @@ ensure_ttl_timer(undefined, State) ->
State;
ensure_ttl_timer(Expiry, State = #q{ttl_timer_ref = undefined,
args_policy_version = Version}) ->
- After = (case Expiry - time_compat:os_system_time(micro_seconds) of
+ After = (case Expiry - os:system_time(micro_seconds) of
V when V > 0 -> V + 999; %% always fire later
_ -> 0
end) div 1000,
@@ -732,7 +738,13 @@ handle_ch_down(DownPid, State = #q{consumers = Consumers,
exclusive_consumer = Holder1},
notify_decorators(State2),
case should_auto_delete(State2) of
- true -> {stop, State2};
+ true ->
+ log_auto_delete(
+ io_lib:format(
+ "because all of its consumers (~p) were on a channel that was closed",
+ [length(ChCTags)]),
+ State),
+ {stop, State2};
false -> {ok, requeue_and_run(ChAckTags,
ensure_expiry_timer(State2))}
end
@@ -781,7 +793,7 @@ calculate_msg_expiry(#basic_message{content = Content}, TTL) ->
{ok, MsgTTL} = rabbit_basic:parse_expiration(Props),
case lists:min([TTL, MsgTTL]) of
undefined -> undefined;
- T -> time_compat:os_system_time(micro_seconds) + T * 1000
+ T -> os:system_time(micro_seconds) + T * 1000
end.
%% Logically this function should invoke maybe_send_drained/2.
@@ -792,7 +804,7 @@ calculate_msg_expiry(#basic_message{content = Content}, TTL) ->
drop_expired_msgs(State) ->
case is_empty(State) of
true -> State;
- false -> drop_expired_msgs(time_compat:os_system_time(micro_seconds),
+ false -> drop_expired_msgs(os:system_time(micro_seconds),
State)
end.
@@ -975,6 +987,7 @@ prioritise_call(Msg, _From, _Len, State) ->
prioritise_cast(Msg, _Len, State) ->
case Msg of
delete_immediately -> 8;
+ {delete_exclusive, _Pid} -> 8;
{set_ram_duration_target, _Duration} -> 8;
{set_maximum_since_use, _Age} -> 8;
{run_backing_queue, _Mod, _Fun} -> 6;
@@ -1099,7 +1112,13 @@ handle_call({basic_cancel, ChPid, ConsumerTag, OkMsg}, _From,
notify_decorators(State1),
case should_auto_delete(State1) of
false -> reply(ok, ensure_expiry_timer(State1));
- true -> stop(ok, State1)
+ true ->
+ log_auto_delete(
+ io_lib:format(
+ "because its last consumer with tag '~s' was cancelled",
+ [ConsumerTag]),
+ State),
+ stop(ok, State1)
end
end;
@@ -1201,6 +1220,10 @@ handle_cast({reject, false, AckTags, ChPid}, State) ->
end) end,
fun () -> ack(AckTags, ChPid, State) end));
+handle_cast({delete_exclusive, ConnPid}, State) ->
+ log_delete_exclusive(ConnPid, State),
+ stop(State);
+
handle_cast(delete_immediately, State) ->
stop(State);
@@ -1225,22 +1248,15 @@ handle_cast({set_maximum_since_use, Age}, State) ->
ok = file_handle_cache:set_maximum_since_use(Age),
noreply(State);
-handle_cast(start_mirroring, State = #q{backing_queue = BQ,
- backing_queue_state = BQS}) ->
- %% lookup again to get policy for init_with_existing_bq
- {ok, Q} = rabbit_amqqueue:lookup(qname(State)),
- true = BQ =/= rabbit_mirror_queue_master, %% assertion
- BQ1 = rabbit_mirror_queue_master,
- BQS1 = BQ1:init_with_existing_bq(Q, BQ, BQS),
- noreply(State#q{backing_queue = BQ1,
- backing_queue_state = BQS1});
-
-handle_cast(stop_mirroring, State = #q{backing_queue = BQ,
- backing_queue_state = BQS}) ->
- BQ = rabbit_mirror_queue_master, %% assertion
- {BQ1, BQS1} = BQ:stop_mirroring(BQS),
- noreply(State#q{backing_queue = BQ1,
- backing_queue_state = BQS1});
+handle_cast(update_mirroring, State = #q{q = Q,
+ mirroring_policy_version = Version}) ->
+ case needs_update_mirroring(Q, Version) of
+ false ->
+ noreply(State);
+ {Policy, NewVersion} ->
+ State1 = State#q{mirroring_policy_version = NewVersion},
+ noreply(update_mirroring(Policy, State1))
+ end;
handle_cast({credit, ChPid, CTag, Credit, Drain},
State = #q{consumers = Consumers,
@@ -1320,6 +1336,7 @@ handle_info({'DOWN', _MonitorRef, process, DownPid, _Reason},
%% match what people expect (see bug 21824). However we need this
%% monitor-and-async- delete in case the connection goes away
%% unexpectedly.
+ log_delete_exclusive(DownPid, State),
stop(State);
handle_info({'DOWN', _MonitorRef, process, DownPid, _Reason}, State) ->
@@ -1375,7 +1392,7 @@ handle_pre_hibernate(State = #q{backing_queue = BQ,
State, #q.stats_timer,
fun () -> emit_stats(State,
[{idle_since,
- time_compat:os_system_time(milli_seconds)},
+ os:system_time(milli_seconds)},
{consumer_utilisation, ''}])
end),
State1 = rabbit_event:stop_stats_timer(State#q{backing_queue_state = BQS3},
@@ -1383,3 +1400,73 @@ handle_pre_hibernate(State = #q{backing_queue = BQ,
{hibernate, stop_rate_timer(State1)}.
format_message_queue(Opt, MQ) -> rabbit_misc:format_message_queue(Opt, MQ).
+
+log_delete_exclusive({ConPid, _ConRef}, State) ->
+ log_delete_exclusive(ConPid, State);
+log_delete_exclusive(ConPid, #q{ q = #amqqueue{ name = Resource } }) ->
+ #resource{ name = QName, virtual_host = VHost } = Resource,
+ rabbit_log_queue:debug("Deleting exclusive queue '~s' in vhost '~s' " ++
+ "because its declaring connection ~p was closed",
+ [QName, VHost, ConPid]).
+
+log_auto_delete(Reason, #q{ q = #amqqueue{ name = Resource } }) ->
+ #resource{ name = QName, virtual_host = VHost } = Resource,
+ rabbit_log_queue:debug("Deleting auto-delete queue '~s' in vhost '~s' " ++
+ Reason,
+ [QName, VHost]).
+
+needs_update_mirroring(Q, Version) ->
+ {ok, UpQ} = rabbit_amqqueue:lookup(Q#amqqueue.name),
+ DBVersion = UpQ#amqqueue.policy_version,
+ case DBVersion > Version of
+ true -> {rabbit_policy:get(<<"ha-mode">>, UpQ), DBVersion};
+ false -> false
+ end.
+
+
+update_mirroring(Policy, State = #q{backing_queue = BQ}) ->
+ case update_to(Policy, BQ) of
+ start_mirroring ->
+ start_mirroring(State);
+ stop_mirroring ->
+ stop_mirroring(State);
+ ignore ->
+ State;
+ update_ha_mode ->
+ update_ha_mode(State)
+ end.
+
+update_to(undefined, rabbit_mirror_queue_master) ->
+ stop_mirroring;
+update_to(_, rabbit_mirror_queue_master) ->
+ update_ha_mode;
+update_to(undefined, BQ) when BQ =/= rabbit_mirror_queue_master ->
+ ignore;
+update_to(_, BQ) when BQ =/= rabbit_mirror_queue_master ->
+ start_mirroring.
+
+start_mirroring(State = #q{backing_queue = BQ,
+ backing_queue_state = BQS}) ->
+ %% lookup again to get policy for init_with_existing_bq
+ {ok, Q} = rabbit_amqqueue:lookup(qname(State)),
+ true = BQ =/= rabbit_mirror_queue_master, %% assertion
+ BQ1 = rabbit_mirror_queue_master,
+ BQS1 = BQ1:init_with_existing_bq(Q, BQ, BQS),
+ State#q{backing_queue = BQ1,
+ backing_queue_state = BQS1}.
+
+stop_mirroring(State = #q{backing_queue = BQ,
+ backing_queue_state = BQS}) ->
+ BQ = rabbit_mirror_queue_master, %% assertion
+ {BQ1, BQS1} = BQ:stop_mirroring(BQS),
+ State#q{backing_queue = BQ1,
+ backing_queue_state = BQS1}.
+
+update_ha_mode(State) ->
+ {ok, Q} = rabbit_amqqueue:lookup(qname(State)),
+ ok = rabbit_mirror_queue_misc:update_mirrors(Q),
+ State.
+
+
+
+
diff --git a/src/rabbit_binding.erl b/src/rabbit_binding.erl
index 51bc883976..7cebd194a6 100644
--- a/src/rabbit_binding.erl
+++ b/src/rabbit_binding.erl
@@ -52,7 +52,7 @@
%% TODO this should really be opaque but that seems to confuse 17.1's
%% dialyzer into objecting to everything that uses it.
--type deletions() :: ?DICT_TYPE().
+-type deletions() :: dict:dict().
-spec recover([rabbit_exchange:name()], [rabbit_amqqueue:name()]) ->
'ok'.
diff --git a/src/rabbit_cli.erl b/src/rabbit_cli.erl
index c0e5c93247..65e8563ddf 100644
--- a/src/rabbit_cli.erl
+++ b/src/rabbit_cli.erl
@@ -171,7 +171,7 @@ name_type() ->
end.
generate_cli_node_name() ->
- Base = rabbit_misc:format("rabbitmq-cli-~2..0b", [rand_compat:uniform(100)]),
+ Base = rabbit_misc:format("rabbitmq-cli-~2..0b", [rand:uniform(100)]),
NameAsList =
case {name_type(), inet_db:res_option(domain)} of
{longnames, []} ->
diff --git a/src/rabbit_config.erl b/src/rabbit_config.erl
new file mode 100644
index 0000000000..67e7523ec0
--- /dev/null
+++ b/src/rabbit_config.erl
@@ -0,0 +1,179 @@
+-module(rabbit_config).
+
+-export([
+ generate_config_file/5,
+ prepare_and_use_config/0,
+ prepare_config/1,
+ update_app_config/1,
+ schema_dir/0,
+ config_files/0,
+ get_advanced_config/0
+ ]).
+
+prepare_and_use_config() ->
+ case legacy_erlang_term_config_used() of
+ true ->
+ %% Use .config file
+ ok;
+ false ->
+ case prepare_config(get_confs()) of
+ ok ->
+ %% No .conf to generate from
+ ok;
+ {ok, GeneratedConfigFile} ->
+ %% Generated config file
+ update_app_config(GeneratedConfigFile);
+ {error, Err} ->
+ {error, Err}
+ end
+ end.
+
+%% we support both the classic Erlang term
+%% config file (rabbitmq.config) as well as rabbitmq.conf
+legacy_erlang_term_config_used() ->
+ case init:get_argument(config) of
+ error -> false;
+ {ok, [Config | _]} ->
+ ConfigFile = Config ++ ".config",
+ rabbit_file:is_file(ConfigFile)
+ andalso
+ get_advanced_config() == none
+ end.
+
+get_confs() ->
+ case init:get_argument(conf) of
+ {ok, Configs} -> Configs;
+ _ -> []
+ end.
+
+prepare_config(Configs) ->
+ case {init:get_argument(conf_dir), init:get_argument(conf_script_dir)} of
+ {{ok, ConfDir}, {ok, ScriptDir}} ->
+ ConfFiles = [Config ++ ".conf" || [Config] <- Configs,
+ rabbit_file:is_file(Config ++
+ ".conf")],
+ case ConfFiles of
+ [] -> ok;
+ _ ->
+ case generate_config_file(ConfFiles, ConfDir, ScriptDir) of
+ {ok, GeneratedConfigFile} ->
+ {ok, GeneratedConfigFile};
+ {error, Reason} ->
+ {error, Reason}
+ end
+ end;
+ _ -> ok
+ end.
+
+update_app_config(ConfigFile) ->
+ ok = application_controller:change_application_data([], [ConfigFile]).
+
+generate_config_file(ConfFiles, ConfDir, ScriptDir) ->
+ generate_config_file(ConfFiles, ConfDir, ScriptDir,
+ schema_dir(), get_advanced_config()).
+
+
+generate_config_file(ConfFiles, ConfDir, ScriptDir, SchemaDir, Advanced) ->
+ prepare_plugin_schemas(SchemaDir),
+ % SchemaFile = filename:join([ScriptDir, "rabbitmq.schema"]),
+ Cuttlefish = filename:join([ScriptDir, "cuttlefish"]),
+ GeneratedDir = filename:join([ConfDir, "generated"]),
+
+ AdvancedConfigArg = case check_advanced_config(Advanced) of
+ {ok, FileName} -> [" -a ", FileName];
+ none -> []
+ end,
+ rabbit_file:recursive_delete([GeneratedDir]),
+ Command = lists:concat(["escript ", "\"", Cuttlefish, "\"",
+ " -f rabbitmq -s ", "\"", SchemaDir, "\"",
+ " -e ", "\"", ConfDir, "\"",
+ [[" -c ", ConfFile] || ConfFile <- ConfFiles],
+ AdvancedConfigArg]),
+ Result = rabbit_misc:os_cmd(Command),
+ case string:str(Result, " -config ") of
+ 0 -> {error, {generation_error, Result}};
+ _ ->
+ [OutFile] = rabbit_file:wildcard("rabbitmq.*.config", GeneratedDir),
+ ResultFile = filename:join([GeneratedDir, "rabbitmq.config"]),
+ rabbit_file:rename(filename:join([GeneratedDir, OutFile]),
+ ResultFile),
+ {ok, ResultFile}
+ end.
+
+schema_dir() ->
+ case init:get_argument(conf_schema_dir) of
+ {ok, SchemaDir} -> SchemaDir;
+ _ ->
+ case code:priv_dir(rabbit) of
+ {error, bad_name} -> filename:join([".", "priv", "schema"]);
+ PrivDir -> filename:join([PrivDir, "schema"])
+ end
+ end.
+
+check_advanced_config(none) -> none;
+check_advanced_config(ConfigName) ->
+ case rabbit_file:is_file(ConfigName) of
+ true -> {ok, ConfigName};
+ false -> none
+ end.
+
+get_advanced_config() ->
+ case init:get_argument(conf_advanced) of
+ %% There can be only one advanced.config
+ {ok, [FileName | _]} ->
+ ConfigName = FileName ++ ".config",
+ case rabbit_file:is_file(ConfigName) of
+ true -> ConfigName;
+ false -> none
+ end;
+ _ -> none
+ end.
+
+
+prepare_plugin_schemas(SchemaDir) ->
+ case rabbit_file:is_dir(SchemaDir) of
+ true -> rabbit_plugins:extract_schemas(SchemaDir);
+ false -> ok
+ end.
+
+
+config_files() ->
+ Abs = fun (F, Ex) -> filename:absname(filename:rootname(F, Ex) ++ Ex) end,
+ case legacy_erlang_term_config_used() of
+ true ->
+ case init:get_argument(config) of
+ {ok, Files} -> [Abs(File, ".config") || [File] <- Files];
+ error -> case config_setting() of
+ none -> [];
+ File -> [Abs(File, ".config")
+ ++
+ " (not found)"]
+ end
+ end;
+ false ->
+ ConfFiles = [Abs(File, ".conf") || File <- get_confs()],
+ AdvancedFiles = case get_advanced_config() of
+ none -> [];
+ FileName -> [Abs(FileName, ".config")]
+ end,
+ AdvancedFiles ++ ConfFiles
+
+ end.
+
+
+%% This is a pain. We want to know where the config file is. But we
+%% can't specify it on the command line if it is missing or the VM
+%% will fail to start, so we need to find it by some mechanism other
+%% than init:get_arguments/0. We can look at the environment variable
+%% which is responsible for setting it... but that doesn't work for a
+%% Windows service since the variable can change and the service not
+%% be reinstalled, so in that case we add a magic application env.
+config_setting() ->
+ case application:get_env(rabbit, windows_service_config) of
+ {ok, File1} -> File1;
+ undefined -> case os:getenv("RABBITMQ_CONFIG_FILE") of
+ false -> none;
+ File2 -> File2
+ end
+ end.
+
diff --git a/src/rabbit_connection_tracking.erl b/src/rabbit_connection_tracking.erl
new file mode 100644
index 0000000000..ab945abc2f
--- /dev/null
+++ b/src/rabbit_connection_tracking.erl
@@ -0,0 +1,337 @@
+%% 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 Developer of the Original Code is GoPivotal, Inc.
+%% Copyright (c) 2007-2016 Pivotal Software, Inc. All rights reserved.
+%%
+
+-module(rabbit_connection_tracking).
+
+%% Abstracts away how tracked connection records are stored
+%% and queried.
+%%
+%% See also:
+%%
+%% * rabbit_connection_tracking_handler
+%% * rabbit_reader
+%% * rabbit_event
+
+-export([boot/0,
+ ensure_tracked_connections_table_for_node/1,
+ ensure_per_vhost_tracked_connections_table_for_node/1,
+ ensure_tracked_connections_table_for_this_node/0,
+ ensure_per_vhost_tracked_connections_table_for_this_node/0,
+ tracked_connection_table_name_for/1, tracked_connection_per_vhost_table_name_for/1,
+ delete_tracked_connections_table_for_node/1, delete_per_vhost_tracked_connections_table_for_node/1,
+ clear_tracked_connection_tables_for_this_node/0,
+ register_connection/1, unregister_connection/1,
+ list/0, list/1, list_on_node/1,
+ tracked_connection_from_connection_created/1,
+ tracked_connection_from_connection_state/1,
+ is_over_connection_limit/1, count_connections_in/1]).
+
+-include_lib("rabbit.hrl").
+
+-import(rabbit_misc, [pget/2]).
+
+%%
+%% API
+%%
+
+-spec boot() -> ok.
+
+%% Sets up and resets connection tracking tables for this
+%% node.
+boot() ->
+ ensure_tracked_connections_table_for_this_node(),
+ rabbit_log:info("Setting up a table for connection tracking on this node: ~p",
+ [tracked_connection_table_name_for(node())]),
+ ensure_per_vhost_tracked_connections_table_for_this_node(),
+ rabbit_log:info("Setting up a table for per-vhost connection counting on this node: ~p",
+ [tracked_connection_per_vhost_table_name_for(node())]),
+ clear_tracked_connection_tables_for_this_node(),
+ ok.
+
+
+-spec ensure_tracked_connections_table_for_this_node() -> ok.
+
+ensure_tracked_connections_table_for_this_node() ->
+ ensure_tracked_connections_table_for_node(node()).
+
+
+-spec ensure_per_vhost_tracked_connections_table_for_this_node() -> ok.
+
+ensure_per_vhost_tracked_connections_table_for_this_node() ->
+ ensure_per_vhost_tracked_connections_table_for_node(node()).
+
+
+-spec ensure_tracked_connections_table_for_node(node()) -> ok.
+
+ensure_tracked_connections_table_for_node(Node) ->
+ TableName = tracked_connection_table_name_for(Node),
+ case mnesia:create_table(TableName, [{record_name, tracked_connection},
+ {attributes, record_info(fields, tracked_connection)}]) of
+ {atomic, ok} -> ok;
+ {aborted, Error} ->
+ rabbit_log:error("Failed to create a tracked connection table for node ~p: ~p", [Node, Error]),
+ ok
+ end.
+
+
+-spec ensure_per_vhost_tracked_connections_table_for_node(node()) -> ok.
+
+ensure_per_vhost_tracked_connections_table_for_node(Node) ->
+ TableName = tracked_connection_per_vhost_table_name_for(Node),
+ case mnesia:create_table(TableName, [{record_name, tracked_connection_per_vhost},
+ {attributes, record_info(fields, tracked_connection_per_vhost)}]) of
+ {atomic, ok} -> ok;
+ {aborted, _} -> ok
+ %% TODO: propagate errors
+ end.
+
+
+-spec clear_tracked_connection_tables_for_this_node() -> ok.
+
+clear_tracked_connection_tables_for_this_node() ->
+ case mnesia:clear_table(tracked_connection_table_name_for(node())) of
+ {atomic, ok} -> ok;
+ {aborted, _} -> ok
+ end,
+ case mnesia:clear_table(tracked_connection_per_vhost_table_name_for(node())) of
+ {atomic, ok} -> ok;
+ {aborted, _} -> ok
+ end.
+
+
+-spec delete_tracked_connections_table_for_node(node()) -> ok.
+
+delete_tracked_connections_table_for_node(Node) ->
+ TableName = tracked_connection_table_name_for(Node),
+ case mnesia:delete_table(TableName) of
+ {atomic, ok} -> ok;
+ {aborted, {no_exists, _}} -> ok;
+ {aborted, Error} ->
+ rabbit_log:error("Failed to delete a tracked connection table for node ~p: ~p", [Node, Error]),
+ ok
+ end.
+
+
+-spec delete_per_vhost_tracked_connections_table_for_node(node()) -> ok.
+
+delete_per_vhost_tracked_connections_table_for_node(Node) ->
+ TableName = tracked_connection_per_vhost_table_name_for(Node),
+ case mnesia:delete_table(TableName) of
+ {atomic, ok} -> ok;
+ {aborted, {no_exists, _}} -> ok;
+ {aborted, Error} ->
+ rabbit_log:error("Failed to delete a per-vhost tracked connection table for node ~p: ~p", [Node, Error]),
+ ok
+ end.
+
+
+-spec tracked_connection_table_name_for(node()) -> atom().
+
+tracked_connection_table_name_for(Node) ->
+ list_to_atom(rabbit_misc:format("tracked_connection_on_node_~s", [Node])).
+
+-spec tracked_connection_per_vhost_table_name_for(node()) -> atom().
+
+tracked_connection_per_vhost_table_name_for(Node) ->
+ list_to_atom(rabbit_misc:format("tracked_connection_per_vhost_on_node_~s", [Node])).
+
+
+-spec register_connection(rabbit_types:tracked_connection()) -> ok.
+
+register_connection(#tracked_connection{vhost = VHost, id = ConnId, node = Node} = Conn) when Node =:= node() ->
+ TableName = tracked_connection_table_name_for(Node),
+ PerVhostTableName = tracked_connection_per_vhost_table_name_for(Node),
+ rabbit_misc:execute_mnesia_transaction(
+ fun() ->
+ %% upsert
+ case mnesia:dirty_read(TableName, ConnId) of
+ [] ->
+ mnesia:write(TableName, Conn, write),
+ mnesia:dirty_update_counter(
+ PerVhostTableName, VHost, 1);
+ [_Row] ->
+ ok
+ end,
+ ok
+ end).
+
+-spec unregister_connection(rabbit_types:connection_name()) -> ok.
+
+unregister_connection(ConnId = {Node, _Name}) when Node =:= node() ->
+ TableName = tracked_connection_table_name_for(Node),
+ PerVhostTableName = tracked_connection_per_vhost_table_name_for(Node),
+ rabbit_misc:execute_mnesia_transaction(
+ fun() ->
+ case mnesia:dirty_read(TableName, ConnId) of
+ [] -> ok;
+ [Row] ->
+ mnesia:dirty_update_counter(
+ PerVhostTableName, Row#tracked_connection.vhost, -1),
+ mnesia:delete({TableName, ConnId})
+ end
+ end).
+
+
+-spec list() -> [rabbit_types:tracked_connection()].
+
+list() ->
+ lists:foldl(
+ fun (Node, Acc) ->
+ Tab = tracked_connection_table_name_for(Node),
+ Acc ++ mnesia:dirty_match_object(Tab, #tracked_connection{_ = '_'})
+ end, [], rabbit_mnesia:cluster_nodes(running)).
+
+
+-spec list(rabbit_types:vhost()) -> [rabbit_types:tracked_connection()].
+
+list(VHost) ->
+ lists:foldl(
+ fun (Node, Acc) ->
+ Tab = tracked_connection_table_name_for(Node),
+ Acc ++ mnesia:dirty_match_object(Tab, #tracked_connection{vhost = VHost, _ = '_'})
+ end, [], rabbit_mnesia:cluster_nodes(running)).
+
+
+-spec list_on_node(node()) -> [rabbit_types:tracked_connection()].
+
+list_on_node(Node) ->
+ try mnesia:dirty_match_object(
+ tracked_connection_table_name_for(Node),
+ #tracked_connection{_ = '_'})
+ catch exit:{aborted, {no_exists, _}} -> []
+ end.
+
+-spec is_over_connection_limit(rabbit_types:vhost()) -> {true, non_neg_integer()} | false.
+
+is_over_connection_limit(VirtualHost) ->
+ case rabbit_vhost_limit:connection_limit(VirtualHost) of
+ %% no limit configured
+ undefined -> false;
+ %% with limit = 0, no connections are allowed
+ {ok, 0} -> {true, 0};
+ {ok, Limit} when is_integer(Limit) andalso Limit > 0 ->
+ ConnectionCount = count_connections_in(VirtualHost),
+ case ConnectionCount >= Limit of
+ false -> false;
+ true -> {true, Limit}
+ end;
+ %% any negative value means "no limit". Note that parameter validation
+ %% will replace negative integers with 'undefined', so this is to be
+ %% explicit and extra defensive
+ {ok, Limit} when is_integer(Limit) andalso Limit < 0 -> false;
+ %% ignore non-integer limits
+ {ok, _Limit} -> false
+ end.
+
+
+-spec count_connections_in(rabbit_types:vhost()) -> non_neg_integer().
+
+count_connections_in(VirtualHost) ->
+ lists:foldl(fun (Node, Acc) ->
+ Tab = tracked_connection_per_vhost_table_name_for(Node),
+ try
+ N = case mnesia:transaction(
+ fun() ->
+ case mnesia:dirty_read({Tab, VirtualHost}) of
+ [] -> 0;
+ [Val] -> Val#tracked_connection_per_vhost.connection_count
+ end
+ end) of
+ {atomic, Val} -> Val;
+ {aborted, _Reason} -> 0
+ end,
+ Acc + N
+ catch _:Err ->
+ rabbit_log:error(
+ "Failed to fetch number of connections in vhost ~p on node ~p:~n~p~n",
+ [VirtualHost, Err, Node]),
+ Acc
+ end
+ end, 0, rabbit_mnesia:cluster_nodes(running)).
+
+%% Returns a #tracked_connection from connection_created
+%% event details.
+%%
+%% @see rabbit_connection_tracking_handler.
+tracked_connection_from_connection_created(EventDetails) ->
+ %% Example event:
+ %%
+ %% [{type,network},
+ %% {pid,<0.329.0>},
+ %% {name,<<"127.0.0.1:60998 -> 127.0.0.1:5672">>},
+ %% {port,5672},
+ %% {peer_port,60998},
+ %% {host,{0,0,0,0,0,65535,32512,1}},
+ %% {peer_host,{0,0,0,0,0,65535,32512,1}},
+ %% {ssl,false},
+ %% {peer_cert_subject,''},
+ %% {peer_cert_issuer,''},
+ %% {peer_cert_validity,''},
+ %% {auth_mechanism,<<"PLAIN">>},
+ %% {ssl_protocol,''},
+ %% {ssl_key_exchange,''},
+ %% {ssl_cipher,''},
+ %% {ssl_hash,''},
+ %% {protocol,{0,9,1}},
+ %% {user,<<"guest">>},
+ %% {vhost,<<"/">>},
+ %% {timeout,14},
+ %% {frame_max,131072},
+ %% {channel_max,65535},
+ %% {client_properties,
+ %% [{<<"capabilities">>,table,
+ %% [{<<"publisher_confirms">>,bool,true},
+ %% {<<"consumer_cancel_notify">>,bool,true},
+ %% {<<"exchange_exchange_bindings">>,bool,true},
+ %% {<<"basic.nack">>,bool,true},
+ %% {<<"connection.blocked">>,bool,true},
+ %% {<<"authentication_failure_close">>,bool,true}]},
+ %% {<<"product">>,longstr,<<"Bunny">>},
+ %% {<<"platform">>,longstr,
+ %% <<"ruby 2.3.0p0 (2015-12-25 revision 53290) [x86_64-darwin15]">>},
+ %% {<<"version">>,longstr,<<"2.3.0.pre">>},
+ %% {<<"information">>,longstr,
+ %% <<"http://rubybunny.info">>}]},
+ %% {connected_at,1453214290847}]
+ Name = pget(name, EventDetails),
+ Node = pget(node, EventDetails),
+ #tracked_connection{id = {Node, Name},
+ name = Name,
+ node = Node,
+ vhost = pget(vhost, EventDetails),
+ username = pget(user, EventDetails),
+ connected_at = pget(connected_at, EventDetails),
+ pid = pget(pid, EventDetails),
+ peer_host = pget(peer_host, EventDetails),
+ peer_port = pget(peer_port, EventDetails)}.
+
+tracked_connection_from_connection_state(#connection{
+ vhost = VHost,
+ connected_at = Ts,
+ peer_host = PeerHost,
+ peer_port = PeerPort,
+ user = Username,
+ name = Name
+ }) ->
+ tracked_connection_from_connection_created(
+ [{name, Name},
+ {node, node()},
+ {vhost, VHost},
+ {user, Username},
+ {connected_at, Ts},
+ {pid, self()},
+ {peer_port, PeerPort},
+ {peer_host, PeerHost}]).
diff --git a/src/rabbit_connection_tracking_handler.erl b/src/rabbit_connection_tracking_handler.erl
new file mode 100644
index 0000000000..fd1df8c88a
--- /dev/null
+++ b/src/rabbit_connection_tracking_handler.erl
@@ -0,0 +1,108 @@
+%% 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 Developer of the Original Code is GoPivotal, Inc.
+%% Copyright (c) 2007-2016 Pivotal Software, Inc. All rights reserved.
+%%
+
+-module(rabbit_connection_tracking_handler).
+
+%% This module keeps track of connection creation and termination events
+%% on its local node. The primary goal here is to decouple connection
+%% tracking from rabbit_reader in rabbit_common.
+%%
+%% Events from other nodes are ignored.
+
+-behaviour(gen_event).
+
+-export([init/1, handle_call/2, handle_event/2, handle_info/2,
+ terminate/2, code_change/3]).
+
+-include_lib("rabbit.hrl").
+-import(rabbit_misc, [pget/2]).
+
+-rabbit_boot_step({?MODULE,
+ [{description, "connection tracking event handler"},
+ {mfa, {gen_event, add_handler,
+ [rabbit_event, ?MODULE, []]}},
+ {cleanup, {gen_event, delete_handler,
+ [rabbit_event, ?MODULE, []]}},
+ {requires, [rabbit_event, rabbit_node_monitor]},
+ {enables, recovery}]}).
+
+
+%%
+%% API
+%%
+
+init([]) ->
+ {ok, []}.
+
+handle_event(#event{type = connection_created, props = Details}, State) ->
+ ThisNode = node(),
+ case pget(node, Details) of
+ ThisNode ->
+ rabbit_connection_tracking:register_connection(
+ rabbit_connection_tracking:tracked_connection_from_connection_created(Details)
+ );
+ _OtherNode ->
+ %% ignore
+ ok
+ end,
+ {ok, State};
+handle_event(#event{type = connection_closed, props = Details}, State) ->
+ ThisNode = node(),
+ case pget(node, Details) of
+ ThisNode ->
+ %% [{name,<<"127.0.0.1:64078 -> 127.0.0.1:5672">>},
+ %% {pid,<0.1774.0>},
+ %% {node, rabbit@hostname}]
+ rabbit_connection_tracking:unregister_connection(
+ {pget(node, Details),
+ pget(name, Details)});
+ _OtherNode ->
+ %% ignore
+ ok
+ end,
+ {ok, State};
+handle_event(#event{type = vhost_deleted, props = Details}, State) ->
+ VHost = pget(name, Details),
+ rabbit_log_connection:info("Closing all connections in vhost '~s' because it's being deleted", [VHost]),
+ [rabbit_networking:close_connection(Pid, rabbit_misc:format("vhost '~s' is deleted", [VHost])) ||
+ #tracked_connection{pid = Pid} <- rabbit_connection_tracking:list(VHost)],
+ {ok, State};
+handle_event(#event{type = user_deleted, props = Details}, State) ->
+ _Username = pget(name, Details),
+ %% TODO: force close and unregister connections from
+ %% this user. Moved to rabbitmq/rabbitmq-server#628.
+ {ok, State};
+%% A node had been deleted from the cluster.
+handle_event(#event{type = node_deleted, props = Details}, State) ->
+ Node = pget(node, Details),
+ rabbit_log_connection:info("Node '~s' was removed from the cluster, deleting its connection tracking tables...", [Node]),
+ rabbit_connection_tracking:delete_tracked_connections_table_for_node(Node),
+ rabbit_connection_tracking:delete_per_vhost_tracked_connections_table_for_node(Node),
+ {ok, State};
+handle_event(_Event, State) ->
+ {ok, State}.
+
+handle_call(_Request, State) ->
+ {ok, not_understood, State}.
+
+handle_info(_Info, State) ->
+ {ok, State}.
+
+terminate(_Arg, _State) ->
+ ok.
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
diff --git a/src/rabbit_control_main.erl b/src/rabbit_control_main.erl
index 92898c2a2c..0e99ac0f7e 100644
--- a/src/rabbit_control_main.erl
+++ b/src/rabbit_control_main.erl
@@ -23,7 +23,7 @@
sync_queue/1, cancel_sync_queue/1, become/1,
purge_queue/1]).
--import(rabbit_misc, [rpc_call/4, rpc_call/5, rpc_call/7]).
+-import(rabbit_misc, [rpc_call/4, rpc_call/5]).
-define(EXTERNAL_CHECK_INTERVAL, 1000).
@@ -74,6 +74,8 @@
{clear_policy, [?VHOST_DEF]},
{list_policies, [?VHOST_DEF]},
+ {set_vhost_limits, [?VHOST_DEF]},
+ {clear_vhost_limits, [?VHOST_DEF]},
{list_queues, [?VHOST_DEF, ?OFFLINE_DEF, ?ONLINE_DEF, ?LOCAL_DEF]},
{list_exchanges, [?VHOST_DEF]},
{list_bindings, [?VHOST_DEF]},
@@ -391,11 +393,8 @@ action(environment, Node, _App, _Opts, Inform) ->
display_call_result(Node, {rabbit, environment, []});
action(rotate_logs, Node, [], _Opts, Inform) ->
- Inform("Reopening logs for node ~p", [Node]),
- call(Node, {rabbit, rotate_logs, [""]});
-action(rotate_logs, Node, Args = [Suffix], _Opts, Inform) ->
- Inform("Rotating logs to files with suffix \"~s\"", [Suffix]),
- call(Node, {rabbit, rotate_logs, Args});
+ Inform("Rotating logs for node ~p", [Node]),
+ call(Node, {rabbit, rotate_logs, []});
action(hipe_compile, _Node, [TargetDir], _Opts, _Inform) ->
ok = application:load(rabbit),
@@ -547,6 +546,17 @@ action(clear_policy, Node, [Key], Opts, Inform) ->
Inform("Clearing policy ~p", [Key]),
rpc_call(Node, rabbit_policy, delete, [VHostArg, list_to_binary(Key)]);
+action(set_vhost_limits, Node, [Defn], Opts, Inform) ->
+ VHostArg = list_to_binary(proplists:get_value(?VHOST_OPT, Opts)),
+ Inform("Setting vhost limits for vhost ~p", [VHostArg]),
+ rpc_call(Node, rabbit_vhost_limit, parse_set, [VHostArg, Defn]),
+ ok;
+
+action(clear_vhost_limits, Node, [], Opts, Inform) ->
+ VHostArg = list_to_binary(proplists:get_value(?VHOST_OPT, Opts)),
+ Inform("Clearing vhost ~p limits", [VHostArg]),
+ rpc_call(Node, rabbit_vhost_limit, clear, [VHostArg]);
+
action(report, Node, _Args, _Opts, Inform) ->
Inform("Reporting server status on ~p~n~n", [erlang:universaltime()]),
[begin ok = action(Action, N, [], [], Inform), io:nl() end ||
@@ -595,41 +605,45 @@ action(purge_queue, Node, [Q], Opts, Inform, Timeout) ->
action(list_users, Node, [], _Opts, Inform, Timeout) ->
Inform("Listing users", []),
- call(Node, {rabbit_auth_backend_internal, list_users, []},
- rabbit_auth_backend_internal:user_info_keys(), true, Timeout);
+ call_emitter(Node, {rabbit_auth_backend_internal, list_users, []},
+ rabbit_auth_backend_internal:user_info_keys(),
+ [{timeout, Timeout}, to_bin_utf8]);
action(list_permissions, Node, [], Opts, Inform, Timeout) ->
VHost = proplists:get_value(?VHOST_OPT, Opts),
Inform("Listing permissions in vhost \"~s\"", [VHost]),
- call(Node, {rabbit_auth_backend_internal, list_vhost_permissions, [VHost]},
- rabbit_auth_backend_internal:vhost_perms_info_keys(), true, Timeout,
- true);
+ call_emitter(Node, {rabbit_auth_backend_internal, list_vhost_permissions, [VHost]},
+ rabbit_auth_backend_internal:vhost_perms_info_keys(),
+ [{timeout, Timeout}, to_bin_utf8, is_escaped]);
action(list_parameters, Node, [], Opts, Inform, Timeout) ->
VHostArg = list_to_binary(proplists:get_value(?VHOST_OPT, Opts)),
Inform("Listing runtime parameters", []),
- call(Node, {rabbit_runtime_parameters, list_formatted, [VHostArg]},
- rabbit_runtime_parameters:info_keys(), Timeout);
+ call_emitter(Node, {rabbit_runtime_parameters, list_formatted, [VHostArg]},
+ rabbit_runtime_parameters:info_keys(),
+ [{timeout, Timeout}]);
action(list_policies, Node, [], Opts, Inform, Timeout) ->
VHostArg = list_to_binary(proplists:get_value(?VHOST_OPT, Opts)),
Inform("Listing policies", []),
- call(Node, {rabbit_policy, list_formatted, [VHostArg]},
- rabbit_policy:info_keys(), Timeout);
+ call_emitter(Node, {rabbit_policy, list_formatted, [VHostArg]},
+ rabbit_policy:info_keys(),
+ [{timeout, Timeout}]);
action(list_vhosts, Node, Args, _Opts, Inform, Timeout) ->
Inform("Listing vhosts", []),
ArgAtoms = default_if_empty(Args, [name]),
- call(Node, {rabbit_vhost, info_all, []}, ArgAtoms, true, Timeout);
+ call_emitter(Node, {rabbit_vhost, info_all, []}, ArgAtoms,
+ [{timeout, Timeout}, to_bin_utf8]);
action(list_user_permissions, _Node, _Args = [], _Opts, _Inform, _Timeout) ->
{error_string,
"list_user_permissions expects a username argument, but none provided."};
action(list_user_permissions, Node, Args = [_Username], _Opts, Inform, Timeout) ->
Inform("Listing permissions for user ~p", Args),
- call(Node, {rabbit_auth_backend_internal, list_user_permissions, Args},
- rabbit_auth_backend_internal:user_perms_info_keys(), true, Timeout,
- true);
+ call_emitter(Node, {rabbit_auth_backend_internal, list_user_permissions, Args},
+ rabbit_auth_backend_internal:user_perms_info_keys(),
+ [{timeout, Timeout}, to_bin_utf8, is_escaped]);
action(list_queues, Node, Args, Opts, Inform, Timeout) ->
case rabbit_cli:mutually_exclusive_flags(
@@ -640,8 +654,36 @@ action(list_queues, Node, Args, Opts, Inform, Timeout) ->
Inform("Listing queues", []),
VHostArg = list_to_binary(proplists:get_value(?VHOST_OPT, Opts)),
ArgAtoms = default_if_empty(Args, [name, messages]),
- call(Node, {rabbit_amqqueue, info_all, [VHostArg, ArgAtoms, Filter]},
- ArgAtoms, Timeout);
+
+ %% Data for emission
+ Nodes = nodes_in_cluster(Node, Timeout),
+ ChunksOpt = {chunks, get_number_of_chunks(Filter, Nodes)},
+ TimeoutOpt = {timeout, Timeout},
+ EmissionRef = make_ref(),
+ EmissionRefOpt = {ref, EmissionRef},
+
+ case Filter of
+ all ->
+ start_emission(Node, {rabbit_amqqueue, emit_info_all,
+ [Nodes, VHostArg, ArgAtoms]},
+ [TimeoutOpt, EmissionRefOpt]),
+ start_emission(Node, {rabbit_amqqueue, emit_info_down,
+ [VHostArg, ArgAtoms]},
+ [TimeoutOpt, EmissionRefOpt]);
+ online ->
+ start_emission(Node, {rabbit_amqqueue, emit_info_all,
+ [Nodes, VHostArg, ArgAtoms]},
+ [TimeoutOpt, EmissionRefOpt]);
+ offline ->
+ start_emission(Node, {rabbit_amqqueue, emit_info_down,
+ [VHostArg, ArgAtoms]},
+ [TimeoutOpt, EmissionRefOpt]);
+ local ->
+ start_emission(Node, {rabbit_amqqueue, emit_info_local,
+ [VHostArg, ArgAtoms]},
+ [TimeoutOpt, EmissionRefOpt])
+ end,
+ display_emission_result(EmissionRef, ArgAtoms, [ChunksOpt, TimeoutOpt]);
{error, ErrStr} ->
{error_string, ErrStr}
end;
@@ -650,8 +692,8 @@ action(list_exchanges, Node, Args, Opts, Inform, Timeout) ->
Inform("Listing exchanges", []),
VHostArg = list_to_binary(proplists:get_value(?VHOST_OPT, Opts)),
ArgAtoms = default_if_empty(Args, [name, type]),
- call(Node, {rabbit_exchange, info_all, [VHostArg, ArgAtoms]},
- ArgAtoms, Timeout);
+ call_emitter(Node, {rabbit_exchange, info_all, [VHostArg, ArgAtoms]},
+ ArgAtoms, [{timeout, Timeout}]);
action(list_bindings, Node, Args, Opts, Inform, Timeout) ->
Inform("Listing bindings", []),
@@ -659,27 +701,31 @@ action(list_bindings, Node, Args, Opts, Inform, Timeout) ->
ArgAtoms = default_if_empty(Args, [source_name, source_kind,
destination_name, destination_kind,
routing_key, arguments]),
- call(Node, {rabbit_binding, info_all, [VHostArg, ArgAtoms]},
- ArgAtoms, Timeout);
+ call_emitter(Node, {rabbit_binding, info_all, [VHostArg, ArgAtoms]},
+ ArgAtoms, [{timeout, Timeout}]);
action(list_connections, Node, Args, _Opts, Inform, Timeout) ->
Inform("Listing connections", []),
ArgAtoms = default_if_empty(Args, [user, peer_host, peer_port, state]),
- call(Node, {rabbit_networking, connection_info_all, [ArgAtoms]},
- ArgAtoms, Timeout);
+ Nodes = nodes_in_cluster(Node, Timeout),
+ call_emitter(Node, {rabbit_networking, emit_connection_info_all, [Nodes, ArgAtoms]},
+ ArgAtoms, [{timeout, Timeout}, {chunks, length(Nodes)}]);
action(list_channels, Node, Args, _Opts, Inform, Timeout) ->
Inform("Listing channels", []),
ArgAtoms = default_if_empty(Args, [pid, user, consumer_count,
messages_unacknowledged]),
- call(Node, {rabbit_channel, info_all, [ArgAtoms]},
- ArgAtoms, Timeout);
+ Nodes = nodes_in_cluster(Node, Timeout),
+ call_emitter(Node, {rabbit_channel, emit_info_all, [Nodes, ArgAtoms]}, ArgAtoms,
+ [{timeout, Timeout}, {chunks, length(Nodes)}]);
action(list_consumers, Node, _Args, Opts, Inform, Timeout) ->
Inform("Listing consumers", []),
VHostArg = list_to_binary(proplists:get_value(?VHOST_OPT, Opts)),
- call(Node, {rabbit_amqqueue, consumers_all, [VHostArg]},
- rabbit_amqqueue:consumer_info_keys(), Timeout);
+ Nodes = nodes_in_cluster(Node, Timeout),
+ call_emitter(Node, {rabbit_amqqueue, emit_consumers_all, [Nodes, VHostArg]},
+ rabbit_amqqueue:consumer_info_keys(),
+ [{timeout, Timeout}, {chunks, length(Nodes)}]);
action(node_health_check, Node, _Args, _Opts, Inform, Timeout) ->
Inform("Checking health of node ~p", [Node]),
@@ -795,17 +841,18 @@ display_info_message_row(IsEscaped, Result, InfoItemKeys) ->
{X, Value} -> Value
end, IsEscaped) || X <- InfoItemKeys]).
-display_info_message(IsEscaped) ->
+display_info_message(IsEscaped, InfoItemKeys) ->
fun ([], _) ->
ok;
- ([FirstResult|_] = List, InfoItemKeys) when is_list(FirstResult) ->
+ ([FirstResult|_] = List, _) when is_list(FirstResult) ->
lists:foreach(fun(Result) ->
display_info_message_row(IsEscaped, Result, InfoItemKeys)
end,
List),
ok;
- (Result, InfoItemKeys) ->
- display_info_message_row(IsEscaped, Result, InfoItemKeys)
+ (Result, _) ->
+ display_info_message_row(IsEscaped, Result, InfoItemKeys),
+ ok
end.
display_info_list(Results, InfoItemKeys) when is_list(Results) ->
@@ -862,7 +909,10 @@ display_call_result(Node, MFA) ->
end.
unsafe_rpc(Node, Mod, Fun, Args) ->
- case rpc_call(Node, Mod, Fun, Args) of
+ unsafe_rpc(Node, Mod, Fun, Args, ?RPC_TIMEOUT).
+
+unsafe_rpc(Node, Mod, Fun, Args, Timeout) ->
+ case rpc_call(Node, Mod, Fun, Args, Timeout) of
{badrpc, _} = Res -> throw(Res);
Normal -> Normal
end.
@@ -881,33 +931,42 @@ ensure_app_running(Node) ->
call(Node, {Mod, Fun, Args}) ->
rpc_call(Node, Mod, Fun, lists:map(fun list_to_binary_utf8/1, Args)).
-call(Node, {Mod, Fun, Args}, InfoKeys, Timeout) ->
- call(Node, {Mod, Fun, Args}, InfoKeys, false, Timeout, false).
+call_emitter(Node, {Mod, Fun, Args}, InfoKeys, Opts) ->
+ Ref = start_emission(Node, {Mod, Fun, Args}, Opts),
+ display_emission_result(Ref, InfoKeys, Opts).
+
+start_emission(Node, {Mod, Fun, Args}, Opts) ->
+ ToBinUtf8 = proplists:get_value(to_bin_utf8, Opts, false),
+ Timeout = proplists:get_value(timeout, Opts, infinity),
+ Ref = proplists:get_value(ref, Opts, make_ref()),
+ rabbit_control_misc:spawn_emitter_caller(
+ Node, Mod, Fun, prepare_call_args(Args, ToBinUtf8),
+ Ref, self(), Timeout),
+ Ref.
+
+display_emission_result(Ref, InfoKeys, Opts) ->
+ IsEscaped = proplists:get_value(is_escaped, Opts, false),
+ Chunks = proplists:get_value(chunks, Opts, 1),
+ Timeout = proplists:get_value(timeout, Opts, infinity),
+ EmissionStatus = rabbit_control_misc:wait_for_info_messages(
+ self(), Ref, display_info_message(IsEscaped, InfoKeys), ok, Timeout, Chunks),
+ emission_to_action_result(EmissionStatus).
+
+%% Convert rabbit_control_misc:wait_for_info_messages/6 return value
+%% into form expected by rabbit_cli:main/3.
+emission_to_action_result({ok, ok}) ->
+ ok;
+emission_to_action_result({error, Error}) ->
+ Error.
-call(Node, {Mod, Fun, Args}, InfoKeys, ToBinUtf8, Timeout) ->
- call(Node, {Mod, Fun, Args}, InfoKeys, ToBinUtf8, Timeout, false).
+prepare_call_args(Args, ToBinUtf8) ->
+ case ToBinUtf8 of
+ true -> valid_utf8_args(Args);
+ false -> Args
+ end.
-call(Node, {Mod, Fun, Args}, InfoKeys, ToBinUtf8, Timeout, IsEscaped) ->
- Args0 = case ToBinUtf8 of
- true -> lists:map(fun list_to_binary_utf8/1, Args);
- false -> Args
- end,
- Ref = make_ref(),
- Pid = self(),
- spawn_link(
- fun () ->
- case rabbit_cli:rpc_call(Node, Mod, Fun, Args0,
- Ref, Pid, Timeout) of
- {error, _} = Error ->
- Pid ! {error, Error};
- {bad_argument, _} = Error ->
- Pid ! {error, Error};
- _ ->
- ok
- end
- end),
- rabbit_control_misc:wait_for_info_messages(
- Pid, Ref, InfoKeys, display_info_message(IsEscaped), Timeout).
+valid_utf8_args(Args) ->
+ lists:map(fun list_to_binary_utf8/1, Args).
list_to_binary_utf8(L) ->
B = list_to_binary(L),
@@ -957,7 +1016,10 @@ split_list([_]) -> exit(even_list_needed);
split_list([A, B | T]) -> [{A, B} | split_list(T)].
nodes_in_cluster(Node) ->
- unsafe_rpc(Node, rabbit_mnesia, cluster_nodes, [running]).
+ unsafe_rpc(Node, rabbit_mnesia, cluster_nodes, [running], ?RPC_TIMEOUT).
+
+nodes_in_cluster(Node, Timeout) ->
+ unsafe_rpc(Node, rabbit_mnesia, cluster_nodes, [running], Timeout).
alarms_by_node(Name) ->
case rpc_call(Name, rabbit, status, []) of
@@ -966,3 +1028,12 @@ alarms_by_node(Name) ->
{_, As} = lists:keyfind(alarms, 1, Status),
{Name, As}
end.
+
+get_number_of_chunks(all, Nodes) ->
+ length(Nodes) + 1;
+get_number_of_chunks(online, Nodes) ->
+ length(Nodes);
+get_number_of_chunks(offline, _) ->
+ 1;
+get_number_of_chunks(local, _) ->
+ 1.
diff --git a/src/rabbit_dead_letter.erl b/src/rabbit_dead_letter.erl
index 91d23c83a4..5b367e410a 100644
--- a/src/rabbit_dead_letter.erl
+++ b/src/rabbit_dead_letter.erl
@@ -49,7 +49,7 @@ make_msg(Msg = #basic_message{content = Content,
_ -> {[RK], fun (H) -> lists:keydelete(<<"CC">>, 1, H) end}
end,
ReasonBin = list_to_binary(atom_to_list(Reason)),
- TimeSec = time_compat:os_system_time(seconds),
+ TimeSec = os:system_time(seconds),
PerMsgTTL = per_msg_ttl_header(Content#content.properties),
HeadersFun2 =
fun (Headers) ->
diff --git a/src/rabbit_error_logger.erl b/src/rabbit_error_logger.erl
index 5ba3ce7a4f..a59afe6c43 100644
--- a/src/rabbit_error_logger.erl
+++ b/src/rabbit_error_logger.erl
@@ -27,7 +27,6 @@
-export([init/1, terminate/2, code_change/3, handle_call/2, handle_event/2,
handle_info/2]).
--import(rabbit_error_logger_file_h, [safe_handle_event/3]).
%%----------------------------------------------------------------------------
@@ -97,7 +96,7 @@ publish(_Other, _Format, _Data, _State) ->
publish1(RoutingKey, Format, Data, LogExch) ->
%% 0-9-1 says the timestamp is a "64 bit POSIX timestamp". That's
%% second resolution, not millisecond.
- Timestamp = time_compat:os_system_time(seconds),
+ Timestamp = os:system_time(seconds),
Args = [truncate:term(A, ?LOG_TRUNC) || A <- Data],
Headers = [{<<"node">>, longstr, list_to_binary(atom_to_list(node()))}],
@@ -106,6 +105,19 @@ publish1(RoutingKey, Format, Data, LogExch) ->
timestamp = Timestamp,
headers = Headers},
list_to_binary(io_lib:format(Format, Args))) of
- {ok, _QPids} -> ok;
- {error, _Err} -> ok
+ {ok, _DeliveredQPids} -> ok;
+ {error, not_found} -> ok
+ end.
+
+
+safe_handle_event(HandleEvent, Event, State) ->
+ try
+ HandleEvent(Event, State)
+ catch
+ _:Error ->
+ io:format(
+ "Error in log handler~n====================~n"
+ "Event: ~P~nError: ~P~nStack trace: ~p~n~n",
+ [Event, 30, Error, 30, erlang:get_stacktrace()]),
+ {ok, State}
end.
diff --git a/src/rabbit_error_logger_file_h.erl b/src/rabbit_error_logger_file_h.erl
deleted file mode 100644
index 930aead392..0000000000
--- a/src/rabbit_error_logger_file_h.erl
+++ /dev/null
@@ -1,180 +0,0 @@
-%% 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 Developer of the Original Code is GoPivotal, Inc.
-%% Copyright (c) 2007-2016 Pivotal Software, Inc. All rights reserved.
-%%
-
--module(rabbit_error_logger_file_h).
--include("rabbit.hrl").
-
--behaviour(gen_event).
-
--export([init/1, handle_event/2, handle_call/2, handle_info/2, terminate/2,
- code_change/3]).
-
--export([safe_handle_event/3]).
-
-%% extracted from error_logger_file_h. Since 18.1 the state of the
-%% error logger module changed. See:
-%% https://github.com/erlang/otp/commit/003091a1fcc749a182505ef5675c763f71eacbb0#diff-d9a19ba08f5d2b60fadfc3aa1566b324R108
-%% github issue:
-%% https://github.com/rabbitmq/rabbitmq-server/issues/324
--record(st, {fd,
- filename,
- prev_handler,
- depth = unlimited}).
-
-%% extracted from error_logger_file_h. See comment above.
-get_depth() ->
- case application:get_env(kernel, error_logger_format_depth) of
- {ok, Depth} when is_integer(Depth) ->
- erlang:max(10, Depth);
- undefined ->
- unlimited
- end.
-
--define(ERTS_NEW_LOGGER_STATE, "7.1").
-
-%% rabbit_error_logger_file_h is a wrapper around the error_logger_file_h
-%% module because the original's init/1 does not match properly
-%% with the result of closing the old handler when swapping handlers.
-%% The first init/1 additionally allows for simple log rotation
-%% when the suffix is not the empty string.
-%% The original init/2 also opened the file in 'write' mode, thus
-%% overwriting old logs. To remedy this, init/2 from
-%% lib/stdlib/src/error_logger_file_h.erl from R14B3 was copied as
-%% init_file/2 and changed so that it opens the file in 'append' mode.
-
-%% Log rotation with empty suffix should result only in file re-opening.
-init({{File, ""}, _}) ->
- init(File);
-%% Used only when swapping handlers in log rotation, pre OTP 18.1
-init({{File, Suffix}, []}) ->
- rotate_logs(File, Suffix),
- init(File);
-%% Used only when swapping handlers in log rotation, since OTP 18.1
-init({{File, Suffix}, ok}) ->
- rotate_logs(File, Suffix),
- init(File);
-%% Used only when swapping handlers and the original handler
-%% failed to terminate or was never installed
-init({{File, _}, error}) ->
- init(File);
-%% Used only when swapping handlers without performing
-%% log rotation
-init({File, []}) ->
- init(File);
-%% Used only when taking over from the tty handler
-init({{File, []}, _}) ->
- init(File);
-init({File, {error_logger, Buf}}) ->
- rabbit_file:ensure_parent_dirs_exist(File),
- init_file(File, {error_logger, Buf});
-init(File) ->
- rabbit_file:ensure_parent_dirs_exist(File),
- init_file(File, []).
-
-init_file(File, {error_logger, Buf}) ->
- case init_file(File, error_logger) of
- {ok, State} ->
- [handle_event(Event, State) ||
- {_, Event} <- lists:reverse(Buf)],
- {ok, State};
- Error ->
- Error
- end;
-init_file(File, PrevHandler) ->
- process_flag(trap_exit, true),
- case file:open(File, [append]) of
- {ok, Fd} ->
- FoundVer = erlang:system_info(version),
- State =
- case rabbit_misc:version_compare(
- ?ERTS_NEW_LOGGER_STATE, FoundVer, lte) of
- true ->
- #st{fd = Fd,
- filename = File,
- prev_handler = PrevHandler,
- depth = get_depth()};
- _ ->
- {Fd, File, PrevHandler}
- end,
- {ok, State};
- Error -> Error
- end.
-
-handle_event(Event, State) ->
- safe_handle_event(fun handle_event0/2, Event, State).
-
-safe_handle_event(HandleEvent, Event, State) ->
- try
- HandleEvent(Event, State)
- catch
- _:Error ->
- io:format(
- "Error in log handler~n====================~n"
- "Event: ~P~nError: ~P~nStack trace: ~p~n~n",
- [Event, 30, Error, 30, erlang:get_stacktrace()]),
- {ok, State}
- end.
-
-%% filter out "application: foo; exited: stopped; type: temporary"
-handle_event0({info_report, _, {_, std_info, _}}, State) ->
- {ok, State};
-%% When a node restarts quickly it is possible the rest of the cluster
-%% will not have had the chance to remove its queues from
-%% Mnesia. That's why rabbit_amqqueue:recover/0 invokes
-%% on_node_down(node()). But before we get there we can receive lots
-%% of messages intended for the old version of the node. The emulator
-%% logs an event for every one of those messages; in extremis this can
-%% bring the server to its knees just logging "Discarding..."
-%% again and again. So just log the first one, then go silent.
-handle_event0(Event = {error, _, {emulator, _, ["Discarding message" ++ _]}},
- State) ->
- case get(discarding_message_seen) of
- true -> {ok, State};
- undefined -> put(discarding_message_seen, true),
- error_logger_file_h:handle_event(t(Event), State)
- end;
-%% Clear this state if we log anything else (but not a progress report).
-handle_event0(Event = {info_msg, _, _}, State) ->
- erase(discarding_message_seen),
- error_logger_file_h:handle_event(t(Event), State);
-handle_event0(Event, State) ->
- error_logger_file_h:handle_event(t(Event), State).
-
-handle_info(Info, State) ->
- error_logger_file_h:handle_info(Info, State).
-
-handle_call(Call, State) ->
- error_logger_file_h:handle_call(Call, State).
-
-terminate(Reason, State) ->
- error_logger_file_h:terminate(Reason, State).
-
-code_change(OldVsn, State, Extra) ->
- error_logger_file_h:code_change(OldVsn, State, Extra).
-
-%%----------------------------------------------------------------------
-
-t(Term) -> truncate:log_event(Term, ?LOG_TRUNC).
-
-rotate_logs(File, Suffix) ->
- case rabbit_file:append_file(File, Suffix) of
- ok -> file:delete(File),
- ok;
- {error, Error} ->
- rabbit_log:error("Failed to append contents of "
- "log file '~s' to '~s':~n~p~n",
- [File, [File, Suffix], Error])
- end.
diff --git a/src/rabbit_exchange.erl b/src/rabbit_exchange.erl
index aaea27f91a..252817fba1 100644
--- a/src/rabbit_exchange.erl
+++ b/src/rabbit_exchange.erl
@@ -342,11 +342,17 @@ i(policy, X) -> case rabbit_policy:name(X) of
none -> '';
Policy -> Policy
end;
-i(Item, _) -> throw({bad_argument, Item}).
+i(Item, #exchange{type = Type} = X) ->
+ case (type_to_module(Type)):info(X, [Item]) of
+ [{Item, I}] -> I;
+ [] -> throw({bad_argument, Item})
+ end.
-info(X = #exchange{}) -> infos(?INFO_KEYS, X).
+info(X = #exchange{type = Type}) ->
+ infos(?INFO_KEYS, X) ++ (type_to_module(Type)):info(X).
-info(X = #exchange{}, Items) -> infos(Items, X).
+info(X = #exchange{type = _Type}, Items) ->
+ infos(Items, X).
info_all(VHostPath) -> map(VHostPath, fun (X) -> info(X) end).
diff --git a/src/rabbit_exchange_type_direct.erl b/src/rabbit_exchange_type_direct.erl
index 8a6886e376..ed675b572a 100644
--- a/src/rabbit_exchange_type_direct.erl
+++ b/src/rabbit_exchange_type_direct.erl
@@ -23,6 +23,7 @@
-export([validate/1, validate_binding/2,
create/2, delete/3, policy_changed/2, add_binding/3,
remove_bindings/3, assert_args_equivalence/2]).
+-export([info/1, info/2]).
-rabbit_boot_step({?MODULE,
[{description, "exchange type direct"},
@@ -31,6 +32,9 @@
{requires, rabbit_registry},
{enables, kernel_ready}]}).
+info(_X) -> [].
+info(_X, _) -> [].
+
description() ->
[{description, <<"AMQP direct exchange, as per the AMQP specification">>}].
diff --git a/src/rabbit_exchange_type_fanout.erl b/src/rabbit_exchange_type_fanout.erl
index d81e407f8f..3aebc07b41 100644
--- a/src/rabbit_exchange_type_fanout.erl
+++ b/src/rabbit_exchange_type_fanout.erl
@@ -23,6 +23,7 @@
-export([validate/1, validate_binding/2,
create/2, delete/3, policy_changed/2, add_binding/3,
remove_bindings/3, assert_args_equivalence/2]).
+-export([info/1, info/2]).
-rabbit_boot_step({?MODULE,
[{description, "exchange type fanout"},
@@ -31,6 +32,9 @@
{requires, rabbit_registry},
{enables, kernel_ready}]}).
+info(_X) -> [].
+info(_X, _) -> [].
+
description() ->
[{description, <<"AMQP fanout exchange, as per the AMQP specification">>}].
diff --git a/src/rabbit_exchange_type_headers.erl b/src/rabbit_exchange_type_headers.erl
index 196873aa22..f7db35be2f 100644
--- a/src/rabbit_exchange_type_headers.erl
+++ b/src/rabbit_exchange_type_headers.erl
@@ -24,6 +24,7 @@
-export([validate/1, validate_binding/2,
create/2, delete/3, policy_changed/2, add_binding/3,
remove_bindings/3, assert_args_equivalence/2]).
+-export([info/1, info/2]).
-rabbit_boot_step({?MODULE,
[{description, "exchange type headers"},
@@ -36,6 +37,9 @@
(rabbit_framing:amqp_table(), rabbit_framing:amqp_table()) ->
boolean().
+info(_X) -> [].
+info(_X, _) -> [].
+
description() ->
[{description, <<"AMQP headers exchange, as per the AMQP specification">>}].
diff --git a/src/rabbit_exchange_type_invalid.erl b/src/rabbit_exchange_type_invalid.erl
index 2510c8a241..9edb00ee49 100644
--- a/src/rabbit_exchange_type_invalid.erl
+++ b/src/rabbit_exchange_type_invalid.erl
@@ -23,6 +23,10 @@
-export([validate/1, validate_binding/2,
create/2, delete/3, policy_changed/2, add_binding/3,
remove_bindings/3, assert_args_equivalence/2]).
+-export([info/1, info/2]).
+
+info(_X) -> [].
+info(_X, _) -> [].
description() ->
[{description,
diff --git a/src/rabbit_exchange_type_topic.erl b/src/rabbit_exchange_type_topic.erl
index 0eccb66cfd..60be070426 100644
--- a/src/rabbit_exchange_type_topic.erl
+++ b/src/rabbit_exchange_type_topic.erl
@@ -24,6 +24,7 @@
-export([validate/1, validate_binding/2,
create/2, delete/3, policy_changed/2, add_binding/3,
remove_bindings/3, assert_args_equivalence/2]).
+-export([info/1, info/2]).
-rabbit_boot_step({?MODULE,
[{description, "exchange type topic"},
@@ -34,6 +35,9 @@
%%----------------------------------------------------------------------------
+info(_X) -> [].
+info(_X, _) -> [].
+
description() ->
[{description, <<"AMQP topic exchange, as per the AMQP specification">>}].
diff --git a/src/rabbit_file.erl b/src/rabbit_file.erl
index 878b9da7a7..d8e215bfc5 100644
--- a/src/rabbit_file.erl
+++ b/src/rabbit_file.erl
@@ -23,6 +23,7 @@
-export([append_file/2, ensure_parent_dirs_exist/1]).
-export([rename/2, delete/1, recursive_delete/1, recursive_copy/2]).
-export([lock_file/1]).
+-export([read_file_info/1]).
-export([filename_as_a_directory/1]).
-import(file_handle_cache, [with_handle/1, with_handle/2]).
diff --git a/src/rabbit_hipe.erl b/src/rabbit_hipe.erl
index d4597d4efc..6957d85cb4 100644
--- a/src/rabbit_hipe.erl
+++ b/src/rabbit_hipe.erl
@@ -79,7 +79,7 @@ do_hipe_compile(HipeModules, CompileFun) ->
Count = length(HipeModules),
io:format("~nHiPE compiling: |~s|~n |",
[string:copies("-", Count)]),
- T1 = time_compat:monotonic_time(),
+ T1 = erlang:monotonic_time(),
%% We use code:get_object_code/1 below to get the beam binary,
%% instead of letting hipe get it itself, because hipe:c/{1,2}
%% expects the given filename to actually exist on disk: it does not
@@ -99,8 +99,8 @@ do_hipe_compile(HipeModules, CompileFun) ->
{'DOWN', MRef, process, _, normal} -> ok;
{'DOWN', MRef, process, _, Reason} -> exit(Reason)
end || {_Pid, MRef} <- PidMRefs],
- T2 = time_compat:monotonic_time(),
- Duration = time_compat:convert_time_unit(T2 - T1, native, seconds),
+ T2 = erlang:monotonic_time(),
+ Duration = erlang:convert_time_unit(T2 - T1, native, seconds),
io:format("|~n~nCompiled ~B modules in ~Bs~n", [Count, Duration]),
{ok, Count, Duration}.
diff --git a/src/rabbit_lager.erl b/src/rabbit_lager.erl
new file mode 100644
index 0000000000..8beee10846
--- /dev/null
+++ b/src/rabbit_lager.erl
@@ -0,0 +1,267 @@
+%% 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 Developer of the Original Code is GoPivotal, Inc.
+%% Copyright (c) 2007-2015 Pivotal Software, Inc. All rights reserved.
+%%
+
+-module(rabbit_lager).
+
+-include("rabbit_log.hrl").
+
+%% API
+-export([start_logger/0, log_locations/0, fold_sinks/2]).
+
+start_logger() ->
+ application:stop(lager),
+ ensure_lager_configured(),
+ lager:start(),
+ fold_sinks(
+ fun
+ (_, [], Acc) ->
+ Acc;
+ (SinkName, _, Acc) ->
+ lager:log(SinkName, info, self(),
+ "Log file opened with Lager", []),
+ Acc
+ end, ok),
+ ensure_log_working().
+
+log_locations() ->
+ ensure_lager_configured(),
+ DefaultHandlers = application:get_env(lager, handlers, []),
+ Sinks = application:get_env(lager, extra_sinks, []),
+ ExtraHandlers = [proplists:get_value(handlers, Props, [])
+ || {_, Props} <- Sinks],
+ lists:sort(log_locations1([DefaultHandlers | ExtraHandlers], [])).
+
+log_locations1([Handlers | Rest], Locations) ->
+ Locations1 = log_locations2(Handlers, Locations),
+ log_locations1(Rest, Locations1);
+log_locations1([], Locations) ->
+ Locations.
+
+log_locations2([{lager_file_backend, Settings} | Rest], Locations) ->
+ FileName = lager_file_name1(Settings),
+ Locations1 = case lists:member(FileName, Locations) of
+ false -> [FileName | Locations];
+ true -> Locations
+ end,
+ log_locations2(Rest, Locations1);
+log_locations2([{lager_console_backend, _} | Rest], Locations) ->
+ Locations1 = case lists:member("<stdout>", Locations) of
+ false -> ["<stdout>" | Locations];
+ true -> Locations
+ end,
+ log_locations2(Rest, Locations1);
+log_locations2([_ | Rest], Locations) ->
+ log_locations2(Rest, Locations);
+log_locations2([], Locations) ->
+ Locations.
+
+fold_sinks(Fun, Acc) ->
+ Handlers = lager_config:global_get(handlers),
+ Sinks = dict:to_list(lists:foldl(
+ fun
+ ({{lager_file_backend, F}, _, S}, Dict) ->
+ dict:append(S, F, Dict);
+ ({_, _, S}, Dict) ->
+ case dict:is_key(S, Dict) of
+ true -> dict:store(S, [], Dict);
+ false -> Dict
+ end
+ end,
+ dict:new(), Handlers)),
+ fold_sinks(Sinks, Fun, Acc).
+
+fold_sinks([{SinkName, FileNames} | Rest], Fun, Acc) ->
+ Acc1 = Fun(SinkName, FileNames, Acc),
+ fold_sinks(Rest, Fun, Acc1);
+fold_sinks([], _, Acc) ->
+ Acc.
+
+ensure_log_working() ->
+ {ok, Handlers} = application:get_env(lager, handlers),
+ [ ensure_lager_handler_file_exist(Handler)
+ || Handler <- Handlers ],
+ Sinks = application:get_env(lager, extra_sinks, []),
+ ensure_extra_sinks_working(Sinks, list_expected_sinks()).
+
+ensure_extra_sinks_working(Sinks, [SinkName | Rest]) ->
+ case proplists:get_value(SinkName, Sinks) of
+ undefined -> throw({error, {cannot_log_to_file, unknown,
+ rabbit_log_lager_event_sink_undefined}});
+ Sink ->
+ SinkHandlers = proplists:get_value(handlers, Sink, []),
+ [ ensure_lager_handler_file_exist(Handler)
+ || Handler <- SinkHandlers ]
+ end,
+ ensure_extra_sinks_working(Sinks, Rest);
+ensure_extra_sinks_working(_Sinks, []) ->
+ ok.
+
+ensure_lager_handler_file_exist(Handler) ->
+ case lager_file_name(Handler) of
+ false -> ok;
+ FileName -> ensure_logfile_exist(FileName)
+ end.
+
+lager_file_name({lager_file_backend, Settings}) ->
+ lager_file_name1(Settings);
+lager_file_name(_) ->
+ false.
+
+lager_file_name1(Settings) when is_list(Settings) ->
+ {file, FileName} = proplists:lookup(file, Settings),
+ lager_util:expand_path(FileName);
+lager_file_name1({FileName, _}) -> lager_util:expand_path(FileName);
+lager_file_name1({FileName, _, _, _, _}) -> lager_util:expand_path(FileName);
+lager_file_name1(_) ->
+ throw({error, {cannot_log_to_file, unknown,
+ lager_file_backend_config_invalid}}).
+
+
+ensure_logfile_exist(FileName) ->
+ LogFile = lager_util:expand_path(FileName),
+ case rabbit_file:read_file_info(LogFile) of
+ {ok,_} -> ok;
+ {error, Err} -> throw({error, {cannot_log_to_file, LogFile, Err}})
+ end.
+
+lager_handlers(Silent) when Silent == silent; Silent == false ->
+ [];
+lager_handlers(tty) ->
+ [{lager_console_backend, info}];
+lager_handlers(FileName) when is_list(FileName) ->
+ LogFile = lager_util:expand_path(FileName),
+ case rabbit_file:ensure_dir(LogFile) of
+ ok -> ok;
+ {error, Reason} ->
+ throw({error, {cannot_log_to_file, LogFile,
+ {cannot_create_parent_dirs, LogFile, Reason}}})
+ end,
+ [{lager_file_backend, [{file, FileName},
+ {level, info},
+ {formatter_config,
+ [date, " ", time, " ", color, "[", severity, "] ",
+ {pid, ""},
+ " ", message, "\n"]},
+ {date, ""},
+ {size, 0}]}].
+
+
+ensure_lager_configured() ->
+ case lager_configured() of
+ false -> configure_lager();
+ true -> ok
+ end.
+
+%% Lager should have handlers and sinks
+lager_configured() ->
+ Sinks = lager:list_all_sinks(),
+ ExpectedSinks = list_expected_sinks(),
+ application:get_env(lager, handlers) =/= undefined
+ andalso
+ lists:all(fun(S) -> lists:member(S, Sinks) end, ExpectedSinks).
+
+configure_lager() ->
+ application:load(lager),
+ %% Turn off reformatting for error_logger messages
+ case application:get_env(lager, error_logger_format_raw) of
+ undefined -> application:set_env(lager, error_logger_format_raw, true);
+ _ -> ok
+ end,
+ case application:get_env(lager, log_root) of
+ undefined ->
+ application:set_env(lager, log_root,
+ application:get_env(rabbit, lager_log_root,
+ undefined));
+ _ -> ok
+ end,
+
+ %% Configure the default sink/handlers.
+ Handlers0 = application:get_env(lager, handlers, undefined),
+ DefaultHandlers = lager_handlers(application:get_env(rabbit,
+ lager_handler,
+ tty)),
+ Handlers = case os:getenv("RABBITMQ_LOGS_source") of
+ %% There are no default handlers in rabbitmq.config, create a
+ %% configuration from $RABBITMQ_LOGS.
+ false when Handlers0 =:= undefined -> DefaultHandlers;
+ %% There are default handlers configure in rabbitmq.config, but
+ %% the user explicitely sets $RABBITMQ_LOGS; create new handlers
+ %% based on that, instead of using rabbitmq.config.
+ "environment" -> DefaultHandlers;
+ %% Use the default handlers configured in rabbitmq.com.
+ _ -> Handlers0
+ end,
+ application:set_env(lager, handlers, Handlers),
+
+ %% Setup extra sink/handlers. If they are not configured, redirect
+ %% messages to the default sink. To know the list of expected extra
+ %% sinks, we look at the 'lager_extra_sinks' compilation option.
+ Sinks0 = application:get_env(lager, extra_sinks, []),
+ Sinks1 = configure_extra_sinks(Sinks0,
+ [error_logger | list_expected_sinks()]),
+ %% TODO Waiting for basho/lager#303
+ %% Sinks2 = lists:keystore(error_logger_lager_event, 1, Sinks1,
+ %% {error_logger_lager_event,
+ %% [{handlers, Handlers}]}),
+ application:set_env(lager, extra_sinks, Sinks1),
+
+ case application:get_env(lager, error_logger_hwm) of
+ undefined ->
+ application:set_env(lager, error_logger_hwm, 100);
+ {ok, Val} when is_integer(Val) andalso Val =< 100 ->
+ application:set_env(lager, error_logger_hwm, 100);
+ {ok, _Val} ->
+ ok
+ end,
+ ok.
+
+configure_extra_sinks(Sinks, [SinkName | Rest]) ->
+ Sink0 = proplists:get_value(SinkName, Sinks, []),
+ Sink1 = case proplists:is_defined(handlers, Sink0) of
+ false -> lists:keystore(handlers, 1, Sink0,
+ {handlers,
+ [{lager_forwarder_backend,
+ lager_util:make_internal_sink_name(lager)
+ }]});
+ true -> Sink0
+ end,
+ Sinks1 = lists:keystore(SinkName, 1, Sinks, {SinkName, Sink1}),
+ configure_extra_sinks(Sinks1, Rest);
+configure_extra_sinks(Sinks, []) ->
+ Sinks.
+
+list_expected_sinks() ->
+ case application:get_env(rabbit, lager_extra_sinks) of
+ {ok, List} ->
+ List;
+ undefined ->
+ CompileOptions = proplists:get_value(options,
+ ?MODULE:module_info(compile),
+ []),
+ AutoList = [lager_util:make_internal_sink_name(M)
+ || M <- proplists:get_value(lager_extra_sinks,
+ CompileOptions, [])],
+ List = case lists:member(?LAGER_SINK, AutoList) of
+ true -> AutoList;
+ false -> [?LAGER_SINK | AutoList]
+ end,
+ %% Store the list in the application environment. If this
+ %% module is later cover-compiled, the compile option will
+ %% be lost, so we will be able to retrieve the list from the
+ %% application environment.
+ application:set_env(rabbit, lager_extra_sinks, List),
+ List
+ end.
diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl
index 203e309b02..74e802b3a0 100644
--- a/src/rabbit_limiter.erl
+++ b/src/rabbit_limiter.erl
@@ -432,7 +432,7 @@ notify_queues(State = #lim{ch_pid = ChPid, queues = Queues}) ->
%% We randomly vary the position of queues in the list,
%% thus ensuring that each queue has an equal chance of
%% being notified first.
- {L1, L2} = lists:split(rand_compat:uniform(L), QList),
+ {L1, L2} = lists:split(rand:uniform(L), QList),
[[ok = rabbit_amqqueue:resume(Q, ChPid) || Q <- L3]
|| L3 <- [L2, L1]],
ok
diff --git a/src/rabbit_log.erl b/src/rabbit_log.erl
index 337fb23f84..f60cf6c0c2 100644
--- a/src/rabbit_log.erl
+++ b/src/rabbit_log.erl
@@ -16,96 +16,113 @@
-module(rabbit_log).
--export([log/3, log/4, debug/1, debug/2, info/1, info/2, warning/1,
- warning/2, error/1, error/2]).
--export([with_local_io/1]).
-
+-export([log/3, log/4]).
+-export([debug/1, debug/2, debug/3,
+ info/1, info/2, info/3,
+ notice/1, notice/2, notice/3,
+ warning/1, warning/2, warning/3,
+ error/1, error/2, error/3,
+ critical/1, critical/2, critical/3,
+ alert/1, alert/2, alert/3,
+ emergency/1, emergency/2, emergency/3,
+ none/1, none/2, none/3]).
+
+-include("rabbit_log.hrl").
%%----------------------------------------------------------------------------
--export_type([level/0]).
-
-type category() :: atom().
--type level() :: 'debug' | 'info' | 'warning' | 'error'.
--spec log(category(), level(), string()) -> 'ok'.
--spec log(category(), level(), string(), [any()]) -> 'ok'.
+-spec log(category(), lager:log_level(), string()) -> 'ok'.
+-spec log(category(), lager:log_level(), string(), [any()]) -> 'ok'.
-spec debug(string()) -> 'ok'.
-spec debug(string(), [any()]) -> 'ok'.
+-spec debug(pid() | [tuple()], string(), [any()]) -> 'ok'.
-spec info(string()) -> 'ok'.
-spec info(string(), [any()]) -> 'ok'.
+-spec info(pid() | [tuple()], string(), [any()]) -> 'ok'.
+-spec notice(string()) -> 'ok'.
+-spec notice(string(), [any()]) -> 'ok'.
+-spec notice(pid() | [tuple()], string(), [any()]) -> 'ok'.
-spec warning(string()) -> 'ok'.
-spec warning(string(), [any()]) -> 'ok'.
+-spec warning(pid() | [tuple()], string(), [any()]) -> 'ok'.
-spec error(string()) -> 'ok'.
-spec error(string(), [any()]) -> 'ok'.
-
--spec with_local_io(fun (() -> A)) -> A.
+-spec error(pid() | [tuple()], string(), [any()]) -> 'ok'.
+-spec critical(string()) -> 'ok'.
+-spec critical(string(), [any()]) -> 'ok'.
+-spec critical(pid() | [tuple()], string(), [any()]) -> 'ok'.
+-spec alert(string()) -> 'ok'.
+-spec alert(string(), [any()]) -> 'ok'.
+-spec alert(pid() | [tuple()], string(), [any()]) -> 'ok'.
+-spec emergency(string()) -> 'ok'.
+-spec emergency(string(), [any()]) -> 'ok'.
+-spec emergency(pid() | [tuple()], string(), [any()]) -> 'ok'.
+-spec none(string()) -> 'ok'.
+-spec none(string(), [any()]) -> 'ok'.
+-spec none(pid() | [tuple()], string(), [any()]) -> 'ok'.
%%----------------------------------------------------------------------------
log(Category, Level, Fmt) -> log(Category, Level, Fmt, []).
log(Category, Level, Fmt, Args) when is_list(Args) ->
- case level(Level) =< catlevel(Category) of
- false -> ok;
- true -> F = case Level of
- debug -> fun error_logger:info_msg/2;
- info -> fun error_logger:info_msg/2;
- warning -> fun error_logger:warning_msg/2;
- error -> fun error_logger:error_msg/2
- end,
- with_local_io(fun () -> F(Fmt, Args) end)
- end.
-
-debug(Fmt) -> log(default, debug, Fmt).
-debug(Fmt, Args) -> log(default, debug, Fmt, Args).
-info(Fmt) -> log(default, info, Fmt).
-info(Fmt, Args) -> log(default, info, Fmt, Args).
-warning(Fmt) -> log(default, warning, Fmt).
-warning(Fmt, Args) -> log(default, warning, Fmt, Args).
-error(Fmt) -> log(default, error, Fmt).
-error(Fmt, Args) -> log(default, error, Fmt, Args).
-
-catlevel(Category) ->
- %% We can get here as part of rabbitmqctl when it is impersonating
- %% a node; in which case the env will not be defined.
- CatLevelList = case application:get_env(rabbit, log_levels) of
- {ok, L} -> L;
- undefined -> []
- end,
- level(proplists:get_value(Category, CatLevelList, info)).
-
-%%--------------------------------------------------------------------
-
-level(debug) -> 4;
-level(info) -> 3;
-level(warning) -> 2;
-level(warn) -> 2;
-level(error) -> 1;
-level(none) -> 0.
-
-%% Execute Fun using the IO system of the local node (i.e. the node on
-%% which the code is executing). Since this is invoked for every log
-%% message, we try to avoid unnecessarily churning group_leader/1.
-with_local_io(Fun) ->
- GL = group_leader(),
- Node = node(),
- case node(GL) of
- Node -> Fun();
- _ -> set_group_leader_to_user_safely(whereis(user)),
- try
- Fun()
- after
- group_leader(GL, self())
- end
- end.
-
-set_group_leader_to_user_safely(undefined) ->
- handle_damaged_io_system();
-set_group_leader_to_user_safely(User) when is_pid(User) ->
- group_leader(User, self()).
-
-handle_damaged_io_system() ->
- Msg = "Erlang VM I/O system is damaged, restart needed~n",
- io:format(standard_error, Msg, []),
- exit(erlang_vm_restart_needed).
+ Sink = case Category of
+ default -> ?LAGER_SINK;
+ _ -> make_internal_sink_name(Category)
+ end,
+ lager:log(Sink, Level, self(), Fmt, Args).
+
+make_internal_sink_name(rabbit_log_connection) -> rabbit_log_connection_lager_event;
+make_internal_sink_name(rabbit_log_channel) -> rabbit_log_channel_lager_event;
+make_internal_sink_name(rabbit_log_mirroring) -> rabbit_log_mirroring_lager_event;
+make_internal_sink_name(rabbit_log_queue) -> rabbit_log_queue_lager_event;
+make_internal_sink_name(rabbit_log_federation) -> rabbit_log_federation_lager_event;
+make_internal_sink_name(Category) ->
+ lager_util:make_internal_sink_name(Category).
+
+debug(Format) -> debug(Format, []).
+debug(Format, Args) -> debug(self(), Format, Args).
+debug(Metadata, Format, Args) ->
+ lager:log(?LAGER_SINK, debug, Metadata, Format, Args).
+
+info(Format) -> info(Format, []).
+info(Format, Args) -> info(self(), Format, Args).
+info(Metadata, Format, Args) ->
+ lager:log(?LAGER_SINK, info, Metadata, Format, Args).
+
+notice(Format) -> notice(Format, []).
+notice(Format, Args) -> notice(self(), Format, Args).
+notice(Metadata, Format, Args) ->
+ lager:log(?LAGER_SINK, notice, Metadata, Format, Args).
+
+warning(Format) -> warning(Format, []).
+warning(Format, Args) -> warning(self(), Format, Args).
+warning(Metadata, Format, Args) ->
+ lager:log(?LAGER_SINK, warning, Metadata, Format, Args).
+
+error(Format) -> ?MODULE:error(Format, []).
+error(Format, Args) -> ?MODULE:error(self(), Format, Args).
+error(Metadata, Format, Args) ->
+ lager:log(?LAGER_SINK, error, Metadata, Format, Args).
+
+critical(Format) -> critical(Format, []).
+critical(Format, Args) -> critical(self(), Format, Args).
+critical(Metadata, Format, Args) ->
+ lager:log(?LAGER_SINK, critical, Metadata, Format, Args).
+
+alert(Format) -> alert(Format, []).
+alert(Format, Args) -> alert(self(), Format, Args).
+alert(Metadata, Format, Args) ->
+ lager:log(?LAGER_SINK, alert, Metadata, Format, Args).
+
+emergency(Format) -> emergency(Format, []).
+emergency(Format, Args) -> emergency(self(), Format, Args).
+emergency(Metadata, Format, Args) ->
+ lager:log(?LAGER_SINK, emergency, Metadata, Format, Args).
+
+none(Format) -> none(Format, []).
+none(Format, Args) -> none(self(), Format, Args).
+none(Metadata, Format, Args) ->
+ lager:log(?LAGER_SINK, none, Metadata, Format, Args).
diff --git a/src/rabbit_mirror_queue_master.erl b/src/rabbit_mirror_queue_master.erl
index d78f6180e7..78d7341584 100644
--- a/src/rabbit_mirror_queue_master.erl
+++ b/src/rabbit_mirror_queue_master.erl
@@ -57,13 +57,13 @@
coordinator :: pid(),
backing_queue :: atom(),
backing_queue_state :: any(),
- seen_status :: ?DICT_TYPE(),
+ seen_status :: dict:dict(),
confirmed :: [rabbit_guid:guid()],
- known_senders :: ?SET_TYPE()
+ known_senders :: sets:set()
}.
-spec promote_backing_queue_state
(rabbit_amqqueue:name(), pid(), atom(), any(), pid(), [any()],
- ?DICT_TYPE(), [pid()]) ->
+ dict:dict(), [pid()]) ->
master_state().
-spec sender_death_fun() -> death_fun().
@@ -211,16 +211,24 @@ stop_all_slaves(Reason, #state{name = QName, gm = GM, wait_timeout = WT}) ->
%% monitor them but they would not have received the GM
%% message. So only wait for slaves which are still
%% not-partitioned.
- [receive
- {'DOWN', MRef, process, _Pid, _Info} ->
- ok
- after WT ->
- rabbit_mirror_queue_misc:log_warning(
- QName, "Missing 'DOWN' message from ~p in node ~p~n",
- [Pid, node(Pid)]),
- ok
- end
- || {Pid, MRef} <- PidsMRefs, rabbit_mnesia:on_running_node(Pid)],
+ PendingSlavePids =
+ lists:foldl(
+ fun({Pid, MRef}, Acc) ->
+ case rabbit_mnesia:on_running_node(Pid) of
+ true ->
+ receive
+ {'DOWN', MRef, process, _Pid, _Info} ->
+ Acc
+ after WT ->
+ rabbit_mirror_queue_misc:log_warning(
+ QName, "Missing 'DOWN' message from ~p in"
+ " node ~p~n", [Pid, node(Pid)]),
+ [Pid | Acc]
+ end;
+ false ->
+ Acc
+ end
+ end, [], PidsMRefs),
%% Normally when we remove a slave another slave or master will
%% notice and update Mnesia. But we just removed them all, and
%% have stopped listening ourselves. So manually clean up.
@@ -228,7 +236,11 @@ stop_all_slaves(Reason, #state{name = QName, gm = GM, wait_timeout = WT}) ->
fun () ->
[Q] = mnesia:read({rabbit_queue, QName}),
rabbit_mirror_queue_misc:store_updated_slaves(
- Q #amqqueue { gm_pids = [], slave_pids = [] })
+ Q #amqqueue { gm_pids = [], slave_pids = [],
+ %% Restarted slaves on running nodes can
+ %% ensure old incarnations are stopped using
+ %% the pending slave pids.
+ slave_pids_pending_shutdown = PendingSlavePids})
end),
ok = gm:forget_group(QName).
diff --git a/src/rabbit_mirror_queue_misc.erl b/src/rabbit_mirror_queue_misc.erl
index 4205fabb83..59522da4a9 100644
--- a/src/rabbit_mirror_queue_misc.erl
+++ b/src/rabbit_mirror_queue_misc.erl
@@ -20,7 +20,7 @@
-export([remove_from_queue/3, on_node_up/0, add_mirrors/3,
report_deaths/4, store_updated_slaves/1,
initial_queue_node/2, suggested_queue_nodes/1,
- is_mirrored/1, update_mirrors/2, validate_policy/1,
+ is_mirrored/1, update_mirrors/2, update_mirrors/1, validate_policy/1,
maybe_auto_sync/1, maybe_drop_master_after_sync/1,
sync_batch_size/1, log_info/3, log_warning/3]).
@@ -64,6 +64,8 @@
-spec is_mirrored(rabbit_types:amqqueue()) -> boolean().
-spec update_mirrors
(rabbit_types:amqqueue(), rabbit_types:amqqueue()) -> 'ok'.
+-spec update_mirrors
+ (rabbit_types:amqqueue()) -> 'ok'.
-spec maybe_drop_master_after_sync(rabbit_types:amqqueue()) -> 'ok'.
-spec maybe_auto_sync(rabbit_types:amqqueue()) -> 'ok'.
-spec log_info(rabbit_amqqueue:name(), string(), [any()]) -> 'ok'.
@@ -402,15 +404,12 @@ update_mirrors(OldQ = #amqqueue{pid = QPid},
NewQ = #amqqueue{pid = QPid}) ->
case {is_mirrored(OldQ), is_mirrored(NewQ)} of
{false, false} -> ok;
- {true, false} -> rabbit_amqqueue:stop_mirroring(QPid);
- {false, true} -> rabbit_amqqueue:start_mirroring(QPid);
- {true, true} -> update_mirrors0(OldQ, NewQ)
+ _ -> rabbit_amqqueue:update_mirroring(QPid)
end.
-update_mirrors0(OldQ = #amqqueue{name = QName},
- NewQ = #amqqueue{name = QName}) ->
- {OldMNode, OldSNodes, _} = actual_queue_nodes(OldQ),
- {NewMNode, NewSNodes} = suggested_queue_nodes(NewQ),
+update_mirrors(Q = #amqqueue{name = QName}) ->
+ {OldMNode, OldSNodes, _} = actual_queue_nodes(Q),
+ {NewMNode, NewSNodes} = suggested_queue_nodes(Q),
OldNodes = [OldMNode | OldSNodes],
NewNodes = [NewMNode | NewSNodes],
%% When a mirror dies, remove_from_queue/2 might have to add new
@@ -424,7 +423,7 @@ update_mirrors0(OldQ = #amqqueue{name = QName},
drop_mirrors(QName, OldNodes -- NewNodes),
%% This is for the case where no extra nodes were added but we changed to
%% a policy requiring auto-sync.
- maybe_auto_sync(NewQ),
+ maybe_auto_sync(Q),
ok.
%% The arrival of a newly synced slave may cause the master to die if
diff --git a/src/rabbit_mirror_queue_mode.erl b/src/rabbit_mirror_queue_mode.erl
index 3733c7f0f8..47b150b5a0 100644
--- a/src/rabbit_mirror_queue_mode.erl
+++ b/src/rabbit_mirror_queue_mode.erl
@@ -16,6 +16,10 @@
-module(rabbit_mirror_queue_mode).
+-behaviour(rabbit_registry_class).
+
+-export([added_to_rabbit_registry/2, removed_from_rabbit_registry/1]).
+
-type master() :: node().
-type slave() :: node().
-type params() :: any().
@@ -42,3 +46,6 @@
%% Are the parameters valid for this mode?
-callback validate_policy(params()) ->
rabbit_policy_validator:validate_results().
+
+added_to_rabbit_registry(_Type, _ModuleName) -> ok.
+removed_from_rabbit_registry(_Type) -> ok.
diff --git a/src/rabbit_mirror_queue_mode_exactly.erl b/src/rabbit_mirror_queue_mode_exactly.erl
index 593f0a4138..c2ffa39f59 100644
--- a/src/rabbit_mirror_queue_mode_exactly.erl
+++ b/src/rabbit_mirror_queue_mode_exactly.erl
@@ -45,7 +45,7 @@ suggested_queue_nodes(Count, MNode, SNodes, _SSNodes, Poss) ->
end}.
shuffle(L) ->
- {_, L1} = lists:unzip(lists:keysort(1, [{rand_compat:uniform(), N} || N <- L])),
+ {_, L1} = lists:unzip(lists:keysort(1, [{rand:uniform(), N} || N <- L])),
L1.
validate_policy(N) when is_integer(N) andalso N > 0 ->
diff --git a/src/rabbit_mirror_queue_slave.erl b/src/rabbit_mirror_queue_slave.erl
index 2cb4455180..12b13c36fd 100644
--- a/src/rabbit_mirror_queue_slave.erl
+++ b/src/rabbit_mirror_queue_slave.erl
@@ -163,9 +163,11 @@ handle_go(Q = #amqqueue{name = QName}) ->
init_it(Self, GM, Node, QName) ->
case mnesia:read({rabbit_queue, QName}) of
- [Q = #amqqueue { pid = QPid, slave_pids = SPids, gm_pids = GMPids }] ->
+ [Q = #amqqueue { pid = QPid, slave_pids = SPids, gm_pids = GMPids,
+ slave_pids_pending_shutdown = PSPids}] ->
case [Pid || Pid <- [QPid | SPids], node(Pid) =:= Node] of
- [] -> add_slave(Q, Self, GM),
+ [] -> stop_pending_slaves(QName, PSPids),
+ add_slave(Q, Self, GM),
{new, QPid, GMPids};
[QPid] -> case rabbit_mnesia:is_process_alive(QPid) of
true -> duplicate_live_master;
@@ -186,6 +188,26 @@ init_it(Self, GM, Node, QName) ->
master_in_recovery
end.
+%% Pending slaves have been asked to stop by the master, but despite the node
+%% being up these did not answer on the expected timeout. Stop local slaves now.
+stop_pending_slaves(QName, Pids) ->
+ [begin
+ rabbit_mirror_queue_misc:log_warning(
+ QName, "Detected stale HA slave, stopping it: ~p~n", [Pid]),
+ case erlang:process_info(Pid, dictionary) of
+ undefined -> ok;
+ {dictionary, Dict} ->
+ case proplists:get_value('$ancestors', Dict) of
+ [Sup, rabbit_amqqueue_sup_sup | _] ->
+ exit(Sup, kill),
+ exit(Pid, kill);
+ _ ->
+ ok
+ end
+ end
+ end || Pid <- Pids, node(Pid) =:= node(),
+ true =:= erlang:is_process_alive(Pid)].
+
%% Add to the end, so they are in descending order of age, see
%% rabbit_mirror_queue_misc:promote_slave/1
add_slave(Q = #amqqueue { slave_pids = SPids, gm_pids = GMPids }, New, GM) ->
diff --git a/src/rabbit_mirror_queue_sync.erl b/src/rabbit_mirror_queue_sync.erl
index 54f0855fce..ad1a25a425 100644
--- a/src/rabbit_mirror_queue_sync.erl
+++ b/src/rabbit_mirror_queue_sync.erl
@@ -104,7 +104,7 @@ master_batch_go0(Args, BatchSize, BQ, BQS) ->
false -> {cont, Acc1}
end
end,
- FoldAcc = {[], 0, {0, BQ:depth(BQS)}, time_compat:monotonic_time()},
+ FoldAcc = {[], 0, {0, BQ:depth(BQS)}, erlang:monotonic_time()},
bq_fold(FoldFun, FoldAcc, Args, BQ, BQS).
master_batch_send({Syncer, Ref, Log, HandleInfo, EmitStats, Parent},
@@ -164,12 +164,12 @@ stop_syncer(Syncer, Msg) ->
end.
maybe_emit_stats(Last, I, EmitStats, Log) ->
- Interval = time_compat:convert_time_unit(
- time_compat:monotonic_time() - Last, native, micro_seconds),
+ Interval = erlang:convert_time_unit(
+ erlang:monotonic_time() - Last, native, micro_seconds),
case Interval > ?SYNC_PROGRESS_INTERVAL of
true -> EmitStats({syncing, I}),
Log("~p messages", [I]),
- time_compat:monotonic_time();
+ erlang:monotonic_time();
false -> Last
end.
diff --git a/src/rabbit_mnesia.erl b/src/rabbit_mnesia.erl
index 596eb62b03..43dd2c3bb8 100644
--- a/src/rabbit_mnesia.erl
+++ b/src/rabbit_mnesia.erl
@@ -148,6 +148,7 @@ auto_cluster(TryNodes, NodeType) ->
rabbit_log:info("Node '~p' selected for auto-clustering~n", [Node]),
{ok, {_, DiscNodes, _}} = discover_cluster0(Node),
init_db_and_upgrade(DiscNodes, NodeType, true),
+ rabbit_connection_tracking:boot(),
rabbit_node_monitor:notify_joined_cluster();
none ->
rabbit_log:warning(
@@ -194,6 +195,7 @@ join_cluster(DiscoveryNode, NodeType) ->
[ClusterNodes, NodeType]),
ok = init_db_with_mnesia(ClusterNodes, NodeType,
true, true),
+ rabbit_connection_tracking:boot(),
rabbit_node_monitor:notify_joined_cluster(),
ok;
{error, Reason} ->
@@ -295,6 +297,9 @@ update_cluster_nodes(DiscoveryNode) ->
%% the last or second to last after the node we're removing to go
%% down
forget_cluster_node(Node, RemoveWhenOffline) ->
+ forget_cluster_node(Node, RemoveWhenOffline, true).
+
+forget_cluster_node(Node, RemoveWhenOffline, EmitNodeDeletedEvent) ->
case lists:member(Node, cluster_nodes(all)) of
true -> ok;
false -> e(not_a_cluster_node)
@@ -306,6 +311,9 @@ forget_cluster_node(Node, RemoveWhenOffline) ->
{false, true} -> rabbit_log:info(
"Removing node ~p from cluster~n", [Node]),
case remove_node_if_mnesia_running(Node) of
+ ok when EmitNodeDeletedEvent ->
+ rabbit_event:notify(node_deleted, [{node, Node}]),
+ ok;
ok -> ok;
{error, _} = Err -> throw(Err)
end
@@ -326,7 +334,10 @@ remove_node_offline_node(Node) ->
%% they are loaded.
rabbit_table:force_load(),
rabbit_table:wait_for_replicated(),
- forget_cluster_node(Node, false),
+ %% We skip the 'node_deleted' event because the
+ %% application is stopped and thus, rabbit_event is not
+ %% enabled.
+ forget_cluster_node(Node, false, false),
force_load_next_boot()
after
stop_mnesia()
diff --git a/src/rabbit_mnesia_rename.erl b/src/rabbit_mnesia_rename.erl
index 0945e31522..0c3e7c2366 100644
--- a/src/rabbit_mnesia_rename.erl
+++ b/src/rabbit_mnesia_rename.erl
@@ -124,7 +124,13 @@ prepare(Node, NodeMapList) ->
take_backup(Backup) ->
start_mnesia(),
- ok = mnesia:backup(Backup),
+ %% We backup only local tables: in particular, this excludes the
+ %% connection tracking tables which have no local replica.
+ LocalTables = mnesia:system_info(local_tables),
+ {ok, Name, _Nodes} = mnesia:activate_checkpoint([
+ {max, LocalTables}
+ ]),
+ ok = mnesia:backup_checkpoint(Name, Backup),
stop_mnesia().
restore_backup(Backup) ->
diff --git a/src/rabbit_msg_store.erl b/src/rabbit_msg_store.erl
index d3ff077c8b..8e2b1c0d49 100644
--- a/src/rabbit_msg_store.erl
+++ b/src/rabbit_msg_store.erl
@@ -157,7 +157,7 @@
-type client_msstate() :: #client_msstate {
server :: server(),
client_ref :: client_ref(),
- file_handle_cache :: ?DICT_TYPE(),
+ file_handle_cache :: dict:dict(),
index_state :: any(),
index_module :: atom(),
dir :: file:filename(),
@@ -171,7 +171,7 @@
fun ((A) -> 'finished' |
{rabbit_types:msg_id(), non_neg_integer(), A}).
-type maybe_msg_id_fun() ::
- 'undefined' | fun ((?GB_SET_TYPE(), 'written' | 'ignored') -> any()).
+ 'undefined' | fun ((gb_sets:set(), 'written' | 'ignored') -> any()).
-type maybe_close_fds_fun() :: 'undefined' | fun (() -> 'ok').
-type deletion_thunk() :: fun (() -> boolean()).
diff --git a/src/rabbit_parameter_validation.erl b/src/rabbit_parameter_validation.erl
index 90ab1d5286..00e83d2757 100644
--- a/src/rabbit_parameter_validation.erl
+++ b/src/rabbit_parameter_validation.erl
@@ -16,7 +16,7 @@
-module(rabbit_parameter_validation).
--export([number/2, binary/2, boolean/2, list/2, regex/2, proplist/3, enum/1]).
+-export([number/2, integer/2, binary/2, boolean/2, list/2, regex/2, proplist/3, enum/1]).
number(_Name, Term) when is_number(Term) ->
ok;
@@ -24,6 +24,12 @@ number(_Name, Term) when is_number(Term) ->
number(Name, Term) ->
{error, "~s should be number, actually was ~p", [Name, Term]}.
+integer(_Name, Term) when is_integer(Term) ->
+ ok;
+
+integer(Name, Term) ->
+ {error, "~s should be number, actually was ~p", [Name, Term]}.
+
binary(_Name, Term) when is_binary(Term) ->
ok;
diff --git a/src/rabbit_password.erl b/src/rabbit_password.erl
index b7987df1d8..0538445ab4 100644
--- a/src/rabbit_password.erl
+++ b/src/rabbit_password.erl
@@ -35,7 +35,7 @@ hash(HashingMod, Cleartext) ->
<<SaltBin/binary, Hash/binary>>.
generate_salt() ->
- Salt = rand_compat:uniform(16#ffffffff),
+ Salt = rand:uniform(16#ffffffff),
<<Salt:32>>.
salted_hash(Salt, Cleartext) ->
diff --git a/src/rabbit_plugins.erl b/src/rabbit_plugins.erl
index 4d8966f7e2..723f725303 100644
--- a/src/rabbit_plugins.erl
+++ b/src/rabbit_plugins.erl
@@ -16,10 +16,15 @@
-module(rabbit_plugins).
-include("rabbit.hrl").
+-include_lib("stdlib/include/zip.hrl").
-export([setup/0, active/0, read_enabled/1, list/1, list/2, dependencies/3]).
-export([ensure/1]).
+-export([extract_schemas/1]).
+-export([validate_plugins/1, format_invalid_plugins/1]).
+% Export for testing purpose.
+-export([is_version_supported/2, validate_plugins/2]).
%%----------------------------------------------------------------------------
-type plugin_name() :: atom().
@@ -43,6 +48,7 @@ ensure(FileJustChanged0) ->
FileJustChanged ->
Enabled = read_enabled(OurFile),
Wanted = prepare_plugins(Enabled),
+ rabbit_config:prepare_and_use_config(),
Current = active(),
Start = Wanted -- Current,
Stop = Current -- Wanted,
@@ -76,6 +82,50 @@ setup() ->
Enabled = read_enabled(EnabledFile),
prepare_plugins(Enabled).
+extract_schemas(SchemaDir) ->
+ application:load(rabbit),
+ {ok, EnabledFile} = application:get_env(rabbit, enabled_plugins_file),
+ Enabled = read_enabled(EnabledFile),
+
+ {ok, PluginsDistDir} = application:get_env(rabbit, plugins_dir),
+
+ AllPlugins = list(PluginsDistDir),
+ Wanted = dependencies(false, Enabled, AllPlugins),
+ WantedPlugins = lookup_plugins(Wanted, AllPlugins),
+ [ extract_schema(Plugin, SchemaDir) || Plugin <- WantedPlugins ],
+ application:unload(rabbit),
+ ok.
+
+extract_schema(#plugin{type = ez, location = Location}, SchemaDir) ->
+ {ok, Files} = zip:extract(Location,
+ [memory, {file_filter,
+ fun(#zip_file{name = Name}) ->
+ string:str(Name, "priv/schema") > 0
+ end}]),
+ lists:foreach(
+ fun({FileName, Content}) ->
+ ok = file:write_file(filename:join([SchemaDir,
+ filename:basename(FileName)]),
+ Content)
+ end,
+ Files),
+ ok;
+extract_schema(#plugin{type = dir, location = Location}, SchemaDir) ->
+ PluginSchema = filename:join([Location,
+ "priv",
+ "schema"]),
+ case rabbit_file:is_dir(PluginSchema) of
+ false -> ok;
+ true ->
+ PluginSchemaFiles =
+ [ filename:join(PluginSchema, FileName)
+ || FileName <- rabbit_file:wildcard(".*\\.schema",
+ PluginSchema) ],
+ [ file:copy(SchemaFile, SchemaDir)
+ || SchemaFile <- PluginSchemaFiles ]
+ end.
+
+
%% @doc Lists the plugins which are currently running.
active() ->
{ok, ExpandDir} = application:get_env(rabbit, plugins_expand_dir),
@@ -96,27 +146,31 @@ list(PluginsDir, IncludeRequiredDeps) ->
%% instance.
application:load(rabbit),
{ok, RabbitDeps} = application:get_key(rabbit, applications),
+ AllPlugins = [plugin_info(PluginsDir, Plug) || Plug <- EZs ++ FreeApps],
{AvailablePlugins, Problems} =
- lists:foldl(fun ({error, EZ, Reason}, {Plugins1, Problems1}) ->
- {Plugins1, [{EZ, Reason} | Problems1]};
- (Plugin = #plugin{name = Name}, {Plugins1, Problems1}) ->
- %% Applications RabbitMQ depends on (eg.
- %% "rabbit_common") can't be considered
- %% plugins, otherwise rabbitmq-plugins would
- %% list them and the user may believe he can
- %% disable them.
- case IncludeRequiredDeps orelse
- not lists:member(Name, RabbitDeps) of
- true -> {[Plugin|Plugins1], Problems1};
- false -> {Plugins1, Problems1}
- end
- end, {[], []},
- [plugin_info(PluginsDir, Plug) || Plug <- EZs ++ FreeApps]),
+ lists:foldl(
+ fun ({error, EZ, Reason}, {Plugins1, Problems1}) ->
+ {Plugins1, [{EZ, Reason} | Problems1]};
+ (Plugin = #plugin{name = Name},
+ {Plugins1, Problems1}) ->
+ %% Applications RabbitMQ depends on (eg.
+ %% "rabbit_common") can't be considered
+ %% plugins, otherwise rabbitmq-plugins would
+ %% list them and the user may believe he can
+ %% disable them.
+ case IncludeRequiredDeps orelse
+ not lists:member(Name, RabbitDeps) of
+ true -> {[Plugin|Plugins1], Problems1};
+ false -> {Plugins1, Problems1}
+ end
+ end, {[], []},
+ AllPlugins),
case Problems of
[] -> ok;
_ -> rabbit_log:warning(
"Problem reading some plugins: ~p~n", [Problems])
end,
+
Plugins = lists:filter(fun(P) -> not plugin_provided_by_otp(P) end,
AvailablePlugins),
ensure_dependencies(Plugins).
@@ -146,8 +200,9 @@ dependencies(Reverse, Sources, AllPlugins) ->
false -> digraph_utils:reachable(Sources, G);
true -> digraph_utils:reaching(Sources, G)
end,
+ OrderedDests = digraph_utils:postorder(digraph_utils:subgraph(G, Dests)),
true = digraph:delete(G),
- Dests.
+ OrderedDests.
%% For a few known cases, an externally provided plugin can be trusted.
%% In this special case, it overrides the plugin.
@@ -195,19 +250,130 @@ prepare_plugins(Enabled) ->
AllPlugins = list(PluginsDistDir),
Wanted = dependencies(false, Enabled, AllPlugins),
WantedPlugins = lookup_plugins(Wanted, AllPlugins),
-
+ {ValidPlugins, Problems} = validate_plugins(WantedPlugins),
+ maybe_warn_about_invalid_plugins(Problems),
case filelib:ensure_dir(ExpandDir ++ "/") of
ok -> ok;
{error, E2} -> throw({error, {cannot_create_plugins_expand_dir,
[ExpandDir, E2]}})
end,
-
- [prepare_plugin(Plugin, ExpandDir) || Plugin <- WantedPlugins],
+ [prepare_plugin(Plugin, ExpandDir) || Plugin <- ValidPlugins],
[prepare_dir_plugin(PluginAppDescPath) ||
PluginAppDescPath <- filelib:wildcard(ExpandDir ++ "/*/ebin/*.app")],
Wanted.
+maybe_warn_about_invalid_plugins([]) ->
+ ok;
+maybe_warn_about_invalid_plugins(InvalidPlugins) ->
+ %% TODO: error message formatting
+ rabbit_log:warning(format_invalid_plugins(InvalidPlugins)).
+
+
+format_invalid_plugins(InvalidPlugins) ->
+ lists:flatten(["Failed to enable some plugins: \r\n"
+ | [format_invalid_plugin(Plugin)
+ || Plugin <- InvalidPlugins]]).
+
+format_invalid_plugin({Name, Errors}) ->
+ [io_lib:format(" ~p:~n", [Name])
+ | [format_invalid_plugin_error(Err) || Err <- Errors]].
+
+format_invalid_plugin_error({missing_dependency, Dep}) ->
+ io_lib:format(" Dependency is missing or invalid: ~p~n", [Dep]);
+%% a plugin doesn't support the effective broker version
+format_invalid_plugin_error({broker_version_mismatch, Version, Required}) ->
+ io_lib:format(" Plugin doesn't support current server version."
+ " Actual broker version: ~p, supported by the plugin: ~p~n", [Version, Required]);
+%% one of dependencies of a plugin doesn't match its version requirements
+format_invalid_plugin_error({{dependency_version_mismatch, Version, Required}, Name}) ->
+ io_lib:format(" Version '~p' of dependency '~p' is unsupported."
+ " Version ranges supported by the plugin: ~p~n",
+ [Version, Name, Required]);
+format_invalid_plugin_error(Err) ->
+ io_lib:format(" Unknown error ~p~n", [Err]).
+
+validate_plugins(Plugins) ->
+ application:load(rabbit),
+ RabbitVersion = RabbitVersion = case application:get_key(rabbit, vsn) of
+ undefined -> "0.0.0";
+ {ok, Val} -> Val
+ end,
+ validate_plugins(Plugins, RabbitVersion).
+
+validate_plugins(Plugins, BrokerVersion) ->
+ lists:foldl(
+ fun(#plugin{name = Name,
+ broker_version_requirements = BrokerVersionReqs,
+ dependency_version_requirements = DepsVersions} = Plugin,
+ {Plugins0, Errors}) ->
+ case is_version_supported(BrokerVersion, BrokerVersionReqs) of
+ true ->
+ case BrokerVersion of
+ "0.0.0" ->
+ rabbit_log:warning(
+ "Running development version of the broker."
+ " Requirement ~p for plugin ~p is ignored.",
+ [BrokerVersionReqs, Name]);
+ _ -> ok
+ end,
+ case check_plugins_versions(Name, Plugins0, DepsVersions) of
+ ok -> {[Plugin | Plugins0], Errors};
+ {error, Err} -> {Plugins0, [{Name, Err} | Errors]}
+ end;
+ false ->
+ Error = [{broker_version_mismatch, BrokerVersion, BrokerVersionReqs}],
+ {Plugins0, [{Name, Error} | Errors]}
+ end
+ end,
+ {[],[]},
+ Plugins).
+
+check_plugins_versions(PluginName, AllPlugins, RequiredVersions) ->
+ ExistingVersions = [{Name, Vsn}
+ || #plugin{name = Name, version = Vsn} <- AllPlugins],
+ Problems = lists:foldl(
+ fun({Name, Versions}, Acc) ->
+ case proplists:get_value(Name, ExistingVersions) of
+ undefined -> [{missing_dependency, Name} | Acc];
+ Version ->
+ case is_version_supported(Version, Versions) of
+ true ->
+ case Version of
+ "" ->
+ rabbit_log:warning(
+ "~p plugin version is not defined."
+ " Requirement ~p for plugin ~p is ignored",
+ [Versions, PluginName]);
+ _ -> ok
+ end,
+ Acc;
+ false ->
+ [{{dependency_version_mismatch, Version, Versions}, Name} | Acc]
+ end
+ end
+ end,
+ [],
+ RequiredVersions),
+ case Problems of
+ [] -> ok;
+ _ -> {error, Problems}
+ end.
+
+is_version_supported("", _) -> true;
+is_version_supported("0.0.0", _) -> true;
+is_version_supported(_Version, []) -> true;
+is_version_supported(Version, ExpectedVersions) ->
+ case lists:any(fun(ExpectedVersion) ->
+ rabbit_misc:version_minor_equivalent(ExpectedVersion, Version)
+ andalso
+ rabbit_misc:version_compare(ExpectedVersion, Version, lte)
+ end,
+ ExpectedVersions) of
+ true -> true;
+ false -> false
+ end.
+
clean_plugins(Plugins) ->
{ok, ExpandDir} = application:get_env(rabbit, plugins_expand_dir),
[clean_plugin(Plugin, ExpandDir) || Plugin <- Plugins].
@@ -280,8 +446,12 @@ mkplugin(Name, Props, Type, Location) ->
Version = proplists:get_value(vsn, Props, "0"),
Description = proplists:get_value(description, Props, ""),
Dependencies = proplists:get_value(applications, Props, []),
+ BrokerVersions = proplists:get_value(broker_version_requirements, Props, []),
+ DepsVersions = proplists:get_value(dependency_version_requirements, Props, []),
#plugin{name = Name, version = Version, description = Description,
- dependencies = Dependencies, location = Location, type = Type}.
+ dependencies = Dependencies, location = Location, type = Type,
+ broker_version_requirements = BrokerVersions,
+ dependency_version_requirements = DepsVersions}.
read_app_file(EZ) ->
case zip:list_dir(EZ) of
@@ -316,4 +486,9 @@ plugin_names(Plugins) ->
[Name || #plugin{name = Name} <- Plugins].
lookup_plugins(Names, AllPlugins) ->
- [P || P = #plugin{name = Name} <- AllPlugins, lists:member(Name, Names)].
+ % Preserve order of Names
+ lists:map(
+ fun(Name) ->
+ lists:keyfind(Name, #plugin.name, AllPlugins)
+ end,
+ Names).
diff --git a/src/rabbit_plugins_main.erl b/src/rabbit_plugins_main.erl
index ff516268c6..4618249ee8 100644
--- a/src/rabbit_plugins_main.erl
+++ b/src/rabbit_plugins_main.erl
@@ -95,6 +95,12 @@ action(enable, Node, ToEnable0, Opts, State = #cli{all = All,
_ -> throw({error_string, fmt_missing(Missing)})
end,
NewEnabled = lists:usort(Enabled ++ ToEnable),
+ Invalid = validate_plugins(NewEnabled, State),
+ case Invalid of
+ [] -> ok;
+ _ -> throw({error_string,
+ rabbit_plugins:format_invalid_plugins(Invalid)})
+ end,
NewImplicit = write_enabled_plugins(NewEnabled, State),
case NewEnabled -- Implicit of
[] -> io:format("Plugin configuration unchanged.~n");
@@ -111,6 +117,12 @@ action(set, Node, NewEnabled0, Opts, State = #cli{all = All,
[] -> ok;
_ -> throw({error_string, fmt_missing(Missing)})
end,
+ Invalid = validate_plugins(NewEnabled, State),
+ case Invalid of
+ [] -> ok;
+ _ -> throw({error_string,
+ rabbit_plugins:format_invalid_plugins(Invalid)})
+ end,
NewImplicit = write_enabled_plugins(NewEnabled, State),
case NewImplicit of
[] -> io:format("All plugins are now disabled.~n");
@@ -151,6 +163,16 @@ action(help, _Node, _Args, _Opts, _State) ->
%%----------------------------------------------------------------------------
+validate_plugins(Names, #cli{all = All}) ->
+ Deps = rabbit_plugins:dependencies(false, Names, All),
+ DepsPlugins = lists:map(
+ fun(Name) ->
+ lists:keyfind(Name, #plugin.name, All)
+ end,
+ Deps),
+ {_, Errors} = rabbit_plugins:validate_plugins(DepsPlugins),
+ Errors.
+
%% Pretty print a list of plugins.
format_plugins(Node, Pattern, Opts, #cli{all = All,
enabled = Enabled,
diff --git a/src/rabbit_policy.erl b/src/rabbit_policy.erl
index eb8cf63327..a9caadf972 100644
--- a/src/rabbit_policy.erl
+++ b/src/rabbit_policy.erl
@@ -276,7 +276,9 @@ update_queue(Q = #amqqueue{name = QName, policy = OldPolicy}, Policies) ->
NewPolicy -> case rabbit_amqqueue:update(
QName, fun(Q1) ->
rabbit_queue_decorator:set(
- Q1#amqqueue{policy = NewPolicy})
+ Q1#amqqueue{policy = NewPolicy,
+ policy_version =
+ Q1#amqqueue.policy_version + 1 })
end) of
#amqqueue{} = Q1 -> {Q, Q1};
not_found -> {Q, Q }
diff --git a/src/rabbit_prelaunch.erl b/src/rabbit_prelaunch.erl
index 569a8d6c5a..fd17860432 100644
--- a/src/rabbit_prelaunch.erl
+++ b/src/rabbit_prelaunch.erl
@@ -75,25 +75,39 @@ duplicate_node_check(NodeName, NodeHost) ->
end.
dist_port_set_check() ->
- case os:getenv("RABBITMQ_CONFIG_FILE") of
- false ->
+ case get_config(os:getenv("RABBITMQ_CONFIG_FILE")) of
+ {ok, [Config]} ->
+ Kernel = pget(kernel, Config, []),
+ case {pget(inet_dist_listen_min, Kernel, none),
+ pget(inet_dist_listen_max, Kernel, none)} of
+ {none, none} -> ok;
+ _ -> rabbit_misc:quit(?DO_NOT_SET_DIST_PORT)
+ end;
+ {ok, _} ->
ok;
- File ->
- case file:consult(File ++ ".config") of
- {ok, [Config]} ->
- Kernel = pget(kernel, Config, []),
- case {pget(inet_dist_listen_min, Kernel, none),
- pget(inet_dist_listen_max, Kernel, none)} of
- {none, none} -> ok;
- _ -> rabbit_misc:quit(?DO_NOT_SET_DIST_PORT)
- end;
- {ok, _} ->
- ok;
- {error, _} ->
- ok
+ {error, _} ->
+ ok
+ end.
+
+get_config(File) ->
+ case consult_file(File) of
+ {ok, Contents} -> {ok, Contents};
+ {error, _} ->
+ case rabbit_config:get_advanced_config() of
+ none -> {error, enoent};
+ FileName -> file:consult(FileName)
end
end.
+consult_file(false) -> {error, nofile};
+consult_file(File) ->
+ FileName = case filename:extension(File) of
+ "" -> File ++ ".config";
+ ".config" -> File;
+ _ -> ""
+ end,
+ file:consult(FileName).
+
dist_port_range_check() ->
case os:getenv("RABBITMQ_DIST_PORT") of
false -> ok;
diff --git a/src/rabbit_queue_consumers.erl b/src/rabbit_queue_consumers.erl
index a8002398e7..cd58de95dd 100644
--- a/src/rabbit_queue_consumers.erl
+++ b/src/rabbit_queue_consumers.erl
@@ -96,7 +96,7 @@
new() -> #state{consumers = priority_queue:new(),
use = {active,
- time_compat:monotonic_time(micro_seconds),
+ erlang:monotonic_time(micro_seconds),
1.0}}.
max_active_priority(#state{consumers = Consumers}) ->
@@ -346,9 +346,9 @@ drain_mode(true) -> drain;
drain_mode(false) -> manual.
utilisation(#state{use = {active, Since, Avg}}) ->
- use_avg(time_compat:monotonic_time(micro_seconds) - Since, 0, Avg);
+ use_avg(erlang:monotonic_time(micro_seconds) - Since, 0, Avg);
utilisation(#state{use = {inactive, Since, Active, Avg}}) ->
- use_avg(Active, time_compat:monotonic_time(micro_seconds) - Since, Avg).
+ use_avg(Active, erlang:monotonic_time(micro_seconds) - Since, Avg).
%%----------------------------------------------------------------------------
@@ -455,10 +455,10 @@ update_use({inactive, _, _, _} = CUInfo, inactive) ->
update_use({active, _, _} = CUInfo, active) ->
CUInfo;
update_use({active, Since, Avg}, inactive) ->
- Now = time_compat:monotonic_time(micro_seconds),
+ Now = erlang:monotonic_time(micro_seconds),
{inactive, Now, Now - Since, Avg};
update_use({inactive, Since, Active, Avg}, active) ->
- Now = time_compat:monotonic_time(micro_seconds),
+ Now = erlang:monotonic_time(micro_seconds),
{active, Now, use_avg(Active, Now - Since, Avg)}.
use_avg(0, 0, Avg) ->
diff --git a/src/rabbit_queue_index.erl b/src/rabbit_queue_index.erl
index 6a14854882..8b96bbffbd 100644
--- a/src/rabbit_queue_index.erl
+++ b/src/rabbit_queue_index.erl
@@ -197,13 +197,13 @@
-type segment() :: ('undefined' |
#segment { num :: non_neg_integer(),
path :: file:filename(),
- journal_entries :: ?ARRAY_TYPE(),
- entries_to_segment :: ?ARRAY_TYPE(),
+ journal_entries :: array:array(),
+ entries_to_segment :: array:array(),
unacked :: non_neg_integer()
}).
-type seq_id() :: integer().
--type seg_dict() :: {?DICT_TYPE(), [segment()]}.
--type on_sync_fun() :: fun ((?GB_SET_TYPE()) -> ok).
+-type seg_dict() :: {dict:dict(), [segment()]}.
+-type on_sync_fun() :: fun ((gb_sets:set()) -> ok).
-type qistate() :: #qistate { dir :: file:filename(),
segments :: 'undefined' | seg_dict(),
journal_handle :: hdl(),
@@ -211,8 +211,8 @@
max_journal_entries :: non_neg_integer(),
on_sync :: on_sync_fun(),
on_sync_msg :: on_sync_fun(),
- unconfirmed :: ?GB_SET_TYPE(),
- unconfirmed_msg :: ?GB_SET_TYPE(),
+ unconfirmed :: gb_sets:set(),
+ unconfirmed_msg :: gb_sets:set(),
pre_publish_cache :: list(),
delivered_cache :: list()
}.
diff --git a/src/rabbit_queue_location_random.erl b/src/rabbit_queue_location_random.erl
index 2579cbb2b1..73d509bf33 100644
--- a/src/rabbit_queue_location_random.erl
+++ b/src/rabbit_queue_location_random.erl
@@ -39,6 +39,6 @@ description() ->
queue_master_location(#amqqueue{}) ->
Cluster = rabbit_queue_master_location_misc:all_nodes(),
- RandomPos = erlang:phash2(time_compat:monotonic_time(), length(Cluster)),
+ RandomPos = erlang:phash2(erlang:monotonic_time(), length(Cluster)),
MasterNode = lists:nth(RandomPos + 1, Cluster),
{ok, MasterNode}.
diff --git a/src/rabbit_registry.erl b/src/rabbit_registry.erl
deleted file mode 100644
index 0428c3533f..0000000000
--- a/src/rabbit_registry.erl
+++ /dev/null
@@ -1,162 +0,0 @@
-%% 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 Developer of the Original Code is GoPivotal, Inc.
-%% Copyright (c) 2007-2016 Pivotal Software, Inc. All rights reserved.
-%%
-
--module(rabbit_registry).
-
--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([register/3, unregister/2,
- binary_to_type/1, lookup_module/2, lookup_all/1]).
-
--define(SERVER, ?MODULE).
--define(ETS_NAME, ?MODULE).
-
--spec start_link() -> rabbit_types:ok_pid_or_error().
--spec register(atom(), binary(), atom()) -> 'ok'.
--spec unregister(atom(), binary()) -> 'ok'.
--spec binary_to_type
- (binary()) -> atom() | rabbit_types:error('not_found').
--spec lookup_module
- (atom(), atom()) -> rabbit_types:ok_or_error2(atom(), 'not_found').
--spec lookup_all(atom()) -> [{atom(), atom()}].
-
-%%---------------------------------------------------------------------------
-
-start_link() ->
- gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
-
-%%---------------------------------------------------------------------------
-
-register(Class, TypeName, ModuleName) ->
- gen_server:call(?SERVER, {register, Class, TypeName, ModuleName}, infinity).
-
-unregister(Class, TypeName) ->
- gen_server:call(?SERVER, {unregister, Class, TypeName}, infinity).
-
-%% This is used with user-supplied arguments (e.g., on exchange
-%% declare), so we restrict it to existing atoms only. This means it
-%% can throw a badarg, indicating that the type cannot have been
-%% registered.
-binary_to_type(TypeBin) when is_binary(TypeBin) ->
- case catch list_to_existing_atom(binary_to_list(TypeBin)) of
- {'EXIT', {badarg, _}} -> {error, not_found};
- TypeAtom -> TypeAtom
- end.
-
-lookup_module(Class, T) when is_atom(T) ->
- case ets:lookup(?ETS_NAME, {Class, T}) of
- [{_, Module}] ->
- {ok, Module};
- [] ->
- {error, not_found}
- end.
-
-lookup_all(Class) ->
- [{K, V} || [K, V] <- ets:match(?ETS_NAME, {{Class, '$1'}, '$2'})].
-
-%%---------------------------------------------------------------------------
-
-internal_binary_to_type(TypeBin) when is_binary(TypeBin) ->
- list_to_atom(binary_to_list(TypeBin)).
-
-internal_register(Class, TypeName, ModuleName)
- when is_atom(Class), is_binary(TypeName), is_atom(ModuleName) ->
- ok = sanity_check_module(class_module(Class), ModuleName),
- RegArg = {{Class, internal_binary_to_type(TypeName)}, ModuleName},
- true = ets:insert(?ETS_NAME, RegArg),
- conditional_register(RegArg),
- ok.
-
-internal_unregister(Class, TypeName) ->
- UnregArg = {Class, internal_binary_to_type(TypeName)},
- conditional_unregister(UnregArg),
- true = ets:delete(?ETS_NAME, UnregArg),
- ok.
-
-%% register exchange decorator route callback only when implemented,
-%% in order to avoid unnecessary decorator calls on the fast
-%% publishing path
-conditional_register({{exchange_decorator, Type}, ModuleName}) ->
- case erlang:function_exported(ModuleName, route, 2) of
- true -> true = ets:insert(?ETS_NAME,
- {{exchange_decorator_route, Type},
- ModuleName});
- false -> ok
- end;
-conditional_register(_) ->
- ok.
-
-conditional_unregister({exchange_decorator, Type}) ->
- true = ets:delete(?ETS_NAME, {exchange_decorator_route, Type}),
- ok;
-conditional_unregister(_) ->
- ok.
-
-sanity_check_module(ClassModule, Module) ->
- case catch lists:member(ClassModule,
- lists:flatten(
- [Bs || {Attr, Bs} <-
- Module:module_info(attributes),
- Attr =:= behavior orelse
- Attr =:= behaviour])) of
- {'EXIT', {undef, _}} -> {error, not_module};
- false -> {error, {not_type, ClassModule}};
- true -> ok
- end.
-
-class_module(exchange) -> rabbit_exchange_type;
-class_module(auth_mechanism) -> rabbit_auth_mechanism;
-class_module(runtime_parameter) -> rabbit_runtime_parameter;
-class_module(exchange_decorator) -> rabbit_exchange_decorator;
-class_module(queue_decorator) -> rabbit_queue_decorator;
-class_module(policy_validator) -> rabbit_policy_validator;
-class_module(ha_mode) -> rabbit_mirror_queue_mode;
-class_module(channel_interceptor) -> rabbit_channel_interceptor;
-class_module(queue_master_locator)-> rabbit_queue_master_locator.
-
-%%---------------------------------------------------------------------------
-
-init([]) ->
- ?ETS_NAME = ets:new(?ETS_NAME, [protected, set, named_table]),
- {ok, none}.
-
-handle_call({register, Class, TypeName, ModuleName}, _From, State) ->
- ok = internal_register(Class, TypeName, ModuleName),
- {reply, ok, State};
-
-handle_call({unregister, Class, TypeName}, _From, State) ->
- ok = internal_unregister(Class, TypeName),
- {reply, ok, State};
-
-handle_call(Request, _From, State) ->
- {stop, {unhandled_call, Request}, State}.
-
-handle_cast(Request, State) ->
- {stop, {unhandled_cast, Request}, State}.
-
-handle_info(Message, State) ->
- {stop, {unhandled_info, Message}, State}.
-
-terminate(_Reason, _State) ->
- ok.
-
-code_change(_OldVsn, State, _Extra) ->
- {ok, State}.
diff --git a/src/rabbit_sasl_report_file_h.erl b/src/rabbit_sasl_report_file_h.erl
deleted file mode 100644
index 9c6d7657f2..0000000000
--- a/src/rabbit_sasl_report_file_h.erl
+++ /dev/null
@@ -1,102 +0,0 @@
-%% 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 Developer of the Original Code is GoPivotal, Inc.
-%% Copyright (c) 2007-2016 Pivotal Software, Inc. All rights reserved.
-%%
-
--module(rabbit_sasl_report_file_h).
--include("rabbit.hrl").
-
--behaviour(gen_event).
-
--export([init/1, handle_event/2, handle_call/2, handle_info/2, terminate/2,
- code_change/3]).
-
--import(rabbit_error_logger_file_h, [safe_handle_event/3]).
-
-%% rabbit_sasl_report_file_h is a wrapper around the sasl_report_file_h
-%% module because the original's init/1 does not match properly
-%% with the result of closing the old handler when swapping handlers.
-%% The first init/1 additionally allows for simple log rotation
-%% when the suffix is not the empty string.
-%% The original init/1 also opened the file in 'write' mode, thus
-%% overwriting old logs. To remedy this, init/1 from
-%% lib/sasl/src/sasl_report_file_h.erl from R14B3 was copied as
-%% init_file/1 and changed so that it opens the file in 'append' mode.
-
-%% Used only when swapping handlers and performing
-%% log rotation
-init({{File, ""}, _}) ->
- init(File);
-init({{File, Suffix}, []}) ->
- case rabbit_file:append_file(File, Suffix) of
- ok -> file:delete(File),
- ok;
- {error, Error} ->
- rabbit_log:error("Failed to append contents of "
- "sasl log file '~s' to '~s':~n~p~n",
- [File, [File, Suffix], Error])
- end,
- init(File);
-%% Used only when swapping handlers and the original handler
-%% failed to terminate or was never installed
-init({{File, _}, error}) ->
- init(File);
-%% Used only when swapping handlers without
-%% doing any log rotation
-init({File, []}) ->
- init(File);
-init({File, _Type} = FileInfo) ->
- rabbit_file:ensure_parent_dirs_exist(File),
- init_file(FileInfo);
-init(File) ->
- rabbit_file:ensure_parent_dirs_exist(File),
- init_file({File, sasl_error_logger_type()}).
-
-init_file({File, Type}) ->
- process_flag(trap_exit, true),
- case file:open(File, [append]) of
- {ok,Fd} -> {ok, {Fd, File, Type}};
- Error -> Error
- end.
-
-handle_event(Event, State) ->
- safe_handle_event(fun handle_event0/2, Event, State).
-
-handle_event0(Event, State) ->
- sasl_report_file_h:handle_event(
- truncate:log_event(Event, ?LOG_TRUNC), State).
-
-handle_info(Info, State) ->
- sasl_report_file_h:handle_info(Info, State).
-
-handle_call(Call, State) ->
- sasl_report_file_h:handle_call(Call, State).
-
-terminate(Reason, State) ->
- sasl_report_file_h:terminate(Reason, State).
-
-code_change(_OldVsn, State, _Extra) ->
- %% There is no sasl_report_file_h:code_change/3
- {ok, State}.
-
-%%----------------------------------------------------------------------
-
-sasl_error_logger_type() ->
- case application:get_env(sasl, errlog_type) of
- {ok, error} -> error;
- {ok, progress} -> progress;
- {ok, all} -> all;
- {ok, Bad} -> throw({error, {wrong_errlog_type, Bad}});
- _ -> all
- end.
diff --git a/src/rabbit_upgrade_functions.erl b/src/rabbit_upgrade_functions.erl
index 67c2a84a0e..3c6f2a4f1f 100644
--- a/src/rabbit_upgrade_functions.erl
+++ b/src/rabbit_upgrade_functions.erl
@@ -24,6 +24,7 @@
-rabbit_upgrade({remove_user_scope, mnesia, []}).
-rabbit_upgrade({hash_passwords, mnesia, []}).
-rabbit_upgrade({add_ip_to_listener, mnesia, []}).
+-rabbit_upgrade({add_opts_to_listener, mnesia, [add_ip_to_listener]}).
-rabbit_upgrade({internal_exchanges, mnesia, []}).
-rabbit_upgrade({user_to_internal_user, mnesia, [hash_passwords]}).
-rabbit_upgrade({topic_trie, mnesia, []}).
@@ -51,13 +52,17 @@
-rabbit_upgrade({down_slave_nodes, mnesia, [queue_decorators]}).
-rabbit_upgrade({queue_state, mnesia, [down_slave_nodes]}).
-rabbit_upgrade({recoverable_slaves, mnesia, [queue_state]}).
+-rabbit_upgrade({policy_version, mnesia, [recoverable_slaves]}).
+-rabbit_upgrade({slave_pids_pending_shutdown, mnesia, [policy_version]}).
-rabbit_upgrade({user_password_hashing, mnesia, [hash_passwords]}).
+-rabbit_upgrade({vhost_limits, mnesia, []}).
%% -------------------------------------------------------------------
-spec remove_user_scope() -> 'ok'.
-spec hash_passwords() -> 'ok'.
-spec add_ip_to_listener() -> 'ok'.
+-spec add_opts_to_listener() -> 'ok'.
-spec internal_exchanges() -> 'ok'.
-spec user_to_internal_user() -> 'ok'.
-spec topic_trie() -> 'ok'.
@@ -84,9 +89,22 @@
-spec queue_state() -> 'ok'.
-spec recoverable_slaves() -> 'ok'.
-spec user_password_hashing() -> 'ok'.
+-spec vhost_limits() -> 'ok'.
+
%%--------------------------------------------------------------------
+%% replaces vhost.dummy (used to avoid having a single-field record
+%% which Mnesia doesn't like) with vhost.limits (which is actually
+%% used)
+vhost_limits() ->
+ transform(
+ rabbit_vhost,
+ fun ({vhost, VHost, _Dummy}) ->
+ {vhost, VHost, undefined}
+ end,
+ [virtual_host, limits]).
+
%% It's a bad idea to use records or record_info here, even for the
%% destination form. Because in the future, the destination form of
%% your current transform may not match the record any more, and it
@@ -122,6 +140,14 @@ add_ip_to_listener() ->
end,
[node, protocol, host, ip_address, port]).
+add_opts_to_listener() ->
+ transform(
+ rabbit_listener,
+ fun ({listener, Node, Protocol, Host, IP, Port}) ->
+ {listener, Node, Protocol, Host, IP, Port, []}
+ end,
+ [node, protocol, host, ip_address, port, opts]).
+
internal_exchanges() ->
Tables = [rabbit_exchange, rabbit_durable_exchange],
AddInternalFun =
@@ -433,6 +459,42 @@ recoverable_slaves(Table) ->
sync_slave_pids, recoverable_slaves, policy, gm_pids, decorators,
state]).
+policy_version() ->
+ ok = policy_version(rabbit_queue),
+ ok = policy_version(rabbit_durable_queue).
+
+policy_version(Table) ->
+ transform(
+ Table,
+ fun ({amqqueue, Name, Durable, AutoDelete, ExclusiveOwner, Arguments,
+ Pid, SlavePids, SyncSlavePids, DSN, Policy, GmPids, Decorators,
+ State}) ->
+ {amqqueue, Name, Durable, AutoDelete, ExclusiveOwner, Arguments,
+ Pid, SlavePids, SyncSlavePids, DSN, Policy, GmPids, Decorators,
+ State, 0}
+ end,
+ [name, durable, auto_delete, exclusive_owner, arguments, pid, slave_pids,
+ sync_slave_pids, recoverable_slaves, policy, gm_pids, decorators, state,
+ policy_version]).
+
+slave_pids_pending_shutdown() ->
+ ok = slave_pids_pending_shutdown(rabbit_queue),
+ ok = slave_pids_pending_shutdown(rabbit_durable_queue).
+
+slave_pids_pending_shutdown(Table) ->
+ transform(
+ Table,
+ fun ({amqqueue, Name, Durable, AutoDelete, ExclusiveOwner, Arguments,
+ Pid, SlavePids, SyncSlavePids, DSN, Policy, GmPids, Decorators,
+ State, PolicyVersion}) ->
+ {amqqueue, Name, Durable, AutoDelete, ExclusiveOwner, Arguments,
+ Pid, SlavePids, SyncSlavePids, DSN, Policy, GmPids, Decorators,
+ State, PolicyVersion, []}
+ end,
+ [name, durable, auto_delete, exclusive_owner, arguments, pid, slave_pids,
+ sync_slave_pids, recoverable_slaves, policy, gm_pids, decorators, state,
+ policy_version, slave_pids_pending_shutdown]).
+
%% Prior to 3.6.0, passwords were hashed using MD5, this populates
%% existing records with said default. Users created with 3.6.0+ will
%% have internal_user.hashing_algorithm populated by the internal
diff --git a/src/rabbit_variable_queue.erl b/src/rabbit_variable_queue.erl
index 297df086ad..3267b02bea 100644
--- a/src/rabbit_variable_queue.erl
+++ b/src/rabbit_variable_queue.erl
@@ -392,10 +392,10 @@
out_counter :: non_neg_integer(),
in_counter :: non_neg_integer(),
rates :: rates(),
- msgs_on_disk :: ?GB_SET_TYPE(),
- msg_indices_on_disk :: ?GB_SET_TYPE(),
- unconfirmed :: ?GB_SET_TYPE(),
- confirmed :: ?GB_SET_TYPE(),
+ msgs_on_disk :: gb_sets:set(),
+ msg_indices_on_disk :: gb_sets:set(),
+ unconfirmed :: gb_sets:set(),
+ confirmed :: gb_sets:set(),
ack_out_counter :: non_neg_integer(),
ack_in_counter :: non_neg_integer(),
disk_read_count :: non_neg_integer(),
@@ -770,7 +770,7 @@ update_rates(State = #vqstate{ in_counter = InCount,
ack_in = AckInRate,
ack_out = AckOutRate,
timestamp = TS }}) ->
- Now = time_compat:monotonic_time(),
+ Now = erlang:monotonic_time(),
Rates = #rates { in = update_rate(Now, TS, InCount, InRate),
out = update_rate(Now, TS, OutCount, OutRate),
@@ -785,7 +785,7 @@ update_rates(State = #vqstate{ in_counter = InCount,
rates = Rates }.
update_rate(Now, TS, Count, Rate) ->
- Time = time_compat:convert_time_unit(Now - TS, native, micro_seconds) /
+ Time = erlang:convert_time_unit(Now - TS, native, micro_seconds) /
?MICROS_PER_SECOND,
if
Time == 0 -> Rate;
@@ -1283,7 +1283,7 @@ init(IsDurable, IndexState, DeltaCount, DeltaBytes, Terms,
count = DeltaCount1,
end_seq_id = NextSeqId })
end,
- Now = time_compat:monotonic_time(),
+ Now = erlang:monotonic_time(),
IoBatchSize = rabbit_misc:get_env(rabbit, msg_store_io_batch_size,
?IO_BATCH_SIZE),
diff --git a/src/rabbit_vhost.erl b/src/rabbit_vhost.erl
index df2f8423b4..01f1046fb8 100644
--- a/src/rabbit_vhost.erl
+++ b/src/rabbit_vhost.erl
@@ -20,11 +20,14 @@
%%----------------------------------------------------------------------------
--export([add/1, delete/1, exists/1, list/0, with/2, assert/1]).
+-export([add/1, delete/1, exists/1, list/0, with/2, assert/1, update/2,
+ set_limits/2, limits_of/1]).
-export([info/1, info/2, info_all/0, info_all/1, info_all/2, info_all/3]).
+
-spec add(rabbit_types:vhost()) -> 'ok'.
-spec delete(rabbit_types:vhost()) -> 'ok'.
+-spec update(rabbit_types:vhost(), rabbit_misc:thunk(A)) -> A.
-spec exists(rabbit_types:vhost()) -> boolean().
-spec list() -> [rabbit_types:vhost()].
-spec with(rabbit_types:vhost(), rabbit_misc:thunk(A)) -> A.
@@ -138,6 +141,32 @@ assert(VHostPath) -> case exists(VHostPath) of
false -> throw({error, {no_such_vhost, VHostPath}})
end.
+update(VHostPath, Fun) ->
+ case mnesia:read({rabbit_vhost, VHostPath}) of
+ [] ->
+ mnesia:abort({no_such_vhost, VHostPath});
+ [V] ->
+ V1 = Fun(V),
+ ok = mnesia:write(rabbit_vhost, V1, write),
+ V1
+ end.
+
+limits_of(VHostPath) when is_binary(VHostPath) ->
+ assert(VHostPath),
+ case mnesia:dirty_read({rabbit_vhost, VHostPath}) of
+ [] ->
+ mnesia:abort({no_such_vhost, VHostPath});
+ [#vhost{limits = Limits}] ->
+ Limits
+ end;
+limits_of(#vhost{virtual_host = Name}) ->
+ limits_of(Name).
+
+set_limits(VHost = #vhost{}, undefined) ->
+ VHost#vhost{limits = undefined};
+set_limits(VHost = #vhost{}, Limits) ->
+ VHost#vhost{limits = Limits}.
+
%%----------------------------------------------------------------------------
infos(Items, X) -> [{Item, i(Item, X)} || Item <- Items].
diff --git a/src/rabbit_vhost_limit.erl b/src/rabbit_vhost_limit.erl
new file mode 100644
index 0000000000..2d9a2f075e
--- /dev/null
+++ b/src/rabbit_vhost_limit.erl
@@ -0,0 +1,99 @@
+%% 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 Developer of the Original Code is GoPivotal, Inc.
+%% Copyright (c) 2007-2016 Pivotal Software, Inc. All rights reserved.
+%%
+
+-module(rabbit_vhost_limit).
+
+-behaviour(rabbit_runtime_parameter).
+
+-include("rabbit.hrl").
+
+-export([register/0]).
+-export([parse_set/2, clear/1]).
+-export([validate/5, notify/4, notify_clear/3]).
+-export([connection_limit/1]).
+
+-import(rabbit_misc, [pget/2]).
+
+-rabbit_boot_step({?MODULE,
+ [{description, "vhost limit parameters"},
+ {mfa, {rabbit_vhost_limit, register, []}},
+ {requires, rabbit_registry},
+ {enables, recovery}]}).
+
+%%----------------------------------------------------------------------------
+
+register() ->
+ rabbit_registry:register(runtime_parameter, <<"vhost-limits">>, ?MODULE).
+
+validate(_VHost, <<"vhost-limits">>, Name, Term, _User) ->
+ rabbit_parameter_validation:proplist(
+ Name, vhost_limit_validation(), Term).
+
+notify(VHost, <<"vhost-limits">>, <<"limits">>, Limits) ->
+ rabbit_event:notify(vhost_limits_set, [{name, <<"limits">>} | Limits]),
+ update_vhost(VHost, Limits).
+
+notify_clear(VHost, <<"vhost-limits">>, <<"limits">>) ->
+ rabbit_event:notify(vhost_limits_cleared, [{name, <<"limits">>}]),
+ update_vhost(VHost, undefined).
+
+connection_limit(VirtualHost) ->
+ get_limit(VirtualHost, <<"max-connections">>).
+
+%%----------------------------------------------------------------------------
+
+parse_set(VHost, Defn) ->
+ case rabbit_misc:json_decode(Defn) of
+ {ok, JSON} ->
+ set(VHost, rabbit_misc:json_to_term(JSON));
+ error ->
+ {error_string, "JSON decoding error"}
+ end.
+
+set(VHost, Defn) ->
+ rabbit_runtime_parameters:set_any(VHost, <<"vhost-limits">>,
+ <<"limits">>, Defn, none).
+
+clear(VHost) ->
+ rabbit_runtime_parameters:clear_any(VHost, <<"vhost-limits">>,
+ <<"limits">>).
+
+vhost_limit_validation() ->
+ [{<<"max-connections">>, fun rabbit_parameter_validation:integer/2, mandatory}].
+
+update_vhost(VHostName, Limits) ->
+ rabbit_misc:execute_mnesia_transaction(
+ fun() ->
+ rabbit_vhost:update(VHostName,
+ fun(VHost) ->
+ rabbit_vhost:set_limits(VHost, Limits)
+ end)
+ end),
+ ok.
+
+get_limit(VirtualHost, Limit) ->
+ case rabbit_runtime_parameters:list(VirtualHost, <<"vhost-limits">>) of
+ [] -> undefined;
+ [Param] -> case pget(value, Param) of
+ undefined -> undefined;
+ Val -> case pget(Limit, Val) of
+ undefined -> undefined;
+ %% no limit
+ N when N < 0 -> undefined;
+ N when N >= 0 -> {ok, N}
+ end
+ end
+ end.
diff --git a/test/channel_interceptor_SUITE.erl b/test/channel_interceptor_SUITE.erl
new file mode 100644
index 0000000000..0e4948ea3c
--- /dev/null
+++ b/test/channel_interceptor_SUITE.erl
@@ -0,0 +1,113 @@
+%% 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 Developer of the Original Code is GoPivotal, Inc.
+%% Copyright (c) 2016 Pivotal Software, Inc. All rights reserved.
+%%
+
+-module(channel_interceptor_SUITE).
+
+-include_lib("common_test/include/ct.hrl").
+-include_lib("amqp_client/include/amqp_client.hrl").
+
+-compile(export_all).
+
+all() ->
+ [
+ {group, non_parallel_tests}
+ ].
+
+groups() ->
+ [
+ {non_parallel_tests, [], [
+ register_interceptor
+ ]}
+ ].
+
+%% -------------------------------------------------------------------
+%% Testsuite setup/teardown.
+%% -------------------------------------------------------------------
+
+init_per_suite(Config) ->
+ rabbit_ct_helpers:log_environment(),
+ rabbit_ct_helpers:run_setup_steps(Config).
+
+end_per_suite(Config) ->
+ rabbit_ct_helpers:run_teardown_steps(Config).
+
+init_per_group(_, Config) ->
+ Config.
+
+end_per_group(_, Config) ->
+ Config.
+
+init_per_testcase(Testcase, Config) ->
+ rabbit_ct_helpers:testcase_started(Config, Testcase),
+ Config1 = rabbit_ct_helpers:set_config(Config, [
+ {rmq_nodename_suffix, Testcase}
+ ]),
+ rabbit_ct_helpers:run_steps(Config1,
+ rabbit_ct_broker_helpers:setup_steps() ++
+ rabbit_ct_client_helpers:setup_steps()).
+
+end_per_testcase(Testcase, Config) ->
+ Config1 = rabbit_ct_helpers:run_steps(Config,
+ rabbit_ct_client_helpers:teardown_steps() ++
+ rabbit_ct_broker_helpers:teardown_steps()),
+ rabbit_ct_helpers:testcase_finished(Config1, Testcase).
+
+%% -------------------------------------------------------------------
+%% Testcases.
+%% -------------------------------------------------------------------
+
+register_interceptor(Config) ->
+ passed = rabbit_ct_broker_helpers:rpc(Config, 0,
+ ?MODULE, register_interceptor1, [Config]).
+
+register_interceptor1(Config) ->
+ PredefinedChannels = rabbit_channel:list(),
+
+ Ch1 = rabbit_ct_client_helpers:open_channel(Config, 0),
+
+ QName = <<"register_interceptor-q">>,
+ amqp_channel:call(Ch1, #'queue.declare'{queue = QName}),
+
+ [ChannelProc] = rabbit_channel:list() -- PredefinedChannels,
+
+ [{interceptors, []}] = rabbit_channel:info(ChannelProc, [interceptors]),
+
+ check_send_receive(Ch1, QName, <<"bar">>, <<"bar">>),
+
+ ok = rabbit_registry:register(channel_interceptor,
+ <<"dummy interceptor">>,
+ dummy_interceptor),
+ [{interceptors, [{dummy_interceptor, undefined}]}] =
+ rabbit_channel:info(ChannelProc, [interceptors]),
+
+ check_send_receive(Ch1, QName, <<"bar">>, <<"">>),
+
+ ok = rabbit_registry:unregister(channel_interceptor,
+ <<"dummy interceptor">>),
+ [{interceptors, []}] = rabbit_channel:info(ChannelProc, [interceptors]),
+
+ check_send_receive(Ch1, QName, <<"bar">>, <<"bar">>),
+ passed.
+
+
+check_send_receive(Ch1, QName, Send, Receive) ->
+ amqp_channel:call(Ch1,
+ #'basic.publish'{routing_key = QName},
+ #amqp_msg{payload = Send}),
+
+ {#'basic.get_ok'{}, #amqp_msg{payload = Receive}} =
+ amqp_channel:call(Ch1, #'basic.get'{queue = QName,
+ no_ack = true}).
diff --git a/test/channel_operation_timeout_test_queue.erl b/test/channel_operation_timeout_test_queue.erl
index 0bb3f5a1c0..4407a24e7f 100644
--- a/test/channel_operation_timeout_test_queue.erl
+++ b/test/channel_operation_timeout_test_queue.erl
@@ -174,10 +174,10 @@
out_counter :: non_neg_integer(),
in_counter :: non_neg_integer(),
rates :: rates(),
- msgs_on_disk :: ?GB_SET_TYPE(),
- msg_indices_on_disk :: ?GB_SET_TYPE(),
- unconfirmed :: ?GB_SET_TYPE(),
- confirmed :: ?GB_SET_TYPE(),
+ msgs_on_disk :: gb_sets:set(),
+ msg_indices_on_disk :: gb_sets:set(),
+ unconfirmed :: gb_sets:set(),
+ confirmed :: gb_sets:set(),
ack_out_counter :: non_neg_integer(),
ack_in_counter :: non_neg_integer(),
disk_read_count :: non_neg_integer(),
@@ -559,7 +559,7 @@ update_rates(State = #vqstate{ in_counter = InCount,
ack_in = AckInRate,
ack_out = AckOutRate,
timestamp = TS }}) ->
- Now = time_compat:monotonic_time(),
+ Now = erlang:monotonic_time(),
Rates = #rates { in = update_rate(Now, TS, InCount, InRate),
out = update_rate(Now, TS, OutCount, OutRate),
@@ -574,7 +574,7 @@ update_rates(State = #vqstate{ in_counter = InCount,
rates = Rates }.
update_rate(Now, TS, Count, Rate) ->
- Time = time_compat:convert_time_unit(Now - TS, native, micro_seconds) /
+ Time = erlang:convert_time_unit(Now - TS, native, micro_seconds) /
?MICROS_PER_SECOND,
if
Time == 0 -> Rate;
@@ -1072,7 +1072,7 @@ init(IsDurable, IndexState, DeltaCount, DeltaBytes, Terms,
count = DeltaCount1,
end_seq_id = NextSeqId })
end,
- Now = time_compat:monotonic_time(),
+ Now = erlang:monotonic_time(),
IoBatchSize = rabbit_misc:get_env(rabbit, msg_store_io_batch_size,
?IO_BATCH_SIZE),
diff --git a/test/cluster_rename_SUITE.erl b/test/cluster_rename_SUITE.erl
index 8ce29a6695..9521e04eb5 100644
--- a/test/cluster_rename_SUITE.erl
+++ b/test/cluster_rename_SUITE.erl
@@ -43,6 +43,12 @@ groups() ->
]}
].
+suite() ->
+ [
+ %% If a test hangs, no need to wait for 30 minutes.
+ {timetrap, {minutes, 8}}
+ ].
+
%% -------------------------------------------------------------------
%% Testsuite setup/teardown.
%% -------------------------------------------------------------------
@@ -245,7 +251,7 @@ rename_node(Config, Nodename, Map) ->
Config1.
rename_node_fail(Config, Nodename, Map) ->
- error = do_rename_node(Config, Nodename, Map),
+ {error, _, _} = do_rename_node(Config, Nodename, Map),
ok.
do_rename_node(Config, Nodename, Map) ->
@@ -265,8 +271,8 @@ do_rename_node(Config, Nodename, Map) ->
{ok, _} ->
Config1 = update_config_after_rename(Config, Map1),
{ok, Config1};
- {error, _, _} ->
- error
+ {error, _, _} = Error ->
+ Error
end.
update_config_after_rename(Config, [Old, New | Rest]) ->
diff --git a/test/config_schema_SUITE.erl b/test/config_schema_SUITE.erl
new file mode 100644
index 0000000000..79e7220e98
--- /dev/null
+++ b/test/config_schema_SUITE.erl
@@ -0,0 +1,143 @@
+%% 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 Developer of the Original Code is GoPivotal, Inc.
+%% Copyright (c) 2016 Pivotal Software, Inc. All rights reserved.
+%%
+
+-module(config_schema_SUITE).
+
+-include_lib("common_test/include/ct.hrl").
+-include_lib("amqp_client/include/amqp_client.hrl").
+
+-compile(export_all).
+
+all() ->
+ [
+ {group, non_parallel_tests}
+ ].
+
+groups() ->
+ [
+ {non_parallel_tests, [], [
+ run_snippets
+ ]}
+ ].
+
+%% -------------------------------------------------------------------
+%% Testsuite setup/teardown.
+%% -------------------------------------------------------------------
+
+init_per_suite(Config) ->
+ rabbit_ct_helpers:log_environment(),
+ rabbit_ct_helpers:run_setup_steps(Config).
+
+end_per_suite(Config) ->
+ rabbit_ct_helpers:run_teardown_steps(Config).
+
+init_per_group(_, Config) ->
+ Config.
+
+end_per_group(_, Config) ->
+ Config.
+
+init_per_testcase(Testcase, Config) ->
+ rabbit_ct_helpers:testcase_started(Config, Testcase),
+ Config1 = rabbit_ct_helpers:set_config(Config, [
+ {rmq_nodename_suffix, Testcase}
+ ]),
+ Config2 = case Testcase of
+ run_snippets ->
+ SchemaDir = filename:join(?config(data_dir, Config1), "schema"),
+ ResultsDir = filename:join(?config(priv_dir, Config1), "results"),
+ Snippets = filename:join(?config(data_dir, Config1),
+ "snippets.config"),
+ ok = file:make_dir(ResultsDir),
+ rabbit_ct_helpers:set_config(Config1, [
+ {schema_dir, SchemaDir},
+ {results_dir, ResultsDir},
+ {conf_snippets, Snippets}
+ ])
+ end,
+ rabbit_ct_helpers:run_steps(Config2,
+ rabbit_ct_broker_helpers:setup_steps() ++
+ rabbit_ct_client_helpers:setup_steps()).
+
+end_per_testcase(Testcase, Config) ->
+ Config1 = rabbit_ct_helpers:run_steps(Config,
+ rabbit_ct_client_helpers:teardown_steps() ++
+ rabbit_ct_broker_helpers:teardown_steps()),
+ rabbit_ct_helpers:testcase_finished(Config1, Testcase).
+
+%% -------------------------------------------------------------------
+%% Testcases.
+%% -------------------------------------------------------------------
+
+run_snippets(Config) ->
+ passed = rabbit_ct_broker_helpers:rpc(Config, 0,
+ ?MODULE, run_snippets1, [Config]).
+
+run_snippets1(Config) ->
+ {ok, [Snippets]} = file:consult(?config(conf_snippets, Config)),
+ lists:map(
+ fun({N, S, C, P}) -> ok = test_snippet(Config, {integer_to_list(N), S, []}, C, P);
+ ({N, S, A, C, P}) -> ok = test_snippet(Config, {integer_to_list(N), S, A}, C, P)
+ end,
+ Snippets),
+ passed.
+
+test_snippet(Config, Snippet, Expected, _Plugins) ->
+ {ConfFile, AdvancedFile} = write_snippet(Config, Snippet),
+ {ok, GeneratedFile} = generate_config(Config, ConfFile, AdvancedFile),
+ {ok, [Generated]} = file:consult(GeneratedFile),
+ Gen = deepsort(Generated),
+ Exp = deepsort(Expected),
+ case Exp of
+ Gen -> ok;
+ _ ->
+ error({config_mismatch, Snippet, Exp, Gen})
+ end.
+
+write_snippet(Config, {Name, Conf, Advanced}) ->
+ ResultsDir = ?config(results_dir, Config),
+ file:make_dir(filename:join(ResultsDir, Name)),
+ ConfFile = filename:join([ResultsDir, Name, "config.conf"]),
+ AdvancedFile = filename:join([ResultsDir, Name, "advanced.config"]),
+
+ file:write_file(ConfFile, Conf),
+ rabbit_file:write_term_file(AdvancedFile, [Advanced]),
+ {ConfFile, AdvancedFile}.
+
+generate_config(Config, ConfFile, AdvancedFile) ->
+ SchemaDir = ?config(schema_dir, Config),
+ ResultsDir = ?config(results_dir, Config),
+ Rabbitmqctl = ?config(rabbitmqctl_cmd, Config),
+ ScriptDir = filename:dirname(Rabbitmqctl),
+ ct:pal("ConfFile=~p ScriptDir=~p SchemaDir=~p AdvancedFile=~p", [ConfFile, ScriptDir, SchemaDir, AdvancedFile]),
+ rabbit_config:generate_config_file([ConfFile], ResultsDir, ScriptDir,
+ SchemaDir, AdvancedFile).
+
+deepsort(List) ->
+ case is_proplist(List) of
+ true ->
+ lists:keysort(1, lists:map(fun({K, V}) -> {K, deepsort(V)};
+ (V) -> V end,
+ List));
+ false ->
+ case is_list(List) of
+ true -> lists:sort(List);
+ false -> List
+ end
+ end.
+
+is_proplist([{_Key, _Val}|_] = List) -> lists:all(fun({_K, _V}) -> true; (_) -> false end, List);
+is_proplist(_) -> false.
diff --git a/test/config_schema_SUITE_data/certs/cacert.pem b/test/config_schema_SUITE_data/certs/cacert.pem
new file mode 100644
index 0000000000..eaf6b67806
--- /dev/null
+++ b/test/config_schema_SUITE_data/certs/cacert.pem
@@ -0,0 +1 @@
+I'm not a certificate
diff --git a/test/config_schema_SUITE_data/certs/cert.pem b/test/config_schema_SUITE_data/certs/cert.pem
new file mode 100644
index 0000000000..eaf6b67806
--- /dev/null
+++ b/test/config_schema_SUITE_data/certs/cert.pem
@@ -0,0 +1 @@
+I'm not a certificate
diff --git a/test/config_schema_SUITE_data/certs/key.pem b/test/config_schema_SUITE_data/certs/key.pem
new file mode 100644
index 0000000000..eaf6b67806
--- /dev/null
+++ b/test/config_schema_SUITE_data/certs/key.pem
@@ -0,0 +1 @@
+I'm not a certificate
diff --git a/test/config_schema_SUITE_data/rabbit-mgmt/access.log b/test/config_schema_SUITE_data/rabbit-mgmt/access.log
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/test/config_schema_SUITE_data/rabbit-mgmt/access.log
diff --git a/test/config_schema_SUITE_data/schema/rabbitmq.schema b/test/config_schema_SUITE_data/schema/rabbitmq.schema
new file mode 100644
index 0000000000..bf9cccbcb8
--- /dev/null
+++ b/test/config_schema_SUITE_data/schema/rabbitmq.schema
@@ -0,0 +1,980 @@
+% ==============================
+% Rabbit app section
+% ==============================
+
+%%
+%% Network Connectivity
+%% ====================
+%%
+
+%% By default, RabbitMQ will listen on all interfaces, using
+%% the standard (reserved) AMQP port.
+%%
+%% {tcp_listeners, [5672]},
+%% To listen on a specific interface, provide a tuple of {IpAddress, Port}.
+%% For example, to listen only on localhost for both IPv4 and IPv6:
+%%
+%% {tcp_listeners, [{"127.0.0.1", 5672},
+%% {"[::1]", 5672}]},
+
+{mapping, "listeners.tcp", "rabbit.tcp_listeners",[
+ {datatype, {enum, [none]}}
+]}.
+
+{mapping, "listeners.tcp.$name", "rabbit.tcp_listeners",[
+ {datatype, [integer, ip]}
+]}.
+
+{translation, "rabbit.tcp_listeners",
+fun(Conf) ->
+ case cuttlefish:conf_get("listeners.tcp", Conf, undefined) of
+ none -> [];
+ _ ->
+ Settings = cuttlefish_variable:filter_by_prefix("listeners.tcp", Conf),
+ [ V || {_, V} <- Settings ]
+ end
+end}.
+
+%% SSL listeners are configured in the same fashion as TCP listeners,
+%% including the option to control the choice of interface.
+%%
+%% {ssl_listeners, [5671]},
+
+{mapping, "listeners.ssl", "rabbit.ssl_listeners",[
+ {datatype, {enum, [none]}}
+]}.
+
+{mapping, "listeners.ssl.$name", "rabbit.ssl_listeners",[
+ {datatype, [integer, ip]}
+]}.
+
+{translation, "rabbit.ssl_listeners",
+fun(Conf) ->
+ case cuttlefish:conf_get("listeners.ssl", Conf, undefined) of
+ none -> [];
+ _ ->
+ Settings = cuttlefish_variable:filter_by_prefix("listeners.ssl", Conf),
+ [ V || {_, V} <- Settings ]
+ end
+end}.
+
+%% Number of Erlang processes that will accept connections for the TCP
+%% and SSL listeners.
+%%
+%% {num_tcp_acceptors, 10},
+%% {num_ssl_acceptors, 1},
+
+{mapping, "num_acceptors.ssl", "rabbit.num_ssl_acceptors", [
+ {datatype, integer}
+]}.
+
+{mapping, "num_acceptors.tcp", "rabbit.num_tcp_acceptors", [
+ {datatype, integer}
+]}.
+
+
+%% Maximum time for AMQP 0-8/0-9/0-9-1 handshake (after socket connection
+%% and SSL handshake), in milliseconds.
+%%
+%% {handshake_timeout, 10000},
+
+{mapping, "handshake_timeout", "rabbit.handshake_timeout", [
+ {datatype, integer}
+]}.
+
+%% Set to 'true' to perform reverse DNS lookups when accepting a
+%% connection. Hostnames will then be shown instead of IP addresses
+%% in rabbitmqctl and the management plugin.
+%%
+%% {reverse_dns_lookups, true},
+
+{mapping, "reverse_dns_lookups", "rabbit.reverse_dns_lookups", [
+ {datatype, {enum, [true, false]}}
+]}.
+
+{mapping, "erlang.K", "vm_args.+K", [
+ {default, "true"},
+ {level, advanced}
+]}.
+
+%%
+%% Security / AAA
+%% ==============
+%%
+
+%% The default "guest" user is only permitted to access the server
+%% via a loopback interface (e.g. localhost).
+%% {loopback_users, [<<"guest">>]},
+%%
+%% Uncomment the following line if you want to allow access to the
+%% guest user from anywhere on the network.
+%% {loopback_users, []},
+
+{mapping, "loopback_users", "rabbit.loopback_users", [
+ {datatype, {enum, [none]}}
+]}.
+
+{mapping, "loopback_users.$user", "rabbit.loopback_users", [
+ {datatype, atom}
+]}.
+
+{translation, "rabbit.loopback_users",
+fun(Conf) ->
+ None = cuttlefish:conf_get("loopback_users", Conf, undefined),
+ case None of
+ none -> [];
+ _ ->
+ Settings = cuttlefish_variable:filter_by_prefix("loopback_users", Conf),
+ [ list_to_binary(U) || {["loopback_users", U], V} <- Settings, V == true ]
+ end
+end}.
+
+%% Configuring SSL.
+%% See http://www.rabbitmq.com/ssl.html for full documentation.
+%%
+%% {ssl_options, [{cacertfile, "/path/to/testca/cacert.pem"},
+%% {certfile, "/path/to/server/cert.pem"},
+%% {keyfile, "/path/to/server/key.pem"},
+%% {verify, verify_peer},
+%% {fail_if_no_peer_cert, false}]},
+
+%% SSL options section ========================================================
+
+{mapping, "ssl_allow_poodle_attack", "rabbit.ssl_allow_poodle_attack",
+[{datatype, {enum, [true, false]}}]}.
+
+{mapping, "ssl_options", "rabbit.ssl_options", [
+ {datatype, {enum, [none]}}
+]}.
+
+{translation, "rabbit.ssl_options",
+fun(Conf) ->
+ case cuttlefish:conf_get("ssl_options", Conf, undefined) of
+ none -> [];
+ _ -> cuttlefish:invalid("Invalid ssl_options")
+ end
+end}.
+
+{mapping, "ssl_options.verify", "rabbit.ssl_options.verify", [
+ {datatype, {enum, [verify_peer, verify_none]}}]}.
+
+{mapping, "ssl_options.fail_if_no_peer_cert", "rabbit.ssl_options.fail_if_no_peer_cert", [
+ {datatype, {enum, [true, false]}}]}.
+
+{mapping, "ssl_options.cacertfile", "rabbit.ssl_options.cacertfile",
+ [{datatype, string}, {validators, ["file_accessible"]}]}.
+
+{mapping, "ssl_options.certfile", "rabbit.ssl_options.certfile",
+ [{datatype, string}, {validators, ["file_accessible"]}]}.
+
+{mapping, "ssl_options.cacerts.$name", "rabbit.ssl_options.cacerts",
+ [{datatype, string}]}.
+
+{translation, "rabbit.ssl_options.cacerts",
+fun(Conf) ->
+ Settings = cuttlefish_variable:filter_by_prefix("ssl_options.cacerts", Conf),
+ [ list_to_binary(V) || {_, V} <- Settings ]
+end}.
+
+{mapping, "ssl_options.cert", "rabbit.ssl_options.cert",
+ [{datatype, string}]}.
+
+{translation, "rabbit.ssl_options.cert",
+fun(Conf) ->
+ list_to_binary(cuttlefish:conf_get("ssl_options.cert", Conf))
+end}.
+
+{mapping, "ssl_options.client_renegotiation", "rabbit.ssl_options.client_renegotiation",
+ [{datatype, {enum, [true, false]}}]}.
+
+{mapping, "ssl_options.crl_check", "rabbit.ssl_options.crl_check",
+ [{datatype, [{enum, [true, false, peer, best_effort]}]}]}.
+
+{mapping, "ssl_options.depth", "rabbit.ssl_options.depth",
+ [{datatype, integer}, {validators, ["byte"]}]}.
+
+{mapping, "ssl_options.dh", "rabbit.ssl_options.dh",
+ [{datatype, string}]}.
+
+{translation, "rabbit.ssl_options.dh",
+fun(Conf) ->
+ list_to_binary(cuttlefish:conf_get("ssl_options.dh", Conf))
+end}.
+
+{mapping, "ssl_options.dhfile", "rabbit.ssl_options.dhfile",
+ [{datatype, string}, {validators, ["file_accessible"]}]}.
+
+{mapping, "ssl_options.honor_cipher_order", "rabbit.ssl_options.honor_cipher_order",
+ [{datatype, {enum, [true, false]}}]}.
+
+{mapping, "ssl_options.key.RSAPrivateKey", "rabbit.ssl_options.key",
+ [{datatype, string}]}.
+
+{mapping, "ssl_options.key.DSAPrivateKey", "rabbit.ssl_options.key",
+ [{datatype, string}]}.
+
+{mapping, "ssl_options.key.PrivateKeyInfo", "rabbit.ssl_options.key",
+ [{datatype, string}]}.
+
+{translation, "rabbit.ssl_options.key",
+fun(Conf) ->
+ case cuttlefish_variable:filter_by_prefix("ssl_options.key", Conf) of
+ [{[_,_,Key], Val}|_] -> {list_to_atom(Key), list_to_binary(Val)};
+ _ -> undefined
+ end
+end}.
+
+{mapping, "ssl_options.keyfile", "rabbit.ssl_options.keyfile",
+ [{datatype, string}, {validators, ["file_accessible"]}]}.
+
+{mapping, "ssl_options.log_alert", "rabbit.ssl_options.log_alert",
+ [{datatype, {enum, [true, false]}}]}.
+
+{mapping, "ssl_options.password", "rabbit.ssl_options.password",
+ [{datatype, string}]}.
+
+{mapping, "ssl_options.psk_identity", "rabbit.ssl_options.psk_identity",
+ [{datatype, string}]}.
+
+{mapping, "ssl_options.reuse_sessions", "rabbit.ssl_options.reuse_sessions",
+ [{datatype, {enum, [true, false]}}]}.
+
+{mapping, "ssl_options.secure_renegotiate", "rabbit.ssl_options.secure_renegotiate",
+ [{datatype, {enum, [true, false]}}]}.
+
+{mapping, "ssl_options.versions.$version", "rabbit.ssl_options.versions",
+ [{datatype, atom}]}.
+
+{translation, "rabbit.ssl_options.versions",
+fun(Conf) ->
+ Settings = cuttlefish_variable:filter_by_prefix("ssl_options.versions", Conf),
+ [ V || {_, V} <- Settings ]
+end}.
+
+%% ===========================================================================
+
+%% Choose the available SASL mechanism(s) to expose.
+%% The two default (built in) mechanisms are 'PLAIN' and
+%% 'AMQPLAIN'. Additional mechanisms can be added via
+%% plugins.
+%%
+%% See http://www.rabbitmq.com/authentication.html for more details.
+%%
+%% {auth_mechanisms, ['PLAIN', 'AMQPLAIN']},
+
+{mapping, "auth_mechanisms.$name", "rabbit.auth_mechanisms", [
+ {datatype, atom}]}.
+
+{translation, "rabbit.auth_mechanisms",
+fun(Conf) ->
+ Settings = cuttlefish_variable:filter_by_prefix("auth_mechanisms", Conf),
+ [ V || {_, V} <- Settings ]
+end}.
+
+
+%% Select an authentication backend to use. RabbitMQ provides an
+%% internal backend in the core.
+%%
+%% {auth_backends, [rabbit_auth_backend_internal]},
+
+{translation, "rabbit.auth_backends",
+fun(Conf) ->
+ Settings = cuttlefish_variable:filter_by_prefix("auth_backends", Conf),
+ BackendModule = fun
+ (internal) -> rabbit_auth_backend_internal;
+ (ldap) -> rabbit_auth_backend_ldap;
+ (http) -> rabbit_auth_backend_http;
+ (amqp) -> rabbit_auth_backend_amqp;
+ (dummy) -> rabbit_auth_backend_dummy;
+ (Other) when is_atom(Other) -> Other;
+ (_) -> cuttlefish:invalid("Unknown/unsupported auth backend")
+ end,
+ AuthBackends = [{Num, {default, BackendModule(V)}} || {["auth_backends", Num], V} <- Settings],
+ AuthNBackends = [{Num, {authn, BackendModule(V)}} || {["auth_backends", Num, "authn"], V} <- Settings],
+ AuthZBackends = [{Num, {authz, BackendModule(V)}} || {["auth_backends", Num, "authz"], V} <- Settings],
+ Backends = lists:foldl(
+ fun({NumStr, {Type, V}}, Acc) ->
+ Num = case catch list_to_integer(NumStr) of
+ N when is_integer(N) -> N;
+ Err ->
+ cuttlefish:invalid(
+ iolist_to_binary(io_lib:format(
+ "Auth backend position in the chain should be an integer ~p", [Err])))
+ end,
+ NewVal = case dict:find(Num, Acc) of
+ {ok, {AuthN, AuthZ}} ->
+ case {Type, AuthN, AuthZ} of
+ {authn, undefined, _} ->
+ {V, AuthZ};
+ {authz, _, undefined} ->
+ {AuthN, V};
+ _ ->
+ cuttlefish:invalid(
+ iolist_to_binary(
+ io_lib:format(
+ "Auth backend already defined for the ~pth ~p backend",
+ [Num, Type])))
+ end;
+ error ->
+ case Type of
+ authn -> {V, undefined};
+ authz -> {undefined, V};
+ default -> {V, V}
+ end
+ end,
+ dict:store(Num, NewVal, Acc)
+ end,
+ dict:new(),
+ AuthBackends ++ AuthNBackends ++ AuthZBackends),
+ lists:map(
+ fun
+ ({Num, {undefined, AuthZ}}) ->
+ cuttlefish:warn(
+ io_lib:format(
+ "Auth backend undefined for the ~pth authz backend. Using ~p",
+ [Num, AuthZ])),
+ {AuthZ, AuthZ};
+ ({Num, {AuthN, undefined}}) ->
+ cuttlefish:warn(
+ io_lib:format(
+ "Authz backend undefined for the ~pth authn backend. Using ~p",
+ [Num, AuthN])),
+ {AuthN, AuthN};
+ ({_Num, {Auth, Auth}}) -> Auth;
+ ({_Num, {AuthN, AuthZ}}) -> {AuthN, AuthZ}
+ end,
+ lists:keysort(1, dict:to_list(Backends)))
+end}.
+
+{mapping, "auth_backends.$num", "rabbit.auth_backends", [
+ {datatype, atom}
+]}.
+
+{mapping, "auth_backends.$num.authn", "rabbit.auth_backends",[
+ {datatype, atom}
+]}.
+
+{mapping, "auth_backends.$num.authz", "rabbit.auth_backends",[
+ {datatype, atom}
+]}.
+
+%% This pertains to both the rabbitmq_auth_mechanism_ssl plugin and
+%% STOMP ssl_cert_login configurations. See the rabbitmq_stomp
+%% configuration section later in this file and the README in
+%% https://github.com/rabbitmq/rabbitmq-auth-mechanism-ssl for further
+%% details.
+%%
+%% To use the SSL cert's CN instead of its DN as the username
+%%
+%% {ssl_cert_login_from, common_name},
+
+{mapping, "ssl_cert_login_from", "rabbit.ssl_cert_login_from", [
+ {datatype, {enum, [distinguished_name, common_name]}}
+]}.
+
+%% SSL handshake timeout, in milliseconds.
+%%
+%% {ssl_handshake_timeout, 5000},
+
+{mapping, "ssl_handshake_timeout", "rabbit.ssl_handshake_timeout", [
+ {datatype, integer}
+]}.
+
+%% Password hashing implementation. Will only affect newly
+%% created users. To recalculate hash for an existing user
+%% it's necessary to update her password.
+%%
+%% When importing definitions exported from versions earlier
+%% than 3.6.0, it is possible to go back to MD5 (only do this
+%% as a temporary measure!) by setting this to rabbit_password_hashing_md5.
+%%
+%% To use SHA-512, set to rabbit_password_hashing_sha512.
+%%
+%% {password_hashing_module, rabbit_password_hashing_sha256},
+
+{mapping, "password_hashing_module", "rabbit.password_hashing_module", [
+ {datatype, atom}
+]}.
+
+%%
+%% Default User / VHost
+%% ====================
+%%
+
+%% On first start RabbitMQ will create a vhost and a user. These
+%% config items control what gets created. See
+%% http://www.rabbitmq.com/access-control.html for further
+%% information about vhosts and access control.
+%%
+%% {default_vhost, <<"/">>},
+%% {default_user, <<"guest">>},
+%% {default_pass, <<"guest">>},
+%% {default_permissions, [<<".*">>, <<".*">>, <<".*">>]},
+
+{mapping, "default_vhost", "rabbit.default_vhost", [
+ {datatype, string}
+]}.
+
+{translation, "rabbit.default_vhost",
+fun(Conf) ->
+ list_to_binary(cuttlefish:conf_get("default_vhost", Conf))
+end}.
+
+{mapping, "default_user", "rabbit.default_user", [
+ {datatype, string}
+]}.
+
+{translation, "rabbit.default_user",
+fun(Conf) ->
+ list_to_binary(cuttlefish:conf_get("default_user", Conf))
+end}.
+
+{mapping, "default_pass", "rabbit.default_pass", [
+ {datatype, string}
+]}.
+
+{translation, "rabbit.default_pass",
+fun(Conf) ->
+ list_to_binary(cuttlefish:conf_get("default_pass", Conf))
+end}.
+
+{mapping, "default_permissions.configure", "rabbit.default_permissions", [
+ {datatype, string}
+]}.
+
+{mapping, "default_permissions.read", "rabbit.default_permissions", [
+ {datatype, string}
+]}.
+
+{mapping, "default_permissions.write", "rabbit.default_permissions", [
+ {datatype, string}
+]}.
+
+{translation, "rabbit.default_permissions",
+fun(Conf) ->
+ Settings = cuttlefish_variable:filter_by_prefix("default_permissions", Conf),
+ Configure = proplists:get_value(["default_permissions", "configure"], Settings),
+ Read = proplists:get_value(["default_permissions", "read"], Settings),
+ Write = proplists:get_value(["default_permissions", "write"], Settings),
+ [list_to_binary(Configure), list_to_binary(Read), list_to_binary(Write)]
+end}.
+
+%% Tags for default user
+%%
+%% For more details about tags, see the documentation for the
+%% Management Plugin at http://www.rabbitmq.com/management.html.
+%%
+%% {default_user_tags, [administrator]},
+
+{mapping, "default_user_tags.$tag", "rabbit.default_user_tags",
+ [{datatype, {enum, [true, false]}}]}.
+
+{translation, "rabbit.default_user_tags",
+fun(Conf) ->
+ Settings = cuttlefish_variable:filter_by_prefix("default_user_tags", Conf),
+ [ list_to_atom(Key) || {[_,Key], Val} <- Settings, Val == true ]
+end}.
+
+%%
+%% Additional network and protocol related configuration
+%% =====================================================
+%%
+
+%% Set the default AMQP heartbeat delay (in seconds).
+%%
+%% {heartbeat, 600},
+
+{mapping, "heartbeat", "rabbit.heartbeat", [{datatype, integer}]}.
+
+%% Set the max permissible size of an AMQP frame (in bytes).
+%%
+%% {frame_max, 131072},
+
+{mapping, "frame_max", "rabbit.frame_max", [{datatype, bytesize}]}.
+
+%% Set the max frame size the server will accept before connection
+%% tuning occurs
+%%
+%% {initial_frame_max, 4096},
+
+{mapping, "initial_frame_max", "rabbit.initial_frame_max", [{datatype, bytesize}]}.
+
+%% Set the max permissible number of channels per connection.
+%% 0 means "no limit".
+%%
+%% {channel_max, 128},
+
+{mapping, "channel_max", "rabbit.channel_max", [{datatype, integer}]}.
+
+%% Customising Socket Options.
+%%
+%% See (http://www.erlang.org/doc/man/inet.html#setopts-2) for
+%% further documentation.
+%%
+%% {tcp_listen_options, [{backlog, 128},
+%% {nodelay, true},
+%% {exit_on_close, false}]},
+
+%% TCP listener section ======================================================
+
+{mapping, "tcp_listen_options", "rabbit.tcp_listen_options", [
+ {datatype, {enum, [none]}}]}.
+
+{translation, "rabbit.tcp_listen_options",
+fun(Conf) ->
+ case cuttlefish:conf_get("tcp_listen_options", Conf, undefined) of
+ none -> [];
+ _ -> cuttlefish:invalid("Invalid tcp_listen_options")
+ end
+end}.
+
+{mapping, "tcp_listen_options.backlog", "rabbit.tcp_listen_options.backlog", [
+ {datatype, integer}
+]}.
+
+{mapping, "tcp_listen_options.nodelay", "rabbit.tcp_listen_options.nodelay", [
+ {datatype, {enum, [true, false]}}
+]}.
+
+{mapping, "tcp_listen_options.buffer", "rabbit.tcp_listen_options.buffer",
+ [{datatype, integer}]}.
+
+{mapping, "tcp_listen_options.delay_send", "rabbit.tcp_listen_options.delay_send",
+ [{datatype, {enum, [true, false]}}]}.
+
+{mapping, "tcp_listen_options.dontroute", "rabbit.tcp_listen_options.dontroute",
+ [{datatype, {enum, [true, false]}}]}.
+
+{mapping, "tcp_listen_options.exit_on_close", "rabbit.tcp_listen_options.exit_on_close",
+ [{datatype, {enum, [true, false]}}]}.
+
+{mapping, "tcp_listen_options.fd", "rabbit.tcp_listen_options.fd",
+ [{datatype, integer}]}.
+
+{mapping, "tcp_listen_options.high_msgq_watermark", "rabbit.tcp_listen_options.high_msgq_watermark",
+ [{datatype, integer}]}.
+
+{mapping, "tcp_listen_options.high_watermark", "rabbit.tcp_listen_options.high_watermark",
+ [{datatype, integer}]}.
+
+{mapping, "tcp_listen_options.keepalive", "rabbit.tcp_listen_options.keepalive",
+ [{datatype, {enum, [true, false]}}]}.
+
+{mapping, "tcp_listen_options.low_msgq_watermark", "rabbit.tcp_listen_options.low_msgq_watermark",
+ [{datatype, integer}]}.
+
+{mapping, "tcp_listen_options.low_watermark", "rabbit.tcp_listen_options.low_watermark",
+ [{datatype, integer}]}.
+
+{mapping, "tcp_listen_options.port", "rabbit.tcp_listen_options.port",
+ [{datatype, integer}, {validators, ["port"]}]}.
+
+{mapping, "tcp_listen_options.priority", "rabbit.tcp_listen_options.priority",
+ [{datatype, integer}]}.
+
+{mapping, "tcp_listen_options.recbuf", "rabbit.tcp_listen_options.recbuf",
+ [{datatype, integer}]}.
+
+{mapping, "tcp_listen_options.send_timeout", "rabbit.tcp_listen_options.send_timeout",
+ [{datatype, integer}]}.
+
+{mapping, "tcp_listen_options.send_timeout_close", "rabbit.tcp_listen_options.send_timeout_close",
+ [{datatype, {enum, [true, false]}}]}.
+
+{mapping, "tcp_listen_options.sndbuf", "rabbit.tcp_listen_options.sndbuf",
+ [{datatype, integer}]}.
+
+{mapping, "tcp_listen_options.tos", "rabbit.tcp_listen_options.tos",
+ [{datatype, integer}]}.
+
+{mapping, "tcp_listen_options.linger.on", "rabbit.tcp_listen_options.linger",
+ [{datatype, {enum, [true, false]}}]}.
+
+{mapping, "tcp_listen_options.linger.timeout", "rabbit.tcp_listen_options.linger",
+ [{datatype, integer}, {validators, ["non_negative_integer"]}]}.
+
+{translation, "rabbit.tcp_listen_options.linger",
+fun(Conf) ->
+ LingerOn = cuttlefish:conf_get("tcp_listen_options.linger.on", Conf, false),
+ LingerTimeout = cuttlefish:conf_get("tcp_listen_options.linger.timeout", Conf, 0),
+ {LingerOn, LingerTimeout}
+end}.
+
+
+%% ==========================================================================
+
+%%
+%% Resource Limits & Flow Control
+%% ==============================
+%%
+%% See http://www.rabbitmq.com/memory.html for full details.
+
+%% Memory-based Flow Control threshold.
+%%
+%% {vm_memory_high_watermark, 0.4},
+
+%% Alternatively, we can set a limit (in bytes) of RAM used by the node.
+%%
+%% {vm_memory_high_watermark, {absolute, 1073741824}},
+%%
+%% Or you can set absolute value using memory units (with RabbitMQ 3.6.0+).
+%%
+%% {vm_memory_high_watermark, {absolute, "1024M"}},
+%%
+%% Supported units suffixes:
+%%
+%% kb, KB: kibibytes (2^10 bytes)
+%% mb, MB: mebibytes (2^20)
+%% gb, GB: gibibytes (2^30)
+
+{mapping, "vm_memory_high_watermark.relative", "rabbit.vm_memory_high_watermark", [
+ {datatype, float}]}.
+
+{mapping, "vm_memory_high_watermark.absolute", "rabbit.vm_memory_high_watermark", [
+ {datatype, [integer, string]}]}.
+
+
+{translation, "rabbit.vm_memory_high_watermark",
+fun(Conf) ->
+ Settings = cuttlefish_variable:filter_by_prefix("vm_memory_high_watermark", Conf),
+ Absolute = proplists:get_value(["vm_memory_high_watermark", "absolute"], Settings),
+ Relative = proplists:get_value(["vm_memory_high_watermark", "relative"], Settings),
+ case {Absolute, Relative} of
+ {undefined, undefined} -> cuttlefish:invalid("No vm watermark defined");
+ {_, undefined} -> {absolute, Absolute};
+ _ -> Relative
+ end
+end}.
+
+%% Fraction of the high watermark limit at which queues start to
+%% page message out to disc in order to free up memory.
+%%
+%% Values greater than 0.9 can be dangerous and should be used carefully.
+%%
+%% {vm_memory_high_watermark_paging_ratio, 0.5},
+
+{mapping, "vm_memory_high_watermark_paging_ratio",
+ "rabbit.vm_memory_high_watermark_paging_ratio",
+ [{datatype, float}, {validators, ["less_than_1"]}]}.
+
+%% Interval (in milliseconds) at which we perform the check of the memory
+%% levels against the watermarks.
+%%
+%% {memory_monitor_interval, 2500},
+
+{mapping, "memory_monitor_interval", "rabbit.memory_monitor_interval",
+ [{datatype, integer}]}.
+
+%% Set disk free limit (in bytes). Once free disk space reaches this
+%% lower bound, a disk alarm will be set - see the documentation
+%% listed above for more details.
+%%
+%% {disk_free_limit, 50000000},
+%%
+%% Or you can set it using memory units (same as in vm_memory_high_watermark)
+%% with RabbitMQ 3.6.0+.
+%% {disk_free_limit, "50MB"},
+%% {disk_free_limit, "50000kB"},
+%% {disk_free_limit, "2GB"},
+
+%% Alternatively, we can set a limit relative to total available RAM.
+%%
+%% Values lower than 1.0 can be dangerous and should be used carefully.
+%% {disk_free_limit, {mem_relative, 2.0}},
+
+{mapping, "disk_free_limit.relative", "rabbit.disk_free_limit", [
+ {datatype, float}]}.
+
+{mapping, "disk_free_limit.absolute", "rabbit.disk_free_limit", [
+ {datatype, [integer, string]}]}.
+
+
+{translation, "rabbit.disk_free_limit",
+fun(Conf) ->
+ Settings = cuttlefish_variable:filter_by_prefix("disk_free_limit", Conf),
+ Absolute = proplists:get_value(["disk_free_limit", "absolute"], Settings),
+ Relative = proplists:get_value(["disk_free_limit", "relative"], Settings),
+ case {Absolute, Relative} of
+ {undefined, undefined} -> cuttlefish:invalid("No disk limit defined");
+ {_, undefined} -> Absolute;
+ _ -> {mem_relative, Relative}
+ end
+end}.
+
+%%
+%% Clustering
+%% =====================
+%%
+
+%% How to respond to cluster partitions.
+%% See http://www.rabbitmq.com/partitions.html for further details.
+%%
+%% {cluster_partition_handling, ignore},
+
+{mapping, "cluster_partition_handling", "rabbit.cluster_partition_handling",
+ [{datatype, {enum, [ignore, pause_minority, autoheal, pause_if_all_down]}}]}.
+
+{mapping, "cluster_partition_handling.pause_if_all_down.recover",
+ "rabbit.cluster_partition_handling",
+ [{datatype, {enum, [ignore, autoheal]}}]}.
+
+{mapping, "cluster_partition_handling.pause_if_all_down.nodes.$name",
+ "rabbit.cluster_partition_handling",
+ [{datatype, atom}]}.
+
+{translation, "rabbit.cluster_partition_handling",
+fun(Conf) ->
+ case cuttlefish:conf_get("cluster_partition_handling", Conf) of
+ pause_if_all_down ->
+ PauseIfAllDownNodes = cuttlefish_variable:filter_by_prefix(
+ "cluster_partition_handling.pause_if_all_down.nodes",
+ Conf),
+ case PauseIfAllDownNodes of
+ [] ->
+ cuttlefish:invalid("Nodes required for pause_if_all_down");
+ _ ->
+ Nodes = [ V || {K,V} <- PauseIfAllDownNodes ],
+ PauseIfAllDownRecover = cuttlefish:conf_get(
+ "cluster_partition_handling.pause_if_all_down.recover",
+ Conf),
+ case PauseIfAllDownRecover of
+ Recover when Recover == ignore; Recover == autoheal ->
+ {pause_if_all_down, Nodes, Recover};
+ Invalid ->
+ cuttlefish:invalid("Recover strategy required for pause_if_all_down")
+ end
+ end;
+ Other -> Other
+ end
+end}.
+
+%% Mirror sync batch size, in messages. Increasing this will speed
+%% up syncing but total batch size in bytes must not exceed 2 GiB.
+%% Available in RabbitMQ 3.6.0 or later.
+%%
+%% {mirroring_sync_batch_size, 4096},
+
+{mapping, "mirroring_sync_batch_size", "rabbit.mirroring_sync_batch_size",
+ [{datatype, bytesize}, {validators, ["size_less_than_2G"]}]}.
+
+%% Make clustering happen *automatically* at startup - only applied
+%% to nodes that have just been reset or started for the first time.
+%% See http://www.rabbitmq.com/clustering.html#auto-config for
+%% further details.
+%%
+%% {cluster_nodes, {['rabbit@my.host.com'], disc}},
+
+{mapping, "cluster_nodes.disc.$node", "rabbit.cluster_nodes",
+ [{datatype, atom}]}.
+
+{mapping, "cluster_nodes.ram.$node", "rabbit.cluster_nodes",
+ [{datatype, atom}]}.
+
+{translation, "rabbit.cluster_nodes",
+fun(Conf) ->
+ DiskNodes = [ V || {_, V} <- cuttlefish_variable:filter_by_prefix("cluster_nodes.disc", Conf)],
+ RamNodes = [ V || {_, V} <- cuttlefish_variable:filter_by_prefix("cluster_nodes.ram", Conf)],
+
+ case {DiskNodes, RamNodes} of
+ {_, []} -> {DiskNodes, disc};
+ {[], _} -> {RamNodes, ram}
+ end
+end}.
+
+
+%% Interval (in milliseconds) at which we send keepalive messages
+%% to other cluster members. Note that this is not the same thing
+%% as net_ticktime; missed keepalive messages will not cause nodes
+%% to be considered down.
+%%
+%% {cluster_keepalive_interval, 10000},
+
+{mapping, "cluster_keepalive_interval", "rabbit.cluster_keepalive_interval",
+ [{datatype, integer}]}.
+
+
+{mapping, "queue_master_locator", "rabbit.queue_master_locator",
+ [{datatype, string}]}.
+
+{translation, "rabbit.queue_master_locator",
+fun(Conf) ->
+ list_to_binary(cuttlefish:conf_get("queue_master_locator", Conf))
+end}.
+
+%%
+%% Statistics Collection
+%% =====================
+%%
+
+%% Set (internal) statistics collection granularity.
+%%
+%% {collect_statistics, none},
+
+{mapping, "collect_statistics", "rabbit.collect_statistics",
+ [{datatype, {enum, [none, coarse, fine]}}]}.
+
+%% Statistics collection interval (in milliseconds). Increasing
+%% this will reduce the load on management database.
+%%
+%% {collect_statistics_interval, 5000},
+
+{mapping, "collect_statistics_interval", "rabbit.collect_statistics_interval",
+ [{datatype, integer}]}.
+
+%%
+%% Misc/Advanced Options
+%% =====================
+%%
+%% NB: Change these only if you understand what you are doing!
+%%
+
+%% Explicitly enable/disable hipe compilation.
+%%
+%% {hipe_compile, true},
+
+{mapping, "hipe_compile", "rabbit.hipe_compile",
+ [{datatype, {enum, [true, false]}}]}.
+
+%% Timeout used when waiting for Mnesia tables in a cluster to
+%% become available.
+%%
+%% {mnesia_table_loading_timeout, 30000},
+
+{mapping, "mnesia_table_loading_timeout", "rabbit.mnesia_table_loading_timeout",
+ [{datatype, integer}]}.
+
+%% Size in bytes below which to embed messages in the queue index. See
+%% http://www.rabbitmq.com/persistence-conf.html
+%%
+%% {queue_index_embed_msgs_below, 4096}
+
+{mapping, "queue_index_embed_msgs_below", "rabbit.queue_index_embed_msgs_below",
+ [{datatype, bytesize}]}.
+
+% ==========================
+% Lager section
+% ==========================
+
+{mapping, "log.dir", "lager.log_root", [
+ {datatype, string},
+ {validators, ["dir_writable"]}]}.
+
+{mapping, "log.console", "lager.handlers", [
+ {datatype, {enum, [true, false]}}
+]}.
+
+{mapping, "log.syslog", "lager.handlers", [
+ {datatype, {enum, [true, false]}}
+]}.
+{mapping, "log.file", "lager.handlers", [
+ {datatype, [{enum, [false]}, string]}
+]}.
+
+{mapping, "log.file.level", "lager.handlers", [
+ {datatype, {enum, [debug, info, warning, error]}}
+]}.
+{mapping, "log.$handler.level", "lager.handlers", [
+ {datatype, {enum, [debug, info, warning, error]}}
+]}.
+{mapping, "log.file.rotation.date", "lager.handlers", [
+ {datatype, string}
+]}.
+{mapping, "log.file.rotation.size", "lager.handlers", [
+ {datatype, integer}
+]}.
+{mapping, "log.file.rotation.count", "lager.handlers", [
+ {datatype, integer}
+]}.
+
+{mapping, "log.syslog.identity", "lager.handlers", [
+ {datatype, string}
+]}.
+{mapping, "log.syslog.facility", "lager.handlers", [
+ {datatype, atom}
+]}.
+
+{translation, "lager.handlers",
+fun(Conf) ->
+ ConsoleHandler = case cuttlefish:conf_get("log.console", Conf, false) of
+ true ->
+ ConsoleLevel = cuttlefish:conf_get("log.console.level", Conf, info),
+ [{lager_console_backend, ConsoleLevel}];
+ false -> []
+ end,
+ FileHandler = case cuttlefish:conf_get("log.file", Conf, false) of
+ false -> [];
+ File ->
+ FileLevel = cuttlefish:conf_get("log.file.level", Conf, info),
+ RotationDate = cuttlefish:conf_get("log.file.rotation.date", Conf, ""),
+ RotationSize = cuttlefish:conf_get("log.file.rotation.size", Conf, 0),
+ RotationCount = cuttlefish:conf_get("log.file.rotation.count", Conf, 10),
+ [{lager_file_backend, [{file, File},
+ {level, FileLevel},
+ {date, RotationDate},
+ {size, RotationSize},
+ {count, RotationCount}]}]
+ end,
+ SyslogHandler = case cuttlefish:conf_get("log.syslog", Conf, false) of
+ false -> [];
+ true ->
+ SyslogLevel = cuttlefish:conf_get("log.syslog.level", Conf, info),
+ Identity = cuttlefish:conf_get("log.syslog.identity", Conf),
+ Facility = cuttlefish:conf_get("log.syslog.facility", Conf),
+ [{lager_syslog_backend, [Identity, Facility, SyslogLevel]}]
+ end,
+ case ConsoleHandler ++ FileHandler ++ SyslogHandler of
+ [] -> undefined;
+ Other -> Other
+ end
+end}.
+
+
+% ===============================
+% Validators
+% ===============================
+
+{validator, "size_less_than_2G", "Byte size should be less than 2G and greater than 0",
+fun(Size) when is_integer(Size) ->
+ Size > 0 andalso Size < 2147483648
+end}.
+
+{validator, "less_than_1", "Flooat is not beetween 0 and 1",
+fun(Float) when is_float(Float) ->
+ Float > 0 andalso Float < 1
+end}.
+
+{validator, "port", "Invalid port number",
+fun(Port) when is_integer(Port) ->
+ Port > 0 andalso Port < 65535
+end}.
+
+{validator, "byte", "Integer is not 0<i<255",
+fun(Int) when is_integer(Int) ->
+ Int > 0 andalso Int < 255
+end}.
+
+{validator, "dir_writable", "Cannot create file in dir",
+fun(Dir) ->
+ TestFile = filename:join(Dir, "test_file"),
+ file:delete(TestFile),
+ Res = ok == file:write_file(TestFile, <<"test">>),
+ file:delete(TestFile),
+ Res
+end}.
+
+{validator, "file_accessible", "file doesnt exist or unaccessible",
+fun(File) ->
+ ReadFile = file:read_file_info(File),
+ element(1, ReadFile) == ok
+end}.
+
+{validator, "is_ip", "string is a valid IP address",
+fun(IpStr) ->
+ Res = inet:parse_address(IpStr),
+ element(1, Res) == ok
+end}.
+
+{validator, "non_negative_integer", "number should be greater or equal to zero",
+fun(Int) when is_integer(Int) ->
+ Int >= 0
+end}.
diff --git a/test/config_schema_SUITE_data/schema/rabbitmq_amqp1_0.schema b/test/config_schema_SUITE_data/schema/rabbitmq_amqp1_0.schema
new file mode 100644
index 0000000000..e6cfb68262
--- /dev/null
+++ b/test/config_schema_SUITE_data/schema/rabbitmq_amqp1_0.schema
@@ -0,0 +1,31 @@
+%% ----------------------------------------------------------------------------
+%% RabbitMQ AMQP 1.0 Support
+%%
+%% See https://github.com/rabbitmq/rabbitmq-amqp1.0/blob/stable/README.md
+%% for details
+%% ----------------------------------------------------------------------------
+
+% {rabbitmq_amqp1_0,[
+%% Connections that are not authenticated with SASL will connect as this
+%% account. See the README for more information.
+%%
+%% Please note that setting this will allow clients to connect without
+%% authenticating!
+%%
+%% {default_user, "guest"},
+{mapping, "amqp1_0.default_user", "rabbitmq_amqp1_0.default_user",
+ [{datatype, [{enum, [none]}, string]}]}.
+%% Enable protocol strict mode. See the README for more information.
+%%
+%% {protocol_strict_mode, false}
+% ]},
+{mapping, "amqp1_0.protocol_strict_mode", "rabbitmq_amqp1_0.protocol_strict_mode",
+ [{datatype, {enum, [true, false]}}]}.
+
+{mapping, "amqp1_0.default_vhost", "rabbitmq_amqp1_0.default_vhost",
+ [{datatype, string}]}.
+
+{translation , "rabbitmq_amqp1_0.default_vhost",
+fun(Conf) ->
+ list_to_binary(cuttlefish:conf_get("amqp1_0.default_vhost", Conf))
+end}. \ No newline at end of file
diff --git a/test/config_schema_SUITE_data/schema/rabbitmq_auth_backend_amqp.schema b/test/config_schema_SUITE_data/schema/rabbitmq_auth_backend_amqp.schema
new file mode 100644
index 0000000000..a30efb6c03
--- /dev/null
+++ b/test/config_schema_SUITE_data/schema/rabbitmq_auth_backend_amqp.schema
@@ -0,0 +1,27 @@
+{mapping, "rabbitmq_auth_backend_amqp.username", "rabbitmq_auth_backend_amqp.username",
+ [{datatype, string}]}.
+
+{translation, "rabbitmq_auth_backend_amqp.username",
+fun(Conf) ->
+ list_to_binary(cuttlefish:conf_get("rabbitmq_auth_backend_amqp.username", Conf))
+end}.
+
+{mapping, "rabbitmq_auth_backend_amqp.vhost", "rabbitmq_auth_backend_amqp.vhost",
+ [{datatype, string}]}.
+
+{translation, "rabbitmq_auth_backend_amqp.vhost",
+fun(Conf) ->
+ list_to_binary(cuttlefish:conf_get("rabbitmq_auth_backend_amqp.vhost", Conf))
+end}.
+
+{mapping, "rabbitmq_auth_backend_amqp.exchange", "rabbitmq_auth_backend_amqp.exchange",
+ [{datatype, string}]}.
+
+{translation, "rabbitmq_auth_backend_amqp.exchange",
+fun(Conf) ->
+ list_to_binary(cuttlefish:conf_get("rabbitmq_auth_backend_amqp.exchange", Conf))
+end}.
+
+
+{mapping, "rabbitmq_auth_backend_amqp.timeout", "rabbitmq_auth_backend_amqp.timeout",
+ [{datatype, [{enum, [infinity]}, integer]}]}.
diff --git a/test/config_schema_SUITE_data/schema/rabbitmq_auth_backend_http.schema b/test/config_schema_SUITE_data/schema/rabbitmq_auth_backend_http.schema
new file mode 100644
index 0000000000..f10eb6710b
--- /dev/null
+++ b/test/config_schema_SUITE_data/schema/rabbitmq_auth_backend_http.schema
@@ -0,0 +1,15 @@
+
+%% ==========================================================================
+%% ----------------------------------------------------------------------------
+%% RabbitMQ HTTP Authorization
+%%
+%% ----------------------------------------------------------------------------
+
+{mapping, "rabbitmq_auth_backend_http.user_path", "rabbitmq_auth_backend_http.user_path",
+ [{datatype, string}, {validators, ["uri"]}]}.
+
+{mapping, "rabbitmq_auth_backend_http.vhost_path", "rabbitmq_auth_backend_http.vhost_path",
+ [{datatype, string}, {validators, ["uri"]}]}.
+
+{mapping, "rabbitmq_auth_backend_http.resource_path", "rabbitmq_auth_backend_http.resource_path",
+ [{datatype, string}, {validators, ["uri"]}]}.
diff --git a/test/config_schema_SUITE_data/schema/rabbitmq_auth_backend_ldap.schema b/test/config_schema_SUITE_data/schema/rabbitmq_auth_backend_ldap.schema
new file mode 100644
index 0000000000..334fd014c1
--- /dev/null
+++ b/test/config_schema_SUITE_data/schema/rabbitmq_auth_backend_ldap.schema
@@ -0,0 +1,183 @@
+%% ----------------------------------------------------------------------------
+%% RabbitMQ LDAP Plugin
+%%
+%% See http://www.rabbitmq.com/ldap.html for details.
+%%
+%% ----------------------------------------------------------------------------
+
+% {rabbitmq_auth_backend_ldap,
+% [
+%%
+%% Connecting to the LDAP server(s)
+%% ================================
+%%
+
+%% Specify servers to bind to. You *must* set this in order for the plugin
+%% to work properly.
+%%
+%% {servers, ["your-server-name-goes-here"]},
+
+{mapping, "rabbitmq_auth_backend_ldap.servers", "rabbitmq_auth_backend_ldap.servers",
+ [{datatype, {enum, [none]}}]}.
+
+{mapping, "rabbitmq_auth_backend_ldap.servers.$server", "rabbitmq_auth_backend_ldap.servers",
+ [{datatype, string}]}.
+
+{translation, "rabbitmq_auth_backend_ldap.servers",
+fun(Conf) ->
+ case cuttlefish:conf_get("rabbitmq_auth_backend_ldap.servers", Conf, undefined) of
+ none -> [];
+ _ ->
+ Settings = cuttlefish_variable:filter_by_prefix("rabbitmq_auth_backend_ldap.servers", Conf),
+ [ V || {_, V} <- Settings ]
+ end
+end}.
+
+%% Connect to the LDAP server using SSL
+%%
+%% {use_ssl, false},
+
+{mapping, "rabbitmq_auth_backend_ldap.use_ssl", "rabbitmq_auth_backend_ldap.use_ssl",
+ [{datatype, {enum, [true, false]}}]}.
+
+%% Specify the LDAP port to connect to
+%%
+%% {port, 389},
+
+{mapping, "rabbitmq_auth_backend_ldap.port", "rabbitmq_auth_backend_ldap.port",
+ [{datatype, integer}]}.
+
+%% LDAP connection timeout, in milliseconds or 'infinity'
+%%
+%% {timeout, infinity},
+
+{mapping, "rabbitmq_auth_backend_ldap.timeout", "rabbitmq_auth_backend_ldap.timeout",
+ [{datatype, [integer, {atom, infinity}]}]}.
+
+%% Enable logging of LDAP queries.
+%% One of
+%% - false (no logging is performed)
+%% - true (verbose logging of the logic used by the plugin)
+%% - network (as true, but additionally logs LDAP network traffic)
+%%
+%% Defaults to false.
+%%
+%% {log, false},
+
+{mapping, "rabbitmq_auth_backend_ldap.log", "rabbitmq_auth_backend_ldap.log",
+ [{datatype, {enum, [true, false, network]}}]}.
+
+%%
+%% Authentication
+%% ==============
+%%
+
+%% Pattern to convert the username given through AMQP to a DN before
+%% binding
+%%
+%% {user_dn_pattern, "cn=${username},ou=People,dc=example,dc=com"},
+
+{mapping, "rabbitmq_auth_backend_ldap.user_dn_pattern", "rabbitmq_auth_backend_ldap.user_dn_pattern",
+ [{datatype, string}]}.
+
+%% Alternatively, you can convert a username to a Distinguished
+%% Name via an LDAP lookup after binding. See the documentation for
+%% full details.
+
+%% When converting a username to a dn via a lookup, set these to
+%% the name of the attribute that represents the user name, and the
+%% base DN for the lookup query.
+%%
+%% {dn_lookup_attribute, "userPrincipalName"},
+%% {dn_lookup_base, "DC=gopivotal,DC=com"},
+
+{mapping, "rabbitmq_auth_backend_ldap.dn_lookup_attribute", "rabbitmq_auth_backend_ldap.dn_lookup_attribute",
+ [{datatype, [{enum, [none]}, string]}]}.
+
+{mapping, "rabbitmq_auth_backend_ldap.dn_lookup_base", "rabbitmq_auth_backend_ldap.dn_lookup_base",
+ [{datatype, [{enum, [none]}, string]}]}.
+
+{mapping, "rabbitmq_auth_backend_ldap.dn_lookup_bind", "rabbitmq_auth_backend_ldap.dn_lookup_bind",
+ [{datatype, [{enum, [as_user]}]}]}.
+
+{mapping, "rabbitmq_auth_backend_ldap.dn_lookup_bind.user_dn", "rabbitmq_auth_backend_ldap.dn_lookup_bind",
+ [{datatype, [string]}]}.
+
+{mapping, "rabbitmq_auth_backend_ldap.dn_lookup_bind.password", "rabbitmq_auth_backend_ldap.dn_lookup_bind",
+ [{datatype, [string]}]}.
+
+{translation, "rabbitmq_auth_backend_ldap.dn_lookup_bind",
+fun(Conf) ->
+ case cuttlefish:conf_get("rabbitmq_auth_backend_ldap.dn_lookup_bind", Conf, undefined) of
+ as_user -> as_user;
+ _ ->
+ User = cuttlefish:conf_get("rabbitmq_auth_backend_ldap.dn_lookup_bind.user_dn", Conf),
+ Pass = cuttlefish:conf_get("rabbitmq_auth_backend_ldap.dn_lookup_bind.password", Conf),
+ case {User, Pass} of
+ {undefined, _} -> as_user;
+ {_, undefined} -> as_user;
+ _ -> {User, Pass}
+ end
+ end
+end}.
+
+%% Controls how to bind for authorisation queries and also to
+%% retrieve the details of users logging in without presenting a
+%% password (e.g., SASL EXTERNAL).
+%% One of
+%% - as_user (to bind as the authenticated user - requires a password)
+%% - anon (to bind anonymously)
+%% - {UserDN, Password} (to bind with a specified user name and password)
+%%
+%% Defaults to 'as_user'.
+%%
+%% {other_bind, as_user},
+
+{mapping, "rabbitmq_auth_backend_ldap.other_bind", "rabbitmq_auth_backend_ldap.other_bind",
+ [{datatype, {enum, [as_user, anon]}}]}.
+
+{mapping, "rabbitmq_auth_backend_ldap.other_bind.user_dn", "rabbitmq_auth_backend_ldap.other_bind",
+ [{datatype, string}]}.
+
+{mapping, "rabbitmq_auth_backend_ldap.other_bind.password", "rabbitmq_auth_backend_ldap.other_bind",
+ [{datatype, string}]}.
+
+{translation, "rabbitmq_auth_backend_ldap.other_bind",
+fun(Conf) ->
+ case cuttlefish:conf_get("rabbitmq_auth_backend_ldap.other_bind", Conf, undefined) of
+ as_user -> as_user;
+ anon -> anon;
+ _ ->
+ User = cuttlefish:conf_get("rabbitmq_auth_backend_ldap.other_bind.user_dn", Conf),
+ Pass = cuttlefish:conf_get("rabbitmq_auth_backend_ldap.other_bind.password", Conf),
+ case {User, Pass} of
+ {undefined, _} -> as_user;
+ {_, undefined} -> as_user;
+ _ -> {User, Pass}
+ end
+ end
+end}.
+
+%%
+%% Authorisation
+%% =============
+%%
+
+%% The LDAP plugin can perform a variety of queries against your
+%% LDAP server to determine questions of authorisation. See
+%% http://www.rabbitmq.com/ldap.html#authorisation for more
+%% information.
+
+%% Set the query to use when determining vhost access
+%%
+%% {vhost_access_query, {in_group,
+%% "ou=${vhost}-users,ou=vhosts,dc=example,dc=com"}},
+
+%% Set the query to use when determining resource (e.g., queue) access
+%%
+%% {resource_access_query, {constant, true}},
+
+%% Set queries to determine which tags a user has
+%%
+%% {tag_queries, []}
+% ]},
diff --git a/test/config_schema_SUITE_data/schema/rabbitmq_clusterer.schema b/test/config_schema_SUITE_data/schema/rabbitmq_clusterer.schema
new file mode 100644
index 0000000000..ba127f00c1
--- /dev/null
+++ b/test/config_schema_SUITE_data/schema/rabbitmq_clusterer.schema
@@ -0,0 +1,58 @@
+{mapping, "clusterer.config", "rabbitmq_clusterer.config",
+ [{datatype, string}, {validators, ["file_accessible"]}]}.
+
+{translation, "rabbitmq_clusterer.config",
+fun(Conf) ->
+ case cuttlefish:conf_get("clusterer.config", Conf, undefined) of
+ String when is_list(String) ->
+ case cuttlefish_variable:filter_by_prefix("clusterer", Conf) of
+ [{["clusterer", "config"], String}] -> String;
+ _ -> cuttlefish:invalid("Config for clusterer defined in "++
+ String ++ " file. " ++
+ "All other clusterer configurations should be removed")
+ end;
+ _ -> []
+ end
+end}.
+
+{mapping, "clusterer.version", "rabbitmq_clusterer.config.version",
+ [{datatype, integer}]}.
+
+{mapping, "clusterer.nodes.$node", "rabbitmq_clusterer.config.nodes",
+ [{datatype, atom}]}.
+
+{mapping, "clusterer.nodes.ram.$node", "rabbitmq_clusterer.config.nodes",
+ [{datatype, atom}]}.
+
+{mapping, "clusterer.nodes.disk.$node", "rabbitmq_clusterer.config.nodes",
+ [{datatype, atom}]}.
+
+{mapping, "clusterer.nodes.disc.$node", "rabbitmq_clusterer.config.nodes",
+ [{datatype, atom}]}.
+
+{translation, "rabbitmq_clusterer.config.nodes",
+fun(Conf) ->
+ DiskNodes = cuttlefish_variable:filter_by_prefix("clusterer.nodes", Conf)
+ ++ cuttlefish_variable:filter_by_prefix("clusterer.nodes.disk", Conf)
+ ++ cuttlefish_variable:filter_by_prefix("clusterer.nodes.disc", Conf),
+ RamNodes = cuttlefish_variable:filter_by_prefix("clusterer.nodes.ram", Conf),
+ [{Node, disk} || {_, Node} <- DiskNodes] ++ [{Node, ram} || Node <- RamNodes]
+end}.
+
+{mapping, "clusterer.gospel", "rabbitmq_clusterer.config.gospel",
+ [{datatype, {enum, [reset]}}]}.
+
+{mapping, "clusterer.gospel.node", "rabbitmq_clusterer.config.gospel",
+ [{datatype, atom}]}.
+
+{translation, "rabbitmq_clusterer.config.gospel",
+fun(Conf) ->
+ case cuttlefish:conf_get("clusterer.gospel", Conf, undefined) of
+ reset -> reset;
+ _ ->
+ {node, cuttlefish:conf_get("clusterer.gospel.node", Conf)}
+ end
+end}.
+
+
+
diff --git a/test/config_schema_SUITE_data/schema/rabbitmq_management.schema b/test/config_schema_SUITE_data/schema/rabbitmq_management.schema
new file mode 100644
index 0000000000..7ac6d21b93
--- /dev/null
+++ b/test/config_schema_SUITE_data/schema/rabbitmq_management.schema
@@ -0,0 +1,203 @@
+%% ----------------------------------------------------------------------------
+%% RabbitMQ Management Plugin
+%%
+%% See http://www.rabbitmq.com/management.html for details
+%% ----------------------------------------------------------------------------
+
+ % {rabbitmq_management,
+ % [%% Pre-Load schema definitions from the following JSON file. See
+%% http://www.rabbitmq.com/management.html#load-definitions
+%%
+%% {load_definitions, "/path/to/schema.json"},
+{mapping, "management.load_definitions", "rabbitmq_management.load_definitions",
+ [{datatype, string},
+ {validators, ["file_accessible"]}]}.
+
+%% Log all requests to the management HTTP API to a file.
+%%
+%% {http_log_dir, "/path/to/access.log"},
+
+{mapping, "management.http_log_dir", "rabbitmq_management.http_log_dir",
+ [{datatype, string}]}.
+
+
+%% Change the port on which the HTTP listener listens,
+%% specifying an interface for the web server to bind to.
+%% Also set the listener to use SSL and provide SSL options.
+%%
+%% {listener, [{port, 12345},
+%% {ip, "127.0.0.1"},
+%% {ssl, true},
+%% {ssl_opts, [{cacertfile, "/path/to/cacert.pem"},
+%% {certfile, "/path/to/cert.pem"},
+%% {keyfile, "/path/to/key.pem"}]}]},
+
+{mapping, "management.listener.port", "rabbitmq_management.listener.port",
+ [{datatype, integer}]}.
+
+{mapping, "management.listener.ip", "rabbitmq_management.listener.ip",
+ [{datatype, string},
+ {validators, ["is_ip"]}]}.
+
+{mapping, "management.listener.ssl", "rabbitmq_management.listener.ssl",
+ [{datatype, {enum, [true, false]}}]}.
+
+
+%% SSL options section ========================================================
+
+{mapping, "management.listener.ssl_opts", "rabbitmq_management.listener.ssl_opts", [
+ {datatype, {enum, [none]}}
+]}.
+
+{translation, "rabbitmq_management.listener.ssl_opts",
+fun(Conf) ->
+ case cuttlefish:conf_get("management.listener.ssl_opts", Conf, undefined) of
+ none -> [];
+ _ -> cuttlefish:invalid("Invalid management.listener.ssl_opts")
+ end
+end}.
+
+{mapping, "management.listener.ssl_opts.verify", "rabbitmq_management.listener.ssl_opts.verify", [
+ {datatype, {enum, [verify_peer, verify_none]}}]}.
+
+{mapping, "management.listener.ssl_opts.fail_if_no_peer_cert", "rabbitmq_management.listener.ssl_opts.fail_if_no_peer_cert", [
+ {datatype, {enum, [true, false]}}]}.
+
+{mapping, "management.listener.ssl_opts.cacertfile", "rabbitmq_management.listener.ssl_opts.cacertfile",
+ [{datatype, string}, {validators, ["file_accessible"]}]}.
+
+{mapping, "management.listener.ssl_opts.certfile", "rabbitmq_management.listener.ssl_opts.certfile",
+ [{datatype, string}, {validators, ["file_accessible"]}]}.
+
+{mapping, "management.listener.ssl_opts.cacerts.$name", "rabbitmq_management.listener.ssl_opts.cacerts",
+ [{datatype, string}]}.
+
+{translation, "rabbitmq_management.listener.ssl_opts.cacerts",
+fun(Conf) ->
+ Settings = cuttlefish_variable:filter_by_prefix("management.listener.ssl_opts.cacerts", Conf),
+ [ list_to_binary(V) || {_, V} <- Settings ]
+end}.
+
+{mapping, "management.listener.ssl_opts.cert", "rabbitmq_management.listener.ssl_opts.cert",
+ [{datatype, string}]}.
+
+{translation, "rabbitmq_management.listener.ssl_opts.cert",
+fun(Conf) ->
+ list_to_binary(cuttlefish:conf_get("management.listener.ssl_opts.cert", Conf))
+end}.
+
+{mapping, "management.listener.ssl_opts.client_renegotiation", "rabbitmq_management.listener.ssl_opts.client_renegotiation",
+ [{datatype, {enum, [true, false]}}]}.
+
+{mapping, "management.listener.ssl_opts.crl_check", "rabbitmq_management.listener.ssl_opts.crl_check",
+ [{datatype, [{enum, [true, false, peer, best_effort]}]}]}.
+
+{mapping, "management.listener.ssl_opts.depth", "rabbitmq_management.listener.ssl_opts.depth",
+ [{datatype, integer}, {validators, ["byte"]}]}.
+
+{mapping, "management.listener.ssl_opts.dh", "rabbitmq_management.listener.ssl_opts.dh",
+ [{datatype, string}]}.
+
+{translation, "rabbitmq_management.listener.ssl_opts.dh",
+fun(Conf) ->
+ list_to_binary(cuttlefish:conf_get("management.listener.ssl_opts.dh", Conf))
+end}.
+
+{mapping, "management.listener.ssl_opts.dhfile", "rabbitmq_management.listener.ssl_opts.dhfile",
+ [{datatype, string}, {validators, ["file_accessible"]}]}.
+
+{mapping, "management.listener.ssl_opts.honor_cipher_order", "rabbitmq_management.listener.ssl_opts.honor_cipher_order",
+ [{datatype, {enum, [true, false]}}]}.
+
+{mapping, "management.listener.ssl_opts.key.RSAPrivateKey", "rabbitmq_management.listener.ssl_opts.key",
+ [{datatype, string}]}.
+
+{mapping, "management.listener.ssl_opts.key.DSAPrivateKey", "rabbitmq_management.listener.ssl_opts.key",
+ [{datatype, string}]}.
+
+{mapping, "management.listener.ssl_opts.key.PrivateKeyInfo", "rabbitmq_management.listener.ssl_opts.key",
+ [{datatype, string}]}.
+
+{translation, "rabbitmq_management.listener.ssl_opts.key",
+fun(Conf) ->
+ case cuttlefish_variable:filter_by_prefix("management.listener.ssl_opts.key", Conf) of
+ [{[_,_,Key], Val}|_] -> {list_to_atom(Key), list_to_binary(Val)};
+ _ -> undefined
+ end
+end}.
+
+{mapping, "management.listener.ssl_opts.keyfile", "rabbitmq_management.listener.ssl_opts.keyfile",
+ [{datatype, string}, {validators, ["file_accessible"]}]}.
+
+{mapping, "management.listener.ssl_opts.log_alert", "rabbitmq_management.listener.ssl_opts.log_alert",
+ [{datatype, {enum, [true, false]}}]}.
+
+{mapping, "management.listener.ssl_opts.password", "rabbitmq_management.listener.ssl_opts.password",
+ [{datatype, string}]}.
+
+{mapping, "management.listener.ssl_opts.psk_identity", "rabbitmq_management.listener.ssl_opts.psk_identity",
+ [{datatype, string}]}.
+
+{mapping, "management.listener.ssl_opts.reuse_sessions", "rabbitmq_management.listener.ssl_opts.reuse_sessions",
+ [{datatype, {enum, [true, false]}}]}.
+
+{mapping, "management.listener.ssl_opts.secure_renegotiate", "rabbitmq_management.listener.ssl_opts.secure_renegotiate",
+ [{datatype, {enum, [true, false]}}]}.
+
+{mapping, "management.listener.ssl_opts.versions.$version", "rabbitmq_management.listener.ssl_opts.versions",
+ [{datatype, atom}]}.
+
+{translation, "rabbitmq_management.listener.ssl_opts.versions",
+fun(Conf) ->
+ Settings = cuttlefish_variable:filter_by_prefix("management.listener.ssl_opts.versions", Conf),
+ [ V || {_, V} <- Settings ]
+end}.
+
+%% ===========================================================================
+
+
+%% One of 'basic', 'detailed' or 'none'. See
+%% http://www.rabbitmq.com/management.html#fine-stats for more details.
+%% {rates_mode, basic},
+{mapping, "management.rates_mode", "rabbitmq_management.rates_mode",
+ [{datatype, {enum, [basic, detailed, none]}}]}.
+
+%% Configure how long aggregated data (such as message rates and queue
+%% lengths) is retained. Please read the plugin's documentation in
+%% http://www.rabbitmq.com/management.html#configuration for more
+%% details.
+%%
+%% {sample_retention_policies,
+%% [{global, [{60, 5}, {3600, 60}, {86400, 1200}]},
+%% {basic, [{60, 5}, {3600, 60}]},
+%% {detailed, [{10, 5}]}]}
+% ]},
+
+{mapping, "management.sample_retention_policies.$section.$interval",
+ "rabbitmq_management.sample_retention_policies",
+ [{datatype, integer}]}.
+
+{translation, "rabbitmq_management.sample_retention_policies",
+fun(Conf) ->
+ Global = cuttlefish_variable:filter_by_prefix("management.sample_retention_policies.global", Conf),
+ Basic = cuttlefish_variable:filter_by_prefix("management.sample_retention_policies.basic", Conf),
+ Detailed = cuttlefish_variable:filter_by_prefix("management.sample_retention_policies.detailed", Conf),
+ TranslateKey = fun("minute") -> 60;
+ ("hour") -> 3600;
+ ("day") -> 86400;
+ (Other) -> list_to_integer(Other)
+ end,
+ TranslatePolicy = fun(Section) ->
+ [ {TranslateKey(Key), Val} || {[_,_,_,Key], Val} <- Section ]
+ end,
+ [{global, TranslatePolicy(Global)},
+ {basic, TranslatePolicy(Basic)},
+ {detailed, TranslatePolicy(Detailed)}]
+end}.
+
+
+{validator, "is_dir", "is not directory",
+fun(File) ->
+ ReadFile = file:list_dir(File),
+ element(1, ReadFile) == ok
+end}.
diff --git a/test/config_schema_SUITE_data/schema/rabbitmq_metronome.schema b/test/config_schema_SUITE_data/schema/rabbitmq_metronome.schema
new file mode 100644
index 0000000000..53cf8f003e
--- /dev/null
+++ b/test/config_schema_SUITE_data/schema/rabbitmq_metronome.schema
@@ -0,0 +1,9 @@
+
+{mapping, "metronome.exchange", "rabbitmq_metronome.exchange",
+ [{datatype, string}]}.
+
+{translation, "rabbitmq_metronome.exchange",
+fun(Conf) ->
+ Exchange = cuttlefish:conf_get("metronome.exchange", Conf),
+ list_to_binary(Exchange)
+end}. \ No newline at end of file
diff --git a/test/config_schema_SUITE_data/schema/rabbitmq_mqtt.schema b/test/config_schema_SUITE_data/schema/rabbitmq_mqtt.schema
new file mode 100644
index 0000000000..ffda10beaa
--- /dev/null
+++ b/test/config_schema_SUITE_data/schema/rabbitmq_mqtt.schema
@@ -0,0 +1,248 @@
+%% ----------------------------------------------------------------------------
+%% RabbitMQ MQTT Adapter
+%%
+%% See https://github.com/rabbitmq/rabbitmq-mqtt/blob/stable/README.md
+%% for details
+%% ----------------------------------------------------------------------------
+
+% {rabbitmq_mqtt,
+% [%% Set the default user name and password. Will be used as the default login
+%% if a connecting client provides no other login details.
+%%
+%% Please note that setting this will allow clients to connect without
+%% authenticating!
+%%
+%% {default_user, <<"guest">>},
+%% {default_pass, <<"guest">>},
+
+{mapping, "mqtt.default_user", "rabbitmq_mqtt.default_user", [
+ {datatype, string}
+]}.
+
+{mapping, "mqtt.default_pass", "rabbitmq_mqtt.default_pass", [
+ {datatype, string}
+]}.
+
+{translation, "rabbitmq_mqtt.default_user",
+fun(Conf) ->
+ list_to_binary(cuttlefish:conf_get("mqtt.default_user", Conf))
+end}.
+
+{translation, "rabbitmq_mqtt.default_pass",
+fun(Conf) ->
+ list_to_binary(cuttlefish:conf_get("mqtt.default_pass", Conf))
+end}.
+
+%% Enable anonymous access. If this is set to false, clients MUST provide
+%% login information in order to connect. See the default_user/default_pass
+%% configuration elements for managing logins without authentication.
+%%
+%% {allow_anonymous, true},
+
+{mapping, "mqtt.allow_anonymous", "rabbitmq_mqtt.allow_anonymous",
+ [{datatype, {enum, [true, false]}}]}.
+
+%% If you have multiple chosts, specify the one to which the
+%% adapter connects.
+%%
+%% {vhost, <<"/">>},
+
+{mapping, "mqtt.vhost", "rabbitmq_mqtt.vhost", [{datatype, string}]}.
+
+{translation, "rabbitmq_mqtt.vhost",
+fun(Conf) ->
+ list_to_binary(cuttlefish:conf_get("mqtt.vhost", Conf))
+end}.
+
+%% Specify the exchange to which messages from MQTT clients are published.
+%%
+%% {exchange, <<"amq.topic">>},
+
+{mapping, "mqtt.exchange", "rabbitmq_mqtt.exchange", [{datatype, string}]}.
+
+{translation, "rabbitmq_mqtt.exchange",
+fun(Conf) ->
+ list_to_binary(cuttlefish:conf_get("mqtt.exchange", Conf))
+end}.
+
+%% Specify TTL (time to live) to control the lifetime of non-clean sessions.
+%%
+%% {subscription_ttl, 1800000},
+{mapping, "mqtt.subscription_ttl", "rabbitmq_mqtt.subscription_ttl", [
+ {datatype, [{enum, [undefined, infinity]}, integer]}
+]}.
+
+{translation, "rabbitmq_mqtt.subscription_ttl",
+fun(Conf) ->
+ case cuttlefish:conf_get("mqtt.subscription_ttl", Conf, undefined) of
+ undefined -> undefined;
+ infinity -> undefined;
+ Ms -> Ms
+ end
+end}.
+
+%% Set the prefetch count (governing the maximum number of unacknowledged
+%% messages that will be delivered).
+%%
+%% {prefetch, 10},
+{mapping, "mqtt.prefetch", "rabbitmq_mqtt.prefetch",
+ [{datatype, integer}]}.
+
+
+{mapping, "mqtt.retained_message_store", "rabbitmq_mqtt.retained_message_store",
+ [{datatype, atom}]}.
+
+{mapping, "mqtt.retained_message_store_dets_sync_interval", "rabbitmq_mqtt.retained_message_store_dets_sync_interval",
+ [{datatype, integer}]}.
+
+
+
+%% TCP/SSL Configuration (as per the broker configuration).
+%%
+%% {tcp_listeners, [1883]},
+%% {ssl_listeners, []},
+
+{mapping, "mqtt.listeners.tcp", "rabbitmq_mqtt.tcp_listeners",[
+ {datatype, {enum, [none]}}
+]}.
+
+{mapping, "mqtt.listeners.tcp.$name", "rabbitmq_mqtt.tcp_listeners",[
+ {datatype, [integer, ip]}
+]}.
+
+{translation, "rabbitmq_mqtt.tcp_listeners",
+fun(Conf) ->
+ case cuttlefish:conf_get("mqtt.listeners.tcp", Conf, undefined) of
+ none -> [];
+ _ ->
+ Settings = cuttlefish_variable:filter_by_prefix("mqtt.listeners.tcp", Conf),
+ [ V || {_, V} <- Settings ]
+ end
+end}.
+
+{mapping, "mqtt.listeners.ssl", "rabbitmq_mqtt.ssl_listeners",[
+ {datatype, {enum, [none]}}
+]}.
+
+{mapping, "mqtt.listeners.ssl.$name", "rabbitmq_mqtt.ssl_listeners",[
+ {datatype, [integer, ip]}
+]}.
+
+{translation, "rabbitmq_mqtt.ssl_listeners",
+fun(Conf) ->
+ case cuttlefish:conf_get("mqtt.listeners.ssl", Conf, undefined) of
+ none -> [];
+ _ ->
+ Settings = cuttlefish_variable:filter_by_prefix("mqtt.listeners.ssl", Conf),
+ [ V || {_, V} <- Settings ]
+ end
+end}.
+
+%% Number of Erlang processes that will accept connections for the TCP
+%% and SSL listeners.
+%%
+%% {num_tcp_acceptors, 10},
+%% {num_ssl_acceptors, 1},
+
+{mapping, "mqtt.num_acceptors.ssl", "rabbitmq_mqtt.num_ssl_acceptors", [
+ {datatype, integer}
+]}.
+
+{mapping, "mqtt.num_acceptors.tcp", "rabbitmq_mqtt.num_tcp_acceptors", [
+ {datatype, integer}
+]}.
+
+{mapping, "mqtt.ssl_cert_login", "rabbitmq_mqtt.ssl_cert_login", [
+ {datatype, {enum, [true, false]}}]}.
+
+
+%% TCP/Socket options (as per the broker configuration).
+%%
+%% {tcp_listen_options, [{backlog, 128},
+%% {nodelay, true}]}
+% ]},
+
+%% TCP listener section ======================================================
+
+{mapping, "mqtt.tcp_listen_options", "rabbitmq_mqtt.rabbit.tcp_listen_options", [
+ {datatype, {enum, [none]}}]}.
+
+{translation, "rabbitmq_mqtt.rabbit.tcp_listen_options",
+fun(Conf) ->
+ case cuttlefish:conf_get("mqtt.tcp_listen_options") of
+ none -> [];
+ _ -> cuttlefish:invalid("Invalid mqtt.tcp_listen_options")
+ end
+end}.
+
+{mapping, "mqtt.tcp_listen_options.backlog", "rabbitmq_mqtt.tcp_listen_options.backlog", [
+ {datatype, integer}
+]}.
+
+{mapping, "mqtt.tcp_listen_options.nodelay", "rabbitmq_mqtt.tcp_listen_options.nodelay", [
+ {datatype, {enum, [true, false]}}
+]}.
+
+{mapping, "mqtt.tcp_listen_options.buffer", "rabbitmq_mqtt.tcp_listen_options.buffer",
+ [{datatype, integer}]}.
+
+{mapping, "mqtt.tcp_listen_options.delay_send", "rabbitmq_mqtt.tcp_listen_options.delay_send",
+ [{datatype, {enum, [true, false]}}]}.
+
+{mapping, "mqtt.tcp_listen_options.dontroute", "rabbitmq_mqtt.tcp_listen_options.dontroute",
+ [{datatype, {enum, [true, false]}}]}.
+
+{mapping, "mqtt.tcp_listen_options.exit_on_close", "rabbitmq_mqtt.tcp_listen_options.exit_on_close",
+ [{datatype, {enum, [true, false]}}]}.
+
+{mapping, "mqtt.tcp_listen_options.fd", "rabbitmq_mqtt.tcp_listen_options.fd",
+ [{datatype, integer}]}.
+
+{mapping, "mqtt.tcp_listen_options.high_msgq_watermark", "rabbitmq_mqtt.tcp_listen_options.high_msgq_watermark",
+ [{datatype, integer}]}.
+
+{mapping, "mqtt.tcp_listen_options.high_watermark", "rabbitmq_mqtt.tcp_listen_options.high_watermark",
+ [{datatype, integer}]}.
+
+{mapping, "mqtt.tcp_listen_options.keepalive", "rabbitmq_mqtt.tcp_listen_options.keepalive",
+ [{datatype, {enum, [true, false]}}]}.
+
+{mapping, "mqtt.tcp_listen_options.low_msgq_watermark", "rabbitmq_mqtt.tcp_listen_options.low_msgq_watermark",
+ [{datatype, integer}]}.
+
+{mapping, "mqtt.tcp_listen_options.low_watermark", "rabbitmq_mqtt.tcp_listen_options.low_watermark",
+ [{datatype, integer}]}.
+
+{mapping, "mqtt.tcp_listen_options.port", "rabbitmq_mqtt.tcp_listen_options.port",
+ [{datatype, integer}, {validators, ["port"]}]}.
+
+{mapping, "mqtt.tcp_listen_options.priority", "rabbitmq_mqtt.tcp_listen_options.priority",
+ [{datatype, integer}]}.
+
+{mapping, "mqtt.tcp_listen_options.recbuf", "rabbitmq_mqtt.tcp_listen_options.recbuf",
+ [{datatype, integer}]}.
+
+{mapping, "mqtt.tcp_listen_options.send_timeout", "rabbitmq_mqtt.tcp_listen_options.send_timeout",
+ [{datatype, integer}]}.
+
+{mapping, "mqtt.tcp_listen_options.send_timeout_close", "rabbitmq_mqtt.tcp_listen_options.send_timeout_close",
+ [{datatype, {enum, [true, false]}}]}.
+
+{mapping, "mqtt.tcp_listen_options.sndbuf", "rabbitmq_mqtt.tcp_listen_options.sndbuf",
+ [{datatype, integer}]}.
+
+{mapping, "mqtt.tcp_listen_options.tos", "rabbitmq_mqtt.tcp_listen_options.tos",
+ [{datatype, integer}]}.
+
+{mapping, "mqtt.tcp_listen_options.linger.on", "rabbitmq_mqtt.tcp_listen_options.linger",
+ [{datatype, {enum, [true, false]}}]}.
+
+{mapping, "mqtt.tcp_listen_options.linger.timeout", "rabbitmq_mqtt.tcp_listen_options.linger",
+ [{datatype, integer}]}.
+
+{translation, "rabbitmq_mqtt.tcp_listen_options.linger",
+fun(Conf) ->
+ LingerOn = cuttlefish:conf_get("mqtt.tcp_listen_options.linger.on", Conf, false),
+ LingerTimeout = cuttlefish:conf_get("mqtt.tcp_listen_options.linger.timeout", Conf, 0),
+ {LingerOn, LingerTimeout}
+end}.
diff --git a/test/config_schema_SUITE_data/schema/rabbitmq_stomp.schema b/test/config_schema_SUITE_data/schema/rabbitmq_stomp.schema
new file mode 100644
index 0000000000..b7619f0b28
--- /dev/null
+++ b/test/config_schema_SUITE_data/schema/rabbitmq_stomp.schema
@@ -0,0 +1,110 @@
+%% ==========================================================================
+%% ----------------------------------------------------------------------------
+%% RabbitMQ Stomp Adapter
+%%
+%% See http://www.rabbitmq.com/stomp.html for details
+%% ----------------------------------------------------------------------------
+
+% {rabbitmq_stomp,
+% [%% Network Configuration - the format is generally the same as for the broker
+
+%% Listen only on localhost (ipv4 & ipv6) on a specific port.
+%% {tcp_listeners, [{"127.0.0.1", 61613},
+%% {"::1", 61613}]},
+
+{mapping, "stomp.listeners.tcp", "rabbitmq_stomp.tcp_listeners",[
+ {datatype, {enum, [none]}}
+]}.
+
+{mapping, "stomp.listeners.tcp.$name", "rabbitmq_stomp.tcp_listeners",[
+ {datatype, [integer, ip]}
+]}.
+
+{translation, "rabbitmq_stomp.tcp_listeners",
+fun(Conf) ->
+ case cuttlefish:conf_get("stomp.listeners.tcp", Conf, undefined) of
+ none -> [];
+ _ ->
+ Settings = cuttlefish_variable:filter_by_prefix("stomp.listeners.tcp", Conf),
+ [ V || {_, V} <- Settings ]
+ end
+end}.
+
+{mapping, "stomp.listeners.ssl", "rabbitmq_stomp.ssl_listeners",[
+ {datatype, {enum, [none]}}
+]}.
+
+{mapping, "stomp.listeners.ssl.$name", "rabbitmq_stomp.ssl_listeners",[
+ {datatype, [integer, ip]}
+]}.
+
+{translation, "rabbitmq_stomp.ssl_listeners",
+fun(Conf) ->
+ case cuttlefish:conf_get("stomp.listeners.ssl", Conf, undefined) of
+ none -> [];
+ _ ->
+ Settings = cuttlefish_variable:filter_by_prefix("stomp.listeners.ssl", Conf),
+ [ V || {_, V} <- Settings ]
+ end
+end}.
+
+%% Number of Erlang processes that will accept connections for the TCP
+%% and SSL listeners.
+%%
+%% {num_tcp_acceptors, 10},
+%% {num_ssl_acceptors, 1},
+
+{mapping, "stomp.num_acceptors.ssl", "rabbitmq_stomp.num_ssl_acceptors", [
+ {datatype, integer}
+]}.
+
+{mapping, "stomp.num_acceptors.tcp", "rabbitmq_stomp.num_tcp_acceptors", [
+ {datatype, integer}
+]}.
+
+%% Additional SSL options
+
+%% Extract a name from the client's certificate when using SSL.
+%%
+%% {ssl_cert_login, true},
+
+{mapping, "stomp.ssl_cert_login", "rabbitmq_stomp.ssl_cert_login",
+ [{datatype, {enum, [true, false]}}]}.
+
+%% Set a default user name and password. This is used as the default login
+%% whenever a CONNECT frame omits the login and passcode headers.
+%%
+%% Please note that setting this will allow clients to connect without
+%% authenticating!
+%%
+%% {default_user, [{login, "guest"},
+%% {passcode, "guest"}]},
+
+{mapping, "stomp.default_vhost", "rabbitmq_stomp.default_vhost", [
+ {datatype, string}
+]}.
+
+{translation, "rabbitmq_stomp.default_vhost",
+fun(Conf) ->
+ list_to_binary(cuttlefish:conf_get("stomp.default_vhost", Conf, "/"))
+end}.
+
+{mapping, "stomp.default_user", "rabbitmq_stomp.default_user.login", [
+ {datatype, string}
+]}.
+
+{mapping, "stomp.default_pass", "rabbitmq_stomp.default_user.passcode", [
+ {datatype, string}
+]}.
+
+%% If a default user is configured, or you have configured use SSL client
+%% certificate based authentication, you can choose to allow clients to
+%% omit the CONNECT frame entirely. If set to true, the client is
+%% automatically connected as the default user or user supplied in the
+%% SSL certificate whenever the first frame sent on a session is not a
+%% CONNECT frame.
+%%
+%% {implicit_connect, true}
+% ]},
+{mapping, "stomp.implicit_connect", "rabbitmq_stomp.implicit_connect",
+ [{datatype, {enum, [true, false]}}]}.
diff --git a/test/config_schema_SUITE_data/schema/rabbitmq_web_mqtt.schema b/test/config_schema_SUITE_data/schema/rabbitmq_web_mqtt.schema
new file mode 100644
index 0000000000..acdab62c32
--- /dev/null
+++ b/test/config_schema_SUITE_data/schema/rabbitmq_web_mqtt.schema
@@ -0,0 +1,44 @@
+{mapping, "web_mqtt.num_acceptors.tcp", "rabbitmq_web_mqtt.num_tcp_acceptors",
+ [{datatype, integer}]}.
+{mapping, "web_mqtt.num_acceptors.ssl", "rabbitmq_web_mqtt.num_ssl_acceptors",
+ [{datatype, integer}]}.
+
+{mapping, "web_mqtt.tcp.port", "rabbitmq_web_mqtt.tcp_config.port",
+ [{datatype, integer}]}.
+{mapping, "web_mqtt.tcp.backlog", "rabbitmq_web_mqtt.tcp_config.backlog",
+ [{datatype, integer}]}.
+{mapping, "web_mqtt.tcp.ip", "rabbitmq_web_mqtt.tcp_config.ip",
+ [{datatype, string}, {validators, ["is_ip"]}]}.
+
+
+{mapping, "web_mqtt.ssl.port", "rabbitmq_web_mqtt.ssl_config.port",
+ [{datatype, integer}]}.
+{mapping, "web_mqtt.ssl.backlog", "rabbitmq_web_mqtt.ssl_config.backlog",
+ [{datatype, integer}]}.
+{mapping, "web_mqtt.ssl.ip", "rabbitmq_web_mqtt.ssl_config.ip",
+ [{datatype, string}, {validators, ["is_ip"]}]}.
+{mapping, "web_mqtt.ssl.certfile", "rabbitmq_web_mqtt.ssl_config.certfile",
+ [{datatype, string}, {validators, ["file_accessible"]}]}.
+{mapping, "web_mqtt.ssl.keyfile", "rabbitmq_web_mqtt.ssl_config.keyfile",
+ [{datatype, string}, {validators, ["file_accessible"]}]}.
+{mapping, "web_mqtt.ssl.cacertfile", "rabbitmq_web_mqtt.ssl_config.cacertfile",
+ [{datatype, string}, {validators, ["file_accessible"]}]}.
+{mapping, "web_mqtt.ssl.password", "rabbitmq_web_mqtt.ssl_config.password",
+ [{datatype, string}]}.
+
+
+{mapping, "web_mqtt.cowboy_opts.max_empty_lines", "rabbitmq_web_mqtt.cowboy_opts.max_empty_lines",
+ [{datatype, integer}]}.
+{mapping, "web_mqtt.cowboy_opts.max_header_name_length", "rabbitmq_web_mqtt.cowboy_opts.max_header_name_length",
+ [{datatype, integer}]}.
+{mapping, "web_mqtt.cowboy_opts.max_header_value_length", "rabbitmq_web_mqtt.cowboy_opts.max_header_value_length",
+ [{datatype, integer}]}.
+{mapping, "web_mqtt.cowboy_opts.max_headers", "rabbitmq_web_mqtt.cowboy_opts.max_headers",
+ [{datatype, integer}]}.
+{mapping, "web_mqtt.cowboy_opts.max_keepalive", "rabbitmq_web_mqtt.cowboy_opts.max_keepalive",
+ [{datatype, integer}]}.
+{mapping, "web_mqtt.cowboy_opts.max_request_line_length", "rabbitmq_web_mqtt.cowboy_opts.max_request_line_length",
+ [{datatype, integer}]}.
+{mapping, "web_mqtt.cowboy_opts.timeout", "rabbitmq_web_mqtt.cowboy_opts.timeout",
+ [{datatype, integer}]}.
+
diff --git a/test/config_schema_SUITE_data/schema/rabbitmq_web_stomp.schema b/test/config_schema_SUITE_data/schema/rabbitmq_web_stomp.schema
new file mode 100644
index 0000000000..389da07d14
--- /dev/null
+++ b/test/config_schema_SUITE_data/schema/rabbitmq_web_stomp.schema
@@ -0,0 +1,64 @@
+{mapping, "web_stomp.port", "rabbitmq_web_stomp.port",
+ [{datatype, integer}]}.
+
+{mapping, "web_stomp.ws_frame", "rabbitmq_web_stomp.ws_frame",
+ [{datatype, {enum, [binary, text]}}]}.
+
+{mapping, "web_stomp.num_acceptors.tcp", "rabbitmq_web_stomp.num_tcp_acceptors",
+ [{datatype, integer}]}.
+
+{mapping, "web_stomp.num_acceptors.ssl", "rabbitmq_web_stomp.num_ssl_acceptors",
+ [{datatype, integer}]}.
+
+{mapping, "web_stomp.tcp.port", "rabbitmq_web_stomp.tcp_config.port",
+ [{datatype, integer}]}.
+{mapping, "web_stomp.tcp.backlog", "rabbitmq_web_stomp.tcp_config.backlog",
+ [{datatype, integer}]}.
+{mapping, "web_stomp.tcp.ip", "rabbitmq_web_stomp.tcp_config.ip",
+ [{datatype, string}, {validators, ["is_ip"]}]}.
+
+
+{mapping, "web_stomp.ssl.port", "rabbitmq_web_stomp.ssl_config.port",
+ [{datatype, integer}]}.
+{mapping, "web_stomp.ssl.backlog", "rabbitmq_web_stomp.ssl_config.backlog",
+ [{datatype, integer}]}.
+{mapping, "web_stomp.ssl.ip", "rabbitmq_web_stomp.ssl_config.ip",
+ [{datatype, string}, {validators, ["is_ip"]}]}.
+{mapping, "web_stomp.ssl.certfile", "rabbitmq_web_stomp.ssl_config.certfile",
+ [{datatype, string}, {validators, ["file_accessible"]}]}.
+{mapping, "web_stomp.ssl.keyfile", "rabbitmq_web_stomp.ssl_config.keyfile",
+ [{datatype, string}, {validators, ["file_accessible"]}]}.
+{mapping, "web_stomp.ssl.cacertfile", "rabbitmq_web_stomp.ssl_config.cacertfile",
+ [{datatype, string}, {validators, ["file_accessible"]}]}.
+{mapping, "web_stomp.ssl.password", "rabbitmq_web_stomp.ssl_config.password",
+ [{datatype, string}]}.
+
+
+{mapping, "web_stomp.cowboy_opts.max_empty_lines", "rabbitmq_web_stomp.cowboy_opts.max_empty_lines",
+ [{datatype, integer}]}.
+{mapping, "web_stomp.cowboy_opts.max_header_name_length", "rabbitmq_web_stomp.cowboy_opts.max_header_name_length",
+ [{datatype, integer}]}.
+{mapping, "web_stomp.cowboy_opts.max_header_value_length", "rabbitmq_web_stomp.cowboy_opts.max_header_value_length",
+ [{datatype, integer}]}.
+{mapping, "web_stomp.cowboy_opts.max_headers", "rabbitmq_web_stomp.cowboy_opts.max_headers",
+ [{datatype, integer}]}.
+{mapping, "web_stomp.cowboy_opts.max_keepalive", "rabbitmq_web_stomp.cowboy_opts.max_keepalive",
+ [{datatype, integer}]}.
+{mapping, "web_stomp.cowboy_opts.max_request_line_length", "rabbitmq_web_stomp.cowboy_opts.max_request_line_length",
+ [{datatype, integer}]}.
+{mapping, "web_stomp.cowboy_opts.timeout", "rabbitmq_web_stomp.cowboy_opts.timeout",
+ [{datatype, integer}]}.
+
+
+{mapping, "web_stomp.sockjs_opts.url", "rabbitmq_web_stomp.sockjs_opts.sockjs_url",
+ [{datatype, string}]}.
+{mapping, "web_stomp.sockjs_opts.websocket", "rabbitmq_web_stomp.sockjs_opts.websocket",
+ [{datatype, {enum, [true, false]}}]}.
+{mapping, "web_stomp.sockjs_opts.cookie_needed", "rabbitmq_web_stomp.sockjs_opts.cookie_needed",
+ [{datatype, {enum, [true, false]}}]}.
+{mapping, "web_stomp.sockjs_opts.heartbeat_delay", "rabbitmq_web_stomp.sockjs_opts.heartbeat_delay",
+ [{datatype, integer}]}.
+{mapping, "web_stomp.sockjs_opts.disconnect_delay", "rabbitmq_web_stomp.sockjs_opts.disconnect_delay",
+ [{datatype, integer}]}.
+{mapping, "web_stomp.sockjs_opts.response_limit", "rabbitmq_web_stomp.sockjs_opts.response_limit",
+ [{datatype, integer}]}.
diff --git a/test/config_schema_SUITE_data/snippets.config b/test/config_schema_SUITE_data/snippets.config
new file mode 100644
index 0000000000..abfc28441e
--- /dev/null
+++ b/test/config_schema_SUITE_data/snippets.config
@@ -0,0 +1,732 @@
+[
+{1,
+"auth_backends.1 = internal",
+[{rabbit, [{auth_backends, [rabbit_auth_backend_internal]}]}],[]}
+,
+{2,
+"auth_backends.1 = ldap",
+[{rabbit, [{auth_backends, [rabbit_auth_backend_ldap]}]}],[]}
+,
+
+{3,
+"auth_backends.1 = ldap
+auth_backends.2 = internal",
+
+[{rabbit, [
+ {auth_backends, [rabbit_auth_backend_ldap, rabbit_auth_backend_internal]}
+ ]
+ }],[]}
+
+,
+
+{4,
+"auth_backends.1 = ldap
+# uses module name instead of a short alias, \"http\"
+auth_backends.2 = rabbit_auth_backend_http",
+
+[{rabbit, [{auth_backends, [rabbit_auth_backend_ldap, rabbit_auth_backend_http]}]}],[]}
+
+,
+
+{5,
+"auth_backends.1.authn = internal
+# uses module name because this backend is from a 3rd party
+auth_backends.1.authz = rabbit_auth_backend_ip_range",
+[{rabbit, [{auth_backends, [{rabbit_auth_backend_internal, rabbit_auth_backend_ip_range}]}]}],[]}
+,
+{6,
+"auth_backends.1.authn = ldap
+auth_backends.1.authz = internal",
+[{rabbit, [{auth_backends, [{rabbit_auth_backend_ldap, rabbit_auth_backend_internal}]}]}],[]}
+,
+
+{7,
+"auth_backends.1.authn = ldap
+auth_backends.1.authz = internal
+auth_backends.2 = internal",
+[{rabbit, [
+ {auth_backends, [{rabbit_auth_backend_ldap, rabbit_auth_backend_internal},
+ rabbit_auth_backend_internal]}
+ ]
+ }],[]}
+,
+
+
+{8,
+"ssl_options.cacertfile = test/config_schema_SUITE_data/certs/cacert.pem
+ssl_options.certfile = test/config_schema_SUITE_data/certs/cert.pem
+ssl_options.keyfile = test/config_schema_SUITE_data/certs/key.pem
+ssl_options.verify = verify_peer
+ssl_options.fail_if_no_peer_cert = true",
+[
+ {rabbit, [{ssl_options, [{cacertfile, "test/config_schema_SUITE_data/certs/cacert.pem"},
+ {certfile, "test/config_schema_SUITE_data/certs/cert.pem"},
+ {keyfile, "test/config_schema_SUITE_data/certs/key.pem"},
+ {verify, verify_peer},
+ {fail_if_no_peer_cert, true}]}
+ ]}],[]}
+,
+
+{9,
+"listeners.tcp.default = 5673",
+[{rabbit, [{tcp_listeners, [5673]}]}],[]}
+,
+
+{10,
+"listeners.ssl = none",
+[{rabbit, [{ssl_listeners, []}]}],[]}
+,
+{11,
+"num_acceptors.ssl = 1",
+[{rabbit, [{num_ssl_acceptors, 1}]}],[]}
+,
+{12,
+"default_user = guest
+default_pass = guest
+default_user_tags.administrator = true
+default_permissions.configure = .*
+default_permissions.read = .*
+default_permissions.write = .*",
+[{rabbit, [
+{default_user, <<"guest">>},
+{default_pass, <<"guest">>},
+{default_user_tags, [administrator]},
+{default_permissions, [<<".*">>, <<".*">>, <<".*">>]}]}],[]}
+,
+{13,
+"cluster_nodes.disc.1 = rabbit@hostname1
+cluster_nodes.disc.2 = rabbit@hostname2",
+[{rabbit, [
+ {cluster_nodes, {[rabbit@hostname2,rabbit@hostname1], disc}}
+]}],[]}
+,
+{14,
+"tcp_listen_options.backlog = 128
+tcp_listen_options.nodelay = true
+tcp_listen_options.exit_on_close = false",
+[{rabbit, [{tcp_listen_options, [{backlog, 128},
+{nodelay, true},
+{exit_on_close, false}]}]}],[]}
+,
+{15,
+"auth_backends.1.authn = ldap
+auth_backends.1.authz = internal
+auth_backends.2 = internal",
+[{rabbit,[{auth_backends, [{rabbit_auth_backend_ldap, rabbit_auth_backend_internal},
+ rabbit_auth_backend_internal]}]}],[]}
+,
+{16,
+"rabbitmq_auth_backend_ldap.servers.1 = some_server
+ rabbitmq_auth_backend_ldap.servers.2 = some_other_server",
+[{rabbitmq_auth_backend_ldap, [{servers, ["some_server", "some_other_server"]}]}],
+[rabbitmq_auth_backend_ldap]}
+,
+{17,
+"rabbitmq_auth_backend_ldap.dn_lookup_attribute = userPrincipalName
+rabbitmq_auth_backend_ldap.dn_lookup_base = DC=gopivotal,DC=com
+rabbitmq_auth_backend_ldap.dn_lookup_bind = as_user",
+[{rabbitmq_auth_backend_ldap, [{dn_lookup_attribute, "userPrincipalName"},
+{dn_lookup_base, "DC=gopivotal,DC=com"},
+{dn_lookup_bind, as_user}]}],
+[rabbitmq_auth_backend_ldap]}
+,
+{18,
+"rabbitmq_auth_backend_ldap.dn_lookup_bind.user_dn = username
+rabbitmq_auth_backend_ldap.dn_lookup_bind.password = password",
+[{rabbitmq_auth_backend_ldap, [
+{dn_lookup_bind, {"username", "password"}}]}],
+[rabbitmq_auth_backend_ldap]}
+,
+{19,
+"rabbitmq_auth_backend_ldap.other_bind = anon",
+[{rabbitmq_auth_backend_ldap, [{other_bind, anon}]}],
+[rabbitmq_auth_backend_ldap]}
+,
+{20,
+"rabbitmq_auth_backend_ldap.other_bind = as_user",
+[{rabbitmq_auth_backend_ldap, [{other_bind, as_user}]}],
+[rabbitmq_auth_backend_ldap]}
+,
+{21,
+"rabbitmq_auth_backend_ldap.other_bind.user_dn = username
+rabbitmq_auth_backend_ldap.other_bind.password = password",
+[{rabbitmq_auth_backend_ldap, [{other_bind, {"username", "password"}}]}],
+[rabbitmq_auth_backend_ldap]}
+,
+{22,
+"listeners.tcp.default = 5672
+collect_statistics_interval = 10000
+management.http_log_dir = test/config_schema_SUITE_data/rabbit-mgmt
+management.rates_mode = basic",
+[{rabbit, [ {tcp_listeners, [5672]},
+ {collect_statistics_interval, 10000}]},
+ {rabbitmq_management, [ {http_log_dir, "test/config_schema_SUITE_data/rabbit-mgmt"},
+ {rates_mode, basic}]}
+],
+[rabbitmq_management]}
+,
+{23,
+"management.listener.port = 12345",
+[{rabbitmq_management, [{listener, [{port, 12345}]}]}],
+[rabbitmq_management]}
+,
+{24,
+"management.listener.port = 15671
+management.listener.ssl = true
+management.listener.ssl_opts.cacertfile = test/config_schema_SUITE_data/certs/cacert.pem
+management.listener.ssl_opts.certfile = test/config_schema_SUITE_data/certs/cert.pem
+management.listener.ssl_opts.keyfile = test/config_schema_SUITE_data/certs/key.pem",
+[{rabbitmq_management,
+ [{listener, [{port, 15671},
+ {ssl, true},
+ {ssl_opts, [{cacertfile, "test/config_schema_SUITE_data/certs/cacert.pem"},
+ {certfile, "test/config_schema_SUITE_data/certs/cert.pem"},
+ {keyfile, "test/config_schema_SUITE_data/certs/key.pem"}]}
+ ]}
+ ]}
+],
+[rabbitmq_management]}
+,
+{25,
+"management.sample_retention_policies.global.minute = 5
+management.sample_retention_policies.global.hour = 60
+management.sample_retention_policies.global.day = 1200
+
+management.sample_retention_policies.basic.minute = 5
+management.sample_retention_policies.basic.hour = 60
+
+management.sample_retention_policies.detailed.10 = 5",
+[{rabbitmq_management,[
+ {sample_retention_policies,
+ %% List of {MaxAgeInSeconds, SampleEveryNSeconds}
+ [{global, [{60, 5}, {3600, 60}, {86400, 1200}]},
+ {basic, [{60, 5}, {3600, 60}]},
+ {detailed, [{10, 5}]}]}
+]}],
+[rabbitmq_management]}
+,
+{26,
+"vm_memory_high_watermark.absolute = 1073741824",
+[{rabbit, [{vm_memory_high_watermark, {absolute, 1073741824}}]}],[]}
+,
+{27,
+"vm_memory_high_watermark.absolute = 1024MB",
+[{rabbit, [{vm_memory_high_watermark, {absolute, "1024MB"}}]}],[]}
+,
+{28,
+"vm_memory_high_watermark_paging_ratio = 0.75
+vm_memory_high_watermark.relative = 0.4",
+[{rabbit, [{vm_memory_high_watermark_paging_ratio, 0.75},
+ {vm_memory_high_watermark, 0.4}]}],[]}
+,
+{29,
+"listeners.tcp.default = 5672
+mqtt.default_user = guest
+mqtt.default_pass = guest
+mqtt.allow_anonymous = true
+mqtt.vhost = /
+mqtt.exchange = amq.topic
+mqtt.subscription_ttl = 1800000
+mqtt.prefetch = 10
+mqtt.listeners.ssl = none
+## Default MQTT with TLS port is 8883
+# mqtt.listeners.ssl.default = 8883
+mqtt.listeners.tcp.default = 1883
+mqtt.tcp_listen_options.backlog = 128
+mqtt.tcp_listen_options.nodelay = true",
+[{rabbit, [{tcp_listeners, [5672]}]},
+ {rabbitmq_mqtt, [{default_user, <<"guest">>},
+ {default_pass, <<"guest">>},
+ {allow_anonymous, true},
+ {vhost, <<"/">>},
+ {exchange, <<"amq.topic">>},
+ {subscription_ttl, 1800000},
+ {prefetch, 10},
+ {ssl_listeners, []},
+ %% Default MQTT with TLS port is 8883
+ %% {ssl_listeners, [8883]}
+ {tcp_listeners, [1883]},
+ {tcp_listen_options, [{backlog, 128},
+ {nodelay, true}]}]}
+ ],
+[rabbitmq_mqtt]}
+,
+{30,
+"ssl_options.cacertfile = test/config_schema_SUITE_data/certs/cacert.pem
+ssl_options.certfile = test/config_schema_SUITE_data/certs/cert.pem
+ssl_options.keyfile = test/config_schema_SUITE_data/certs/key.pem
+ssl_options.verify = verify_peer
+ssl_options.fail_if_no_peer_cert = true
+
+mqtt.listeners.ssl.default = 8883
+mqtt.listeners.tcp.default = 1883",
+[{rabbit, [
+ {ssl_options, [{cacertfile, "test/config_schema_SUITE_data/certs/cacert.pem"},
+ {certfile, "test/config_schema_SUITE_data/certs/cert.pem"},
+ {keyfile, "test/config_schema_SUITE_data/certs/key.pem"},
+ {verify, verify_peer},
+ {fail_if_no_peer_cert, true}]}
+ ]},
+ {rabbitmq_mqtt, [
+ {ssl_listeners, [8883]},
+ {tcp_listeners, [1883]}
+ ]}
+ ],
+[rabbitmq_mqtt]}
+,
+{31,
+"mqtt.ssl_cert_login = true",
+[{rabbitmq_mqtt, [{ssl_cert_login, true}]}], [rabbitmq_mqtt]}
+,
+
+{32,
+"ssl_cert_login_from = common_name",
+[{rabbit, [{ssl_cert_login_from, common_name}]}], [rabbitmq_mqtt]}
+,
+
+
+{33,
+"listeners.tcp.default = 5672
+mqtt.default_user = guest
+mqtt.default_pass = guest
+mqtt.allow_anonymous = true
+mqtt.vhost = /
+mqtt.exchange = amq.topic
+mqtt.subscription_ttl = undefined
+mqtt.prefetch = 10",
+[{rabbit, [{tcp_listeners, [5672]}]},
+ {rabbitmq_mqtt, [{default_user, <<"guest">>},
+ {default_pass, <<"guest">>},
+ {allow_anonymous, true},
+ {vhost, <<"/">>},
+ {exchange, <<"amq.topic">>},
+ {subscription_ttl, undefined},
+ {prefetch, 10}]}
+ ],
+[rabbitmq_mqtt]}
+,
+{34,
+"mqtt.default_user = guest
+mqtt.default_pass = guest
+mqtt.allow_anonymous = true
+mqtt.vhost = /
+mqtt.exchange = amq.topic
+mqtt.subscription_ttl = 1800000
+mqtt.prefetch = 10
+## use DETS (disk-based) store for retained messages
+mqtt.retained_message_store = rabbit_mqtt_retained_msg_store_dets
+## only used by DETS store
+mqtt.retained_message_store_dets_sync_interval = 2000
+
+mqtt.listeners.ssl = none
+mqtt.listeners.tcp.default = 1883",
+[{rabbitmq_mqtt, [{default_user, <<"guest">>},
+ {default_pass, <<"guest">>},
+ {allow_anonymous, true},
+ {vhost, <<"/">>},
+ {exchange, <<"amq.topic">>},
+ {subscription_ttl, 1800000},
+ {prefetch, 10},
+ %% use DETS (disk-based) store for retained messages
+ {retained_message_store, rabbit_mqtt_retained_msg_store_dets},
+ %% only used by DETS store
+ {retained_message_store_dets_sync_interval, 2000},
+ {ssl_listeners, []},
+ {tcp_listeners, [1883]}]}
+ ],
+[rabbitmq_mqtt]}
+,
+
+{35,
+"listeners.tcp.1 = 192.168.1.99:5672",
+[
+ {rabbit, [
+ {tcp_listeners, [{"192.168.1.99", 5672}]}
+ ]}
+], []}
+,
+{36,
+"listeners.tcp.1 = 127.0.0.1:5672
+listeners.tcp.2 = ::1:5672",
+[
+ {rabbit, [
+ {tcp_listeners, [{"127.0.0.1", 5672},
+ {"::1", 5672}]}
+ ]}
+], []}
+,
+{37,
+"listeners.tcp.1 = :::5672",
+[
+ {rabbit, [
+ {tcp_listeners, [{"::", 5672}]}
+ ]}
+], []}
+,
+{38,
+"listeners.tcp.1 = 192.168.1.99:5672",
+[
+ {rabbit, [
+ {tcp_listeners, [{"192.168.1.99", 5672}]}
+ ]}
+], []}
+,
+{39,
+"listeners.tcp.1 = fe80::2acf:e9ff:fe17:f97b:5672",
+[
+ {rabbit, [
+ {tcp_listeners, [{"fe80::2acf:e9ff:fe17:f97b", 5672}]}
+ ]}
+], []}
+,
+{40,
+"tcp_listen_options.backlog = 128
+tcp_listen_options.nodelay = true
+tcp_listen_options.sndbuf = 196608
+tcp_listen_options.recbuf = 196608",
+[
+ {rabbit, [
+ {tcp_listen_options, [
+ {backlog, 128},
+ {nodelay, true},
+ {sndbuf, 196608},
+ {recbuf, 196608}
+ ]}
+ ]}
+], []}
+,
+
+{42,
+"tcp_listen_options.backlog = 4096
+tcp_listen_options.nodelay = true",
+[
+ {kernel, [
+ {inet_default_connect_options, [{nodelay, true}]},
+ {inet_default_listen_options, [{nodelay, true}]}
+ ]}]
+,
+[
+ {kernel, [
+ {inet_default_connect_options, [{nodelay, true}]},
+ {inet_default_listen_options, [{nodelay, true}]}
+ ]},
+ {rabbit, [
+ {tcp_listen_options, [
+ {backlog, 4096},
+ {nodelay, true}
+ ]}
+ ]}
+], []}
+,
+
+{43,
+"tcp_listen_options.backlog = 4096
+tcp_listen_options.nodelay = true",
+[
+ {rabbit, [
+ {tcp_listen_options, [
+ {backlog, 4096},
+ {nodelay, true}
+ ]}
+ ]}
+], []}
+,
+
+{44,
+"ssl_handshake_timeout = 10000",
+[
+ {rabbit, [
+ %% 10 seconds
+ {ssl_handshake_timeout, 10000}
+ ]}
+], []}
+,
+
+{45,
+"cluster_partition_handling = pause_if_all_down
+
+## Recover strategy. Can be either 'autoheal' or 'ignore'
+cluster_partition_handling.pause_if_all_down.recover = ignore
+
+## Node names to check
+cluster_partition_handling.pause_if_all_down.nodes.1 = rabbit@myhost1
+cluster_partition_handling.pause_if_all_down.nodes.2 = rabbit@myhost2",
+[{rabbit, [{cluster_partition_handling, {pause_if_all_down, [rabbit@myhost2, rabbit@myhost1], ignore}}]}], []}
+,
+{46,
+"cluster_partition_handling = autoheal",
+[{rabbit, [{cluster_partition_handling, autoheal}]}], []}
+,
+{47,
+"password_hashing_module = rabbit_password_hashing_sha512",
+[
+ {rabbit, [{password_hashing_module, rabbit_password_hashing_sha512}]}
+],[]}
+,
+
+{48,
+"listeners.ssl.1 = 5671
+ssl_options.cacertfile = test/config_schema_SUITE_data/certs/cacert.pem
+ssl_options.certfile = test/config_schema_SUITE_data/certs/cert.pem
+ssl_options.keyfile = test/config_schema_SUITE_data/certs/key.pem
+ssl_options.verify = verify_peer
+ssl_options.fail_if_no_peer_cert = false"
+,
+[
+ {rabbit, [
+ {ssl_listeners, [5671]},
+ {ssl_options, [{cacertfile,"test/config_schema_SUITE_data/certs/cacert.pem"},
+ {certfile,"test/config_schema_SUITE_data/certs/cert.pem"},
+ {keyfile,"test/config_schema_SUITE_data/certs/key.pem"},
+ {verify,verify_peer},
+ {fail_if_no_peer_cert,false}]}
+ ]}
+],[]}
+,
+
+
+{49,
+"listeners.ssl.1 = 5671
+ssl_options.cacertfile = test/config_schema_SUITE_data/certs/cacert.pem
+ssl_options.certfile = test/config_schema_SUITE_data/certs/cert.pem
+ssl_options.keyfile = test/config_schema_SUITE_data/certs/key.pem
+ssl_options.password = t0p$3kRe7",
+[
+ {rabbit, [
+ {ssl_listeners, [5671]},
+ {ssl_options, [{cacertfile,"test/config_schema_SUITE_data/certs/cacert.pem"},
+ {certfile, "test/config_schema_SUITE_data/certs/cert.pem"},
+ {keyfile, "test/config_schema_SUITE_data/certs/key.pem"},
+ {password, "t0p$3kRe7"}
+ ]}
+ ]}
+],[]}
+,
+
+{50,
+"listeners.ssl.1 = 5671
+ssl_options.cacertfile = test/config_schema_SUITE_data/certs/cacert.pem
+ssl_options.certfile = test/config_schema_SUITE_data/certs/cert.pem
+ssl_options.keyfile = test/config_schema_SUITE_data/certs/key.pem
+ssl_options.versions.tls1_2 = tlsv1.2
+ssl_options.versions.tls1_1 = tlsv1.1
+ssl_options.versions.tls1 = tlsv1",
+[{ssl, [{versions, ['tlsv1.2', 'tlsv1.1', tlsv1]}]}],
+[{ssl, [{versions, ['tlsv1.2', 'tlsv1.1', tlsv1]}]},
+ {rabbit, [
+ {ssl_listeners, [5671]},
+ {ssl_options, [{cacertfile,"test/config_schema_SUITE_data/certs/cacert.pem"},
+ {certfile, "test/config_schema_SUITE_data/certs/cert.pem"},
+ {keyfile, "test/config_schema_SUITE_data/certs/key.pem"},
+ {versions, ['tlsv1.2', 'tlsv1.1', tlsv1]}
+ ]}
+ ]}
+],[]}
+,
+{51,
+"listeners.ssl.1 = 5671
+ssl_options.cacertfile = test/config_schema_SUITE_data/certs/cacert.pem
+ssl_options.certfile = test/config_schema_SUITE_data/certs/cert.pem
+ssl_options.keyfile = test/config_schema_SUITE_data/certs/key.pem
+ssl_options.versions.tls1_2 = tlsv1.2
+ssl_options.versions.tls1_1 = tlsv1.1",
+[{ssl, [{versions, ['tlsv1.2', 'tlsv1.1']}]}],
+[
+ {ssl, [{versions, ['tlsv1.2', 'tlsv1.1']}]},
+ {rabbit, [
+ {ssl_listeners, [5671]},
+ {ssl_options, [{cacertfile,"test/config_schema_SUITE_data/certs/cacert.pem"},
+ {certfile, "test/config_schema_SUITE_data/certs/cert.pem"},
+ {keyfile, "test/config_schema_SUITE_data/certs/key.pem"},
+ {versions, ['tlsv1.2', 'tlsv1.1']}
+ ]}
+ ]}
+],[]}
+,
+{52,
+"listeners.ssl.1 = 5671
+ssl_allow_poodle_attack = true
+ssl_options.cacertfile = test/config_schema_SUITE_data/certs/cacert.pem
+ssl_options.certfile = test/config_schema_SUITE_data/certs/cert.pem
+ssl_options.keyfile = test/config_schema_SUITE_data/certs/key.pem
+ssl_options.verify = verify_peer
+ssl_options.fail_if_no_peer_cert = false",
+[
+ {rabbit, [
+ {ssl_listeners, [5671]},
+ {ssl_allow_poodle_attack, true},
+ {ssl_options, [{cacertfile,"test/config_schema_SUITE_data/certs/cacert.pem"},
+ {certfile,"test/config_schema_SUITE_data/certs/cert.pem"},
+ {keyfile,"test/config_schema_SUITE_data/certs/key.pem"},
+ {verify,verify_peer},
+ {fail_if_no_peer_cert,false}]}
+ ]}
+],[]}
+,
+{53,
+"listeners.ssl.1 = 5671
+ssl_options.cacertfile = test/config_schema_SUITE_data/certs/cacert.pem
+ssl_options.certfile = test/config_schema_SUITE_data/certs/cert.pem
+ssl_options.keyfile = test/config_schema_SUITE_data/certs/key.pem
+ssl_options.depth = 2
+ssl_options.verify = verify_peer
+ssl_options.fail_if_no_peer_cert = false",
+[
+ {rabbit, [
+ {ssl_listeners, [5671]},
+ {ssl_options, [{cacertfile,"test/config_schema_SUITE_data/certs/cacert.pem"},
+ {certfile,"test/config_schema_SUITE_data/certs/cert.pem"},
+ {keyfile,"test/config_schema_SUITE_data/certs/key.pem"},
+ {depth, 2},
+ {verify,verify_peer},
+ {fail_if_no_peer_cert,false}]}
+ ]}
+],[]}
+,
+{54,
+"stomp.listeners.tcp.1 = 12345",
+[{rabbitmq_stomp, [{tcp_listeners, [12345]}]}],[rabbitmq_stomp]}
+,
+{55,
+"stomp.listeners.tcp.1 = 127.0.0.1:61613
+stomp.listeners.tcp.2 = ::1:61613",
+[{rabbitmq_stomp, [{tcp_listeners, [{"127.0.0.1", 61613},
+ {"::1", 61613}]}]}],[rabbitmq_stomp]}
+,
+{56,
+"ssl_options.cacertfile = test/config_schema_SUITE_data/certs/cacert.pem
+ssl_options.certfile = test/config_schema_SUITE_data/certs/cert.pem
+ssl_options.keyfile = test/config_schema_SUITE_data/certs/key.pem
+ssl_options.verify = verify_peer
+ssl_options.fail_if_no_peer_cert = true
+
+stomp.listeners.tcp.1 = 61613
+stomp.listeners.ssl.1 = 61614",
+[{rabbit,[
+{ssl_options, [{cacertfile, "test/config_schema_SUITE_data/certs/cacert.pem"},
+ {certfile, "test/config_schema_SUITE_data/certs/cert.pem"},
+ {keyfile, "test/config_schema_SUITE_data/certs/key.pem"},
+ {verify, verify_peer},
+ {fail_if_no_peer_cert, true}]}]},
+{rabbitmq_stomp, [{tcp_listeners, [61613]},
+{ssl_listeners, [61614]}]}
+ ],[]}
+,
+
+{57,
+"stomp.default_user = guest
+stomp.default_pass = guest",
+[{rabbitmq_stomp, [{default_user, [{login, "guest"},{passcode, "guest"}]}]}],
+[rabbitmq_stomp]}
+,
+{58,
+"stomp.ssl_cert_login = true",
+[{rabbitmq_stomp, [{ssl_cert_login, true}]}],
+[rabbitmq_stomp]}
+,
+{59,
+"ssl_cert_login_from = common_name",
+[{rabbit, [{ssl_cert_login_from, common_name}]}], []}
+,
+{60,
+"stomp.default_user = guest
+stomp.default_pass = guest
+stomp.implicit_connect = true",
+[{rabbitmq_stomp, [{default_user,[{login, "guest"}, {passcode, "guest"}]},{implicit_connect, true}]}],
+[rabbitmq_stomp]}
+,
+{61,
+"stomp.default_vhost = /",
+[{rabbitmq_stomp, [{default_vhost, <<"/">>}]}],
+[rabbitmq_stomp]}
+,
+{62,
+"management.listener.port = 15672
+management.listener.ip = 127.0.0.1",
+[{rabbitmq_management,
+ [{listener, [{port, 15672},
+ {ip, "127.0.0.1"}
+ ]}
+ ]}
+],
+[rabbitmq_management]}
+,
+{63,
+"management.listener.port = 15672
+management.listener.ssl = true
+
+management.listener.ssl_opts.cacertfile = test/config_schema_SUITE_data/certs/cacert.pem
+management.listener.ssl_opts.certfile = test/config_schema_SUITE_data/certs/cert.pem
+management.listener.ssl_opts.keyfile = test/config_schema_SUITE_data/certs/key.pem",
+[{rabbitmq_management,
+ [{listener, [{port, 15672},
+ {ssl, true},
+ {ssl_opts, [{cacertfile, "test/config_schema_SUITE_data/certs/cacert.pem"},
+ {certfile, "test/config_schema_SUITE_data/certs/cert.pem"},
+ {keyfile, "test/config_schema_SUITE_data/certs/key.pem"}]}
+ ]}
+ ]}
+],
+[rabbitmq_management]},
+{64,
+"web_stomp.port = 12345",
+[{rabbitmq_web_stomp, [{port, 12345}]}],
+[rabbitmq_web_stomp]},
+{65,
+"web_stomp.ssl.port = 15671
+web_stomp.ssl.backlog = 1024
+web_stomp.ssl.certfile = test/config_schema_SUITE_data/certs/cert.pem
+web_stomp.ssl.keyfile = test/config_schema_SUITE_data/certs/key.pem
+web_stomp.ssl.cacertfile = test/config_schema_SUITE_data/certs/cacert.pem
+web_stomp.ssl.password = changeme",
+[{rabbitmq_web_stomp,
+ [{ssl_config, [{port, 15671},
+ {backlog, 1024},
+ {certfile, "test/config_schema_SUITE_data/certs/cert.pem"},
+ {keyfile, "test/config_schema_SUITE_data/certs/key.pem"},
+ {cacertfile, "test/config_schema_SUITE_data/certs/cacert.pem"},
+ {password, "changeme"}]}]}],
+[rabbitmq_web_stomp]},
+{66,
+"web_stomp.ws_frame = binary",
+[{rabbitmq_web_stomp, [{ws_frame, binary}]}],
+[rabbitmq_web_stomp]},
+{67,
+"web_stomp.cowboy_opts.max_keepalive = 10",
+[{rabbitmq_web_stomp,[{cowboy_opts, [{max_keepalive, 10}]}]}],
+[rabbitmq_web_stomp]},
+{68,
+"web_stomp.sockjs_opts.url = https://cdn.jsdelivr.net/sockjs/0.3.4/sockjs.min.js",
+[{rabbitmq_web_stomp,
+ [{sockjs_opts, [{sockjs_url, "https://cdn.jsdelivr.net/sockjs/0.3.4/sockjs.min.js"}]}]}],
+[rabbitmq_web_stomp]},
+{69,
+"auth_backends.1 = http
+rabbitmq_auth_backend_http.user_path = http://some-server/auth/user
+rabbitmq_auth_backend_http.vhost_path = http://some-server/auth/vhost
+rabbitmq_auth_backend_http.resource_path = http://some-server/auth/resource",
+[{rabbit, [{auth_backends, [rabbit_auth_backend_http]}]},
+ {rabbitmq_auth_backend_http,
+ [{user_path, "http://some-server/auth/user"},
+ {vhost_path, "http://some-server/auth/vhost"},
+ {resource_path, "http://some-server/auth/resource"}]}],
+[rabbitmq_auth_backend_http]},
+{70,
+"tcp_listen_options.linger.on = true
+tcp_listen_options.linger.timeout = 100",
+[{rabbit, [{tcp_listen_options, [{linger, {true, 100}}]}]}],
+[]},
+{72,
+"tcp_listen_options.linger.on = false
+tcp_listen_options.linger.timeout = 100",
+[{rabbit, [{tcp_listen_options, [{linger, {false, 100}}]}]}],
+[]},
+{73,
+"tcp_listen_options.linger.on = true",
+[{rabbit, [{tcp_listen_options, [{linger, {true, 0}}]}]}],
+[]},
+{74,
+"tcp_listen_options.linger.timeout = 100",
+[{rabbit, [{tcp_listen_options, [{linger, {false, 100}}]}]}],
+[]}
+].
diff --git a/test/dummy_interceptor.erl b/test/dummy_interceptor.erl
new file mode 100644
index 0000000000..6d510a3073
--- /dev/null
+++ b/test/dummy_interceptor.erl
@@ -0,0 +1,26 @@
+-module(dummy_interceptor).
+
+-behaviour(rabbit_channel_interceptor).
+
+-include_lib("rabbit_common/include/rabbit.hrl").
+-include_lib("rabbit_common/include/rabbit_framing.hrl").
+
+
+-compile(export_all).
+
+init(_Ch) ->
+ undefined.
+
+description() ->
+ [{description,
+ <<"Empties payload on publish">>}].
+
+intercept(#'basic.publish'{} = Method, Content, _IState) ->
+ Content2 = Content#content{payload_fragments_rev = []},
+ {Method, Content2};
+
+intercept(Method, Content, _VHost) ->
+ {Method, Content}.
+
+applies_to() ->
+ ['basic.publish'].
diff --git a/test/dynamic_ha_SUITE.erl b/test/dynamic_ha_SUITE.erl
index 5872d97d4c..bba7fad707 100644
--- a/test/dynamic_ha_SUITE.erl
+++ b/test/dynamic_ha_SUITE.erl
@@ -31,6 +31,7 @@
%% The first two are change_policy, the last two are change_cluster
-include_lib("common_test/include/ct.hrl").
+-include_lib("proper/include/proper.hrl").
-include_lib("eunit/include/eunit.hrl").
-include_lib("amqp_client/include/amqp_client.hrl").
@@ -61,6 +62,10 @@ groups() ->
{cluster_size_3, [], [
change_policy,
rapid_change
+ % FIXME: Re-enable those tests when the know issues are
+ % fixed.
+ %failing_random_policies,
+ %random_policy
]}
]}
].
@@ -137,7 +142,7 @@ change_policy(Config) ->
assert_slaves(A, ?QNAME, {A, [C]}, [{A, [B, C]}]),
%% Clear the policy, and we go back to non-mirrored
- rabbit_ct_broker_helpers:clear_policy(Config, A, ?POLICY),
+ ok = rabbit_ct_broker_helpers:clear_policy(Config, A, ?POLICY),
assert_slaves(A, ?QNAME, {A, ''}),
%% Test switching "away" from an unmirrored node
@@ -206,7 +211,7 @@ rapid_loop(Config, Node, MRef) ->
after 0 ->
rabbit_ct_broker_helpers:set_ha_policy(Config, Node, ?POLICY,
<<"all">>),
- rabbit_ct_broker_helpers:clear_policy(Config, Node, ?POLICY),
+ ok = rabbit_ct_broker_helpers:clear_policy(Config, Node, ?POLICY),
rapid_loop(Config, Node, MRef)
end.
@@ -253,6 +258,23 @@ promote_on_shutdown(Config) ->
durable = true}),
ok.
+random_policy(Config) ->
+ run_proper(fun prop_random_policy/1, [Config]).
+
+failing_random_policies(Config) ->
+ [A, B | _] = Nodes = rabbit_ct_broker_helpers:get_node_configs(Config,
+ nodename),
+ %% Those set of policies were found as failing by PropEr in the
+ %% `random_policy` test above. We add them explicitely here to make
+ %% sure they get tested.
+ ?assertEqual(true, test_random_policy(Config, Nodes,
+ [{nodes, [A, B]}, {nodes, [A]}])),
+ ?assertEqual(true, test_random_policy(Config, Nodes,
+ [{exactly, 3}, undefined, all, {nodes, [B]}])),
+ ?assertEqual(true, test_random_policy(Config, Nodes,
+ [all, undefined, {exactly, 2}, all, {exactly, 3}, {exactly, 3},
+ undefined, {exactly, 3}, all])).
+
%%----------------------------------------------------------------------------
assert_slaves(RPCNode, QName, Exp) ->
@@ -327,3 +349,131 @@ get_stacktrace() ->
_:e ->
erlang:get_stacktrace()
end.
+
+%%----------------------------------------------------------------------------
+run_proper(Fun, Args) ->
+ ?assertEqual(true,
+ proper:counterexample(erlang:apply(Fun, Args),
+ [{numtests, 25},
+ {on_output, fun(F, A) -> ct:pal(?LOW_IMPORTANCE, F, A) end}])).
+
+prop_random_policy(Config) ->
+ Nodes = rabbit_ct_broker_helpers:get_node_configs(
+ Config, nodename),
+ ?FORALL(
+ Policies, non_empty(list(policy_gen(Nodes))),
+ test_random_policy(Config, Nodes, Policies)).
+
+test_random_policy(Config, Nodes, Policies) ->
+ [NodeA | _] = Nodes,
+ Ch = rabbit_ct_client_helpers:open_channel(Config, NodeA),
+ amqp_channel:call(Ch, #'queue.declare'{queue = ?QNAME}),
+ %% Add some load so mirrors can be busy synchronising
+ rabbit_ct_client_helpers:publish(Ch, ?QNAME, 100000),
+ %% Apply policies in parallel on all nodes
+ apply_in_parallel(Config, Nodes, Policies),
+ %% Give it some time to generate all internal notifications
+ timer:sleep(2000),
+ %% Check the result
+ Result = wait_for_last_policy(?QNAME, NodeA, Policies, 30),
+ %% Cleanup
+ amqp_channel:call(Ch, #'queue.delete'{queue = ?QNAME}),
+ _ = rabbit_ct_broker_helpers:clear_policy(Config, NodeA, ?POLICY),
+ Result.
+
+apply_in_parallel(Config, Nodes, Policies) ->
+ Self = self(),
+ [spawn_link(fun() ->
+ [begin
+ apply_policy(Config, N, Policy)
+ end || Policy <- Policies],
+ Self ! parallel_task_done
+ end) || N <- Nodes],
+ [receive
+ parallel_task_done ->
+ ok
+ end || _ <- Nodes].
+
+%% Proper generators
+policy_gen(Nodes) ->
+ %% Stop mirroring needs to be called often to trigger rabbitmq-server#803
+ frequency([{3, undefined},
+ {1, all},
+ {1, {nodes, nodes_gen(Nodes)}},
+ {1, {exactly, choose(1, 3)}}
+ ]).
+
+nodes_gen(Nodes) ->
+ ?LET(List, non_empty(list(oneof(Nodes))),
+ sets:to_list(sets:from_list(List))).
+
+%% Checks
+wait_for_last_policy(QueueName, NodeA, TestedPolicies, Tries) ->
+ %% Ensure the owner/master is able to process a call request,
+ %% which means that all pending casts have been processed.
+ %% Use the information returned by owner/master to verify the
+ %% test result
+ Info = find_queue(QueueName, NodeA),
+ Pid = proplists:get_value(pid, Info),
+ Node = node(Pid),
+ %% Gets owner/master
+ case rpc:call(Node, gen_server, call, [Pid, info], 5000) of
+ {badrpc, _} ->
+ %% The queue is probably being migrated to another node.
+ %% Let's wait a bit longer.
+ timer:sleep(1000),
+ wait_for_last_policy(QueueName, NodeA, TestedPolicies, Tries - 1);
+ FinalInfo ->
+ %% The last policy is the final state
+ LastPolicy = lists:last(TestedPolicies),
+ case verify_policy(LastPolicy, FinalInfo) of
+ true ->
+ true;
+ false when Tries =:= 1 ->
+ Policies = rpc:call(Node, rabbit_policy, list, [], 5000),
+ ct:pal(
+ "Last policy not applied:~n"
+ " Queue node: ~s (~p)~n"
+ " Queue info: ~p~n"
+ " Configured policies: ~p~n"
+ " Tested policies: ~p",
+ [Node, Pid, FinalInfo, Policies, TestedPolicies]),
+ false;
+ false ->
+ timer:sleep(1000),
+ wait_for_last_policy(QueueName, NodeA, TestedPolicies,
+ Tries - 1)
+ end
+ end.
+
+verify_policy(undefined, Info) ->
+ %% If the queue is not mirrored, it returns ''
+ '' == proplists:get_value(slave_pids, Info);
+verify_policy(all, Info) ->
+ 2 == length(proplists:get_value(slave_pids, Info));
+verify_policy({exactly, 1}, Info) ->
+ %% If the queue is mirrored, it returns a list
+ [] == proplists:get_value(slave_pids, Info);
+verify_policy({exactly, N}, Info) ->
+ (N - 1) == length(proplists:get_value(slave_pids, Info));
+verify_policy({nodes, Nodes}, Info) ->
+ Master = node(proplists:get_value(pid, Info)),
+ Slaves = [node(P) || P <- proplists:get_value(slave_pids, Info)],
+ lists:sort(Nodes) == lists:sort([Master | Slaves]).
+
+%% Policies
+apply_policy(Config, N, undefined) ->
+ _ = rabbit_ct_broker_helpers:clear_policy(Config, N, ?POLICY);
+apply_policy(Config, N, all) ->
+ rabbit_ct_broker_helpers:set_ha_policy(
+ Config, N, ?POLICY, <<"all">>,
+ [{<<"ha-sync-mode">>, <<"automatic">>}]);
+apply_policy(Config, N, {nodes, Nodes}) ->
+ NNodes = [rabbit_misc:atom_to_binary(Node) || Node <- Nodes],
+ rabbit_ct_broker_helpers:set_ha_policy(
+ Config, N, ?POLICY, {<<"nodes">>, NNodes},
+ [{<<"ha-sync-mode">>, <<"automatic">>}]);
+apply_policy(Config, N, {exactly, Exactly}) ->
+ rabbit_ct_broker_helpers:set_ha_policy(
+ Config, N, ?POLICY, {<<"exactly">>, Exactly},
+ [{<<"ha-sync-mode">>, <<"automatic">>}]).
diff --git a/test/per_vhost_connection_limit_SUITE.erl b/test/per_vhost_connection_limit_SUITE.erl
new file mode 100644
index 0000000000..4fae129a35
--- /dev/null
+++ b/test/per_vhost_connection_limit_SUITE.erl
@@ -0,0 +1,795 @@
+%% 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 Developer of the Original Code is GoPivotal, Inc.
+%% Copyright (c) 2011-2016 Pivotal Software, Inc. All rights reserved.
+%%
+
+-module(per_vhost_connection_limit_SUITE).
+
+-include_lib("common_test/include/ct.hrl").
+-include_lib("amqp_client/include/amqp_client.hrl").
+-include_lib("eunit/include/eunit.hrl").
+
+-compile(export_all).
+
+all() ->
+ [
+ {group, cluster_size_1},
+ {group, cluster_size_2}
+ ].
+
+groups() ->
+ [
+ {cluster_size_1, [], [
+ most_basic_single_node_connection_count,
+ single_node_single_vhost_connection_count,
+ single_node_multiple_vhosts_connection_count,
+ single_node_list_in_vhost,
+ single_node_single_vhost_limit,
+ single_node_single_vhost_zero_limit,
+ single_node_multiple_vhosts_limit,
+ single_node_multiple_vhosts_zero_limit,
+ single_node_vhost_deletion_forces_connection_closure
+ ]},
+ {cluster_size_2, [], [
+ most_basic_cluster_connection_count,
+ cluster_single_vhost_connection_count,
+ cluster_multiple_vhosts_connection_count,
+ cluster_node_restart_connection_count,
+ cluster_node_list_on_node,
+ cluster_single_vhost_limit,
+ cluster_single_vhost_limit2,
+ cluster_single_vhost_zero_limit,
+ cluster_multiple_vhosts_zero_limit,
+ cluster_vhost_deletion_forces_connection_closure
+ ]},
+ {cluster_rename, [], [
+ vhost_limit_after_node_renamed
+ ]}
+ ].
+
+suite() ->
+ [
+ %% If a test hangs, no need to wait for 30 minutes.
+ {timetrap, {minutes, 8}}
+ ].
+
+%% see partitions_SUITE
+-define(DELAY, 9000).
+
+%% -------------------------------------------------------------------
+%% Testsuite setup/teardown.
+%% -------------------------------------------------------------------
+
+init_per_suite(Config) ->
+ rabbit_ct_helpers:log_environment(),
+ rabbit_ct_helpers:run_setup_steps(Config, [
+ fun rabbit_ct_broker_helpers:enable_dist_proxy_manager/1
+ ]).
+
+end_per_suite(Config) ->
+ rabbit_ct_helpers:run_teardown_steps(Config).
+
+init_per_group(cluster_size_1, Config) ->
+ init_per_multinode_group(cluster_size_1, Config, 1);
+init_per_group(cluster_size_2, Config) ->
+ init_per_multinode_group(cluster_size_2, Config, 2);
+init_per_group(cluster_rename, Config) ->
+ init_per_multinode_group(cluster_rename, Config, 2).
+
+init_per_multinode_group(Group, Config, NodeCount) ->
+ Suffix = rabbit_ct_helpers:testcase_absname(Config, "", "-"),
+ Config1 = rabbit_ct_helpers:set_config(Config, [
+ {rmq_nodes_count, NodeCount},
+ {rmq_nodename_suffix, Suffix}
+ ]),
+ case Group of
+ cluster_rename ->
+ % The broker is managed by {init,end}_per_testcase().
+ Config1;
+ _ ->
+ rabbit_ct_helpers:run_steps(Config1,
+ rabbit_ct_broker_helpers:setup_steps() ++
+ rabbit_ct_client_helpers:setup_steps())
+ end.
+
+end_per_group(cluster_rename, Config) ->
+ % The broker is managed by {init,end}_per_testcase().
+ Config;
+end_per_group(_Group, Config) ->
+ rabbit_ct_helpers:run_steps(Config,
+ rabbit_ct_client_helpers:teardown_steps() ++
+ rabbit_ct_broker_helpers:teardown_steps()).
+
+init_per_testcase(vhost_limit_after_node_renamed = Testcase, Config) ->
+ rabbit_ct_helpers:testcase_started(Config, Testcase),
+ rabbit_ct_helpers:run_steps(Config,
+ rabbit_ct_broker_helpers:setup_steps() ++
+ rabbit_ct_client_helpers:setup_steps());
+init_per_testcase(Testcase, Config) ->
+ rabbit_ct_helpers:testcase_started(Config, Testcase),
+ clear_all_connection_tracking_tables(Config),
+ Config.
+
+end_per_testcase(vhost_limit_after_node_renamed = Testcase, Config) ->
+ Config1 = ?config(save_config, Config),
+ rabbit_ct_helpers:run_steps(Config1,
+ rabbit_ct_client_helpers:teardown_steps() ++
+ rabbit_ct_broker_helpers:teardown_steps()),
+ rabbit_ct_helpers:testcase_finished(Config1, Testcase);
+end_per_testcase(Testcase, Config) ->
+ clear_all_connection_tracking_tables(Config),
+ rabbit_ct_helpers:testcase_finished(Config, Testcase).
+
+clear_all_connection_tracking_tables(Config) ->
+ [rabbit_ct_broker_helpers:rpc(Config,
+ N,
+ rabbit_connection_tracking,
+ clear_tracked_connection_tables_for_this_node,
+ []) || N <- rabbit_ct_broker_helpers:get_node_configs(Config, nodename)].
+
+%% -------------------------------------------------------------------
+%% Test cases.
+%% -------------------------------------------------------------------
+
+most_basic_single_node_connection_count(Config) ->
+ VHost = <<"/">>,
+ ?assertEqual(0, count_connections_in(Config, VHost)),
+ [Conn] = open_connections(Config, [0]),
+ ?assertEqual(1, count_connections_in(Config, VHost)),
+ close_connections([Conn]),
+ ?assertEqual(0, count_connections_in(Config, VHost)).
+
+single_node_single_vhost_connection_count(Config) ->
+ VHost = <<"/">>,
+ ?assertEqual(0, count_connections_in(Config, VHost)),
+
+ [Conn1] = open_connections(Config, [0]),
+ ?assertEqual(1, count_connections_in(Config, VHost)),
+ close_connections([Conn1]),
+ ?assertEqual(0, count_connections_in(Config, VHost)),
+
+ [Conn2] = open_connections(Config, [0]),
+ ?assertEqual(1, count_connections_in(Config, VHost)),
+
+ [Conn3] = open_connections(Config, [0]),
+ ?assertEqual(2, count_connections_in(Config, VHost)),
+
+ [Conn4] = open_connections(Config, [0]),
+ ?assertEqual(3, count_connections_in(Config, VHost)),
+
+ kill_connections([Conn4]),
+ ?assertEqual(2, count_connections_in(Config, VHost)),
+
+ [Conn5] = open_connections(Config, [0]),
+ ?assertEqual(3, count_connections_in(Config, VHost)),
+
+ close_connections([Conn2, Conn3, Conn5]),
+ ?assertEqual(0, count_connections_in(Config, VHost)).
+
+single_node_multiple_vhosts_connection_count(Config) ->
+ VHost1 = <<"vhost1">>,
+ VHost2 = <<"vhost2">>,
+
+ set_up_vhost(Config, VHost1),
+ set_up_vhost(Config, VHost2),
+
+ ?assertEqual(0, count_connections_in(Config, VHost1)),
+ ?assertEqual(0, count_connections_in(Config, VHost2)),
+
+ [Conn1] = open_connections(Config, [{0, VHost1}]),
+ ?assertEqual(1, count_connections_in(Config, VHost1)),
+ close_connections([Conn1]),
+ ?assertEqual(0, count_connections_in(Config, VHost1)),
+
+ [Conn2] = open_connections(Config, [{0, VHost2}]),
+ ?assertEqual(1, count_connections_in(Config, VHost2)),
+
+ [Conn3] = open_connections(Config, [{0, VHost1}]),
+ ?assertEqual(1, count_connections_in(Config, VHost1)),
+ ?assertEqual(1, count_connections_in(Config, VHost2)),
+
+ [Conn4] = open_connections(Config, [{0, VHost1}]),
+ ?assertEqual(2, count_connections_in(Config, VHost1)),
+
+ kill_connections([Conn4]),
+ ?assertEqual(1, count_connections_in(Config, VHost1)),
+
+ [Conn5] = open_connections(Config, [{0, VHost2}]),
+ ?assertEqual(2, count_connections_in(Config, VHost2)),
+
+ [Conn6] = open_connections(Config, [{0, VHost2}]),
+ ?assertEqual(3, count_connections_in(Config, VHost2)),
+
+ close_connections([Conn2, Conn3, Conn5, Conn6]),
+ ?assertEqual(0, count_connections_in(Config, VHost1)),
+ ?assertEqual(0, count_connections_in(Config, VHost2)),
+
+ rabbit_ct_broker_helpers:delete_vhost(Config, VHost1),
+ rabbit_ct_broker_helpers:delete_vhost(Config, VHost2).
+
+single_node_list_in_vhost(Config) ->
+ VHost1 = <<"vhost1">>,
+ VHost2 = <<"vhost2">>,
+
+ set_up_vhost(Config, VHost1),
+ set_up_vhost(Config, VHost2),
+
+ ?assertEqual(0, length(connections_in(Config, VHost1))),
+ ?assertEqual(0, length(connections_in(Config, VHost2))),
+
+ [Conn1] = open_connections(Config, [{0, VHost1}]),
+ [#tracked_connection{vhost = VHost1}] = connections_in(Config, VHost1),
+ close_connections([Conn1]),
+ ?assertEqual(0, length(connections_in(Config, VHost1))),
+
+ [Conn2] = open_connections(Config, [{0, VHost2}]),
+ [#tracked_connection{vhost = VHost2}] = connections_in(Config, VHost2),
+
+ [Conn3] = open_connections(Config, [{0, VHost1}]),
+ [#tracked_connection{vhost = VHost1}] = connections_in(Config, VHost1),
+
+ [Conn4] = open_connections(Config, [{0, VHost1}]),
+ kill_connections([Conn4]),
+ [#tracked_connection{vhost = VHost1}] = connections_in(Config, VHost1),
+
+ [Conn5, Conn6] = open_connections(Config, [{0, VHost2}, {0, VHost2}]),
+ [<<"vhost1">>, <<"vhost2">>] =
+ lists:usort(lists:map(fun (#tracked_connection{vhost = V}) -> V end,
+ all_connections(Config))),
+
+ close_connections([Conn2, Conn3, Conn5, Conn6]),
+ ?assertEqual(0, length(all_connections(Config))),
+
+ rabbit_ct_broker_helpers:delete_vhost(Config, VHost1),
+ rabbit_ct_broker_helpers:delete_vhost(Config, VHost2).
+
+most_basic_cluster_connection_count(Config) ->
+ VHost = <<"/">>,
+ ?assertEqual(0, count_connections_in(Config, VHost)),
+ [Conn1] = open_connections(Config, [0]),
+ ?assertEqual(1, count_connections_in(Config, VHost)),
+
+ [Conn2] = open_connections(Config, [1]),
+ ?assertEqual(2, count_connections_in(Config, VHost)),
+
+ [Conn3] = open_connections(Config, [1]),
+ ?assertEqual(3, count_connections_in(Config, VHost)),
+
+ close_connections([Conn1, Conn2, Conn3]),
+ ?assertEqual(0, count_connections_in(Config, VHost)).
+
+cluster_single_vhost_connection_count(Config) ->
+ VHost = <<"/">>,
+ ?assertEqual(0, count_connections_in(Config, VHost)),
+
+ [Conn1] = open_connections(Config, [0]),
+ ?assertEqual(1, count_connections_in(Config, VHost)),
+ close_connections([Conn1]),
+ ?assertEqual(0, count_connections_in(Config, VHost)),
+
+ [Conn2] = open_connections(Config, [1]),
+ ?assertEqual(1, count_connections_in(Config, VHost)),
+
+ [Conn3] = open_connections(Config, [0]),
+ ?assertEqual(2, count_connections_in(Config, VHost)),
+
+ [Conn4] = open_connections(Config, [1]),
+ ?assertEqual(3, count_connections_in(Config, VHost)),
+
+ kill_connections([Conn4]),
+ ?assertEqual(2, count_connections_in(Config, VHost)),
+
+ [Conn5] = open_connections(Config, [1]),
+ ?assertEqual(3, count_connections_in(Config, VHost)),
+
+ close_connections([Conn2, Conn3, Conn5]),
+ ?assertEqual(0, count_connections_in(Config, VHost)).
+
+cluster_multiple_vhosts_connection_count(Config) ->
+ VHost1 = <<"vhost1">>,
+ VHost2 = <<"vhost2">>,
+
+ set_up_vhost(Config, VHost1),
+ set_up_vhost(Config, VHost2),
+
+ ?assertEqual(0, count_connections_in(Config, VHost1)),
+ ?assertEqual(0, count_connections_in(Config, VHost2)),
+
+ [Conn1] = open_connections(Config, [{0, VHost1}]),
+ ?assertEqual(1, count_connections_in(Config, VHost1)),
+ close_connections([Conn1]),
+ ?assertEqual(0, count_connections_in(Config, VHost1)),
+
+ [Conn2] = open_connections(Config, [{1, VHost2}]),
+ ?assertEqual(1, count_connections_in(Config, VHost2)),
+
+ [Conn3] = open_connections(Config, [{1, VHost1}]),
+ ?assertEqual(1, count_connections_in(Config, VHost1)),
+ ?assertEqual(1, count_connections_in(Config, VHost2)),
+
+ [Conn4] = open_connections(Config, [{0, VHost1}]),
+ ?assertEqual(2, count_connections_in(Config, VHost1)),
+
+ kill_connections([Conn4]),
+ ?assertEqual(1, count_connections_in(Config, VHost1)),
+
+ [Conn5] = open_connections(Config, [{1, VHost2}]),
+ ?assertEqual(2, count_connections_in(Config, VHost2)),
+
+ [Conn6] = open_connections(Config, [{0, VHost2}]),
+ ?assertEqual(3, count_connections_in(Config, VHost2)),
+
+ close_connections([Conn2, Conn3, Conn5, Conn6]),
+ ?assertEqual(0, count_connections_in(Config, VHost1)),
+ ?assertEqual(0, count_connections_in(Config, VHost2)),
+
+ rabbit_ct_broker_helpers:delete_vhost(Config, VHost1),
+ rabbit_ct_broker_helpers:delete_vhost(Config, VHost2).
+
+cluster_node_restart_connection_count(Config) ->
+ VHost = <<"/">>,
+ ?assertEqual(0, count_connections_in(Config, VHost)),
+
+ [Conn1] = open_connections(Config, [0]),
+ ?assertEqual(1, count_connections_in(Config, VHost)),
+ close_connections([Conn1]),
+ ?assertEqual(0, count_connections_in(Config, VHost)),
+
+ [Conn2] = open_connections(Config, [1]),
+ ?assertEqual(1, count_connections_in(Config, VHost)),
+
+ [Conn3] = open_connections(Config, [0]),
+ ?assertEqual(2, count_connections_in(Config, VHost)),
+
+ [Conn4] = open_connections(Config, [1]),
+ ?assertEqual(3, count_connections_in(Config, VHost)),
+
+ [Conn5] = open_connections(Config, [1]),
+ ?assertEqual(4, count_connections_in(Config, VHost)),
+
+ rabbit_ct_broker_helpers:restart_broker(Config, 1),
+ ?assertEqual(1, count_connections_in(Config, VHost)),
+
+ close_connections([Conn2, Conn3, Conn4, Conn5]),
+ ?assertEqual(0, count_connections_in(Config, VHost)).
+
+cluster_node_list_on_node(Config) ->
+ [A, B] = rabbit_ct_broker_helpers:get_node_configs(Config, nodename),
+
+ ?assertEqual(0, length(all_connections(Config))),
+ ?assertEqual(0, length(connections_on_node(Config, 0))),
+
+ [Conn1] = open_connections(Config, [0]),
+ [#tracked_connection{node = A}] = connections_on_node(Config, 0),
+ close_connections([Conn1]),
+ ?assertEqual(0, length(connections_on_node(Config, 0))),
+
+ [_Conn2] = open_connections(Config, [1]),
+ [#tracked_connection{node = B}] = connections_on_node(Config, 1),
+
+ [Conn3] = open_connections(Config, [0]),
+ ?assertEqual(1, length(connections_on_node(Config, 0))),
+
+ [Conn4] = open_connections(Config, [1]),
+ ?assertEqual(2, length(connections_on_node(Config, 1))),
+
+ kill_connections([Conn4]),
+ ?assertEqual(1, length(connections_on_node(Config, 1))),
+
+ [Conn5] = open_connections(Config, [0]),
+ ?assertEqual(2, length(connections_on_node(Config, 0))),
+
+ rabbit_ct_broker_helpers:stop_broker(Config, 1),
+ await_running_node_refresh(Config, 0),
+
+ ?assertEqual(2, length(all_connections(Config))),
+ ?assertEqual(0, length(connections_on_node(Config, 0, B))),
+
+ close_connections([Conn3, Conn5]),
+ ?assertEqual(0, length(all_connections(Config, 0))),
+
+ rabbit_ct_broker_helpers:start_broker(Config, 1).
+
+single_node_single_vhost_limit(Config) ->
+ single_node_single_vhost_limit_with(Config, 5),
+ single_node_single_vhost_limit_with(Config, -1).
+
+single_node_single_vhost_limit_with(Config, WatermarkLimit) ->
+ VHost = <<"/">>,
+ set_vhost_connection_limit(Config, VHost, 3),
+
+ ?assertEqual(0, count_connections_in(Config, VHost)),
+
+ [Conn1, Conn2, Conn3] = open_connections(Config, [0, 0, 0]),
+
+ %% we've crossed the limit
+ expect_that_client_connection_is_rejected(Config, 0),
+ expect_that_client_connection_is_rejected(Config, 0),
+ expect_that_client_connection_is_rejected(Config, 0),
+
+ set_vhost_connection_limit(Config, VHost, WatermarkLimit),
+ [Conn4, Conn5] = open_connections(Config, [0, 0]),
+
+ close_connections([Conn1, Conn2, Conn3, Conn4, Conn5]),
+ ?assertEqual(0, count_connections_in(Config, VHost)),
+
+ set_vhost_connection_limit(Config, VHost, -1).
+
+single_node_single_vhost_zero_limit(Config) ->
+ VHost = <<"/">>,
+ set_vhost_connection_limit(Config, VHost, 0),
+
+ ?assertEqual(0, count_connections_in(Config, VHost)),
+
+ %% with limit = 0 no connections are allowed
+ expect_that_client_connection_is_rejected(Config),
+ expect_that_client_connection_is_rejected(Config),
+ expect_that_client_connection_is_rejected(Config),
+
+ set_vhost_connection_limit(Config, VHost, -1),
+ [Conn1, Conn2] = open_connections(Config, [0, 0]),
+
+ close_connections([Conn1, Conn2]),
+ ?assertEqual(0, count_connections_in(Config, VHost)).
+
+
+single_node_multiple_vhosts_limit(Config) ->
+ VHost1 = <<"vhost1">>,
+ VHost2 = <<"vhost2">>,
+
+ set_up_vhost(Config, VHost1),
+ set_up_vhost(Config, VHost2),
+
+ set_vhost_connection_limit(Config, VHost1, 2),
+ set_vhost_connection_limit(Config, VHost2, 2),
+
+ ?assertEqual(0, count_connections_in(Config, VHost1)),
+ ?assertEqual(0, count_connections_in(Config, VHost2)),
+
+ [Conn1, Conn2, Conn3, Conn4] = open_connections(Config, [
+ {0, VHost1},
+ {0, VHost1},
+ {0, VHost2},
+ {0, VHost2}]),
+
+ %% we've crossed the limit
+ expect_that_client_connection_is_rejected(Config, 0, VHost1),
+ expect_that_client_connection_is_rejected(Config, 0, VHost2),
+
+ [Conn5] = open_connections(Config, [0]),
+
+ set_vhost_connection_limit(Config, VHost1, 5),
+ set_vhost_connection_limit(Config, VHost2, -10),
+
+ [Conn6, Conn7, Conn8, Conn9, Conn10] = open_connections(Config, [
+ {0, VHost1},
+ {0, VHost1},
+ {0, VHost1},
+ {0, VHost2},
+ {0, VHost2}]),
+
+ close_connections([Conn1, Conn2, Conn3, Conn4, Conn5,
+ Conn6, Conn7, Conn8, Conn9, Conn10]),
+ ?assertEqual(0, count_connections_in(Config, VHost1)),
+ ?assertEqual(0, count_connections_in(Config, VHost2)),
+
+ set_vhost_connection_limit(Config, VHost1, -1),
+ set_vhost_connection_limit(Config, VHost2, -1),
+
+ rabbit_ct_broker_helpers:delete_vhost(Config, VHost1),
+ rabbit_ct_broker_helpers:delete_vhost(Config, VHost2).
+
+
+single_node_multiple_vhosts_zero_limit(Config) ->
+ VHost1 = <<"vhost1">>,
+ VHost2 = <<"vhost2">>,
+
+ set_up_vhost(Config, VHost1),
+ set_up_vhost(Config, VHost2),
+
+ set_vhost_connection_limit(Config, VHost1, 0),
+ set_vhost_connection_limit(Config, VHost2, 0),
+
+ ?assertEqual(0, count_connections_in(Config, VHost1)),
+ ?assertEqual(0, count_connections_in(Config, VHost2)),
+
+ %% with limit = 0 no connections are allowed
+ expect_that_client_connection_is_rejected(Config, 0, VHost1),
+ expect_that_client_connection_is_rejected(Config, 0, VHost2),
+ expect_that_client_connection_is_rejected(Config, 0, VHost1),
+
+ set_vhost_connection_limit(Config, VHost1, -1),
+ [Conn1, Conn2] = open_connections(Config, [{0, VHost1}, {0, VHost1}]),
+
+ close_connections([Conn1, Conn2]),
+ ?assertEqual(0, count_connections_in(Config, VHost1)),
+ ?assertEqual(0, count_connections_in(Config, VHost2)),
+
+ set_vhost_connection_limit(Config, VHost1, -1),
+ set_vhost_connection_limit(Config, VHost2, -1).
+
+
+cluster_single_vhost_limit(Config) ->
+ VHost = <<"/">>,
+ set_vhost_connection_limit(Config, VHost, 2),
+
+ ?assertEqual(0, count_connections_in(Config, VHost)),
+
+ %% here connections are opened to different nodes
+ [Conn1, Conn2] = open_connections(Config, [{0, VHost}, {1, VHost}]),
+
+ %% we've crossed the limit
+ expect_that_client_connection_is_rejected(Config, 0, VHost),
+ expect_that_client_connection_is_rejected(Config, 1, VHost),
+
+ set_vhost_connection_limit(Config, VHost, 5),
+
+ [Conn3, Conn4] = open_connections(Config, [{0, VHost}, {0, VHost}]),
+
+ close_connections([Conn1, Conn2, Conn3, Conn4]),
+ ?assertEqual(0, count_connections_in(Config, VHost)),
+
+ set_vhost_connection_limit(Config, VHost, -1).
+
+cluster_single_vhost_limit2(Config) ->
+ VHost = <<"/">>,
+ set_vhost_connection_limit(Config, VHost, 2),
+
+ ?assertEqual(0, count_connections_in(Config, VHost)),
+
+ %% here a limit is reached on one node first
+ [Conn1, Conn2] = open_connections(Config, [{0, VHost}, {0, VHost}]),
+
+ %% we've crossed the limit
+ expect_that_client_connection_is_rejected(Config, 0, VHost),
+ expect_that_client_connection_is_rejected(Config, 1, VHost),
+
+ set_vhost_connection_limit(Config, VHost, 5),
+
+ [Conn3, Conn4, Conn5, {error, not_allowed}] = open_connections(Config, [
+ {1, VHost},
+ {1, VHost},
+ {1, VHost},
+ {1, VHost}]),
+
+ close_connections([Conn1, Conn2, Conn3, Conn4, Conn5]),
+ ?assertEqual(0, count_connections_in(Config, VHost)),
+
+ set_vhost_connection_limit(Config, VHost, -1).
+
+
+cluster_single_vhost_zero_limit(Config) ->
+ VHost = <<"/">>,
+ set_vhost_connection_limit(Config, VHost, 0),
+
+ ?assertEqual(0, count_connections_in(Config, VHost)),
+
+ %% with limit = 0 no connections are allowed
+ expect_that_client_connection_is_rejected(Config, 0),
+ expect_that_client_connection_is_rejected(Config, 1),
+ expect_that_client_connection_is_rejected(Config, 0),
+
+ set_vhost_connection_limit(Config, VHost, -1),
+ [Conn1, Conn2, Conn3, Conn4] = open_connections(Config, [0, 1, 0, 1]),
+
+ close_connections([Conn1, Conn2, Conn3, Conn4]),
+ ?assertEqual(0, count_connections_in(Config, VHost)),
+
+ set_vhost_connection_limit(Config, VHost, -1).
+
+
+cluster_multiple_vhosts_zero_limit(Config) ->
+ VHost1 = <<"vhost1">>,
+ VHost2 = <<"vhost2">>,
+
+ set_up_vhost(Config, VHost1),
+ set_up_vhost(Config, VHost2),
+
+ set_vhost_connection_limit(Config, VHost1, 0),
+ set_vhost_connection_limit(Config, VHost2, 0),
+
+ ?assertEqual(0, count_connections_in(Config, VHost1)),
+ ?assertEqual(0, count_connections_in(Config, VHost2)),
+
+ %% with limit = 0 no connections are allowed
+ expect_that_client_connection_is_rejected(Config, 0, VHost1),
+ expect_that_client_connection_is_rejected(Config, 0, VHost2),
+ expect_that_client_connection_is_rejected(Config, 1, VHost1),
+ expect_that_client_connection_is_rejected(Config, 1, VHost2),
+
+ set_vhost_connection_limit(Config, VHost1, -1),
+ set_vhost_connection_limit(Config, VHost2, -1),
+
+ [Conn1, Conn2, Conn3, Conn4] = open_connections(Config, [
+ {0, VHost1},
+ {0, VHost2},
+ {1, VHost1},
+ {1, VHost2}]),
+
+ close_connections([Conn1, Conn2, Conn3, Conn4]),
+ ?assertEqual(0, count_connections_in(Config, VHost1)),
+ ?assertEqual(0, count_connections_in(Config, VHost2)),
+
+ set_vhost_connection_limit(Config, VHost1, -1),
+ set_vhost_connection_limit(Config, VHost2, -1).
+
+
+single_node_vhost_deletion_forces_connection_closure(Config) ->
+ VHost1 = <<"vhost1">>,
+ VHost2 = <<"vhost2">>,
+
+ set_up_vhost(Config, VHost1),
+ set_up_vhost(Config, VHost2),
+
+ ?assertEqual(0, count_connections_in(Config, VHost1)),
+ ?assertEqual(0, count_connections_in(Config, VHost2)),
+
+ [Conn1] = open_connections(Config, [{0, VHost1}]),
+ ?assertEqual(1, count_connections_in(Config, VHost1)),
+
+ [_Conn2] = open_connections(Config, [{0, VHost2}]),
+ ?assertEqual(1, count_connections_in(Config, VHost2)),
+
+ rabbit_ct_broker_helpers:delete_vhost(Config, VHost2),
+ timer:sleep(200),
+ ?assertEqual(0, count_connections_in(Config, VHost2)),
+
+ close_connections([Conn1]),
+ ?assertEqual(0, count_connections_in(Config, VHost1)),
+
+ rabbit_ct_broker_helpers:delete_vhost(Config, VHost1).
+
+cluster_vhost_deletion_forces_connection_closure(Config) ->
+ VHost1 = <<"vhost1">>,
+ VHost2 = <<"vhost2">>,
+
+ set_up_vhost(Config, VHost1),
+ set_up_vhost(Config, VHost2),
+
+ ?assertEqual(0, count_connections_in(Config, VHost1)),
+ ?assertEqual(0, count_connections_in(Config, VHost2)),
+
+ [Conn1] = open_connections(Config, [{0, VHost1}]),
+ ?assertEqual(1, count_connections_in(Config, VHost1)),
+
+ [_Conn2] = open_connections(Config, [{1, VHost2}]),
+ ?assertEqual(1, count_connections_in(Config, VHost2)),
+
+ rabbit_ct_broker_helpers:delete_vhost(Config, VHost2),
+ timer:sleep(200),
+ ?assertEqual(0, count_connections_in(Config, VHost2)),
+
+ close_connections([Conn1]),
+ ?assertEqual(0, count_connections_in(Config, VHost1)),
+
+ rabbit_ct_broker_helpers:delete_vhost(Config, VHost1).
+
+vhost_limit_after_node_renamed(Config) ->
+ A = rabbit_ct_broker_helpers:get_node_config(Config, 0, nodename),
+
+ VHost = <<"/renaming_node">>,
+ set_up_vhost(Config, VHost),
+ set_vhost_connection_limit(Config, VHost, 2),
+
+ ?assertEqual(0, count_connections_in(Config, VHost)),
+
+ [Conn1, Conn2, {error, not_allowed}] = open_connections(Config,
+ [{0, VHost}, {1, VHost}, {0, VHost}]),
+ ?assertEqual(2, count_connections_in(Config, VHost)),
+ close_connections([Conn1, Conn2]),
+
+ Config1 = cluster_rename_SUITE:stop_rename_start(Config, A, [A, 'new-A']),
+
+ ?assertEqual(0, count_connections_in(Config1, VHost)),
+
+ [Conn3, Conn4, {error, not_allowed}] = open_connections(Config,
+ [{0, VHost}, {1, VHost}, {0, VHost}]),
+ ?assertEqual(2, count_connections_in(Config1, VHost)),
+ close_connections([Conn3, Conn4]),
+
+ set_vhost_connection_limit(Config1, VHost, -1),
+ {save_config, Config1}.
+
+%% -------------------------------------------------------------------
+%% Helpers
+%% -------------------------------------------------------------------
+
+open_connections(Config, NodesAndVHosts) ->
+ Conns = lists:map(fun
+ ({Node, VHost}) ->
+ rabbit_ct_client_helpers:open_unmanaged_connection(Config, Node,
+ VHost);
+ (Node) ->
+ rabbit_ct_client_helpers:open_unmanaged_connection(Config, Node)
+ end, NodesAndVHosts),
+ timer:sleep(500),
+ Conns.
+
+close_connections(Conns) ->
+ lists:foreach(fun
+ (Conn) ->
+ rabbit_ct_client_helpers:close_connection(Conn)
+ end, Conns),
+ timer:sleep(500).
+
+kill_connections(Conns) ->
+ lists:foreach(fun
+ (Conn) ->
+ (catch exit(Conn, please_terminate))
+ end, Conns),
+ timer:sleep(500).
+
+count_connections_in(Config, VHost) ->
+ count_connections_in(Config, VHost, 0).
+count_connections_in(Config, VHost, NodeIndex) ->
+ timer:sleep(200),
+ rabbit_ct_broker_helpers:rpc(Config, NodeIndex,
+ rabbit_connection_tracking,
+ count_connections_in, [VHost]).
+
+connections_in(Config, VHost) ->
+ connections_in(Config, 0, VHost).
+connections_in(Config, NodeIndex, VHost) ->
+ rabbit_ct_broker_helpers:rpc(Config, NodeIndex,
+ rabbit_connection_tracking,
+ list, [VHost]).
+
+connections_on_node(Config) ->
+ connections_on_node(Config, 0).
+connections_on_node(Config, NodeIndex) ->
+ Node = rabbit_ct_broker_helpers:get_node_config(Config, NodeIndex, nodename),
+ rabbit_ct_broker_helpers:rpc(Config, NodeIndex,
+ rabbit_connection_tracking,
+ list_on_node, [Node]).
+connections_on_node(Config, NodeIndex, NodeForListing) ->
+ rabbit_ct_broker_helpers:rpc(Config, NodeIndex,
+ rabbit_connection_tracking,
+ list_on_node, [NodeForListing]).
+
+all_connections(Config) ->
+ all_connections(Config, 0).
+all_connections(Config, NodeIndex) ->
+ rabbit_ct_broker_helpers:rpc(Config, NodeIndex,
+ rabbit_connection_tracking,
+ list, []).
+
+set_up_vhost(Config, VHost) ->
+ rabbit_ct_broker_helpers:add_vhost(Config, VHost),
+ rabbit_ct_broker_helpers:set_full_permissions(Config, <<"guest">>, VHost),
+ set_vhost_connection_limit(Config, VHost, -1).
+
+set_vhost_connection_limit(Config, VHost, Count) ->
+ set_vhost_connection_limit(Config, 0, VHost, Count).
+
+set_vhost_connection_limit(Config, NodeIndex, VHost, Count) ->
+ Node = rabbit_ct_broker_helpers:get_node_config(
+ Config, NodeIndex, nodename),
+ rabbit_ct_broker_helpers:control_action(
+ set_vhost_limits, Node,
+ ["{\"max-connections\": " ++ integer_to_list(Count) ++ "}"],
+ [{"-p", binary_to_list(VHost)}]).
+
+await_running_node_refresh(_Config, _NodeIndex) ->
+ timer:sleep(250).
+
+expect_that_client_connection_is_rejected(Config) ->
+ expect_that_client_connection_is_rejected(Config, 0).
+
+expect_that_client_connection_is_rejected(Config, NodeIndex) ->
+ {error, not_allowed} =
+ rabbit_ct_client_helpers:open_unmanaged_connection(Config, NodeIndex).
+
+expect_that_client_connection_is_rejected(Config, NodeIndex, VHost) ->
+ {error, not_allowed} =
+ rabbit_ct_client_helpers:open_unmanaged_connection(Config, NodeIndex, VHost).
diff --git a/test/per_vhost_connection_limit_partitions_SUITE.erl b/test/per_vhost_connection_limit_partitions_SUITE.erl
new file mode 100644
index 0000000000..051f3f13b7
--- /dev/null
+++ b/test/per_vhost_connection_limit_partitions_SUITE.erl
@@ -0,0 +1,170 @@
+%% 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 Developer of the Original Code is GoPivotal, Inc.
+%% Copyright (c) 2011-2015 Pivotal Software, Inc. All rights reserved.
+%%
+
+-module(per_vhost_connection_limit_partitions_SUITE).
+
+-include_lib("common_test/include/ct.hrl").
+-include_lib("amqp_client/include/amqp_client.hrl").
+-include_lib("eunit/include/eunit.hrl").
+
+-compile(export_all).
+
+-import(rabbit_ct_client_helpers, [open_unmanaged_connection/2,
+ open_unmanaged_connection/3]).
+
+
+all() ->
+ [
+ {group, net_ticktime_1}
+ ].
+
+groups() ->
+ [
+ {net_ticktime_1, [], [
+ cluster_full_partition_with_autoheal
+ ]}
+ ].
+
+suite() ->
+ [
+ %% If a test hangs, no need to wait for 30 minutes.
+ {timetrap, {minutes, 8}}
+ ].
+
+%% see partitions_SUITE
+-define(DELAY, 12000).
+
+%% -------------------------------------------------------------------
+%% Testsuite setup/teardown.
+%% -------------------------------------------------------------------
+
+init_per_suite(Config) ->
+ rabbit_ct_helpers:log_environment(),
+ rabbit_ct_helpers:run_setup_steps(Config, [
+ fun rabbit_ct_broker_helpers:enable_dist_proxy_manager/1
+ ]).
+
+end_per_suite(Config) ->
+ rabbit_ct_helpers:run_teardown_steps(Config).
+
+init_per_group(net_ticktime_1 = Group, Config) ->
+ Config1 = rabbit_ct_helpers:set_config(Config, [{net_ticktime, 1}]),
+ init_per_multinode_group(Group, Config1, 3).
+
+init_per_multinode_group(_Group, Config, NodeCount) ->
+ Suffix = rabbit_ct_helpers:testcase_absname(Config, "", "-"),
+ Config1 = rabbit_ct_helpers:set_config(Config, [
+ {rmq_nodes_count, NodeCount},
+ {rmq_nodename_suffix, Suffix},
+ {rmq_nodes_clustered, false}
+ ]),
+ rabbit_ct_helpers:run_steps(Config1,
+ rabbit_ct_broker_helpers:setup_steps() ++
+ rabbit_ct_client_helpers:setup_steps() ++ [
+ fun rabbit_ct_broker_helpers:enable_dist_proxy/1,
+ fun rabbit_ct_broker_helpers:cluster_nodes/1
+ ]).
+
+end_per_group(_Group, Config) ->
+ rabbit_ct_helpers:run_steps(Config,
+ rabbit_ct_client_helpers:teardown_steps() ++
+ rabbit_ct_broker_helpers:teardown_steps()).
+
+init_per_testcase(Testcase, Config) ->
+ rabbit_ct_helpers:testcase_started(Config, Testcase).
+
+end_per_testcase(Testcase, Config) ->
+ rabbit_ct_helpers:testcase_finished(Config, Testcase).
+
+%% -------------------------------------------------------------------
+%% Test cases.
+%% -------------------------------------------------------------------
+
+cluster_full_partition_with_autoheal(Config) ->
+ VHost = <<"/">>,
+ rabbit_ct_broker_helpers:set_partition_handling_mode_globally(Config, autoheal),
+
+ ?assertEqual(0, count_connections_in(Config, VHost)),
+ [A, B, C] = rabbit_ct_broker_helpers:get_node_configs(Config, nodename),
+
+ %% 6 connections, 2 per node
+ Conn1 = open_unmanaged_connection(Config, A),
+ Conn2 = open_unmanaged_connection(Config, A),
+ Conn3 = open_unmanaged_connection(Config, B),
+ Conn4 = open_unmanaged_connection(Config, B),
+ Conn5 = open_unmanaged_connection(Config, C),
+ Conn6 = open_unmanaged_connection(Config, C),
+ ?assertEqual(6, count_connections_in(Config, VHost)),
+
+ %% B drops off the network, non-reachable by either A or C
+ rabbit_ct_broker_helpers:block_traffic_between(A, B),
+ rabbit_ct_broker_helpers:block_traffic_between(B, C),
+ timer:sleep(?DELAY),
+
+ %% A and C are still connected, so 4 connections are tracked
+ ?assertEqual(4, count_connections_in(Config, VHost)),
+
+ rabbit_ct_broker_helpers:allow_traffic_between(A, B),
+ rabbit_ct_broker_helpers:allow_traffic_between(B, C),
+ timer:sleep(?DELAY),
+
+ %% during autoheal B's connections were dropped
+ ?assertEqual(4, count_connections_in(Config, VHost)),
+
+ lists:foreach(fun (Conn) ->
+ (catch rabbit_ct_client_helpers:close_connection(Conn))
+ end, [Conn1, Conn2, Conn3, Conn4,
+ Conn5, Conn6]),
+
+ passed.
+
+
+%% -------------------------------------------------------------------
+%% Helpers
+%% -------------------------------------------------------------------
+
+count_connections_in(Config, VHost) ->
+ count_connections_in(Config, VHost, 0).
+count_connections_in(Config, VHost, NodeIndex) ->
+ rabbit_ct_broker_helpers:rpc(Config, NodeIndex,
+ rabbit_connection_tracking,
+ count_connections_in, [VHost]).
+
+connections_in(Config, VHost) ->
+ connections_in(Config, 0, VHost).
+connections_in(Config, NodeIndex, VHost) ->
+ rabbit_ct_broker_helpers:rpc(Config, NodeIndex,
+ rabbit_connection_tracking,
+ list, [VHost]).
+
+connections_on_node(Config) ->
+ connections_on_node(Config, 0).
+connections_on_node(Config, NodeIndex) ->
+ Node = rabbit_ct_broker_helpers:get_node_config(Config, NodeIndex, nodename),
+ rabbit_ct_broker_helpers:rpc(Config, NodeIndex,
+ rabbit_connection_tracking,
+ list_on_node, [Node]).
+connections_on_node(Config, NodeIndex, NodeForListing) ->
+ rabbit_ct_broker_helpers:rpc(Config, NodeIndex,
+ rabbit_connection_tracking,
+ list_on_node, [NodeForListing]).
+
+all_connections(Config) ->
+ all_connections(Config, 0).
+all_connections(Config, NodeIndex) ->
+ rabbit_ct_broker_helpers:rpc(Config, NodeIndex,
+ rabbit_connection_tracking,
+ list, []).
diff --git a/test/plugin_versioning_SUITE.erl b/test/plugin_versioning_SUITE.erl
new file mode 100644
index 0000000000..7fcfe433e0
--- /dev/null
+++ b/test/plugin_versioning_SUITE.erl
@@ -0,0 +1,177 @@
+%% 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 Developer of the Original Code is GoPivotal, Inc.
+%% Copyright (c) 2016 Pivotal Software, Inc. All rights reserved.
+%%
+
+-module(plugin_versioning_SUITE).
+
+-include_lib("common_test/include/ct.hrl").
+-include_lib("amqp_client/include/amqp_client.hrl").
+
+-compile(export_all).
+
+all() ->
+ [
+ {group, parallel_tests}
+ ].
+
+groups() ->
+ [
+ {parallel_tests, [parallel], [
+ version_support,
+ plugin_validation
+ ]}
+ ].
+
+%% -------------------------------------------------------------------
+%% Testsuite setup/teardown.
+%% -------------------------------------------------------------------
+
+init_per_suite(Config) ->
+ rabbit_ct_helpers:log_environment(),
+ rabbit_ct_helpers:run_setup_steps(Config).
+
+end_per_suite(Config) ->
+ rabbit_ct_helpers:run_teardown_steps(Config).
+
+init_per_group(_, Config) ->
+ Config.
+
+end_per_group(_, Config) ->
+ Config.
+
+init_per_testcase(Testcase, Config) ->
+ rabbit_ct_helpers:testcase_started(Config, Testcase).
+
+end_per_testcase(Testcase, Config) ->
+ rabbit_ct_helpers:testcase_finished(Config, Testcase).
+
+%% -------------------------------------------------------------------
+%% Testcases.
+%% -------------------------------------------------------------------
+
+version_support(_Config) ->
+ Examples = [
+ {[], "any version", true} %% anything goes
+ ,{[], "0.0.0", true} %% ditto
+ ,{[], "3.5.6", true} %% ditto
+ ,{["something"], "something", true} %% equal values match
+ ,{["3.5.4"], "something", false}
+ ,{["3.4.5", "3.6.0"], "0.0.0", true} %% zero version always match
+ ,{["3.4.5", "3.6.0"], "", true} %% empty version always match
+ ,{["something", "3.5.6"], "3.5.7", true} %% 3.5.7 matches ~> 3.5.6
+ ,{["3.4.0", "3.5.6"], "3.6.1", false} %% 3.6.x isn't supported
+ ,{["3.5.2", "3.6.1", "3.7.1"], "3.5.2", true} %% 3.5.2 matches ~> 3.5.2
+ ,{["3.5.2", "3.6.1", "3.7.1"], "3.5.1", false} %% lesser than the lower boundary
+ ,{["3.5.2", "3.6.1", "3.7.1"], "3.6.2", true} %% 3.6.2 matches ~> 3.6.1
+ ,{["3.5.2", "3.6.1", "3.6.8"], "3.6.2", true} %% 3.6.2 still matches ~> 3.6.1
+ ,{["3.5", "3.6", "3.7"], "3.5.1", false} %% x.y values are not supported
+ ,{["3"], "3.5.1", false} %% x values are not supported
+ ,{["3.5.2", "3.6.1"], "3.6.2.999", true} %% x.y.z.p values are supported
+ ,{["3.5.2", "3.6.2.333"], "3.6.2.999", true} %% x.y.z.p values are supported
+ ,{["3.5.2", "3.6.2.333"], "3.6.2.222", false} %% x.y.z.p values are supported
+ ],
+
+ lists:foreach(
+ fun({Versions, RabbitVersion, Expected}) ->
+ {Expected, RabbitVersion, Versions} =
+ {rabbit_plugins:is_version_supported(RabbitVersion, Versions),
+ RabbitVersion, Versions}
+ end,
+ Examples),
+ ok.
+
+-record(validation_example, {rabbit_version, plugins, errors, valid}).
+
+plugin_validation(_Config) ->
+ Examples = [
+ #validation_example{
+ rabbit_version = "3.7.1",
+ plugins =
+ [{plugin_a, "3.7.2", ["3.5.6", "3.7.1"], []},
+ {plugin_b, "3.7.2", ["3.7.0"], [{plugin_a, ["3.6.3", "3.7.1"]}]}],
+ errors = [],
+ valid = [plugin_a, plugin_b]},
+
+ #validation_example{
+ rabbit_version = "3.7.1",
+ plugins =
+ [{plugin_a, "3.7.1", ["3.7.6"], []},
+ {plugin_b, "3.7.2", ["3.7.0"], [{plugin_a, ["3.6.3", "3.7.0"]}]}],
+ errors =
+ [{plugin_a, [{broker_version_mismatch, "3.7.1", ["3.7.6"]}]},
+ {plugin_b, [{missing_dependency, plugin_a}]}],
+ valid = []
+ },
+
+ #validation_example{
+ rabbit_version = "3.7.1",
+ plugins =
+ [{plugin_a, "3.7.1", ["3.7.6"], []},
+ {plugin_b, "3.7.2", ["3.7.0"], [{plugin_a, ["3.7.0"]}]},
+ {plugin_c, "3.7.2", ["3.7.0"], [{plugin_b, ["3.7.3"]}]}],
+ errors =
+ [{plugin_a, [{broker_version_mismatch, "3.7.1", ["3.7.6"]}]},
+ {plugin_b, [{missing_dependency, plugin_a}]},
+ {plugin_c, [{missing_dependency, plugin_b}]}],
+ valid = []
+ },
+
+ #validation_example{
+ rabbit_version = "3.7.1",
+ plugins =
+ [{plugin_a, "3.7.1", ["3.7.1"], []},
+ {plugin_b, "3.7.2", ["3.7.0"], [{plugin_a, ["3.7.3"]}]},
+ {plugin_d, "3.7.2", ["3.7.0"], [{plugin_c, ["3.7.3"]}]}],
+ errors =
+ [{plugin_b, [{{dependency_version_mismatch, "3.7.1", ["3.7.3"]}, plugin_a}]},
+ {plugin_d, [{missing_dependency, plugin_c}]}],
+ valid = [plugin_a]
+ },
+ #validation_example{
+ rabbit_version = "0.0.0",
+ plugins =
+ [{plugin_a, "", ["3.7.1"], []},
+ {plugin_b, "3.7.2", ["3.7.0"], [{plugin_a, ["3.7.3"]}]}],
+ errors = [],
+ valid = [plugin_a, plugin_b]
+ }],
+ lists:foreach(
+ fun(#validation_example{rabbit_version = RabbitVersion,
+ plugins = PluginsExamples,
+ errors = Errors,
+ valid = ExpectedValid}) ->
+ Plugins = make_plugins(PluginsExamples),
+ {Valid, Invalid} = rabbit_plugins:validate_plugins(Plugins,
+ RabbitVersion),
+ Errors = lists:reverse(Invalid),
+ ExpectedValid = lists:reverse(lists:map(fun(#plugin{name = Name}) ->
+ Name
+ end,
+ Valid))
+ end,
+ Examples),
+ ok.
+
+make_plugins(Plugins) ->
+ lists:map(
+ fun({Name, Version, RabbitVersions, PluginsVersions}) ->
+ Deps = [K || {K,_V} <- PluginsVersions],
+ #plugin{name = Name,
+ version = Version,
+ dependencies = Deps,
+ broker_version_requirements = RabbitVersions,
+ dependency_version_requirements = PluginsVersions}
+ end,
+ Plugins).
diff --git a/test/priority_queue_SUITE.erl b/test/priority_queue_SUITE.erl
index 05853ebc1f..8e1b48dd3b 100644
--- a/test/priority_queue_SUITE.erl
+++ b/test/priority_queue_SUITE.erl
@@ -49,7 +49,8 @@ groups() ->
{cluster_size_3, [], [
mirror_queue_auto_ack,
mirror_fast_reset_policy,
- mirror_reset_policy
+ mirror_reset_policy,
+ mirror_stop_pending_slaves
]}
].
@@ -523,6 +524,36 @@ mirror_reset_policy(Config, Wait) ->
rabbit_ct_client_helpers:close_connection(Conn),
passed.
+mirror_stop_pending_slaves(Config) ->
+ A = rabbit_ct_broker_helpers:get_node_config(Config, 0, nodename),
+ B = rabbit_ct_broker_helpers:get_node_config(Config, 1, nodename),
+ C = rabbit_ct_broker_helpers:get_node_config(Config, 2, nodename),
+
+ [ok = rabbit_ct_broker_helpers:rpc(
+ Config, Nodename, application, set_env, [rabbit, slave_wait_timeout, 0]) || Nodename <- [A, B, C]],
+
+ {Conn, Ch} = rabbit_ct_client_helpers:open_connection_and_channel(Config, A),
+ Q = <<"mirror_stop_pending_slaves-queue">>,
+ declare(Ch, Q, 5),
+ publish_many(Ch, Q, 20000),
+
+ [begin
+ rabbit_ct_broker_helpers:set_ha_policy(
+ Config, A, <<"^mirror_stop_pending_slaves-queue$">>, <<"all">>,
+ [{<<"ha-sync-mode">>, <<"automatic">>}]),
+ wait_for_sync(Config, A, rabbit_misc:r(<<"/">>, queue, Q), 2),
+ rabbit_ct_broker_helpers:clear_policy(
+ Config, A, <<"^mirror_stop_pending_slaves-queue$">>)
+ end || _ <- lists:seq(1, 15)],
+
+ delete(Ch, Q),
+
+ [ok = rabbit_ct_broker_helpers:rpc(
+ Config, Nodename, application, set_env, [rabbit, slave_wait_timeout, 15000]) || Nodename <- [A, B, C]],
+
+ rabbit_ct_client_helpers:close_connection(Conn),
+ passed.
+
%%----------------------------------------------------------------------------
declare(Ch, Q, Args) when is_list(Args) ->
@@ -546,7 +577,7 @@ publish_payload(Ch, Q, PPds) ->
amqp_channel:wait_for_confirms(Ch).
publish_many(_Ch, _Q, 0) -> ok;
-publish_many( Ch, Q, N) -> publish1(Ch, Q, rand_compat:uniform(5)),
+publish_many( Ch, Q, N) -> publish1(Ch, Q, rand:uniform(5)),
publish_many(Ch, Q, N - 1).
publish1(Ch, Q, P) ->
diff --git a/test/unit_SUITE.erl b/test/unit_SUITE.erl
index 43e812fa3d..5faeccdaab 100644
--- a/test/unit_SUITE.erl
+++ b/test/unit_SUITE.erl
@@ -743,8 +743,9 @@ version_equivalance(_Config) ->
true = rabbit_misc:version_minor_equivalent("3.0.0", "3.0.0"),
true = rabbit_misc:version_minor_equivalent("3.0.0", "3.0.1"),
true = rabbit_misc:version_minor_equivalent("%%VSN%%", "%%VSN%%"),
+ % Support for 4-number versions
+ true = rabbit_misc:version_minor_equivalent("3.0.0", "3.0.0.1"),
false = rabbit_misc:version_minor_equivalent("3.0.0", "3.1.0"),
false = rabbit_misc:version_minor_equivalent("3.0.0", "3.0"),
- false = rabbit_misc:version_minor_equivalent("3.0.0", "3.0.0.1"),
false = rabbit_misc:version_minor_equivalent("3.0.0", "3.0.foo"),
passed.
diff --git a/test/unit_inbroker_SUITE.erl b/test/unit_inbroker_SUITE.erl
index e9ecbf5444..8de282c50a 100644
--- a/test/unit_inbroker_SUITE.erl
+++ b/test/unit_inbroker_SUITE.erl
@@ -116,7 +116,7 @@ groups() ->
log_management, %% Check log files.
log_management_during_startup, %% Check log files.
memory_high_watermark, %% Trigger alarm.
- rotate_logs_without_suffix, %% Check log files.
+ externally_rotated_logs_are_automatically_reopened, %% Check log files.
server_status %% Trigger alarm.
]},
{backing_queue_tests, [], [
@@ -1316,7 +1316,7 @@ maybe_switch_queue_mode(VQ) ->
random_queue_mode() ->
Modes = [lazy, default],
- lists:nth(rand_compat:uniform(length(Modes)), Modes).
+ lists:nth(rand:uniform(length(Modes)), Modes).
pub_res({_, VQS}) ->
VQS;
@@ -1842,69 +1842,74 @@ log_management(Config) ->
?MODULE, log_management1, [Config]).
log_management1(_Config) ->
- override_group_leader(),
+ [LogFile] = rabbit:log_locations(),
+ Suffix = ".0",
- MainLog = rabbit:log_location(kernel),
- SaslLog = rabbit:log_location(sasl),
- Suffix = ".1",
-
- ok = test_logs_working(MainLog, SaslLog),
+ ok = test_logs_working([LogFile]),
%% prepare basic logs
- file:delete([MainLog, Suffix]),
- file:delete([SaslLog, Suffix]),
-
- %% simple logs reopening
- ok = control_action(rotate_logs, []),
- ok = test_logs_working(MainLog, SaslLog),
+ file:delete(LogFile ++ Suffix),
+ ok = test_logs_working([LogFile]),
%% simple log rotation
- ok = control_action(rotate_logs, [Suffix]),
- [true, true] = non_empty_files([[MainLog, Suffix], [SaslLog, Suffix]]),
- [true, true] = empty_files([MainLog, SaslLog]),
- ok = test_logs_working(MainLog, SaslLog),
-
- %% reopening logs with log rotation performed first
- ok = clean_logs([MainLog, SaslLog], Suffix),
ok = control_action(rotate_logs, []),
- ok = file:rename(MainLog, [MainLog, Suffix]),
- ok = file:rename(SaslLog, [SaslLog, Suffix]),
- ok = test_logs_working([MainLog, Suffix], [SaslLog, Suffix]),
+ %% FIXME: rabbit:rotate_logs/0 is asynchronous due to a limitation
+ %% in Lager. Therefore, we have no choice but to wait an arbitrary
+ %% amount of time.
+ timer:sleep(2000),
+ [true, true] = non_empty_files([LogFile ++ Suffix, LogFile]),
+ ok = test_logs_working([LogFile]),
+
+ %% log rotation on empty files
+ ok = clean_logs([LogFile], Suffix),
ok = control_action(rotate_logs, []),
- ok = test_logs_working(MainLog, SaslLog),
+ timer:sleep(2000),
+ [{error, enoent}, true] = non_empty_files([LogFile ++ Suffix, LogFile]),
- %% log rotation on empty files (the main log will have a ctl action logged)
- ok = clean_logs([MainLog, SaslLog], Suffix),
+ %% logs with suffix are not writable
+ ok = control_action(rotate_logs, []),
+ timer:sleep(2000),
+ ok = make_files_non_writable([LogFile ++ Suffix]),
ok = control_action(rotate_logs, []),
- ok = control_action(rotate_logs, [Suffix]),
- [false, true] = empty_files([[MainLog, Suffix], [SaslLog, Suffix]]),
+ timer:sleep(2000),
+ ok = test_logs_working([LogFile]),
- %% logs with suffix are not writable
- ok = control_action(rotate_logs, [Suffix]),
- ok = make_files_non_writable([[MainLog, Suffix], [SaslLog, Suffix]]),
- ok = control_action(rotate_logs, [Suffix]),
- ok = test_logs_working(MainLog, SaslLog),
+ %% rotate when original log files are not writable
+ ok = make_files_non_writable([LogFile]),
+ ok = control_action(rotate_logs, []),
+ timer:sleep(2000),
%% logging directed to tty (first, remove handlers)
- ok = delete_log_handlers([rabbit_sasl_report_file_h,
- rabbit_error_logger_file_h]),
- ok = clean_logs([MainLog, SaslLog], Suffix),
- ok = application:set_env(rabbit, sasl_error_logger, tty),
- ok = application:set_env(rabbit, error_logger, tty),
- ok = control_action(rotate_logs, []),
- [{error, enoent}, {error, enoent}] = empty_files([MainLog, SaslLog]),
+ ok = control_action(stop_app, []),
+ ok = clean_logs([LogFile], Suffix),
+ ok = application:set_env(rabbit, lager_handler, tty),
+ application:unset_env(lager, handlers),
+ application:unset_env(lager, extra_sinks),
+ ok = control_action(start_app, []),
+ timer:sleep(200),
+ rabbit_log:info("test info"),
+ [{error, enoent}] = empty_files([LogFile]),
%% rotate logs when logging is turned off
- ok = application:set_env(rabbit, sasl_error_logger, false),
- ok = application:set_env(rabbit, error_logger, silent),
- ok = control_action(rotate_logs, []),
- [{error, enoent}, {error, enoent}] = empty_files([MainLog, SaslLog]),
+ ok = control_action(stop_app, []),
+ ok = clean_logs([LogFile], Suffix),
+ ok = application:set_env(rabbit, lager_handler, false),
+ application:unset_env(lager, handlers),
+ application:unset_env(lager, extra_sinks),
+ ok = control_action(start_app, []),
+ timer:sleep(200),
+ rabbit_log:error("test error"),
+ timer:sleep(200),
+ [{error, enoent}] = empty_files([LogFile]),
%% cleanup
- ok = application:set_env(rabbit, sasl_error_logger, {file, SaslLog}),
- ok = application:set_env(rabbit, error_logger, {file, MainLog}),
- ok = add_log_handlers([{rabbit_error_logger_file_h, MainLog},
- {rabbit_sasl_report_file_h, SaslLog}]),
+ ok = control_action(stop_app, []),
+ ok = clean_logs([LogFile], Suffix),
+ ok = application:set_env(rabbit, lager_handler, LogFile),
+ application:unset_env(lager, handlers),
+ application:unset_env(lager, extra_sinks),
+ ok = control_action(start_app, []),
+ ok = test_logs_working([LogFile]),
passed.
log_management_during_startup(Config) ->
@@ -1912,137 +1917,111 @@ log_management_during_startup(Config) ->
?MODULE, log_management_during_startup1, [Config]).
log_management_during_startup1(_Config) ->
- MainLog = rabbit:log_location(kernel),
- SaslLog = rabbit:log_location(sasl),
+ [LogFile] = rabbit:log_locations(),
+ Suffix = ".0",
%% start application with simple tty logging
ok = control_action(stop_app, []),
- ok = application:set_env(rabbit, error_logger, tty),
- ok = application:set_env(rabbit, sasl_error_logger, tty),
- ok = add_log_handlers([{error_logger_tty_h, []},
- {sasl_report_tty_h, []}]),
+ ok = clean_logs([LogFile], Suffix),
+ ok = application:set_env(rabbit, lager_handler, tty),
+ application:unset_env(lager, handlers),
+ application:unset_env(lager, extra_sinks),
ok = control_action(start_app, []),
- %% start application with tty logging and
- %% proper handlers not installed
- ok = control_action(stop_app, []),
- ok = error_logger:tty(false),
- ok = delete_log_handlers([sasl_report_tty_h]),
- ok = case catch control_action(start_app, []) of
- ok -> exit({got_success_but_expected_failure,
- log_rotation_tty_no_handlers_test});
- {badrpc, {'EXIT', {error,
- {cannot_log_to_tty, _, not_installed}}}} -> ok
- end,
-
- %% fix sasl logging
- ok = application:set_env(rabbit, sasl_error_logger, {file, SaslLog}),
-
%% start application with logging to non-existing directory
- TmpLog = "/tmp/rabbit-tests/test.log",
- delete_file(TmpLog),
+ NonExistent = "/tmp/non-existent/test.log",
+ delete_file(NonExistent),
+ delete_file(filename:dirname(NonExistent)),
ok = control_action(stop_app, []),
- ok = application:set_env(rabbit, error_logger, {file, TmpLog}),
-
- ok = delete_log_handlers([rabbit_error_logger_file_h]),
- ok = add_log_handlers([{error_logger_file_h, MainLog}]),
+ ok = application:set_env(rabbit, lager_handler, NonExistent),
+ application:unset_env(lager, handlers),
+ application:unset_env(lager, extra_sinks),
ok = control_action(start_app, []),
%% start application with logging to directory with no
%% write permissions
ok = control_action(stop_app, []),
- TmpDir = "/tmp/rabbit-tests",
- ok = set_permissions(TmpDir, 8#00400),
- ok = delete_log_handlers([rabbit_error_logger_file_h]),
- ok = add_log_handlers([{error_logger_file_h, MainLog}]),
+ NoPermission1 = "/var/empty/test.log",
+ delete_file(NoPermission1),
+ delete_file(filename:dirname(NoPermission1)),
+ ok = control_action(stop_app, []),
+ ok = application:set_env(rabbit, lager_handler, NoPermission1),
+ application:unset_env(lager, handlers),
+ application:unset_env(lager, extra_sinks),
ok = case control_action(start_app, []) of
ok -> exit({got_success_but_expected_failure,
log_rotation_no_write_permission_dir_test});
- {badrpc, {'EXIT',
- {error, {cannot_log_to_file, _, _}}}} -> ok
+ {badrpc,
+ {'EXIT', {error, {cannot_log_to_file, _, Reason1}}}}
+ when Reason1 =:= enoent orelse Reason1 =:= eacces -> ok;
+ {badrpc,
+ {'EXIT',
+ {error, {cannot_log_to_file, _,
+ {cannot_create_parent_dirs, _, Reason1}}}}}
+ when Reason1 =:= eperm orelse
+ Reason1 =:= eacces orelse
+ Reason1 =:= enoent-> ok
end,
%% start application with logging to a subdirectory which
%% parent directory has no write permissions
- ok = control_action(stop_app, []),
- TmpTestDir = "/tmp/rabbit-tests/no-permission/test/log",
- ok = application:set_env(rabbit, error_logger, {file, TmpTestDir}),
- ok = add_log_handlers([{error_logger_file_h, MainLog}]),
+ NoPermission2 = "/var/empty/non-existent/test.log",
+ delete_file(NoPermission2),
+ delete_file(filename:dirname(NoPermission2)),
+ case control_action(stop_app, []) of
+ ok -> ok;
+ {error, lager_not_running} -> ok
+ end,
+ ok = application:set_env(rabbit, lager_handler, NoPermission2),
+ application:unset_env(lager, handlers),
+ application:unset_env(lager, extra_sinks),
ok = case control_action(start_app, []) of
ok -> exit({got_success_but_expected_failure,
log_rotatation_parent_dirs_test});
{badrpc,
+ {'EXIT', {error, {cannot_log_to_file, _, Reason2}}}}
+ when Reason2 =:= enoent orelse Reason2 =:= eacces -> ok;
+ {badrpc,
{'EXIT',
{error, {cannot_log_to_file, _,
- {error,
- {cannot_create_parent_dirs, _, eacces}}}}}} -> ok
+ {cannot_create_parent_dirs, _, Reason2}}}}}
+ when Reason2 =:= eperm orelse
+ Reason2 =:= eacces orelse
+ Reason2 =:= enoent-> ok
end,
- ok = set_permissions(TmpDir, 8#00700),
- ok = set_permissions(TmpLog, 8#00600),
- ok = delete_file(TmpLog),
- ok = file:del_dir(TmpDir),
- %% start application with standard error_logger_file_h
- %% handler not installed
- ok = control_action(stop_app, []),
- ok = application:set_env(rabbit, error_logger, {file, MainLog}),
- ok = control_action(start_app, []),
-
- %% start application with standard sasl handler not installed
- %% and rabbit main log handler installed correctly
- ok = control_action(stop_app, []),
- ok = delete_log_handlers([rabbit_sasl_report_file_h]),
+ %% cleanup
+ ok = application:set_env(rabbit, lager_handler, LogFile),
+ application:unset_env(lager, handlers),
+ application:unset_env(lager, extra_sinks),
ok = control_action(start_app, []),
passed.
-%% "rabbitmqctl rotate_logs" without additional parameters
-%% shouldn't truncate files.
-rotate_logs_without_suffix(Config) ->
+externally_rotated_logs_are_automatically_reopened(Config) ->
passed = rabbit_ct_broker_helpers:rpc(Config, 0,
- ?MODULE, rotate_logs_without_suffix1, [Config]).
-
-rotate_logs_without_suffix1(_Config) ->
- override_group_leader(),
-
- MainLog = rabbit:log_location(kernel),
- SaslLog = rabbit:log_location(sasl),
- Suffix = ".1",
- file:delete(MainLog),
- file:delete(SaslLog),
+ ?MODULE, externally_rotated_logs_are_automatically_reopened1, [Config]).
- %% Empty log-files should be created
- ok = control_action(rotate_logs, []),
- [true, true] = empty_files([MainLog, SaslLog]),
+externally_rotated_logs_are_automatically_reopened1(_Config) ->
+ [LogFile] = rabbit:log_locations(),
- %% Write something to log files and simulate external log rotation
- ok = test_logs_working(MainLog, SaslLog),
- ok = file:rename(MainLog, [MainLog, Suffix]),
- ok = file:rename(SaslLog, [SaslLog, Suffix]),
+ %% Make sure log file is opened
+ ok = test_logs_working([LogFile]),
- %% Create non-empty files
- TestData = "test-data\n",
- file:write_file(MainLog, TestData),
- file:write_file(SaslLog, TestData),
+ %% Move it away - i.e. external log rotation happened
+ file:rename(LogFile, [LogFile, ".rotation_test"]),
- %% Nothing should be truncated - neither moved files which are still
- %% opened by server, nor new log files that should be just reopened.
- ok = control_action(rotate_logs, []),
- [true, true, true, true] =
- non_empty_files([MainLog, SaslLog, [MainLog, Suffix],
- [SaslLog, Suffix]]),
-
- %% And log files should be re-opened - new log records should go to
- %% new files.
- ok = test_logs_working(MainLog, SaslLog),
- true = (rabbit_file:file_size(MainLog) > length(TestData)),
- true = (rabbit_file:file_size(SaslLog) > length(TestData)),
+ %% New files should be created - test_logs_working/1 will check that
+ %% LogFile is not empty after doing some logging. And it's exactly
+ %% what we need to check here.
+ ok = test_logs_working([LogFile]),
passed.
-override_group_leader() ->
- %% Override group leader, otherwise SASL fake events are ignored by
- %% the error_logger local to RabbitMQ.
- {group_leader, Leader} = erlang:process_info(whereis(rabbit), group_leader),
- erlang:group_leader(Leader, self()).
+empty_or_nonexist_files(Files) ->
+ [case file:read_file_info(File) of
+ {ok, FInfo} -> FInfo#file_info.size == 0;
+ {error, enoent} -> true;
+ Error -> Error
+ end || File <- Files].
empty_files(Files) ->
[case file:read_file_info(File) of
@@ -2056,12 +2035,11 @@ non_empty_files(Files) ->
_ -> not(EmptyFile)
end || EmptyFile <- empty_files(Files)].
-test_logs_working(MainLogFile, SaslLogFile) ->
- ok = rabbit_log:error("Log a test message~n"),
- ok = error_logger:error_report(crash_report, [fake_crash_report, ?MODULE]),
+test_logs_working(LogFiles) ->
+ ok = rabbit_log:error("Log a test message"),
%% give the error loggers some time to catch up
- timer:sleep(100),
- [true, true] = non_empty_files([MainLogFile, SaslLogFile]),
+ timer:sleep(200),
+ lists:all(fun(LogFile) -> [true] =:= non_empty_files([LogFile]) end, LogFiles),
ok.
set_permissions(Path, Mode) ->