Merge branch 'master' into EMQX-871-872
This commit is contained in:
commit
25f03ed87a
|
@ -38,8 +38,9 @@ emqx_test(){
|
|||
packagename=$(basename "${PACKAGE_PATH}/${EMQX_NAME}"-*.zip)
|
||||
unzip -q "${PACKAGE_PATH}/${packagename}"
|
||||
export EMQX_ZONE__EXTERNAL__SERVER_KEEPALIVE=60 \
|
||||
EMQX_MQTT__MAX_TOPIC_ALIAS=10
|
||||
sed -i '/emqx_telemetry/d' "${PACKAGE_PATH}"/emqx/data/loaded_plugins
|
||||
EMQX_MQTT__MAX_TOPIC_ALIAS=10
|
||||
[[ $(arch) == *arm* || $(arch) == aarch64 ]] && export EMQX_ZONES__DEFAULT__LISTENERS__MQTT_QUIC__ENABLED=false
|
||||
# sed -i '/emqx_telemetry/d' "${PACKAGE_PATH}"/emqx/data/loaded_plugins
|
||||
|
||||
echo "running ${packagename} start"
|
||||
if ! "${PACKAGE_PATH}"/emqx/bin/emqx start; then
|
||||
|
@ -48,7 +49,7 @@ emqx_test(){
|
|||
exit 1
|
||||
fi
|
||||
IDLE_TIME=0
|
||||
while ! curl http://localhost:8081/status >/dev/null 2>&1; do
|
||||
while ! curl http://localhost:8081/api/v5/status >/dev/null 2>&1; do
|
||||
if [ $IDLE_TIME -gt 10 ]
|
||||
then
|
||||
echo "emqx running error"
|
||||
|
@ -113,17 +114,31 @@ emqx_test(){
|
|||
}
|
||||
|
||||
running_test(){
|
||||
export EMQX_ZONE__EXTERNAL__SERVER_KEEPALIVE=60 \
|
||||
EMQX_MQTT__MAX_TOPIC_ALIAS=10
|
||||
sed -i '/emqx_telemetry/d' /var/lib/emqx/loaded_plugins
|
||||
# sed -i '/emqx_telemetry/d' /var/lib/emqx/loaded_plugins
|
||||
emqx_env_vars=$(dirname "$(readlink "$(command -v emqx)")")/../releases/emqx_vars
|
||||
|
||||
if ! emqx start; then
|
||||
if [ -f "$emqx_env_vars" ];
|
||||
then
|
||||
tee -a "$emqx_env_vars" <<EOF
|
||||
export EMQX_ZONE__EXTERNAL__SERVER_KEEPALIVE=60
|
||||
export EMQX_MQTT__MAX_TOPIC_ALIAS=10
|
||||
EOF
|
||||
## for ARM, due to CI env issue, skip start of quic listener for the moment
|
||||
[[ $(arch) == *arm* || $(arch) == aarch64 ]] && tee -a "$emqx_env_vars" <<EOF
|
||||
export EMQX_ZONES__DEFAULT__LISTENERS__MQTT_QUIC__ENABLED=false
|
||||
EOF
|
||||
else
|
||||
echo "Error: cannot locate emqx_vars"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! su - emqx -c "emqx start"; then
|
||||
cat /var/log/emqx/erlang.log.1 || true
|
||||
cat /var/log/emqx/emqx.log.1 || true
|
||||
exit 1
|
||||
fi
|
||||
IDLE_TIME=0
|
||||
while ! curl http://localhost:8081/status >/dev/null 2>&1; do
|
||||
while ! curl http://localhost:8081/api/v5/status >/dev/null 2>&1; do
|
||||
if [ $IDLE_TIME -gt 10 ]
|
||||
then
|
||||
echo "emqx running error"
|
||||
|
@ -138,14 +153,13 @@ running_test(){
|
|||
|
||||
if [ "$(sed -n '/^ID=/p' /etc/os-release | sed -r 's/ID=(.*)/\1/g' | sed 's/"//g')" = ubuntu ] \
|
||||
|| [ "$(sed -n '/^ID=/p' /etc/os-release | sed -r 's/ID=(.*)/\1/g' | sed 's/"//g')" = debian ] ;then
|
||||
|
||||
if ! service emqx start; then
|
||||
cat /var/log/emqx/erlang.log.1 || true
|
||||
cat /var/log/emqx/emqx.log.1 || true
|
||||
exit 1
|
||||
fi
|
||||
IDLE_TIME=0
|
||||
while ! curl http://localhost:8081/status >/dev/null 2>&1; do
|
||||
while ! curl http://localhost:8081/api/v5/status >/dev/null 2>&1; do
|
||||
if [ $IDLE_TIME -gt 10 ]
|
||||
then
|
||||
echo "emqx service error"
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
EMQX_NAME=emqx
|
||||
EMQX_CLUSTER__DISCOVERY=static
|
||||
EMQX_CLUSTER__STATIC__SEEDS="emqx@node1.emqx.io, emqx@node2.emqx.io"
|
||||
EMQX_LISTENER__TCP__EXTERNAL__PROXY_PROTOCOL=on
|
||||
EMQX_LISTENER__WS__EXTERNAL__PROXY_PROTOCOL=on
|
||||
EMQX_LOG__LEVEL=debug
|
||||
EMQX_LOADED_PLUGINS=emqx_sn
|
||||
EMQX_CLUSTER__DISCOVERY_STRATEGY=static
|
||||
EMQX_CLUSTER__STATIC__SEEDS="[emqx@node1.emqx.io, emqx@node2.emqx.io]"
|
||||
EMQX_ZONES__DEFAULT__LISTENERS__MQTT_TCP__PROXY_PROTOCOL=true
|
||||
EMQX_ZONES__DEFAULT__LISTENERS__MQTT_WS__PROXY_PROTOCOL=true
|
||||
EMQX_LOG__CONSOLE_HANDLER__ENABLE=true
|
||||
EMQX_LOG__CONSOLE_HANDLER__LEVEL=debug
|
||||
EMQX_LOG__PRIMARY_LEVEL=debug
|
||||
|
|
|
@ -10,5 +10,4 @@ EMQX_AUTH__PGSQL__PASSWORD=public
|
|||
EMQX_AUTH__PGSQL__DATABASE=mqtt
|
||||
EMQX_AUTH__REDIS__SERVER=redis_server:6379
|
||||
EMQX_AUTH__REDIS__PASSWORD=public
|
||||
CUTTLEFISH_ENV_OVERRIDE_PREFIX=EMQX_
|
||||
HOCON_ENV_OVERRIDE_PREFIX=EMQX_
|
||||
|
|
|
@ -33,13 +33,6 @@ services:
|
|||
- conf.cluster.env
|
||||
environment:
|
||||
- "EMQX_HOST=node1.emqx.io"
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- |
|
||||
sed -i "s 127.0.0.1 $$(ip route show |grep "link" |awk '{print $$1}') g" /opt/emqx/etc/acl.conf
|
||||
sed -i '/emqx_telemetry/d' /opt/emqx/data/loaded_plugins
|
||||
/opt/emqx/bin/emqx foreground
|
||||
healthcheck:
|
||||
test: ["CMD", "/opt/emqx/bin/emqx_ctl", "status"]
|
||||
interval: 5s
|
||||
|
@ -57,13 +50,6 @@ services:
|
|||
- conf.cluster.env
|
||||
environment:
|
||||
- "EMQX_HOST=node2.emqx.io"
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- |
|
||||
sed -i "s 127.0.0.1 $$(ip route show |grep "link" |awk '{print $$1}') g" /opt/emqx/etc/acl.conf
|
||||
sed -i '/emqx_telemetry/d' /opt/emqx/data/loaded_plugins
|
||||
/opt/emqx/bin/emqx foreground
|
||||
healthcheck:
|
||||
test: ["CMD", "/opt/emqx/bin/emqx", "ping"]
|
||||
interval: 5s
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
version: "3"
|
||||
|
||||
services:
|
||||
mongo1:
|
||||
hostname: mongo1
|
||||
container_name: mongo1
|
||||
image: mongo:${MONGO_TAG}
|
||||
environment:
|
||||
MONGO_INITDB_DATABASE: mqtt
|
||||
networks:
|
||||
- emqx_bridge
|
||||
expose:
|
||||
- 27017
|
||||
ports:
|
||||
- 27011:27017
|
||||
restart: always
|
||||
command:
|
||||
--ipv6
|
||||
--bind_ip_all
|
||||
--replSet rs0
|
||||
|
||||
mongo2:
|
||||
hostname: mongo2
|
||||
container_name: mongo2
|
||||
image: mongo:${MONGO_TAG}
|
||||
environment:
|
||||
MONGO_INITDB_DATABASE: mqtt
|
||||
networks:
|
||||
- emqx_bridge
|
||||
expose:
|
||||
- 27017
|
||||
ports:
|
||||
- 27012:27017
|
||||
restart: always
|
||||
command:
|
||||
--ipv6
|
||||
--bind_ip_all
|
||||
--replSet rs0
|
||||
|
||||
mongo3:
|
||||
hostname: mongo3
|
||||
container_name: mongo3
|
||||
image: mongo:${MONGO_TAG}
|
||||
environment:
|
||||
MONGO_INITDB_DATABASE: mqtt
|
||||
networks:
|
||||
- emqx_bridge
|
||||
expose:
|
||||
- 27017
|
||||
ports:
|
||||
- 27013:27017
|
||||
restart: always
|
||||
command:
|
||||
--ipv6
|
||||
--bind_ip_all
|
||||
--replSet rs0
|
||||
|
||||
mongo_client:
|
||||
image: mongo:${MONGO_TAG}
|
||||
container_name: mongo_client
|
||||
networks:
|
||||
- emqx_bridge
|
||||
depends_on:
|
||||
- mongo1
|
||||
- mongo2
|
||||
- mongo3
|
||||
command:
|
||||
- /bin/bash
|
||||
- -c
|
||||
- |
|
||||
while ! mongo --host mongo1 --eval 'db.runCommand("ping").ok' --quiet > /dev/null 2>&1; do
|
||||
sleep 1
|
||||
done
|
||||
while ! mongo --host mongo2 --eval 'db.runCommand("ping").ok' --quiet > /dev/null 2>&1; do
|
||||
sleep 1
|
||||
done
|
||||
while ! mongo --host mongo3 --eval 'db.runCommand("ping").ok' --quiet > /dev/null 2>&1; do
|
||||
sleep 1
|
||||
done
|
||||
mongo --host mongo1 --eval "rs.initiate( { _id : 'rs0', members: [ { _id : 0, host : 'mongo1:27017' }, { _id : 1, host : 'mongo2:27017' }, { _id : 2, host : 'mongo3:27017' } ] })" --quiet
|
||||
mongo --host mongo1 --eval "rs.status()" --quiet
|
|
@ -0,0 +1,98 @@
|
|||
version: "3"
|
||||
|
||||
services:
|
||||
mongo1:
|
||||
hostname: mongo1
|
||||
container_name: mongo1
|
||||
image: mongo:${MONGO_TAG}
|
||||
environment:
|
||||
MONGO_INITDB_DATABASE: mqtt
|
||||
networks:
|
||||
- emqx_bridge
|
||||
expose:
|
||||
- 27017
|
||||
ports:
|
||||
- 27011:27017
|
||||
restart: always
|
||||
volumes:
|
||||
- ../../apps/emqx/etc/certs/cert.pem:/etc/certs/cert.pem
|
||||
- ../../apps/emqx/etc/certs/key.pem:/etc/certs/key.pem
|
||||
command:
|
||||
- /bin/bash
|
||||
- -c
|
||||
- |
|
||||
cat /etc/certs/key.pem /etc/certs/cert.pem > /etc/certs/mongodb.pem
|
||||
mongod --ipv6 --bind_ip_all --tlsMode requireTLS --tlsCertificateKeyFile /etc/certs/mongodb.pem --replSet rs0
|
||||
|
||||
mongo2:
|
||||
hostname: mongo2
|
||||
container_name: mongo2
|
||||
image: mongo:${MONGO_TAG}
|
||||
environment:
|
||||
MONGO_INITDB_DATABASE: mqtt
|
||||
networks:
|
||||
- emqx_bridge
|
||||
expose:
|
||||
- 27017
|
||||
ports:
|
||||
- 27012:27017
|
||||
restart: always
|
||||
volumes:
|
||||
- ../../apps/emqx/etc/certs/cert.pem:/etc/certs/cert.pem
|
||||
- ../../apps/emqx/etc/certs/key.pem:/etc/certs/key.pem
|
||||
command:
|
||||
- /bin/bash
|
||||
- -c
|
||||
- |
|
||||
cat /etc/certs/key.pem /etc/certs/cert.pem > /etc/certs/mongodb.pem
|
||||
mongod --ipv6 --bind_ip_all --tlsMode requireTLS --tlsCertificateKeyFile /etc/certs/mongodb.pem --replSet rs0
|
||||
|
||||
mongo3:
|
||||
hostname: mongo3
|
||||
container_name: mongo3
|
||||
image: mongo:${MONGO_TAG}
|
||||
environment:
|
||||
MONGO_INITDB_DATABASE: mqtt
|
||||
networks:
|
||||
- emqx_bridge
|
||||
expose:
|
||||
- 27017
|
||||
ports:
|
||||
- 27013:27017
|
||||
restart: always
|
||||
volumes:
|
||||
- ../../apps/emqx/etc/certs/cert.pem:/etc/certs/cert.pem
|
||||
- ../../apps/emqx/etc/certs/key.pem:/etc/certs/key.pem
|
||||
command:
|
||||
- /bin/bash
|
||||
- -c
|
||||
- |
|
||||
cat /etc/certs/key.pem /etc/certs/cert.pem > /etc/certs/mongodb.pem
|
||||
mongod --ipv6 --bind_ip_all --tlsMode requireTLS --tlsCertificateKeyFile /etc/certs/mongodb.pem --replSet rs0
|
||||
|
||||
mongo_client:
|
||||
image: mongo:${MONGO_TAG}
|
||||
container_name: mongo_client
|
||||
networks:
|
||||
- emqx_bridge
|
||||
depends_on:
|
||||
- mongo1
|
||||
- mongo2
|
||||
- mongo3
|
||||
volumes:
|
||||
- ../../apps/emqx/etc/certs/cacert.pem:/etc/certs/cacert.pem
|
||||
command:
|
||||
- /bin/bash
|
||||
- -c
|
||||
- |
|
||||
while ! mongo --host mongo1 --tls --tlsCAFile /etc/certs/cacert.pem --tlsAllowInvalidHostnames --eval 'db.runCommand("ping").ok' --quiet > /dev/null 2>&1; do
|
||||
sleep 1
|
||||
done
|
||||
while ! mongo --host mongo2 --tls --tlsCAFile /etc/certs/cacert.pem --tlsAllowInvalidHostnames --eval 'db.runCommand("ping").ok' --quiet > /dev/null 2>&1; do
|
||||
sleep 1
|
||||
done
|
||||
while ! mongo --host mongo3 --tls --tlsCAFile /etc/certs/cacert.pem --tlsAllowInvalidHostnames --eval 'db.runCommand("ping").ok' --quiet > /dev/null 2>&1; do
|
||||
sleep 1
|
||||
done
|
||||
mongo --host mongo1 --tls --tlsCAFile /etc/certs/cacert.pem --tlsAllowInvalidHostnames --eval "rs.initiate( { _id : 'rs0', members: [ { _id : 0, host : 'mongo1:27017' }, { _id : 1, host : 'mongo2:27017' }, { _id : 2, host : 'mongo3:27017' } ] })" --quiet
|
||||
mongo --host mongo1 --tls --tlsCAFile /etc/certs/cacert.pem --tlsAllowInvalidHostnames --eval "rs.status()" --quiet
|
|
@ -9,6 +9,8 @@ services:
|
|||
MONGO_INITDB_DATABASE: mqtt
|
||||
networks:
|
||||
- emqx_bridge
|
||||
ports:
|
||||
- "27017:27017"
|
||||
command:
|
||||
--ipv6
|
||||
--bind_ip_all
|
|
@ -0,0 +1,23 @@
|
|||
version: '3.9'
|
||||
|
||||
services:
|
||||
mongo_server:
|
||||
container_name: mongo
|
||||
image: mongo:${MONGO_TAG}
|
||||
restart: always
|
||||
environment:
|
||||
MONGO_INITDB_DATABASE: mqtt
|
||||
volumes:
|
||||
- ../../apps/emqx/etc/certs/cert.pem:/etc/certs/cert.pem
|
||||
- ../../apps/emqx/etc/certs/key.pem:/etc/certs/key.pem
|
||||
networks:
|
||||
- emqx_bridge
|
||||
ports:
|
||||
- "27017:27017"
|
||||
command:
|
||||
- /bin/bash
|
||||
- -c
|
||||
- |
|
||||
cat /etc/certs/key.pem /etc/certs/cert.pem > /etc/certs/mongodb.pem
|
||||
mongod --ipv6 --bind_ip_all --sslMode requireSSL --sslPEMKeyFile /etc/certs/mongodb.pem
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
version: '3.9'
|
||||
|
||||
services:
|
||||
mongo_server:
|
||||
container_name: mongo
|
||||
image: mongo:${MONGO_TAG}
|
||||
restart: always
|
||||
environment:
|
||||
MONGO_INITDB_DATABASE: mqtt
|
||||
volumes:
|
||||
- ../../apps/emqx_auth_mongo/test/emqx_auth_mongo_SUITE_data/mongodb.pem/:/etc/certs/mongodb.pem
|
||||
networks:
|
||||
- emqx_bridge
|
||||
command:
|
||||
--ipv6
|
||||
--bind_ip_all
|
||||
--sslMode requireSSL
|
||||
--sslPEMKeyFile /etc/certs/mongodb.pem
|
|
@ -11,9 +11,11 @@ services:
|
|||
MYSQL_USER: ssluser
|
||||
MYSQL_PASSWORD: public
|
||||
volumes:
|
||||
- ../../apps/emqx_auth_mysql/test/emqx_auth_mysql_SUITE_data/ca.pem:/etc/certs/ca-cert.pem
|
||||
- ../../apps/emqx_auth_mysql/test/emqx_auth_mysql_SUITE_data/server-cert.pem:/etc/certs/server-cert.pem
|
||||
- ../../apps/emqx_auth_mysql/test/emqx_auth_mysql_SUITE_data/server-key.pem:/etc/certs/server-key.pem
|
||||
- ../../apps/emqx/etc/certs/cacert.pem:/etc/certs/ca-cert.pem
|
||||
- ../../apps/emqx/etc/certs/cert.pem:/etc/certs/server-cert.pem
|
||||
- ../../apps/emqx/etc/certs/key.pem:/etc/certs/server-key.pem
|
||||
ports:
|
||||
- "3306:3306"
|
||||
networks:
|
||||
- emqx_bridge
|
||||
command:
|
||||
|
|
|
@ -5,7 +5,9 @@ services:
|
|||
container_name: redis
|
||||
image: redis:${REDIS_TAG}
|
||||
volumes:
|
||||
- ../../apps/emqx_auth_redis/test/emqx_auth_redis_SUITE_data/certs:/tls
|
||||
- ../../apps/emqx/etc/certs/cacert.pem:/etc/certs/ca.crt
|
||||
- ../../apps/emqx/etc/certs/cert.pem:/etc/certs/redis.crt
|
||||
- ../../apps/emqx/etc/certs/key.pem:/etc/certs/redis.key
|
||||
- ./redis/:/data/conf
|
||||
command: bash -c "/bin/bash /data/conf/redis.sh --node cluster --tls-enabled && tail -f /var/log/redis-server.log"
|
||||
networks:
|
||||
|
|
|
@ -5,7 +5,9 @@ services:
|
|||
container_name: redis
|
||||
image: redis:${REDIS_TAG}
|
||||
volumes:
|
||||
- ../../apps/emqx_auth_redis/test/emqx_auth_redis_SUITE_data/certs:/tls
|
||||
- ../../apps/emqx/etc/certs/cacert.pem:/etc/certs/ca.crt
|
||||
- ../../apps/emqx/etc/certs/cert.pem:/etc/certs/redis.crt
|
||||
- ../../apps/emqx/etc/certs/key.pem:/etc/certs/redis.key
|
||||
- ./redis/:/data/conf
|
||||
command: bash -c "/bin/bash /data/conf/redis.sh --node sentinel --tls-enabled && tail -f /var/log/redis-server.log"
|
||||
networks:
|
||||
|
|
|
@ -5,15 +5,17 @@ services:
|
|||
container_name: redis
|
||||
image: redis:${REDIS_TAG}
|
||||
volumes:
|
||||
- ../../apps/emqx_auth_redis/test/emqx_auth_redis_SUITE_data/certs:/tls
|
||||
- ../../apps/emqx/etc/certs/cacert.pem:/etc/certs/ca.crt
|
||||
- ../../apps/emqx/etc/certs/cert.pem:/etc/certs/redis.crt
|
||||
- ../../apps/emqx/etc/certs/key.pem:/etc/certs/redis.key
|
||||
command:
|
||||
- redis-server
|
||||
- "--bind 0.0.0.0 ::"
|
||||
- --requirepass public
|
||||
- --tls-port 6380
|
||||
- --tls-cert-file /tls/redis.crt
|
||||
- --tls-key-file /tls/redis.key
|
||||
- --tls-ca-cert-file /tls/ca.crt
|
||||
- --tls-cert-file /etc/certs/redis.crt
|
||||
- --tls-key-file /etc/certs/redis.key
|
||||
- --tls-ca-cert-file /etc/certs/ca.crt
|
||||
restart: always
|
||||
networks:
|
||||
- emqx_bridge
|
||||
|
|
|
@ -2,9 +2,9 @@ ARG BUILD_FROM=postgres:11
|
|||
FROM ${BUILD_FROM}
|
||||
ARG POSTGRES_USER=postgres
|
||||
COPY --chown=$POSTGRES_USER .ci/docker-compose-file/pgsql/pg_hba.conf /var/lib/postgresql/pg_hba.conf
|
||||
COPY --chown=$POSTGRES_USER apps/emqx_auth_pgsql/test/emqx_auth_pgsql_SUITE_data/server-key.pem /var/lib/postgresql/server.key
|
||||
COPY --chown=$POSTGRES_USER apps/emqx_auth_pgsql/test/emqx_auth_pgsql_SUITE_data/server-cert.pem /var/lib/postgresql/server.crt
|
||||
COPY --chown=$POSTGRES_USER apps/emqx_auth_pgsql/test/emqx_auth_pgsql_SUITE_data/ca.pem /var/lib/postgresql/root.crt
|
||||
COPY --chown=$POSTGRES_USER apps/emqx/etc/certs/key.pem /var/lib/postgresql/server.key
|
||||
COPY --chown=$POSTGRES_USER apps/emqx/etc/certs/cert.pem /var/lib/postgresql/server.crt
|
||||
COPY --chown=$POSTGRES_USER apps/emqx/etc/certs/cacert.pem /var/lib/postgresql/root.crt
|
||||
RUN chmod 600 /var/lib/postgresql/pg_hba.conf
|
||||
RUN chmod 600 /var/lib/postgresql/server.key
|
||||
RUN chmod 600 /var/lib/postgresql/server.crt
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
daemonize yes
|
||||
bind 0.0.0.0 ::
|
||||
logfile /var/log/redis-server.log
|
||||
tls-cert-file /tls/redis.crt
|
||||
tls-key-file /tls/redis.key
|
||||
tls-ca-cert-file /tls/ca.crt
|
||||
tls-cert-file /etc/certs/redis.crt
|
||||
tls-key-file /etc/certs/redis.key
|
||||
tls-ca-cert-file /etc/certs/ca.crt
|
||||
tls-replication yes
|
||||
tls-cluster yes
|
||||
protected-mode no
|
||||
|
|
|
@ -91,7 +91,7 @@ do
|
|||
fi
|
||||
if [ "${node}" = "cluster" ] ; then
|
||||
if $tls ; then
|
||||
yes "yes" | redis-cli --cluster create "$LOCAL_IP:8000" "$LOCAL_IP:8001" "$LOCAL_IP:8002" --pass public --no-auth-warning --tls true --cacert /tls/ca.crt --cert /tls/redis.crt --key /tls/redis.key;
|
||||
yes "yes" | redis-cli --cluster create "$LOCAL_IP:8000" "$LOCAL_IP:8001" "$LOCAL_IP:8002" --pass public --no-auth-warning --tls true --cacert /etc/certs/ca.crt --cert /etc/certs/redis.crt --key /etc/certs/redis.key;
|
||||
else
|
||||
yes "yes" | redis-cli --cluster create "$LOCAL_IP:7000" "$LOCAL_IP:7001" "$LOCAL_IP:7002" --pass public --no-auth-warning;
|
||||
fi
|
||||
|
@ -107,9 +107,9 @@ EOF
|
|||
cat >>/_sentinel.conf<<EOF
|
||||
tls-port 26380
|
||||
tls-replication yes
|
||||
tls-cert-file /tls/redis.crt
|
||||
tls-key-file /tls/redis.key
|
||||
tls-ca-cert-file /tls/ca.crt
|
||||
tls-cert-file /etc/certs/redis.crt
|
||||
tls-key-file /etc/certs/redis.key
|
||||
tls-ca-cert-file /etc/certs/ca.crt
|
||||
sentinel monitor mymaster $LOCAL_IP 8000 1
|
||||
EOF
|
||||
else
|
||||
|
|
|
@ -131,6 +131,13 @@
|
|||
[shell bench]
|
||||
???publish complete
|
||||
??SH-PROMPT:
|
||||
!sleep 5
|
||||
?SH-PROMPT
|
||||
|
||||
!curl --user admin:public --silent --show-error http://localhost:8081/api/v4/rules | jq --raw-output ".data[0].metrics[] | select(.node==\"emqx@127.0.0.1\").matched"
|
||||
?300
|
||||
?SH-PROMPT
|
||||
|
||||
!curl http://127.0.0.1:8080/counter
|
||||
???{"data":300,"code":0}
|
||||
?SH-PROMPT
|
||||
|
|
|
@ -3,7 +3,6 @@ name: Bug Report
|
|||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: Support
|
||||
assignees: tigercl
|
||||
|
||||
---
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@ name: Feature Request
|
|||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: Feature
|
||||
assignees: tigercl
|
||||
|
||||
---
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@ name: Support Needed
|
|||
about: Asking a question about usages, docs or anything you're insterested in
|
||||
title: ''
|
||||
labels: Support
|
||||
assignees: tigercl
|
||||
|
||||
---
|
||||
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
<!-- Please describe the current behavior and link to a relevant issue. -->
|
||||
Fixes <issue-number>
|
||||
|
||||
**If your build fails** due to your commit message not passing the build checks, please review the guidelines here: https://github.com/emqx/emqx/blob/master/CONTRIBUTING.md.
|
||||
|
||||
## PR Checklist
|
||||
Please convert it to a draft if any of the following conditions are not met. Reviewers may skip over until all the items are checked:
|
||||
|
||||
- [ ] Tests for the changes have been added (for bug fixes / features)
|
||||
- [ ] Docs have been added / updated (for bug fixes / features)
|
||||
- [ ] In case of non-backward compatible changes, reviewer should check this item as a write-off, and add details in **Backward Compatibility** section
|
||||
|
||||
## Backward Compatibility
|
||||
|
||||
## More information
|
|
@ -42,6 +42,7 @@ jobs:
|
|||
if: endsWith(github.repository, 'emqx')
|
||||
run: |
|
||||
make -C source deps-all
|
||||
rm source/rebar.lock
|
||||
zip -ryq source.zip source/* source/.[^.]*
|
||||
- name: get_all_deps
|
||||
if: endsWith(github.repository, 'enterprise')
|
||||
|
@ -63,6 +64,7 @@ jobs:
|
|||
if: endsWith(github.repository, 'emqx')
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
profile: ${{fromJSON(needs.prepare.outputs.profiles)}}
|
||||
exclude:
|
||||
|
@ -131,6 +133,7 @@ jobs:
|
|||
needs: prepare
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
profile: ${{fromJSON(needs.prepare.outputs.profiles)}}
|
||||
erl_otp:
|
||||
|
@ -179,11 +182,11 @@ jobs:
|
|||
cd source
|
||||
pkg_name=$(basename _packages/${{ matrix.profile }}/${{ matrix.profile }}-*.zip)
|
||||
unzip -q _packages/${{ matrix.profile }}/$pkg_name
|
||||
gsed -i '/emqx_telemetry/d' ./emqx/data/loaded_plugins
|
||||
# gsed -i '/emqx_telemetry/d' ./emqx/data/loaded_plugins
|
||||
./emqx/bin/emqx start || cat emqx/log/erlang.log.1
|
||||
ready='no'
|
||||
for i in {1..10}; do
|
||||
if curl -fs 127.0.0.1:18083 > /dev/null; then
|
||||
if curl -fs 127.0.0.1:8081/api/v5/status > /dev/null; then
|
||||
ready='yes'
|
||||
break
|
||||
fi
|
||||
|
@ -210,6 +213,7 @@ jobs:
|
|||
needs: prepare
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
profile: ${{fromJSON(needs.prepare.outputs.profiles)}}
|
||||
arch:
|
||||
|
@ -336,19 +340,13 @@ jobs:
|
|||
needs: prepare
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
profile: ${{fromJSON(needs.prepare.outputs.profiles)}}
|
||||
arch:
|
||||
- [amd64, x86_64]
|
||||
- [arm64v8, aarch64]
|
||||
- [arm32v7, arm]
|
||||
- [i386, i386]
|
||||
- [s390x, s390x]
|
||||
exclude:
|
||||
- profile: emqx-ee
|
||||
arch: [i386, i386]
|
||||
- profile: emqx-ee
|
||||
arch: [s390x, s390x]
|
||||
|
||||
steps:
|
||||
- uses: actions/download-artifact@v2
|
||||
|
|
|
@ -108,11 +108,11 @@ jobs:
|
|||
run: |
|
||||
pkg_name=$(basename _packages/${EMQX_NAME}/emqx-*.zip)
|
||||
unzip -q _packages/${EMQX_NAME}/$pkg_name
|
||||
gsed -i '/emqx_telemetry/d' ./emqx/data/loaded_plugins
|
||||
# gsed -i '/emqx_telemetry/d' ./emqx/data/loaded_plugins
|
||||
./emqx/bin/emqx start || cat emqx/log/erlang.log.1
|
||||
ready='no'
|
||||
for i in {1..10}; do
|
||||
if curl -fs 127.0.0.1:18083 > /dev/null; then
|
||||
if curl -fs 127.0.0.1:8081/api/v5/status > /dev/null; then
|
||||
ready='yes'
|
||||
break
|
||||
fi
|
||||
|
|
|
@ -36,10 +36,9 @@ jobs:
|
|||
timeout-minutes: 5
|
||||
run: |
|
||||
set -e -u -x
|
||||
echo "CUTTLEFISH_ENV_OVERRIDE_PREFIX=EMQX_" >> .ci/docker-compose-file/conf.cluster.env
|
||||
echo "HOCON_ENV_OVERRIDE_PREFIX=EMQX_" >> .ci/docker-compose-file/conf.cluster.env
|
||||
echo "EMQX_ZONE__EXTERNAL__RETRY_INTERVAL=2s" >> .ci/docker-compose-file/conf.cluster.env
|
||||
echo "EMQX_MQTT__MAX_TOPIC_ALIAS=10" >> .ci/docker-compose-file/conf.cluster.env
|
||||
echo "EMQX_ZONES__DEFAULT__MQTT__RETRY_INTERVAL=2s" >> .ci/docker-compose-file/conf.cluster.env
|
||||
echo "EMQX_ZONES__DEFAULT__MQTT__MAX_TOPIC_ALIAS=10" >> .ci/docker-compose-file/conf.cluster.env
|
||||
docker-compose \
|
||||
-f .ci/docker-compose-file/docker-compose-emqx-cluster.yaml \
|
||||
-f .ci/docker-compose-file/docker-compose-python.yaml \
|
||||
|
@ -48,13 +47,13 @@ jobs:
|
|||
echo "['$(date -u +"%Y-%m-%dT%H:%M:%SZ")']:waiting emqx";
|
||||
sleep 5;
|
||||
done
|
||||
- name: verify EMQX_LOADED_PLUGINS override working
|
||||
run: |
|
||||
expected="{emqx_sn, true}."
|
||||
output=$(docker exec -i node1.emqx.io bash -c "cat data/loaded_plugins" | tail -n1)
|
||||
if [ "$expected" != "$output" ]; then
|
||||
exit 1
|
||||
fi
|
||||
# - name: verify EMQX_LOADED_PLUGINS override working
|
||||
# run: |
|
||||
# expected="{emqx_sn, true}."
|
||||
# output=$(docker exec -i node1.emqx.io bash -c "cat data/loaded_plugins" | tail -n1)
|
||||
# if [ "$expected" != "$output" ]; then
|
||||
# exit 1
|
||||
# fi
|
||||
- name: make paho tests
|
||||
run: |
|
||||
if ! docker exec -i python /scripts/pytest.sh; then
|
||||
|
@ -118,8 +117,8 @@ jobs:
|
|||
--set image.pullPolicy=Never \
|
||||
--set emqxAclConfig="" \
|
||||
--set image.pullPolicy=Never \
|
||||
--set emqxConfig.EMQX_ZONE__EXTERNAL__RETRY_INTERVAL=2s \
|
||||
--set emqxConfig.EMQX_MQTT__MAX_TOPIC_ALIAS=10 \
|
||||
--set emqxConfig.EMQX_ZONES__DEFAULT__MQTT__RETRY_INTERVAL=2s \
|
||||
--set emqxConfig.EMQX_ZONES__DEFAULT__MQTT__MAX_TOPIC_ALIAS=10 \
|
||||
deploy/charts/emqx \
|
||||
--debug
|
||||
|
||||
|
@ -131,11 +130,27 @@ jobs:
|
|||
echo "waiting emqx started";
|
||||
sleep 10;
|
||||
done
|
||||
- name: get pods log
|
||||
- name: get emqx-0 pods log
|
||||
if: failure()
|
||||
env:
|
||||
KUBECONFIG: "/etc/rancher/k3s/k3s.yaml"
|
||||
run: kubectl describe pods emqx-0
|
||||
run: |
|
||||
kubectl describe pods emqx-0
|
||||
kubectl logs emqx-0
|
||||
- name: get emqx-1 pods log
|
||||
if: failure()
|
||||
env:
|
||||
KUBECONFIG: "/etc/rancher/k3s/k3s.yaml"
|
||||
run: |
|
||||
kubectl describe pods emqx-1
|
||||
kubectl logs emqx-1
|
||||
- name: get emqx-2 pods log
|
||||
if: failure()
|
||||
env:
|
||||
KUBECONFIG: "/etc/rancher/k3s/k3s.yaml"
|
||||
run: |
|
||||
kubectl describe pods emqx-2
|
||||
kubectl logs emqx-2
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
repository: emqx/paho.mqtt.testing
|
||||
|
|
|
@ -45,6 +45,8 @@ emqx_dialyzer_*_plt
|
|||
*/emqx_dashboard/priv/www
|
||||
dist.zip
|
||||
scripts/git-token
|
||||
etc/*.seg
|
||||
apps/*/etc/*.all
|
||||
_upgrade_base/
|
||||
TAGS
|
||||
erlang_ls.config
|
||||
.els_cache/
|
||||
|
|
11
Makefile
11
Makefile
|
@ -1,5 +1,5 @@
|
|||
$(shell $(CURDIR)/scripts/git-hooks-init.sh)
|
||||
REBAR_VERSION = 3.14.3-emqx-8
|
||||
REBAR_VERSION = 3.16.1-emqx-1
|
||||
REBAR = $(CURDIR)/rebar3
|
||||
BUILD = $(CURDIR)/build
|
||||
SCRIPTS = $(CURDIR)/scripts
|
||||
|
@ -73,7 +73,8 @@ coveralls: $(REBAR)
|
|||
@ENABLE_COVER_COMPILE=1 $(REBAR) as test coveralls send
|
||||
|
||||
.PHONY: $(REL_PROFILES)
|
||||
$(REL_PROFILES:%=%): $(REBAR) get-dashboard
|
||||
|
||||
$(REL_PROFILES:%=%): $(REBAR) get-dashboard conf-segs
|
||||
@$(REBAR) as $(@) do compile,release
|
||||
|
||||
## Not calling rebar3 clean because
|
||||
|
@ -91,6 +92,7 @@ $(PROFILES:%=clean-%):
|
|||
|
||||
.PHONY: clean-all
|
||||
clean-all:
|
||||
@rm -f rebar.lock
|
||||
@rm -rf _build
|
||||
|
||||
.PHONY: deps-all
|
||||
|
@ -111,7 +113,7 @@ xref: $(REBAR)
|
|||
dialyzer: $(REBAR)
|
||||
@$(REBAR) as check dialyzer
|
||||
|
||||
COMMON_DEPS := $(REBAR) get-dashboard $(CONF_SEGS)
|
||||
COMMON_DEPS := $(REBAR) get-dashboard conf-segs
|
||||
|
||||
## rel target is to create release package without relup
|
||||
.PHONY: $(REL_PROFILES:%=%-rel) $(PKG_PROFILES:%=%-rel)
|
||||
|
@ -152,3 +154,6 @@ quickrun:
|
|||
./_build/$(PROFILE)/rel/emqx/bin/emqx console
|
||||
|
||||
include docker.mk
|
||||
|
||||
conf-segs:
|
||||
@scripts/merge-config.escript
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
EMQ X, highly scalable, highly available distributed MQTT messaging platform for IoT.
|
||||
Copyright (c) 2017-2021 EMQ Technologies Co., Ltd. All Rights Reserved.
|
||||
|
||||
This product contains code developed at EMQ Technologies Co., Ltd.
|
||||
Visit https://www.emqx.come to learn more.
|
10
README-CN.md
10
README-CN.md
|
@ -9,7 +9,7 @@
|
|||
[](https://askemq.com)
|
||||
[](https://www.youtube.com/channel/UCir_r04HIsLjf2qqyZ4A8Cg)
|
||||
|
||||
[](https://careers.emqx.cn/)
|
||||
[](https://careers.emqx.cn/)
|
||||
|
||||
[English](./README.md) | 简体中文 | [日本語](./README-JP.md) | [русский](./README-RU.md)
|
||||
|
||||
|
@ -18,7 +18,7 @@
|
|||
从 3.0 版本开始,*EMQ X* 完整支持 MQTT V5.0 协议规范,向下兼容 MQTT V3.1 和 V3.1.1,并支持 MQTT-SN、CoAP、LwM2M、WebSocket 和 STOMP 等通信协议。EMQ X 3.0 单集群可支持千万级别的 MQTT 并发连接。
|
||||
|
||||
- 新功能的完整列表,请参阅 [EMQ X Release Notes](https://github.com/emqx/emqx/releases)。
|
||||
- 获取更多信息,请访问 [EMQ X 官网](https://www.emqx.cn/)。
|
||||
- 获取更多信息,请访问 [EMQ X 官网](https://www.emqx.io/zh)。
|
||||
|
||||
## 安装
|
||||
|
||||
|
@ -34,7 +34,7 @@ docker run -d --name emqx -p 1883:1883 -p 8081:8081 -p 8083:8083 -p 8883:8883 -p
|
|||
|
||||
#### 二进制软件包安装
|
||||
|
||||
需从 [EMQ X 下载](https://www.emqx.cn/downloads) 页面获取相应操作系统的二进制软件包。
|
||||
需从 [EMQ X 下载](https://www.emqx.com/zh/downloads) 页面获取相应操作系统的二进制软件包。
|
||||
|
||||
- [单节点安装文档](https://docs.emqx.cn/broker/latest/getting-started/install.html)
|
||||
- [集群配置文档](https://docs.emqx.cn/broker/latest/advanced/cluster.html)
|
||||
|
@ -133,7 +133,7 @@ DIALYZER_ANALYSE_APP=emqx_lwm2m,emqx_authz make dialyzer
|
|||
- [Facebook](https://www.facebook.com/emqxmqtt)
|
||||
- [Reddit](https://www.reddit.com/r/emqx/)
|
||||
- [Weibo](https://weibo.com/emqtt)
|
||||
- [Blog](https://www.emqx.cn/blog)
|
||||
- [Blog](https://www.emqx.com/zh/blog)
|
||||
|
||||
欢迎你将任何 bug、问题和功能请求提交到 [emqx/emqx](https://github.com/emqx/emqx/issues)。
|
||||
|
||||
|
@ -145,7 +145,7 @@ DIALYZER_ANALYSE_APP=emqx_lwm2m,emqx_authz make dialyzer
|
|||
|
||||
[MQTT Version 5.0](https://docs.oasis-open.org/mqtt/mqtt/v5.0/cs02/mqtt-v5.0-cs02.html)
|
||||
|
||||
[MQTT SN](http://mqtt.org/new/wp-content/uploads/2009/06/MQTT-SN_spec_v1.2.pdf)
|
||||
[MQTT SN](https://www.oasis-open.org/committees/download.php/66091/MQTT-SN_spec_v1.2.pdf)
|
||||
|
||||
## 开源许可
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
[](https://twitter.com/EMQTech)
|
||||
[](https://www.youtube.com/channel/UC5FjR77ErAxvZENEWzQaO5Q)
|
||||
|
||||
[](https://www.emqx.io/careers)
|
||||
[](https://www.emqx.com/en/careers)
|
||||
|
||||
[English](./README.md) | [简体中文](./README-CN.md) | 日本語 | [русский](./README-RU.md)
|
||||
|
||||
|
@ -35,7 +35,7 @@ docker run -d --name emqx -p 1883:1883 -p 8083:8083 -p 8883:8883 -p 8084:8084 -p
|
|||
|
||||
#### バイナリパッケージによるインストール
|
||||
|
||||
それぞれのOSに対応したバイナリソフトウェアパッケージは、[EMQ Xのダウンロード](https://www.emqx.io/downloads)ページから取得できます。
|
||||
それぞれのOSに対応したバイナリソフトウェアパッケージは、[EMQ Xのダウンロード](https://www.emqx.com/en/downloads)ページから取得できます。
|
||||
|
||||
- [シングルノードインストール](https://docs.emqx.io/broker/latest/en/getting-started/installation.html)
|
||||
- [マルチノードインストール](https://docs.emqx.io/broker/latest/en/advanced/cluster.html)
|
||||
|
@ -125,7 +125,7 @@ DIALYZER_ANALYSE_APP=emqx_lwm2m,emqx_authz make dialyzer
|
|||
|
||||
[MQTT Version 5.0](https://docs.oasis-open.org/mqtt/mqtt/v5.0/cs02/mqtt-v5.0-cs02.html)
|
||||
|
||||
[MQTT SN](http://mqtt.org/new/wp-content/uploads/2009/06/MQTT-SN_spec_v1.2.pdf)
|
||||
[MQTT SN](https://www.oasis-open.org/committees/download.php/66091/MQTT-SN_spec_v1.2.pdf)
|
||||
|
||||
## License
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
[](https://github.com/emqx/emqx/discussions)
|
||||
[](https://www.youtube.com/channel/UC5FjR77ErAxvZENEWzQaO5Q)
|
||||
|
||||
[](https://www.emqx.io/careers)
|
||||
[](https://www.emqx.com/en/careers)
|
||||
|
||||
[English](./README.md) | [简体中文](./README-CN.md) | [日本語](./README-JP.md) | русский
|
||||
|
||||
|
@ -18,7 +18,7 @@
|
|||
Начиная с релиза 3.0, брокер *EMQ X* полностью поддерживает протокол MQTT версии 5.0, и обратно совместим с версиями 3.1 и 3.1.1, а также протоколами MQTT-SN, CoAP, LwM2M, WebSocket и STOMP. Начиная с релиза 3.0, брокер *EMQ X* может масштабироваться до более чем 10 миллионов одновременных MQTT соединений на один кластер.
|
||||
|
||||
- Полный список возможностей доступен по ссылке: [EMQ X Release Notes](https://github.com/emqx/emqx/releases).
|
||||
- Более подробная информация доступна на нашем сайте: [EMQ X homepage](https://www.emqx.io).
|
||||
- Более подробная информация доступна на нашем сайте: [EMQ X homepage](https://www.emqx.io/).
|
||||
|
||||
## Установка
|
||||
|
||||
|
@ -34,7 +34,7 @@ docker run -d --name emqx -p 1883:1883 -p 8081:8081 -p 8083:8083 -p 8883:8883 -p
|
|||
|
||||
#### Установка бинарного пакета
|
||||
|
||||
Сборки для различных операционных систем: [Загрузить EMQ X](https://www.emqx.io/downloads).
|
||||
Сборки для различных операционных систем: [Загрузить EMQ X](https://www.emqx.com/en/downloads).
|
||||
|
||||
- [Установка на одном сервере](https://docs.emqx.io/en/broker/latest/getting-started/install.html)
|
||||
- [Установка на кластере](https://docs.emqx.io/en/broker/latest/advanced/cluster.html)
|
||||
|
@ -135,7 +135,7 @@ DIALYZER_ANALYSE_APP=emqx_lwm2m,emqx_authz make dialyzer
|
|||
|
||||
[MQTT Version 5.0](https://docs.oasis-open.org/mqtt/mqtt/v5.0/cs02/mqtt-v5.0-cs02.html)
|
||||
|
||||
[MQTT SN](http://mqtt.org/new/wp-content/uploads/2009/06/MQTT-SN_spec_v1.2.pdf)
|
||||
[MQTT SN](https://www.oasis-open.org/committees/download.php/66091/MQTT-SN_spec_v1.2.pdf)
|
||||
|
||||
## Лицензия
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
[](https://twitter.com/EMQTech)
|
||||
[](https://www.youtube.com/channel/UC5FjR77ErAxvZENEWzQaO5Q)
|
||||
|
||||
[](https://www.emqx.io/careers)
|
||||
[](https://www.emqx.com/en/careers)
|
||||
|
||||
English | [简体中文](./README-CN.md) | [日本語](./README-JP.md) | [русский](./README-RU.md)
|
||||
|
||||
|
@ -17,7 +17,7 @@ English | [简体中文](./README-CN.md) | [日本語](./README-JP.md) | [рус
|
|||
Starting from 3.0 release, *EMQ X* broker fully supports MQTT V5.0 protocol specifications and backward compatible with MQTT V3.1 and V3.1.1, as well as other communication protocols such as MQTT-SN, CoAP, LwM2M, WebSocket and STOMP. The 3.0 release of the *EMQ X* broker can scaled to 10+ million concurrent MQTT connections on one cluster.
|
||||
|
||||
- For full list of new features, please read [EMQ X Release Notes](https://github.com/emqx/emqx/releases).
|
||||
- For more information, please visit [EMQ X homepage](https://www.emqx.io).
|
||||
- For more information, please visit [EMQ X homepage](https://www.emqx.io/).
|
||||
|
||||
## Installation
|
||||
|
||||
|
@ -33,7 +33,7 @@ docker run -d --name emqx -p 1883:1883 -p 8081:8081 -p 8083:8083 -p 8883:8883 -p
|
|||
|
||||
#### Installing via Binary Package
|
||||
|
||||
Get the binary package of the corresponding OS from [EMQ X Download](https://www.emqx.io/downloads) page.
|
||||
Get the binary package of the corresponding OS from [EMQ X Download](https://www.emqx.com/en/downloads) page.
|
||||
|
||||
- [Single Node Install](https://docs.emqx.io/en/broker/latest/getting-started/install.html)
|
||||
- [Multi Node Install](https://docs.emqx.io/en/broker/latest/advanced/cluster.html)
|
||||
|
@ -134,7 +134,7 @@ You can read the mqtt protocol via the following links:
|
|||
|
||||
[MQTT Version 5.0](https://docs.oasis-open.org/mqtt/mqtt/v5.0/cs02/mqtt-v5.0-cs02.html)
|
||||
|
||||
[MQTT SN](http://mqtt.org/new/wp-content/uploads/2009/06/MQTT-SN_spec_v1.2.pdf)
|
||||
[MQTT SN](https://www.oasis-open.org/committees/download.php/66091/MQTT-SN_spec_v1.2.pdf)
|
||||
|
||||
## License
|
||||
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
EMQ X, highly scalable, highly available distributed MQTT messaging platform for IoT.
|
||||
Copyright (c) 2017-2021 EMQ Technologies Co., Ltd. All Rights Reserved.
|
||||
|
||||
This product contains code developed at EMQ Technologies Co., Ltd.
|
||||
Visit https://www.emqx.come to learn more.
|
|
@ -1,26 +0,0 @@
|
|||
%%--------------------------------------------------------------------
|
||||
%% [ACL](https://docs.emqx.io/broker/v3/en/config.html)
|
||||
%%
|
||||
%% -type(who() :: all | binary() |
|
||||
%% {ipaddr, esockd_access:cidr()} |
|
||||
%% {client, binary()} |
|
||||
%% {user, binary()}).
|
||||
%%
|
||||
%% -type(access() :: subscribe | publish | pubsub).
|
||||
%%
|
||||
%% -type(topic() :: binary()).
|
||||
%%
|
||||
%% -type(rule() :: {allow, all} |
|
||||
%% {allow, who(), access(), list(topic())} |
|
||||
%% {deny, all} |
|
||||
%% {deny, who(), access(), list(topic())}).
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
{allow, {user, "dashboard"}, subscribe, ["$SYS/#"]}.
|
||||
|
||||
{allow, {ipaddr, "127.0.0.1"}, pubsub, ["$SYS/#", "#"]}.
|
||||
|
||||
{deny, all, subscribe, ["$SYS/#", {eq, "#"}]}.
|
||||
|
||||
{allow, all}.
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
%%--------------------------------------------------------------------
|
||||
%% For paho interoperability test cases
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
{deny, {client, "myclientid"}, subscribe, ["test/nosubscribe"]}.
|
||||
|
||||
{allow, {user, "dashboard"}, subscribe, ["$SYS/#"]}.
|
||||
|
||||
{allow, {ipaddr, "127.0.0.1"}, pubsub, ["$SYS/#", "#"]}.
|
||||
|
||||
{deny, all, subscribe, ["$SYS/#", {eq, "#"}]}.
|
||||
|
||||
{allow, all}.
|
||||
|
File diff suppressed because it is too large
Load Diff
|
@ -23,6 +23,10 @@
|
|||
|
||||
-define(Otherwise, true).
|
||||
|
||||
-define(COMMON_SHARD, emqx_common_shard).
|
||||
-define(SHARED_SUB_SHARD, emqx_shared_sub_shard).
|
||||
-define(MOD_DELAYED_SHARD, emqx_delayed_shard).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Banner
|
||||
%%--------------------------------------------------------------------
|
||||
|
@ -31,12 +35,6 @@
|
|||
|
||||
-define(ERTS_MINIMUM_REQUIRED, "10.0").
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Configs
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
-define(NO_PRIORITY_TABLE, none).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Topics' prefix: $SYS | $queue | $share
|
||||
%%--------------------------------------------------------------------
|
||||
|
@ -86,6 +84,9 @@
|
|||
|
||||
-define(ROUTE_SHARD, route_shard).
|
||||
|
||||
|
||||
-define(RULE_ENGINE_SHARD, emqx_rule_engine_shard).
|
||||
|
||||
-record(route, {
|
||||
topic :: binary(),
|
||||
dest :: node() | {binary(), node()}
|
||||
|
@ -101,8 +102,7 @@
|
|||
descr :: string(),
|
||||
vendor :: string() | undefined,
|
||||
active = false :: boolean(),
|
||||
info = #{} :: map(),
|
||||
type :: atom()
|
||||
info = #{} :: map()
|
||||
}).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
|
@ -134,4 +134,3 @@
|
|||
}).
|
||||
|
||||
-endif.
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
-ifndef(EMQX_ENTERPRISE).
|
||||
|
||||
-define(EMQX_RELEASE, {opensource, "5.0-pre"}).
|
||||
-define(EMQX_RELEASE, {opensource, "5.0-alpha.3"}).
|
||||
|
||||
-else.
|
||||
|
||||
|
|
|
@ -12,12 +12,11 @@
|
|||
[ {gproc, {git, "https://github.com/uwiger/gproc", {tag, "0.8.0"}}}
|
||||
, {jiffy, {git, "https://github.com/emqx/jiffy", {tag, "1.0.5"}}}
|
||||
, {cowboy, {git, "https://github.com/emqx/cowboy", {tag, "2.8.2"}}}
|
||||
, {esockd, {git, "https://github.com/emqx/esockd", {tag, "5.8.0"}}}
|
||||
, {ekka, {git, "https://github.com/emqx/ekka", {tag, "0.10.2"}}}
|
||||
, {esockd, {git, "https://github.com/emqx/esockd", {tag, "5.8.2"}}}
|
||||
, {ekka, {git, "https://github.com/emqx/ekka", {tag, "0.10.4"}}}
|
||||
, {gen_rpc, {git, "https://github.com/emqx/gen_rpc", {tag, "2.5.1"}}}
|
||||
, {cuttlefish, {git, "https://github.com/emqx/cuttlefish", {tag, "v4.0.1"}}} %% todo delete when plugins use hocon
|
||||
, {hocon, {git, "https://github.com/emqx/hocon.git", {tag, "0.9.0"}}}
|
||||
, {pbkdf2, {git, "https://github.com/emqx/erlang-pbkdf2.git", {branch, "2.0.4"}}}
|
||||
, {hocon, {git, "https://github.com/emqx/hocon.git", {tag, "0.10.5"}}}
|
||||
, {pbkdf2, {git, "https://github.com/emqx/erlang-pbkdf2.git", {tag, "2.0.4"}}}
|
||||
, {recon, {git, "https://github.com/ferd/recon", {tag, "2.5.1"}}}
|
||||
, {snabbkaffe, {git, "https://github.com/kafka4beam/snabbkaffe.git", {tag, "0.13.0"}}}
|
||||
]}.
|
||||
|
@ -30,7 +29,7 @@
|
|||
[ meck
|
||||
, {bbmustache,"1.10.0"}
|
||||
, {emqx_ct_helpers, {git,"https://github.com/emqx/emqx-ct-helpers", {branch,"hocon"}}}
|
||||
, {emqtt, {git, "https://github.com/emqx/emqtt", {tag, "1.2.3.1"}}}
|
||||
, {emqtt, {git, "https://github.com/emqx/emqtt", {tag, "1.4.2"}}}
|
||||
]},
|
||||
{extra_src_dirs, [{"test",[recursive]}]}
|
||||
]}
|
||||
|
|
|
@ -1,11 +1,30 @@
|
|||
Bcrypt = {bcrypt, {git, "https://github.com/emqx/erlang-bcrypt.git", {branch, "0.6.0"}}},
|
||||
AddBcrypt = fun(C) ->
|
||||
{deps, Deps0} = lists:keyfind(deps, 1, C),
|
||||
Deps = [Bcrypt | Deps0],
|
||||
lists:keystore(deps, 1, C, {deps, Deps})
|
||||
end,
|
||||
IsCentos6 = fun() ->
|
||||
case file:read_file("/etc/centos-release") of
|
||||
{ok, <<"CentOS release 6", _/binary >>} ->
|
||||
true;
|
||||
_ ->
|
||||
false
|
||||
end
|
||||
end,
|
||||
|
||||
case os:type() of
|
||||
{win32, _} -> CONFIG;
|
||||
_ -> AddBcrypt(CONFIG)
|
||||
end.
|
||||
IsWin32 = fun() ->
|
||||
win32 =:= element(1, os:type())
|
||||
end,
|
||||
|
||||
IsQuicSupp = fun() ->
|
||||
not (IsCentos6() orelse IsWin32() orelse
|
||||
false =/= os:getenv("BUILD_WITHOUT_QUIC")
|
||||
)
|
||||
end,
|
||||
|
||||
Bcrypt = {bcrypt, {git, "https://github.com/emqx/erlang-bcrypt.git", {branch, "0.6.0"}}},
|
||||
Quicer = {quicer, {git, "https://github.com/emqx/quic.git", {branch, "main"}}},
|
||||
|
||||
ExtraDeps = fun(C) ->
|
||||
{deps, Deps0} = lists:keyfind(deps, 1, C),
|
||||
Deps = Deps0 ++ [Bcrypt || not IsWin32()] ++
|
||||
[ Quicer || IsQuicSupp()],
|
||||
lists:keystore(deps, 1, C, {deps, Deps})
|
||||
end,
|
||||
|
||||
ExtraDeps(CONFIG).
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
{vsn, "5.0.0"}, % strict semver, bump manually!
|
||||
{modules, []},
|
||||
{registered, []},
|
||||
{applications, [kernel,stdlib,gproc,gen_rpc,esockd,cowboy,sasl,os_mon]},
|
||||
{applications, [kernel,stdlib,gproc,gen_rpc,esockd,cowboy,sasl,os_mon,jiffy]},
|
||||
{mod, {emqx_app,[]}},
|
||||
{env, []},
|
||||
{licenses, ["Apache-2.0"]},
|
||||
|
|
|
@ -1,111 +0,0 @@
|
|||
%% -*- mode: erlang -*-
|
||||
{VSN,
|
||||
[
|
||||
{"4.3.4",
|
||||
[{load_module,emqx_shared_sub,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_app,brutal_purge,soft_purge,[]}
|
||||
]},
|
||||
{"4.3.3",
|
||||
[{load_module,emqx_packet,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_shared_sub,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_ws_connection,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_cm,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_app,brutal_purge,soft_purge,[]}
|
||||
]},
|
||||
{"4.3.2",
|
||||
[{load_module,emqx_packet,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_shared_sub,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_ws_connection,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_http_lib,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_channel,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_app,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_connection,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_cm,brutal_purge,soft_purge,[]}
|
||||
]},
|
||||
{"4.3.1",
|
||||
[{load_module,emqx_packet,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_shared_sub,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_ws_connection,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_connection,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_frame,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_cm,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_congestion,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_node_dump,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_channel,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_app,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_plugins,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_logger_textfmt,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_http_lib,brutal_purge,soft_purge,[]}]},
|
||||
{"4.3.0",
|
||||
[{load_module,emqx_packet,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_shared_sub,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_logger_jsonfmt,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_ws_connection,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_congestion,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_connection,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_frame,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_trie,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_cm,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_node_dump,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_channel,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_app,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_plugins,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_logger_textfmt,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_metrics,brutal_purge,soft_purge,[]},
|
||||
{apply,{emqx_metrics,upgrade_retained_delayed_counter_type,[]}},
|
||||
{load_module,emqx_http_lib,brutal_purge,soft_purge,[]}]},
|
||||
{<<".*">>,[]}],
|
||||
[
|
||||
{"4.3.4",
|
||||
[{load_module,emqx_shared_sub,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_app,brutal_purge,soft_purge,[]}
|
||||
]},
|
||||
{"4.3.3",
|
||||
[{load_module,emqx_packet,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_shared_sub,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_ws_connection,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_cm,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_app,brutal_purge,soft_purge,[]}
|
||||
]},
|
||||
{"4.3.2",
|
||||
[{load_module,emqx_packet,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_shared_sub,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_ws_connection,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_http_lib,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_channel,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_app,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_connection,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_cm,brutal_purge,soft_purge,[]}
|
||||
]},
|
||||
{"4.3.1",
|
||||
[{load_module,emqx_packet,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_shared_sub,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_ws_connection,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_connection,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_frame,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_cm,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_congestion,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_node_dump,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_channel,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_app,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_plugins,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_logger_textfmt,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_http_lib,brutal_purge,soft_purge,[]}]},
|
||||
{"4.3.0",
|
||||
[{load_module,emqx_packet,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_shared_sub,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_logger_jsonfmt,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_ws_connection,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_connection,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_congestion,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_frame,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_trie,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_cm,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_node_dump,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_channel,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_app,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_plugins,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_logger_textfmt,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_metrics,brutal_purge,soft_purge,[]},
|
||||
{load_module,emqx_http_lib,brutal_purge,soft_purge,[]}]},
|
||||
{<<".*">>,[]}]}.
|
|
@ -29,10 +29,6 @@
|
|||
, stop/0
|
||||
]).
|
||||
|
||||
-export([ get_env/1
|
||||
, get_env/2
|
||||
]).
|
||||
|
||||
%% PubSub API
|
||||
-export([ subscribe/1
|
||||
, subscribe/2
|
||||
|
@ -126,15 +122,6 @@ is_running(Node) ->
|
|||
Pid when is_pid(Pid) -> true
|
||||
end.
|
||||
|
||||
%% @doc Get environment
|
||||
-spec(get_env(Key :: atom()) -> maybe(term())).
|
||||
get_env(Key) ->
|
||||
get_env(Key, undefined).
|
||||
|
||||
-spec(get_env(Key :: atom(), Default :: term()) -> term()).
|
||||
get_env(Key, Default) ->
|
||||
application:get_env(?APP, Key, Default).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% PubSub API
|
||||
%%--------------------------------------------------------------------
|
||||
|
@ -227,7 +214,6 @@ shutdown() ->
|
|||
shutdown(Reason) ->
|
||||
?LOG(critical, "emqx shutdown for ~s", [Reason]),
|
||||
_ = emqx_alarm_handler:unload(),
|
||||
_ = emqx_plugins:unload(),
|
||||
lists:foreach(fun application:stop/1
|
||||
, lists:reverse(default_started_applications())
|
||||
).
|
||||
|
@ -235,13 +221,8 @@ shutdown(Reason) ->
|
|||
reboot() ->
|
||||
lists:foreach(fun application:start/1 , default_started_applications()).
|
||||
|
||||
-ifdef(EMQX_ENTERPRISE).
|
||||
default_started_applications() ->
|
||||
[gproc, esockd, ranch, cowboy, ekka, emqx].
|
||||
-else.
|
||||
default_started_applications() ->
|
||||
[gproc, esockd, ranch, cowboy, ekka, emqx, emqx_modules].
|
||||
-endif.
|
||||
[gproc, esockd, ranch, cowboy, ekka, quicer, emqx] ++ emqx_feature().
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Internal functions
|
||||
|
@ -253,3 +234,9 @@ reload_config(ConfFile) ->
|
|||
[application:set_env(App, Par, Val) || {Par, Val} <- Vals]
|
||||
end, Conf).
|
||||
|
||||
-ifndef(EMQX_DEP_APPS).
|
||||
emqx_feature() -> [].
|
||||
-else.
|
||||
emqx_feature() ->
|
||||
?EMQX_DEP_APPS.
|
||||
-endif.
|
||||
|
|
|
@ -18,66 +18,43 @@
|
|||
|
||||
-include("emqx.hrl").
|
||||
|
||||
-export([authenticate/1]).
|
||||
|
||||
-export([ check_acl/3
|
||||
-export([ authenticate/1
|
||||
, authorize/3
|
||||
]).
|
||||
|
||||
-type(result() :: #{auth_result := emqx_types:auth_result(),
|
||||
anonymous := boolean()
|
||||
}).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% APIs
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
-spec(authenticate(emqx_types:clientinfo()) -> {ok, result()} | {error, term()}).
|
||||
authenticate(ClientInfo = #{zone := Zone}) ->
|
||||
AuthResult = default_auth_result(Zone),
|
||||
case emqx_zone:get_env(Zone, bypass_auth_plugins, false) of
|
||||
true ->
|
||||
return_auth_result(AuthResult);
|
||||
false ->
|
||||
return_auth_result(run_hooks('client.authenticate', [ClientInfo], AuthResult))
|
||||
-spec(authenticate(emqx_types:clientinfo()) ->
|
||||
ok | {ok, binary()} | {continue, map()} | {continue, binary(), map()} | {error, term()}).
|
||||
authenticate(Credential) ->
|
||||
run_hooks('client.authenticate', [Credential], ok).
|
||||
|
||||
%% @doc Check Authorization
|
||||
-spec authorize(emqx_types:clientinfo(), emqx_types:pubsub(), emqx_types:topic())
|
||||
-> allow | deny.
|
||||
authorize(ClientInfo = #{zone := Zone}, PubSub, Topic) ->
|
||||
case emqx_authz_cache:is_enabled(Zone) of
|
||||
true -> check_authorization_cache(ClientInfo, PubSub, Topic);
|
||||
false -> do_authorize(ClientInfo, PubSub, Topic)
|
||||
end.
|
||||
|
||||
%% @doc Check ACL
|
||||
-spec(check_acl(emqx_types:clientinfo(), emqx_types:pubsub(), emqx_types:topic())
|
||||
-> allow | deny).
|
||||
check_acl(ClientInfo, PubSub, Topic) ->
|
||||
case emqx_acl_cache:is_enabled() of
|
||||
true -> check_acl_cache(ClientInfo, PubSub, Topic);
|
||||
false -> do_check_acl(ClientInfo, PubSub, Topic)
|
||||
end.
|
||||
|
||||
check_acl_cache(ClientInfo, PubSub, Topic) ->
|
||||
case emqx_acl_cache:get_acl_cache(PubSub, Topic) of
|
||||
check_authorization_cache(ClientInfo = #{zone := Zone}, PubSub, Topic) ->
|
||||
case emqx_authz_cache:get_authz_cache(Zone, PubSub, Topic) of
|
||||
not_found ->
|
||||
AclResult = do_check_acl(ClientInfo, PubSub, Topic),
|
||||
emqx_acl_cache:put_acl_cache(PubSub, Topic, AclResult),
|
||||
AclResult;
|
||||
AclResult -> AclResult
|
||||
AuthzResult = do_authorize(ClientInfo, PubSub, Topic),
|
||||
emqx_authz_cache:put_authz_cache(Zone, PubSub, Topic, AuthzResult),
|
||||
AuthzResult;
|
||||
AuthzResult -> AuthzResult
|
||||
end.
|
||||
|
||||
do_check_acl(ClientInfo = #{zone := Zone}, PubSub, Topic) ->
|
||||
Default = emqx_zone:get_env(Zone, acl_nomatch, deny),
|
||||
case run_hooks('client.check_acl', [ClientInfo, PubSub, Topic], Default) of
|
||||
do_authorize(ClientInfo, PubSub, Topic) ->
|
||||
case run_hooks('client.authorize', [ClientInfo, PubSub, Topic], allow) of
|
||||
allow -> allow;
|
||||
_Other -> deny
|
||||
end.
|
||||
|
||||
default_auth_result(Zone) ->
|
||||
case emqx_zone:get_env(Zone, allow_anonymous, false) of
|
||||
true -> #{auth_result => success, anonymous => true};
|
||||
false -> #{auth_result => not_authorized, anonymous => false}
|
||||
end.
|
||||
|
||||
-compile({inline, [run_hooks/3]}).
|
||||
run_hooks(Name, Args, Acc) ->
|
||||
ok = emqx_metrics:inc(Name), emqx_hooks:run_fold(Name, Args, Acc).
|
||||
|
||||
-compile({inline, [return_auth_result/1]}).
|
||||
return_auth_result(Result = #{auth_result := success}) ->
|
||||
{ok, Result};
|
||||
return_auth_result(Result) ->
|
||||
{error, maps:get(auth_result, Result, unknown_error)}.
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
-module(emqx_alarm).
|
||||
|
||||
-behaviour(gen_server).
|
||||
-behaviour(emqx_config_handler).
|
||||
|
||||
-include("emqx.hrl").
|
||||
-include("logger.hrl").
|
||||
|
@ -29,10 +30,14 @@
|
|||
-boot_mnesia({mnesia, [boot]}).
|
||||
-copy_mnesia({mnesia, [copy]}).
|
||||
|
||||
-export([ start_link/1
|
||||
-export([pre_config_update/2]).
|
||||
|
||||
-export([ start_link/0
|
||||
, stop/0
|
||||
]).
|
||||
|
||||
-export([format/1]).
|
||||
|
||||
%% API
|
||||
-export([ activate/1
|
||||
, activate/2
|
||||
|
@ -75,21 +80,16 @@
|
|||
}).
|
||||
|
||||
-record(state, {
|
||||
actions :: [action()],
|
||||
|
||||
size_limit :: non_neg_integer(),
|
||||
|
||||
validity_period :: non_neg_integer(),
|
||||
|
||||
timer = undefined :: undefined | reference()
|
||||
timer :: reference()
|
||||
}).
|
||||
|
||||
-type action() :: log | publish | event.
|
||||
|
||||
-define(ACTIVATED_ALARM, emqx_activated_alarm).
|
||||
|
||||
-define(DEACTIVATED_ALARM, emqx_deactivated_alarm).
|
||||
|
||||
-rlog_shard({?COMMON_SHARD, ?ACTIVATED_ALARM}).
|
||||
-rlog_shard({?COMMON_SHARD, ?DEACTIVATED_ALARM}).
|
||||
|
||||
-ifdef(TEST).
|
||||
-compile(export_all).
|
||||
-compile(nowarn_export_all).
|
||||
|
@ -120,8 +120,8 @@ mnesia(copy) ->
|
|||
%% API
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
start_link(Opts) ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [Opts], []).
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||
|
||||
stop() ->
|
||||
gen_server:stop(?MODULE).
|
||||
|
@ -153,27 +153,50 @@ get_alarms(activated) ->
|
|||
get_alarms(deactivated) ->
|
||||
gen_server:call(?MODULE, {get_alarms, deactivated}).
|
||||
|
||||
pre_config_update(#{<<"validity_period">> := Period0} = NewConf, OldConf) ->
|
||||
?MODULE ! {update_timer, hocon_postprocess:duration(Period0)},
|
||||
merge(OldConf, NewConf);
|
||||
pre_config_update(NewConf, OldConf) ->
|
||||
merge(OldConf, NewConf).
|
||||
|
||||
merge(undefined, New) -> New;
|
||||
merge(Old, New) -> maps:merge(Old, New).
|
||||
|
||||
format(#activated_alarm{name = Name, message = Message, activate_at = At, details = Details}) ->
|
||||
Now = erlang:system_time(microsecond),
|
||||
#{
|
||||
node => node(),
|
||||
name => Name,
|
||||
message => Message,
|
||||
duration => Now - At,
|
||||
details => Details
|
||||
};
|
||||
format(#deactivated_alarm{name = Name, message = Message, activate_at = At, details = Details,
|
||||
deactivate_at = DAt}) ->
|
||||
#{
|
||||
node => node(),
|
||||
name => Name,
|
||||
message => Message,
|
||||
duration => DAt - At,
|
||||
details => Details
|
||||
};
|
||||
format(_) ->
|
||||
{error, unknow_alarm}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% gen_server callbacks
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
init([]) ->
|
||||
Opts = [{actions, [log, publish]}],
|
||||
init([Opts]);
|
||||
init([Opts]) ->
|
||||
deactivate_all_alarms(),
|
||||
Actions = proplists:get_value(actions, Opts),
|
||||
SizeLimit = proplists:get_value(size_limit, Opts),
|
||||
ValidityPeriod = timer:seconds(proplists:get_value(validity_period, Opts)),
|
||||
{ok, ensure_delete_timer(#state{actions = Actions,
|
||||
size_limit = SizeLimit,
|
||||
validity_period = ValidityPeriod})}.
|
||||
emqx_config_handler:add_handler([alarm], ?MODULE),
|
||||
{ok, #state{timer = ensure_timer(undefined, get_validity_period())}}.
|
||||
|
||||
%% suppress dialyzer warning due to dirty read/write race condition.
|
||||
%% TODO: change from dirty_read/write to transactional.
|
||||
%% TODO: handle mnesia write errors.
|
||||
-dialyzer([{nowarn_function, [handle_call/3]}]).
|
||||
handle_call({activate_alarm, Name, Details}, _From, State = #state{actions = Actions}) ->
|
||||
handle_call({activate_alarm, Name, Details}, _From, State) ->
|
||||
case mnesia:dirty_read(?ACTIVATED_ALARM, Name) of
|
||||
[#activated_alarm{name = Name}] ->
|
||||
{reply, {error, already_existed}, State};
|
||||
|
@ -182,18 +205,17 @@ handle_call({activate_alarm, Name, Details}, _From, State = #state{actions = Act
|
|||
details = Details,
|
||||
message = normalize_message(Name, Details),
|
||||
activate_at = erlang:system_time(microsecond)},
|
||||
mnesia:dirty_write(?ACTIVATED_ALARM, Alarm),
|
||||
do_actions(activate, Alarm, Actions),
|
||||
ekka_mnesia:dirty_write(?ACTIVATED_ALARM, Alarm),
|
||||
do_actions(activate, Alarm, emqx_config:get([alarm, actions])),
|
||||
{reply, ok, State}
|
||||
end;
|
||||
|
||||
handle_call({deactivate_alarm, Name, Details}, _From, State = #state{
|
||||
actions = Actions, size_limit = SizeLimit}) ->
|
||||
handle_call({deactivate_alarm, Name, Details}, _From, State) ->
|
||||
case mnesia:dirty_read(?ACTIVATED_ALARM, Name) of
|
||||
[] ->
|
||||
{reply, {error, not_found}, State};
|
||||
[Alarm] ->
|
||||
deactivate_alarm(Details, SizeLimit, Actions, Alarm),
|
||||
deactivate_alarm(Details, Alarm),
|
||||
{reply, ok, State}
|
||||
end;
|
||||
|
||||
|
@ -202,9 +224,14 @@ handle_call(delete_all_deactivated_alarms, _From, State) ->
|
|||
{reply, ok, State};
|
||||
|
||||
handle_call({get_alarms, all}, _From, State) ->
|
||||
Alarms = [normalize(Alarm) ||
|
||||
Alarm <- ets:tab2list(?ACTIVATED_ALARM)
|
||||
++ ets:tab2list(?DEACTIVATED_ALARM)],
|
||||
{atomic, Alarms} =
|
||||
ekka_mnesia:ro_transaction(
|
||||
?COMMON_SHARD,
|
||||
fun() ->
|
||||
[normalize(Alarm) ||
|
||||
Alarm <- ets:tab2list(?ACTIVATED_ALARM)
|
||||
++ ets:tab2list(?DEACTIVATED_ALARM)]
|
||||
end),
|
||||
{reply, Alarms, State};
|
||||
|
||||
handle_call({get_alarms, activated}, _From, State) ->
|
||||
|
@ -223,11 +250,15 @@ handle_cast(Msg, State) ->
|
|||
?LOG(error, "Unexpected msg: ~p", [Msg]),
|
||||
{noreply, State}.
|
||||
|
||||
handle_info({timeout, TRef, delete_expired_deactivated_alarm},
|
||||
State = #state{timer = TRef,
|
||||
validity_period = ValidityPeriod}) ->
|
||||
delete_expired_deactivated_alarms(erlang:system_time(microsecond) - ValidityPeriod * 1000),
|
||||
{noreply, ensure_delete_timer(State)};
|
||||
handle_info({timeout, _TRef, delete_expired_deactivated_alarm},
|
||||
#state{timer = TRef} = State) ->
|
||||
Period = get_validity_period(),
|
||||
delete_expired_deactivated_alarms(erlang:system_time(microsecond) - Period * 1000),
|
||||
{noreply, State#state{timer = ensure_timer(TRef, Period)}};
|
||||
|
||||
handle_info({update_timer, Period}, #state{timer = TRef} = State) ->
|
||||
?LOG(warning, "update the 'validity_period' timer to ~p", [Period]),
|
||||
{noreply, State#state{timer = ensure_timer(TRef, Period)}};
|
||||
|
||||
handle_info(Info, State) ->
|
||||
?LOG(error, "Unexpected info: ~p", [Info]),
|
||||
|
@ -243,16 +274,18 @@ code_change(_OldVsn, State, _Extra) ->
|
|||
%% Internal functions
|
||||
%%------------------------------------------------------------------------------
|
||||
|
||||
deactivate_alarm(Details, SizeLimit, Actions, #activated_alarm{
|
||||
activate_at = ActivateAt, name = Name, details = Details0,
|
||||
message = Msg0}) ->
|
||||
case SizeLimit > 0 andalso
|
||||
(mnesia:table_info(?DEACTIVATED_ALARM, size) >= SizeLimit) of
|
||||
get_validity_period() ->
|
||||
emqx_config:get([alarm, validity_period]).
|
||||
|
||||
deactivate_alarm(Details, #activated_alarm{activate_at = ActivateAt, name = Name,
|
||||
details = Details0, message = Msg0}) ->
|
||||
SizeLimit = emqx_config:get([alarm, size_limit]),
|
||||
case SizeLimit > 0 andalso (mnesia:table_info(?DEACTIVATED_ALARM, size) >= SizeLimit) of
|
||||
true ->
|
||||
case mnesia:dirty_first(?DEACTIVATED_ALARM) of
|
||||
'$end_of_table' -> ok;
|
||||
ActivateAt2 ->
|
||||
mnesia:dirty_delete(?DEACTIVATED_ALARM, ActivateAt2)
|
||||
ekka_mnesia:dirty_delete(?DEACTIVATED_ALARM, ActivateAt2)
|
||||
end;
|
||||
false -> ok
|
||||
end,
|
||||
|
@ -261,9 +294,9 @@ deactivate_alarm(Details, SizeLimit, Actions, #activated_alarm{
|
|||
DeActAlarm = make_deactivated_alarm(ActivateAt, Name, Details,
|
||||
normalize_message(Name, Details),
|
||||
erlang:system_time(microsecond)),
|
||||
mnesia:dirty_write(?DEACTIVATED_ALARM, HistoryAlarm),
|
||||
mnesia:dirty_delete(?ACTIVATED_ALARM, Name),
|
||||
do_actions(deactivate, DeActAlarm, Actions).
|
||||
ekka_mnesia:dirty_write(?DEACTIVATED_ALARM, HistoryAlarm),
|
||||
ekka_mnesia:dirty_delete(?ACTIVATED_ALARM, Name),
|
||||
do_actions(deactivate, DeActAlarm, emqx_config:get([alarm, actions])).
|
||||
|
||||
make_deactivated_alarm(ActivateAt, Name, Details, Message, DeActivateAt) ->
|
||||
#deactivated_alarm{
|
||||
|
@ -279,7 +312,7 @@ deactivate_all_alarms() ->
|
|||
details = Details,
|
||||
message = Message,
|
||||
activate_at = ActivateAt}) ->
|
||||
mnesia:dirty_write(?DEACTIVATED_ALARM,
|
||||
ekka_mnesia:dirty_write(?DEACTIVATED_ALARM,
|
||||
#deactivated_alarm{
|
||||
activate_at = ActivateAt,
|
||||
name = Name,
|
||||
|
@ -291,7 +324,7 @@ deactivate_all_alarms() ->
|
|||
|
||||
%% Delete all records from the given table, ignore result.
|
||||
clear_table(TableName) ->
|
||||
case mnesia:clear_table(TableName) of
|
||||
case ekka_mnesia:clear_table(TableName) of
|
||||
{aborted, Reason} ->
|
||||
?LOG(warning, "Faile to clear table ~p reason: ~p",
|
||||
[TableName, Reason]);
|
||||
|
@ -299,9 +332,12 @@ clear_table(TableName) ->
|
|||
ok
|
||||
end.
|
||||
|
||||
ensure_delete_timer(State = #state{validity_period = ValidityPeriod}) ->
|
||||
TRef = emqx_misc:start_timer(ValidityPeriod, delete_expired_deactivated_alarm),
|
||||
State#state{timer = TRef}.
|
||||
ensure_timer(OldTRef, Period) ->
|
||||
_ = case is_reference(OldTRef) of
|
||||
true -> erlang:cancel_timer(OldTRef);
|
||||
false -> ok
|
||||
end,
|
||||
emqx_misc:start_timer(Period, delete_expired_deactivated_alarm).
|
||||
|
||||
delete_expired_deactivated_alarms(Checkpoint) ->
|
||||
delete_expired_deactivated_alarms(mnesia:dirty_first(?DEACTIVATED_ALARM), Checkpoint).
|
||||
|
@ -311,7 +347,7 @@ delete_expired_deactivated_alarms('$end_of_table', _Checkpoint) ->
|
|||
delete_expired_deactivated_alarms(ActivatedAt, Checkpoint) ->
|
||||
case ActivatedAt =< Checkpoint of
|
||||
true ->
|
||||
mnesia:dirty_delete(?DEACTIVATED_ALARM, ActivatedAt),
|
||||
ekka_mnesia:dirty_delete(?DEACTIVATED_ALARM, ActivatedAt),
|
||||
NActivatedAt = mnesia:dirty_next(?DEACTIVATED_ALARM, ActivatedAt),
|
||||
delete_expired_deactivated_alarms(NActivatedAt, Checkpoint);
|
||||
false ->
|
||||
|
@ -372,9 +408,9 @@ normalize_message(high_system_memory_usage, #{high_watermark := HighWatermark})
|
|||
normalize_message(high_process_memory_usage, #{high_watermark := HighWatermark}) ->
|
||||
list_to_binary(io_lib:format("Process memory usage is higher than ~p%", [HighWatermark]));
|
||||
normalize_message(high_cpu_usage, #{usage := Usage}) ->
|
||||
list_to_binary(io_lib:format("~p% cpu usage", [Usage]));
|
||||
list_to_binary(io_lib:format("~s cpu usage", [Usage]));
|
||||
normalize_message(too_many_processes, #{usage := Usage}) ->
|
||||
list_to_binary(io_lib:format("~p% process usage", [Usage]));
|
||||
list_to_binary(io_lib:format("~s process usage", [Usage]));
|
||||
normalize_message(partition, #{occurred := Node}) ->
|
||||
list_to_binary(io_lib:format("Partition occurs at node ~s", [Node]));
|
||||
normalize_message(<<"resource", _/binary>>, #{type := Type, id := ID}) ->
|
||||
|
|
|
@ -57,12 +57,14 @@ init(_) ->
|
|||
{ok, []}.
|
||||
|
||||
handle_event({set_alarm, {system_memory_high_watermark, []}}, State) ->
|
||||
emqx_alarm:activate(high_system_memory_usage, #{high_watermark => emqx_os_mon:get_sysmem_high_watermark()}),
|
||||
emqx_alarm:activate(high_system_memory_usage,
|
||||
#{high_watermark => emqx_os_mon:get_sysmem_high_watermark()}),
|
||||
{ok, State};
|
||||
|
||||
handle_event({set_alarm, {process_memory_high_watermark, Pid}}, State) ->
|
||||
emqx_alarm:activate(high_process_memory_usage, #{pid => Pid,
|
||||
high_watermark => emqx_os_mon:get_procmem_high_watermark()}),
|
||||
emqx_alarm:activate(high_process_memory_usage,
|
||||
#{pid => list_to_binary(pid_to_list(Pid)),
|
||||
high_watermark => emqx_os_mon:get_procmem_high_watermark()}),
|
||||
{ok, State};
|
||||
|
||||
handle_event({clear_alarm, system_memory_high_watermark}, State) ->
|
||||
|
|
|
@ -19,16 +19,23 @@
|
|||
-behaviour(application).
|
||||
|
||||
-export([ start/2
|
||||
, prep_stop/1
|
||||
, stop/1
|
||||
, get_description/0
|
||||
, get_release/0
|
||||
, set_init_config_load_done/0
|
||||
]).
|
||||
|
||||
-include("emqx.hrl").
|
||||
|
||||
-define(APP, emqx).
|
||||
|
||||
-define(EMQX_SHARDS, [route_shard]).
|
||||
-define(EMQX_SHARDS, [ ?ROUTE_SHARD
|
||||
, ?COMMON_SHARD
|
||||
, ?SHARED_SUB_SHARD
|
||||
, ?RULE_ENGINE_SHARD
|
||||
, ?MOD_DELAYED_SHARD
|
||||
]).
|
||||
|
||||
-include("emqx_release.hrl").
|
||||
|
||||
|
@ -37,13 +44,16 @@
|
|||
%%--------------------------------------------------------------------
|
||||
|
||||
start(_Type, _Args) ->
|
||||
set_backtrace_depth(),
|
||||
ok = maybe_load_config(),
|
||||
ok = set_backtrace_depth(),
|
||||
print_otp_version_warning(),
|
||||
print_banner(),
|
||||
%% Load application first for ekka_mnesia scanner
|
||||
_ = load_ce_modules(),
|
||||
ekka:start(),
|
||||
ok = ekka_rlog:wait_for_shards(?EMQX_SHARDS, infinity),
|
||||
false == os:getenv("EMQX_NO_QUIC")
|
||||
andalso application:ensure_all_started(quicer),
|
||||
{ok, Sup} = emqx_sup:start_link(),
|
||||
ok = start_autocluster(),
|
||||
%% ok = emqx_plugins:init(),
|
||||
|
@ -55,14 +65,31 @@ start(_Type, _Args) ->
|
|||
print_vsn(),
|
||||
{ok, Sup}.
|
||||
|
||||
-spec(stop(State :: term()) -> term()).
|
||||
stop(_State) ->
|
||||
prep_stop(_State) ->
|
||||
ok = emqx_alarm_handler:unload(),
|
||||
emqx_boot:is_enabled(listeners)
|
||||
andalso emqx_listeners:stop().
|
||||
|
||||
stop(_State) -> ok.
|
||||
|
||||
%% @doc Call this function to make emqx boot without loading config,
|
||||
%% in case we want to delegate the config load to a higher level app
|
||||
%% which manages emqx app.
|
||||
set_init_config_load_done() ->
|
||||
application:set_env(emqx, init_config_load_done, true).
|
||||
|
||||
maybe_load_config() ->
|
||||
case application:get_env(emqx, init_config_load_done, false) of
|
||||
true ->
|
||||
ok;
|
||||
false ->
|
||||
%% the app env 'config_files' should be set before emqx get started.
|
||||
ConfFiles = application:get_env(emqx, config_files, []),
|
||||
emqx_config:init_load(emqx_schema, ConfFiles)
|
||||
end.
|
||||
|
||||
set_backtrace_depth() ->
|
||||
Depth = application:get_env(?APP, backtrace_depth, 16),
|
||||
Depth = emqx_config:get([node, backtrace_depth]),
|
||||
_ = erlang:system_flag(backtrace_depth, Depth),
|
||||
ok.
|
||||
|
||||
|
|
|
@ -14,19 +14,19 @@
|
|||
%% limitations under the License.
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
-module(emqx_acl_cache).
|
||||
-module(emqx_authz_cache).
|
||||
|
||||
-include("emqx.hrl").
|
||||
|
||||
-export([ list_acl_cache/0
|
||||
, get_acl_cache/2
|
||||
, put_acl_cache/3
|
||||
, cleanup_acl_cache/0
|
||||
, empty_acl_cache/0
|
||||
, dump_acl_cache/0
|
||||
, get_cache_max_size/0
|
||||
, get_cache_ttl/0
|
||||
, is_enabled/0
|
||||
-export([ list_authz_cache/1
|
||||
, get_authz_cache/3
|
||||
, put_authz_cache/4
|
||||
, cleanup_authz_cache/1
|
||||
, empty_authz_cache/0
|
||||
, dump_authz_cache/0
|
||||
, get_cache_max_size/1
|
||||
, get_cache_ttl/1
|
||||
, is_enabled/1
|
||||
, drain_cache/0
|
||||
]).
|
||||
|
||||
|
@ -38,93 +38,95 @@
|
|||
, get_oldest_key/0
|
||||
]).
|
||||
|
||||
-type(acl_result() :: allow | deny).
|
||||
-type(authz_result() :: allow | deny).
|
||||
-type(system_time() :: integer()).
|
||||
-type(cache_key() :: {emqx_types:pubsub(), emqx_types:topic()}).
|
||||
-type(cache_val() :: {acl_result(), system_time()}).
|
||||
-type(cache_val() :: {authz_result(), system_time()}).
|
||||
|
||||
-type(acl_cache_entry() :: {cache_key(), cache_val()}).
|
||||
-type(authz_cache_entry() :: {cache_key(), cache_val()}).
|
||||
|
||||
%% Wrappers for key and value
|
||||
cache_k(PubSub, Topic)-> {PubSub, Topic}.
|
||||
cache_v(AclResult)-> {AclResult, time_now()}.
|
||||
cache_v(AuthzResult)-> {AuthzResult, time_now()}.
|
||||
drain_k() -> {?MODULE, drain_timestamp}.
|
||||
|
||||
-spec(is_enabled() -> boolean()).
|
||||
is_enabled() ->
|
||||
application:get_env(emqx, enable_acl_cache, true).
|
||||
-spec(is_enabled(atom()) -> boolean()).
|
||||
is_enabled(Zone) ->
|
||||
emqx_config:get_zone_conf(Zone, [authorization, cache, enable]).
|
||||
|
||||
-spec(get_cache_max_size() -> integer()).
|
||||
get_cache_max_size() ->
|
||||
application:get_env(emqx, acl_cache_max_size, 32).
|
||||
-spec(get_cache_max_size(atom()) -> integer()).
|
||||
get_cache_max_size(Zone) ->
|
||||
emqx_config:get_zone_conf(Zone, [authorization, cache, max_size]).
|
||||
|
||||
-spec(get_cache_ttl() -> integer()).
|
||||
get_cache_ttl() ->
|
||||
application:get_env(emqx, acl_cache_ttl, 60000).
|
||||
-spec(get_cache_ttl(atom()) -> integer()).
|
||||
get_cache_ttl(Zone) ->
|
||||
emqx_config:get_zone_conf(Zone, [authorization, cache, ttl]).
|
||||
|
||||
-spec(list_acl_cache() -> [acl_cache_entry()]).
|
||||
list_acl_cache() ->
|
||||
cleanup_acl_cache(),
|
||||
map_acl_cache(fun(Cache) -> Cache end).
|
||||
-spec(list_authz_cache(atom()) -> [authz_cache_entry()]).
|
||||
list_authz_cache(Zone) ->
|
||||
cleanup_authz_cache(Zone),
|
||||
map_authz_cache(fun(Cache) -> Cache end).
|
||||
|
||||
%% We'll cleanup the cache before replacing an expired acl.
|
||||
-spec(get_acl_cache(emqx_types:pubsub(), emqx_topic:topic()) -> (acl_result() | not_found)).
|
||||
get_acl_cache(PubSub, Topic) ->
|
||||
%% We'll cleanup the cache before replacing an expired authz.
|
||||
-spec get_authz_cache(atom(), emqx_types:pubsub(), emqx_topic:topic()) ->
|
||||
authz_result() | not_found.
|
||||
get_authz_cache(Zone, PubSub, Topic) ->
|
||||
case erlang:get(cache_k(PubSub, Topic)) of
|
||||
undefined -> not_found;
|
||||
{AclResult, CachedAt} ->
|
||||
if_expired(CachedAt,
|
||||
{AuthzResult, CachedAt} ->
|
||||
if_expired(get_cache_ttl(Zone), CachedAt,
|
||||
fun(false) ->
|
||||
AclResult;
|
||||
AuthzResult;
|
||||
(true) ->
|
||||
cleanup_acl_cache(),
|
||||
cleanup_authz_cache(Zone),
|
||||
not_found
|
||||
end)
|
||||
end.
|
||||
|
||||
%% If the cache get full, and also the latest one
|
||||
%% is expired, then delete all the cache entries
|
||||
-spec(put_acl_cache(emqx_types:pubsub(), emqx_topic:topic(), acl_result()) -> ok).
|
||||
put_acl_cache(PubSub, Topic, AclResult) ->
|
||||
MaxSize = get_cache_max_size(), true = (MaxSize =/= 0),
|
||||
-spec put_authz_cache(atom(), emqx_types:pubsub(), emqx_topic:topic(), authz_result())
|
||||
-> ok.
|
||||
put_authz_cache(Zone, PubSub, Topic, AuthzResult) ->
|
||||
MaxSize = get_cache_max_size(Zone), true = (MaxSize =/= 0),
|
||||
Size = get_cache_size(),
|
||||
case Size < MaxSize of
|
||||
true ->
|
||||
add_acl(PubSub, Topic, AclResult);
|
||||
add_authz(PubSub, Topic, AuthzResult);
|
||||
false ->
|
||||
NewestK = get_newest_key(),
|
||||
{_AclResult, CachedAt} = erlang:get(NewestK),
|
||||
if_expired(CachedAt,
|
||||
{_AuthzResult, CachedAt} = erlang:get(NewestK),
|
||||
if_expired(get_cache_ttl(Zone), CachedAt,
|
||||
fun(true) ->
|
||||
% all cache expired, cleanup first
|
||||
empty_acl_cache(),
|
||||
add_acl(PubSub, Topic, AclResult);
|
||||
empty_authz_cache(),
|
||||
add_authz(PubSub, Topic, AuthzResult);
|
||||
(false) ->
|
||||
% cache full, perform cache replacement
|
||||
evict_acl_cache(),
|
||||
add_acl(PubSub, Topic, AclResult)
|
||||
evict_authz_cache(),
|
||||
add_authz(PubSub, Topic, AuthzResult)
|
||||
end)
|
||||
end.
|
||||
|
||||
%% delete all the acl entries
|
||||
-spec(empty_acl_cache() -> ok).
|
||||
empty_acl_cache() ->
|
||||
foreach_acl_cache(fun({CacheK, _CacheV}) -> erlang:erase(CacheK) end),
|
||||
%% delete all the authz entries
|
||||
-spec(empty_authz_cache() -> ok).
|
||||
empty_authz_cache() ->
|
||||
foreach_authz_cache(fun({CacheK, _CacheV}) -> erlang:erase(CacheK) end),
|
||||
set_cache_size(0),
|
||||
keys_queue_set(queue:new()).
|
||||
|
||||
%% delete the oldest acl entry
|
||||
-spec(evict_acl_cache() -> ok).
|
||||
evict_acl_cache() ->
|
||||
%% delete the oldest authz entry
|
||||
-spec(evict_authz_cache() -> ok).
|
||||
evict_authz_cache() ->
|
||||
OldestK = keys_queue_out(),
|
||||
erlang:erase(OldestK),
|
||||
decr_cache_size().
|
||||
|
||||
%% cleanup all the expired cache entries
|
||||
-spec(cleanup_acl_cache() -> ok).
|
||||
cleanup_acl_cache() ->
|
||||
-spec(cleanup_authz_cache(atom()) -> ok).
|
||||
cleanup_authz_cache(Zone) ->
|
||||
keys_queue_set(
|
||||
cleanup_acl(keys_queue_get())).
|
||||
cleanup_authz(get_cache_ttl(Zone), keys_queue_get())).
|
||||
|
||||
get_oldest_key() ->
|
||||
keys_queue_pick(queue_front()).
|
||||
|
@ -132,22 +134,22 @@ get_newest_key() ->
|
|||
keys_queue_pick(queue_rear()).
|
||||
|
||||
get_cache_size() ->
|
||||
case erlang:get(acl_cache_size) of
|
||||
case erlang:get(authz_cache_size) of
|
||||
undefined -> 0;
|
||||
Size -> Size
|
||||
end.
|
||||
|
||||
dump_acl_cache() ->
|
||||
map_acl_cache(fun(Cache) -> Cache end).
|
||||
dump_authz_cache() ->
|
||||
map_authz_cache(fun(Cache) -> Cache end).
|
||||
|
||||
map_acl_cache(Fun) ->
|
||||
[Fun(R) || R = {{SubPub, _T}, _Acl} <- get(), SubPub =:= publish
|
||||
orelse SubPub =:= subscribe].
|
||||
foreach_acl_cache(Fun) ->
|
||||
_ = map_acl_cache(Fun),
|
||||
map_authz_cache(Fun) ->
|
||||
[Fun(R) || R = {{SubPub, _T}, _Authz} <- get(), SubPub =:= publish
|
||||
orelse SubPub =:= subscribe].
|
||||
foreach_authz_cache(Fun) ->
|
||||
_ = map_authz_cache(Fun),
|
||||
ok.
|
||||
|
||||
%% All acl cache entries added before `drain_cache()` invocation will become expired
|
||||
%% All authz cache entries added before `drain_cache()` invocation will become expired
|
||||
drain_cache() ->
|
||||
_ = persistent_term:put(drain_k(), time_now()),
|
||||
ok.
|
||||
|
@ -156,52 +158,52 @@ drain_cache() ->
|
|||
%% Internal functions
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
add_acl(PubSub, Topic, AclResult) ->
|
||||
add_authz(PubSub, Topic, AuthzResult) ->
|
||||
K = cache_k(PubSub, Topic),
|
||||
V = cache_v(AclResult),
|
||||
V = cache_v(AuthzResult),
|
||||
case erlang:get(K) of
|
||||
undefined -> add_new_acl(K, V);
|
||||
{_AclResult, _CachedAt} ->
|
||||
update_acl(K, V)
|
||||
undefined -> add_new_authz(K, V);
|
||||
{_AuthzResult, _CachedAt} ->
|
||||
update_authz(K, V)
|
||||
end.
|
||||
|
||||
add_new_acl(K, V) ->
|
||||
add_new_authz(K, V) ->
|
||||
erlang:put(K, V),
|
||||
keys_queue_in(K),
|
||||
incr_cache_size().
|
||||
|
||||
update_acl(K, V) ->
|
||||
update_authz(K, V) ->
|
||||
erlang:put(K, V),
|
||||
keys_queue_update(K).
|
||||
|
||||
cleanup_acl(KeysQ) ->
|
||||
cleanup_authz(TTL, KeysQ) ->
|
||||
case queue:out(KeysQ) of
|
||||
{{value, OldestK}, KeysQ2} ->
|
||||
{_AclResult, CachedAt} = erlang:get(OldestK),
|
||||
if_expired(CachedAt,
|
||||
{_AuthzResult, CachedAt} = erlang:get(OldestK),
|
||||
if_expired(TTL, CachedAt,
|
||||
fun(false) -> KeysQ;
|
||||
(true) ->
|
||||
erlang:erase(OldestK),
|
||||
decr_cache_size(),
|
||||
cleanup_acl(KeysQ2)
|
||||
cleanup_authz(TTL, KeysQ2)
|
||||
end);
|
||||
{empty, KeysQ} -> KeysQ
|
||||
end.
|
||||
|
||||
incr_cache_size() ->
|
||||
erlang:put(acl_cache_size, get_cache_size() + 1), ok.
|
||||
erlang:put(authz_cache_size, get_cache_size() + 1), ok.
|
||||
decr_cache_size() ->
|
||||
Size = get_cache_size(),
|
||||
case Size > 1 of
|
||||
true ->
|
||||
erlang:put(acl_cache_size, Size-1);
|
||||
erlang:put(authz_cache_size, Size-1);
|
||||
false ->
|
||||
erlang:put(acl_cache_size, 0)
|
||||
erlang:put(authz_cache_size, 0)
|
||||
end,
|
||||
ok.
|
||||
|
||||
set_cache_size(N) ->
|
||||
erlang:put(acl_cache_size, N), ok.
|
||||
erlang:put(authz_cache_size, N), ok.
|
||||
|
||||
%%% Ordered Keys Q %%%
|
||||
keys_queue_in(Key) ->
|
||||
|
@ -234,9 +236,9 @@ keys_queue_remove(Key, KeysQ) ->
|
|||
end, KeysQ).
|
||||
|
||||
keys_queue_set(KeysQ) ->
|
||||
erlang:put(acl_keys_q, KeysQ), ok.
|
||||
erlang:put(authz_keys_q, KeysQ), ok.
|
||||
keys_queue_get() ->
|
||||
case erlang:get(acl_keys_q) of
|
||||
case erlang:get(authz_keys_q) of
|
||||
undefined -> queue:new();
|
||||
KeysQ -> KeysQ
|
||||
end.
|
||||
|
@ -246,8 +248,7 @@ queue_rear() -> fun queue:get_r/1.
|
|||
|
||||
time_now() -> erlang:system_time(millisecond).
|
||||
|
||||
if_expired(CachedAt, Fun) ->
|
||||
TTL = get_cache_ttl(),
|
||||
if_expired(TTL, CachedAt, Fun) ->
|
||||
Now = time_now(),
|
||||
CurrentEvictTimestamp = persistent_term:get(drain_k(), 0),
|
||||
case CachedAt =< CurrentEvictTimestamp orelse (CachedAt + TTL) =< Now of
|
|
@ -51,6 +51,8 @@
|
|||
|
||||
-define(BANNED_TAB, ?MODULE).
|
||||
|
||||
-rlog_shard({?COMMON_SHARD, ?BANNED_TAB}).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Mnesia bootstrap
|
||||
%%--------------------------------------------------------------------
|
||||
|
@ -96,19 +98,19 @@ create(#{who := Who,
|
|||
reason := Reason,
|
||||
at := At,
|
||||
until := Until}) ->
|
||||
mnesia:dirty_write(?BANNED_TAB, #banned{who = Who,
|
||||
by = By,
|
||||
reason = Reason,
|
||||
at = At,
|
||||
until = Until});
|
||||
ekka_mnesia:dirty_write(?BANNED_TAB, #banned{who = Who,
|
||||
by = By,
|
||||
reason = Reason,
|
||||
at = At,
|
||||
until = Until});
|
||||
create(Banned) when is_record(Banned, banned) ->
|
||||
mnesia:dirty_write(?BANNED_TAB, Banned).
|
||||
ekka_mnesia:dirty_write(?BANNED_TAB, Banned).
|
||||
|
||||
-spec(delete({clientid, emqx_types:clientid()}
|
||||
| {username, emqx_types:username()}
|
||||
| {peerhost, emqx_types:peerhost()}) -> ok).
|
||||
delete(Who) ->
|
||||
mnesia:dirty_delete(?BANNED_TAB, Who).
|
||||
ekka_mnesia:dirty_delete(?BANNED_TAB, Who).
|
||||
|
||||
info(InfoKey) ->
|
||||
mnesia:table_info(?BANNED_TAB, InfoKey).
|
||||
|
@ -129,7 +131,7 @@ handle_cast(Msg, State) ->
|
|||
{noreply, State}.
|
||||
|
||||
handle_info({timeout, TRef, expire}, State = #{expiry_timer := TRef}) ->
|
||||
mnesia:async_dirty(fun expire_banned_items/1, [erlang:system_time(second)]),
|
||||
ekka_mnesia:transaction(?COMMON_SHARD, fun expire_banned_items/1, [erlang:system_time(second)]),
|
||||
{noreply, ensure_expiry_timer(State), hibernate};
|
||||
|
||||
handle_info(Info, State) ->
|
||||
|
@ -160,4 +162,3 @@ expire_banned_items(Now) ->
|
|||
mnesia:delete_object(?BANNED_TAB, B, sticky_write);
|
||||
(_, _Acc) -> ok
|
||||
end, ok, ?BANNED_TAB).
|
||||
|
||||
|
|
|
@ -243,7 +243,7 @@ route(Routes, Delivery) ->
|
|||
do_route({To, Node}, Delivery) when Node =:= node() ->
|
||||
{Node, To, dispatch(To, Delivery)};
|
||||
do_route({To, Node}, Delivery) when is_atom(Node) ->
|
||||
{Node, To, forward(Node, To, Delivery, emqx:get_env(rpc_mode, async))};
|
||||
{Node, To, forward(Node, To, Delivery, emqx_config:get([rpc, mode]))};
|
||||
do_route({To, Group}, Delivery) when is_tuple(Group); is_binary(Group) ->
|
||||
{share, To, emqx_shared_sub:dispatch(Group, To, Delivery)}.
|
||||
|
||||
|
|
|
@ -31,6 +31,8 @@
|
|||
|
||||
-export([ info/1
|
||||
, info/2
|
||||
, get_mqtt_conf/2
|
||||
, get_mqtt_conf/3
|
||||
, set_conn_state/2
|
||||
, get_session/1
|
||||
, set_session/2
|
||||
|
@ -63,7 +65,7 @@
|
|||
, maybe_apply/2
|
||||
]).
|
||||
|
||||
-export_type([channel/0]).
|
||||
-export_type([channel/0, opts/0]).
|
||||
|
||||
-record(channel, {
|
||||
%% MQTT ConnInfo
|
||||
|
@ -98,7 +100,9 @@
|
|||
|
||||
-type(channel() :: #channel{}).
|
||||
|
||||
-type(conn_state() :: idle | connecting | connected | disconnected).
|
||||
-type(opts() :: #{zone := atom(), listener := atom(), atom() => term()}).
|
||||
|
||||
-type(conn_state() :: idle | connecting | connected | reauthenticating | disconnected).
|
||||
|
||||
-type(reply() :: {outgoing, emqx_types:packet()}
|
||||
| {outgoing, [emqx_types:packet()]}
|
||||
|
@ -151,7 +155,9 @@ info(connected_at, #channel{conninfo = ConnInfo}) ->
|
|||
info(clientinfo, #channel{clientinfo = ClientInfo}) ->
|
||||
ClientInfo;
|
||||
info(zone, #channel{clientinfo = ClientInfo}) ->
|
||||
maps:get(zone, ClientInfo, undefined);
|
||||
maps:get(zone, ClientInfo);
|
||||
info(listener, #channel{clientinfo = ClientInfo}) ->
|
||||
maps:get(listener, ClientInfo);
|
||||
info(clientid, #channel{clientinfo = ClientInfo}) ->
|
||||
maps:get(clientid, ClientInfo, undefined);
|
||||
info(username, #channel{clientinfo = ClientInfo}) ->
|
||||
|
@ -195,17 +201,20 @@ caps(#channel{clientinfo = #{zone := Zone}}) ->
|
|||
%% Init the channel
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
-spec(init(emqx_types:conninfo(), proplists:proplist()) -> channel()).
|
||||
-spec(init(emqx_types:conninfo(), opts()) -> channel()).
|
||||
init(ConnInfo = #{peername := {PeerHost, _Port},
|
||||
sockname := {_Host, SockPort}}, Options) ->
|
||||
Zone = proplists:get_value(zone, Options),
|
||||
sockname := {_Host, SockPort}}, #{zone := Zone, listener := Listener}) ->
|
||||
Peercert = maps:get(peercert, ConnInfo, undefined),
|
||||
Protocol = maps:get(protocol, ConnInfo, mqtt),
|
||||
MountPoint = emqx_zone:mountpoint(Zone),
|
||||
QuotaPolicy = emqx_zone:quota_policy(Zone),
|
||||
ClientInfo = setting_peercert_infos(
|
||||
MountPoint = case get_mqtt_conf(Zone, mountpoint) of
|
||||
<<>> -> undefined;
|
||||
MP -> MP
|
||||
end,
|
||||
QuotaPolicy = emqx_config:get_listener_conf(Zone, Listener,[rate_limit, quota], []),
|
||||
ClientInfo = set_peercert_infos(
|
||||
Peercert,
|
||||
#{zone => Zone,
|
||||
listener => Listener,
|
||||
protocol => Protocol,
|
||||
peerhost => PeerHost,
|
||||
sockport => SockPort,
|
||||
|
@ -214,7 +223,7 @@ init(ConnInfo = #{peername := {PeerHost, _Port},
|
|||
mountpoint => MountPoint,
|
||||
is_bridge => false,
|
||||
is_superuser => false
|
||||
}, Options),
|
||||
}, Zone, Listener),
|
||||
{NClientInfo, NConnInfo} = take_ws_cookie(ClientInfo, ConnInfo),
|
||||
#channel{conninfo = NConnInfo,
|
||||
clientinfo = NClientInfo,
|
||||
|
@ -222,7 +231,7 @@ init(ConnInfo = #{peername := {PeerHost, _Port},
|
|||
outbound => #{}
|
||||
},
|
||||
auth_cache = #{},
|
||||
quota = emqx_limiter:init(Zone, QuotaPolicy),
|
||||
quota = emqx_limiter:init(Zone, quota_policy(QuotaPolicy)),
|
||||
timers = #{},
|
||||
conn_state = idle,
|
||||
takeover = false,
|
||||
|
@ -230,30 +239,32 @@ init(ConnInfo = #{peername := {PeerHost, _Port},
|
|||
pendings = []
|
||||
}.
|
||||
|
||||
setting_peercert_infos(NoSSL, ClientInfo, _Options)
|
||||
quota_policy(RawPolicy) ->
|
||||
[{Name, {list_to_integer(StrCount),
|
||||
erlang:trunc(hocon_postprocess:duration(StrWind) / 1000)}}
|
||||
|| {Name, [StrCount, StrWind]} <- maps:to_list(RawPolicy)].
|
||||
|
||||
set_peercert_infos(NoSSL, ClientInfo, _, _)
|
||||
when NoSSL =:= nossl;
|
||||
NoSSL =:= undefined ->
|
||||
ClientInfo#{username => undefined};
|
||||
|
||||
setting_peercert_infos(Peercert, ClientInfo, Options) ->
|
||||
set_peercert_infos(Peercert, ClientInfo, Zone, _Listener) ->
|
||||
{DN, CN} = {esockd_peercert:subject(Peercert),
|
||||
esockd_peercert:common_name(Peercert)},
|
||||
Username = peer_cert_as(peer_cert_as_username, Options, Peercert, DN, CN),
|
||||
ClientId = peer_cert_as(peer_cert_as_clientid, Options, Peercert, DN, CN),
|
||||
ClientInfo#{username => Username, clientid => ClientId, dn => DN, cn => CN}.
|
||||
|
||||
-dialyzer([{nowarn_function, [peer_cert_as/5]}]).
|
||||
% esockd_peercert:peercert is opaque
|
||||
% https://github.com/emqx/esockd/blob/master/src/esockd_peercert.erl
|
||||
peer_cert_as(Key, Options, Peercert, DN, CN) ->
|
||||
case proplists:get_value(Key, Options) of
|
||||
PeercetAs = fun(Key) ->
|
||||
case get_mqtt_conf(Zone, Key) of
|
||||
cn -> CN;
|
||||
dn -> DN;
|
||||
crt -> Peercert;
|
||||
pem -> base64:encode(Peercert);
|
||||
md5 -> emqx_passwd:hash(md5, Peercert);
|
||||
pem when is_binary(Peercert) -> base64:encode(Peercert);
|
||||
md5 when is_binary(Peercert) -> emqx_passwd:hash(md5, Peercert);
|
||||
_ -> undefined
|
||||
end.
|
||||
end
|
||||
end,
|
||||
Username = PeercetAs(peer_cert_as_username),
|
||||
ClientId = PeercetAs(peer_cert_as_clientid),
|
||||
ClientInfo#{username => Username, clientid => ClientId, dn => DN, cn => CN}.
|
||||
|
||||
take_ws_cookie(ClientInfo, ConnInfo) ->
|
||||
case maps:take(ws_cookie, ConnInfo) of
|
||||
|
@ -272,65 +283,77 @@ take_ws_cookie(ClientInfo, ConnInfo) ->
|
|||
| {ok, replies(), channel()}
|
||||
| {shutdown, Reason :: term(), channel()}
|
||||
| {shutdown, Reason :: term(), replies(), channel()}).
|
||||
handle_in(?CONNECT_PACKET(), Channel = #channel{conn_state = connected}) ->
|
||||
handle_in(?CONNECT_PACKET(), Channel = #channel{conn_state = ConnState})
|
||||
when ConnState =:= connected orelse ConnState =:= reauthenticating ->
|
||||
handle_out(disconnect, ?RC_PROTOCOL_ERROR, Channel);
|
||||
|
||||
handle_in(?CONNECT_PACKET(), Channel = #channel{conn_state = connecting}) ->
|
||||
handle_out(connack, ?RC_PROTOCOL_ERROR, Channel);
|
||||
|
||||
handle_in(?CONNECT_PACKET(ConnPkt), Channel) ->
|
||||
case pipeline([fun enrich_conninfo/2,
|
||||
fun run_conn_hooks/2,
|
||||
fun check_connect/2,
|
||||
fun enrich_client/2,
|
||||
fun set_log_meta/2,
|
||||
fun check_banned/2,
|
||||
fun auth_connect/2
|
||||
fun check_banned/2
|
||||
], ConnPkt, Channel#channel{conn_state = connecting}) of
|
||||
{ok, NConnPkt, NChannel = #channel{clientinfo = ClientInfo}} ->
|
||||
NChannel1 = NChannel#channel{
|
||||
will_msg = emqx_packet:will_msg(NConnPkt),
|
||||
alias_maximum = init_alias_maximum(NConnPkt, ClientInfo)
|
||||
},
|
||||
case enhanced_auth(?CONNECT_PACKET(NConnPkt), NChannel1) of
|
||||
case authenticate(?CONNECT_PACKET(NConnPkt), NChannel1) of
|
||||
{ok, Properties, NChannel2} ->
|
||||
process_connect(Properties, ensure_connected(NChannel2));
|
||||
{continue, Properties, NChannel2} ->
|
||||
handle_out(auth, {?RC_CONTINUE_AUTHENTICATION, Properties}, NChannel2);
|
||||
{error, ReasonCode, NChannel2} ->
|
||||
handle_out(connack, ReasonCode, NChannel2)
|
||||
{error, ReasonCode} ->
|
||||
handle_out(connack, ReasonCode, NChannel1)
|
||||
end;
|
||||
{error, ReasonCode, NChannel} ->
|
||||
handle_out(connack, ReasonCode, NChannel)
|
||||
end;
|
||||
|
||||
handle_in(Packet = ?AUTH_PACKET(?RC_CONTINUE_AUTHENTICATION, _Properties),
|
||||
handle_in(Packet = ?AUTH_PACKET(ReasonCode, _Properties),
|
||||
Channel = #channel{conn_state = ConnState}) ->
|
||||
case enhanced_auth(Packet, Channel) of
|
||||
{ok, NProperties, NChannel} ->
|
||||
try
|
||||
case {ReasonCode, ConnState} of
|
||||
{?RC_CONTINUE_AUTHENTICATION, connecting} -> ok;
|
||||
{?RC_CONTINUE_AUTHENTICATION, reauthenticating} -> ok;
|
||||
{?RC_RE_AUTHENTICATE, connected} -> ok;
|
||||
_ -> error(protocol_error)
|
||||
end,
|
||||
case authenticate(Packet, Channel) of
|
||||
{ok, NProperties, NChannel} ->
|
||||
case ConnState of
|
||||
connecting ->
|
||||
process_connect(NProperties, ensure_connected(NChannel));
|
||||
_ ->
|
||||
handle_out(auth, {?RC_SUCCESS, NProperties}, NChannel#channel{conn_state = connected})
|
||||
end;
|
||||
{continue, NProperties, NChannel} ->
|
||||
handle_out(auth, {?RC_CONTINUE_AUTHENTICATION, NProperties}, NChannel#channel{conn_state = reauthenticating});
|
||||
{error, NReasonCode} ->
|
||||
case ConnState of
|
||||
connecting ->
|
||||
handle_out(connack, NReasonCode, Channel);
|
||||
_ ->
|
||||
handle_out(disconnect, NReasonCode, Channel)
|
||||
end
|
||||
end
|
||||
catch
|
||||
_Class:_Reason ->
|
||||
case ConnState of
|
||||
connecting ->
|
||||
process_connect(NProperties, ensure_connected(NChannel));
|
||||
connected ->
|
||||
handle_out(auth, {?RC_SUCCESS, NProperties}, NChannel);
|
||||
handle_out(connack, ?RC_PROTOCOL_ERROR, Channel);
|
||||
_ ->
|
||||
handle_out(disconnect, ?RC_PROTOCOL_ERROR, Channel)
|
||||
end;
|
||||
{continue, NProperties, NChannel} ->
|
||||
handle_out(auth, {?RC_CONTINUE_AUTHENTICATION, NProperties}, NChannel);
|
||||
{error, NReasonCode, NChannel} ->
|
||||
handle_out(connack, NReasonCode, NChannel)
|
||||
end
|
||||
end;
|
||||
|
||||
handle_in(Packet = ?AUTH_PACKET(?RC_RE_AUTHENTICATE, _Properties),
|
||||
Channel = #channel{conn_state = connected}) ->
|
||||
case enhanced_auth(Packet, Channel) of
|
||||
{ok, NProperties, NChannel} ->
|
||||
handle_out(auth, {?RC_SUCCESS, NProperties}, NChannel);
|
||||
{continue, NProperties, NChannel} ->
|
||||
handle_out(auth, {?RC_CONTINUE_AUTHENTICATION, NProperties}, NChannel);
|
||||
{error, NReasonCode, NChannel} ->
|
||||
handle_out(disconnect, NReasonCode, NChannel)
|
||||
end;
|
||||
|
||||
handle_in(?PACKET(_), Channel = #channel{conn_state = ConnState}) when ConnState =/= connected ->
|
||||
handle_in(?PACKET(_), Channel = #channel{conn_state = ConnState})
|
||||
when ConnState =/= connected andalso ConnState =/= reauthenticating ->
|
||||
handle_out(disconnect, ?RC_PROTOCOL_ERROR, Channel);
|
||||
|
||||
handle_in(Packet = ?PUBLISH_PACKET(_QoS), Channel) ->
|
||||
|
@ -408,11 +431,12 @@ handle_in(Packet = ?SUBSCRIBE_PACKET(PacketId, Properties, TopicFilters),
|
|||
ok ->
|
||||
TopicFilters0 = parse_topic_filters(TopicFilters),
|
||||
TopicFilters1 = put_subid_in_subopts(Properties, TopicFilters0),
|
||||
TupleTopicFilters0 = check_sub_acls(TopicFilters1, Channel),
|
||||
case emqx_zone:get_env(Zone, acl_deny_action, ignore) =:= disconnect andalso
|
||||
lists:any(fun({_TopicFilter, ReasonCode}) ->
|
||||
ReasonCode =:= ?RC_NOT_AUTHORIZED
|
||||
end, TupleTopicFilters0) of
|
||||
TupleTopicFilters0 = check_sub_authzs(TopicFilters1, Channel),
|
||||
HasAuthzDeny = lists:any(fun({_TopicFilter, ReasonCode}) ->
|
||||
ReasonCode =:= ?RC_NOT_AUTHORIZED
|
||||
end, TupleTopicFilters0),
|
||||
DenyAction = emqx_config:get_zone_conf(Zone, [authorization, deny_action]),
|
||||
case DenyAction =:= disconnect andalso HasAuthzDeny of
|
||||
true -> handle_out(disconnect, ?RC_NOT_AUTHORIZED, Channel);
|
||||
false ->
|
||||
Replace = fun
|
||||
|
@ -469,9 +493,11 @@ handle_in({frame_error, frame_too_large}, Channel = #channel{conn_state = connec
|
|||
handle_in({frame_error, Reason}, Channel = #channel{conn_state = connecting}) ->
|
||||
shutdown(Reason, ?CONNACK_PACKET(?RC_MALFORMED_PACKET), Channel);
|
||||
|
||||
handle_in({frame_error, frame_too_large}, Channel = #channel{conn_state = connected}) ->
|
||||
handle_in({frame_error, frame_too_large}, Channel = #channel{conn_state = ConnState})
|
||||
when ConnState =:= connected orelse ConnState =:= reauthenticating ->
|
||||
handle_out(disconnect, {?RC_PACKET_TOO_LARGE, frame_too_large}, Channel);
|
||||
handle_in({frame_error, Reason}, Channel = #channel{conn_state = connected}) ->
|
||||
handle_in({frame_error, Reason}, Channel = #channel{conn_state = ConnState})
|
||||
when ConnState =:= connected orelse ConnState =:= reauthenticating ->
|
||||
handle_out(disconnect, {?RC_MALFORMED_PACKET, Reason}, Channel);
|
||||
|
||||
handle_in({frame_error, Reason}, Channel = #channel{conn_state = disconnected}) ->
|
||||
|
@ -516,7 +542,7 @@ process_publish(Packet = ?PUBLISH_PACKET(QoS, Topic, PacketId),
|
|||
case pipeline([fun check_quota_exceeded/2,
|
||||
fun process_alias/2,
|
||||
fun check_pub_alias/2,
|
||||
fun check_pub_acl/2,
|
||||
fun check_pub_authz/2,
|
||||
fun check_pub_caps/2
|
||||
], Packet, Channel) of
|
||||
{ok, NPacket, NChannel} ->
|
||||
|
@ -525,7 +551,7 @@ process_publish(Packet = ?PUBLISH_PACKET(QoS, Topic, PacketId),
|
|||
{error, Rc = ?RC_NOT_AUTHORIZED, NChannel} ->
|
||||
?LOG(warning, "Cannot publish message to ~s due to ~s.",
|
||||
[Topic, emqx_reason_codes:text(Rc)]),
|
||||
case emqx_zone:get_env(Zone, acl_deny_action, ignore) of
|
||||
case emqx_config:get_zone_conf(Zone, [authorization, deny_action]) of
|
||||
ignore ->
|
||||
case QoS of
|
||||
?QOS_0 -> {ok, NChannel};
|
||||
|
@ -711,7 +737,7 @@ process_disconnect(ReasonCode, Properties, Channel) ->
|
|||
|
||||
maybe_update_expiry_interval(#{'Session-Expiry-Interval' := Interval},
|
||||
Channel = #channel{conninfo = ConnInfo}) ->
|
||||
Channel#channel{conninfo = ConnInfo#{expiry_interval => Interval}};
|
||||
Channel#channel{conninfo = ConnInfo#{expiry_interval => timer:seconds(Interval)}};
|
||||
maybe_update_expiry_interval(_Properties, Channel) -> Channel.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
|
@ -930,8 +956,9 @@ handle_call({takeover, 'end'}, Channel = #channel{session = Session,
|
|||
AllPendings = lists:append(Delivers, Pendings),
|
||||
disconnect_and_shutdown(takeovered, AllPendings, Channel);
|
||||
|
||||
handle_call(list_acl_cache, Channel) ->
|
||||
{reply, emqx_acl_cache:list_acl_cache(), Channel};
|
||||
handle_call(list_authz_cache, #channel{clientinfo = #{zone := Zone}}
|
||||
= Channel) ->
|
||||
{reply, emqx_authz_cache:list_authz_cache(Zone), Channel};
|
||||
|
||||
handle_call({quota, Policy}, Channel) ->
|
||||
Zone = info(zone, Channel),
|
||||
|
@ -967,9 +994,10 @@ handle_info({sock_closed, Reason}, Channel = #channel{conn_state = connecting})
|
|||
shutdown(Reason, Channel);
|
||||
|
||||
handle_info({sock_closed, Reason}, Channel =
|
||||
#channel{conn_state = connected,
|
||||
clientinfo = ClientInfo = #{zone := Zone}}) ->
|
||||
emqx_zone:enable_flapping_detect(Zone)
|
||||
#channel{conn_state = ConnState,
|
||||
clientinfo = ClientInfo = #{zone := Zone}})
|
||||
when ConnState =:= connected orelse ConnState =:= reauthenticating ->
|
||||
emqx_config:get_zone_conf(Zone, [flapping_detect, enable])
|
||||
andalso emqx_flapping:detect(ClientInfo),
|
||||
Channel1 = ensure_disconnected(Reason, mabye_publish_will_msg(Channel)),
|
||||
case maybe_shutdown(Reason, Channel1) of
|
||||
|
@ -981,8 +1009,8 @@ handle_info({sock_closed, Reason}, Channel = #channel{conn_state = disconnected}
|
|||
?LOG(error, "Unexpected sock_closed: ~p", [Reason]),
|
||||
{ok, Channel};
|
||||
|
||||
handle_info(clean_acl_cache, Channel) ->
|
||||
ok = emqx_acl_cache:empty_acl_cache(),
|
||||
handle_info(clean_authz_cache, Channel) ->
|
||||
ok = emqx_authz_cache:empty_authz_cache(),
|
||||
{ok, Channel};
|
||||
|
||||
handle_info(Info, Channel) ->
|
||||
|
@ -1086,11 +1114,11 @@ clean_timer(Name, Channel = #channel{timers = Timers}) ->
|
|||
interval(alive_timer, #channel{keepalive = KeepAlive}) ->
|
||||
emqx_keepalive:info(interval, KeepAlive);
|
||||
interval(retry_timer, #channel{session = Session}) ->
|
||||
timer:seconds(emqx_session:info(retry_interval, Session));
|
||||
emqx_session:info(retry_interval, Session);
|
||||
interval(await_timer, #channel{session = Session}) ->
|
||||
timer:seconds(emqx_session:info(await_rel_timeout, Session));
|
||||
emqx_session:info(await_rel_timeout, Session);
|
||||
interval(expire_timer, #channel{conninfo = ConnInfo}) ->
|
||||
timer:seconds(maps:get(expiry_interval, ConnInfo));
|
||||
maps:get(expiry_interval, ConnInfo);
|
||||
interval(will_timer, #channel{will_msg = WillMsg}) ->
|
||||
timer:seconds(will_delay_interval(WillMsg)).
|
||||
|
||||
|
@ -1146,17 +1174,16 @@ enrich_conninfo(ConnPkt = #mqtt_packet_connect{
|
|||
{ok, Channel#channel{conninfo = NConnInfo}}.
|
||||
|
||||
%% If the Session Expiry Interval is absent the value 0 is used.
|
||||
-compile({inline, [expiry_interval/2]}).
|
||||
expiry_interval(_Zone, #mqtt_packet_connect{proto_ver = ?MQTT_PROTO_V5,
|
||||
expiry_interval(_, #mqtt_packet_connect{proto_ver = ?MQTT_PROTO_V5,
|
||||
properties = ConnProps}) ->
|
||||
emqx_mqtt_props:get('Session-Expiry-Interval', ConnProps, 0);
|
||||
timer:seconds(emqx_mqtt_props:get('Session-Expiry-Interval', ConnProps, 0));
|
||||
expiry_interval(Zone, #mqtt_packet_connect{clean_start = false}) ->
|
||||
emqx_zone:session_expiry_interval(Zone);
|
||||
expiry_interval(_Zone, #mqtt_packet_connect{clean_start = true}) ->
|
||||
get_mqtt_conf(Zone, session_expiry_interval);
|
||||
expiry_interval(_, #mqtt_packet_connect{clean_start = true}) ->
|
||||
0.
|
||||
|
||||
receive_maximum(Zone, ConnProps) ->
|
||||
MaxInflightConfig = case emqx_zone:max_inflight(Zone) of
|
||||
MaxInflightConfig = case get_mqtt_conf(Zone, max_inflight) of
|
||||
0 -> ?RECEIVE_MAXIMUM_LIMIT;
|
||||
N -> N
|
||||
end,
|
||||
|
@ -1205,8 +1232,9 @@ set_bridge_mode(_ConnPkt, _ClientInfo) -> ok.
|
|||
|
||||
maybe_username_as_clientid(_ConnPkt, ClientInfo = #{username := undefined}) ->
|
||||
{ok, ClientInfo};
|
||||
maybe_username_as_clientid(_ConnPkt, ClientInfo = #{zone := Zone, username := Username}) ->
|
||||
case emqx_zone:use_username_as_clientid(Zone) of
|
||||
maybe_username_as_clientid(_ConnPkt, ClientInfo = #{zone := Zone,
|
||||
username := Username}) ->
|
||||
case get_mqtt_conf(Zone, use_username_as_clientid) of
|
||||
true -> {ok, ClientInfo#{clientid => Username}};
|
||||
false -> ok
|
||||
end.
|
||||
|
@ -1234,82 +1262,67 @@ set_log_meta(_ConnPkt, #channel{clientinfo = #{clientid := ClientId}}) ->
|
|||
%%--------------------------------------------------------------------
|
||||
%% Check banned
|
||||
|
||||
check_banned(_ConnPkt, #channel{clientinfo = ClientInfo = #{zone := Zone}}) ->
|
||||
case emqx_zone:enable_ban(Zone) andalso emqx_banned:check(ClientInfo) of
|
||||
check_banned(_ConnPkt, #channel{clientinfo = ClientInfo}) ->
|
||||
case emqx_banned:check(ClientInfo) of
|
||||
true -> {error, ?RC_BANNED};
|
||||
false -> ok
|
||||
end.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Auth Connect
|
||||
%% Authenticate
|
||||
|
||||
auth_connect(#mqtt_packet_connect{password = Password},
|
||||
authenticate(?CONNECT_PACKET(
|
||||
#mqtt_packet_connect{
|
||||
proto_ver = ?MQTT_PROTO_V5,
|
||||
properties = #{'Authentication-Method' := AuthMethod} = Properties}),
|
||||
#channel{clientinfo = ClientInfo,
|
||||
auth_cache = AuthCache} = Channel) ->
|
||||
AuthData = emqx_mqtt_props:get('Authentication-Data', Properties, undefined),
|
||||
do_authenticate(ClientInfo#{auth_method => AuthMethod,
|
||||
auth_data => AuthData,
|
||||
auth_cache => AuthCache}, Channel);
|
||||
|
||||
authenticate(?CONNECT_PACKET(#mqtt_packet_connect{password = Password}),
|
||||
#channel{clientinfo = ClientInfo} = Channel) ->
|
||||
#{clientid := ClientId,
|
||||
username := Username} = ClientInfo,
|
||||
case emqx_access_control:authenticate(ClientInfo#{password => Password}) of
|
||||
{ok, AuthResult} ->
|
||||
is_anonymous(AuthResult) andalso
|
||||
emqx_metrics:inc('client.auth.anonymous'),
|
||||
NClientInfo = maps:merge(ClientInfo, AuthResult),
|
||||
{ok, Channel#channel{clientinfo = NClientInfo}};
|
||||
{error, Reason} ->
|
||||
?LOG(warning, "Client ~s (Username: '~s') login failed for ~0p",
|
||||
[ClientId, Username, Reason]),
|
||||
{error, emqx_reason_codes:connack_error(Reason)}
|
||||
do_authenticate(ClientInfo#{password => Password}, Channel);
|
||||
|
||||
authenticate(?AUTH_PACKET(_, #{'Authentication-Method' := AuthMethod} = Properties),
|
||||
#channel{clientinfo = ClientInfo,
|
||||
conninfo = #{conn_props := ConnProps},
|
||||
auth_cache = AuthCache} = Channel) ->
|
||||
case emqx_mqtt_props:get('Authentication-Method', ConnProps, undefined) of
|
||||
AuthMethod ->
|
||||
AuthData = emqx_mqtt_props:get('Authentication-Data', Properties, undefined),
|
||||
do_authenticate(ClientInfo#{auth_method => AuthMethod,
|
||||
auth_data => AuthData,
|
||||
auth_cache => AuthCache}, Channel);
|
||||
_ ->
|
||||
{error, ?RC_BAD_AUTHENTICATION_METHOD}
|
||||
end.
|
||||
|
||||
is_anonymous(#{anonymous := true}) -> true;
|
||||
is_anonymous(_AuthResult) -> false.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Enhanced Authentication
|
||||
|
||||
enhanced_auth(?CONNECT_PACKET(#mqtt_packet_connect{
|
||||
proto_ver = Protover,
|
||||
properties = Properties
|
||||
}), Channel) ->
|
||||
case Protover of
|
||||
?MQTT_PROTO_V5 ->
|
||||
AuthMethod = emqx_mqtt_props:get('Authentication-Method', Properties, undefined),
|
||||
AuthData = emqx_mqtt_props:get('Authentication-Data', Properties, undefined),
|
||||
do_enhanced_auth(AuthMethod, AuthData, Channel);
|
||||
_ ->
|
||||
{ok, #{}, Channel}
|
||||
do_authenticate(#{auth_method := AuthMethod} = Credential, Channel) ->
|
||||
Properties = #{'Authentication-Method' => AuthMethod},
|
||||
case emqx_access_control:authenticate(Credential) of
|
||||
ok ->
|
||||
{ok, Properties, Channel#channel{auth_cache = #{}}};
|
||||
{ok, AuthData} ->
|
||||
{ok, Properties#{'Authentication-Data' => AuthData},
|
||||
Channel#channel{auth_cache = #{}}};
|
||||
{continue, AuthCache} ->
|
||||
{continue, Properties, Channel#channel{auth_cache = AuthCache}};
|
||||
{continue, AuthData, AuthCache} ->
|
||||
{continue, Properties#{'Authentication-Data' => AuthData},
|
||||
Channel#channel{auth_cache = AuthCache}};
|
||||
{error, Reason} ->
|
||||
{error, emqx_reason_codes:connack_error(Reason)}
|
||||
end;
|
||||
|
||||
enhanced_auth(?AUTH_PACKET(_ReasonCode, Properties), Channel = #channel{conninfo = ConnInfo}) ->
|
||||
AuthMethod = emqx_mqtt_props:get('Authentication-Method',
|
||||
emqx_mqtt_props:get(conn_props, ConnInfo, #{}),
|
||||
undefined
|
||||
),
|
||||
NAuthMethod = emqx_mqtt_props:get('Authentication-Method', Properties, undefined),
|
||||
AuthData = emqx_mqtt_props:get('Authentication-Data', Properties, undefined),
|
||||
case NAuthMethod =:= undefined orelse NAuthMethod =/= AuthMethod of
|
||||
true ->
|
||||
{error, emqx_reason_codes:connack_error(bad_authentication_method), Channel};
|
||||
false ->
|
||||
do_enhanced_auth(AuthMethod, AuthData, Channel)
|
||||
end.
|
||||
|
||||
do_enhanced_auth(undefined, undefined, Channel) ->
|
||||
{ok, #{}, Channel};
|
||||
do_enhanced_auth(undefined, _AuthData, Channel) ->
|
||||
{error, emqx_reason_codes:connack_error(not_authorized), Channel};
|
||||
do_enhanced_auth(_AuthMethod, undefined, Channel) ->
|
||||
{error, emqx_reason_codes:connack_error(not_authorized), Channel};
|
||||
do_enhanced_auth(AuthMethod, AuthData, Channel = #channel{auth_cache = Cache}) ->
|
||||
case run_hooks('client.enhanced_authenticate', [AuthMethod, AuthData], Cache) of
|
||||
{ok, NAuthData, NCache} ->
|
||||
NProperties = #{'Authentication-Method' => AuthMethod,
|
||||
'Authentication-Data' => NAuthData},
|
||||
{ok, NProperties, Channel#channel{auth_cache = NCache}};
|
||||
{continue, NAuthData, NCache} ->
|
||||
NProperties = #{'Authentication-Method' => AuthMethod,
|
||||
'Authentication-Data' => NAuthData},
|
||||
{continue, NProperties, Channel#channel{auth_cache = NCache}};
|
||||
_ ->
|
||||
{error, emqx_reason_codes:connack_error(not_authorized), Channel}
|
||||
do_authenticate(Credential, Channel) ->
|
||||
case emqx_access_control:authenticate(Credential) of
|
||||
ok ->
|
||||
{ok, #{}, Channel};
|
||||
{error, Reason} ->
|
||||
{error, emqx_reason_codes:connack_error(Reason)}
|
||||
end.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
|
@ -1401,12 +1414,12 @@ check_pub_alias(#mqtt_packet{
|
|||
check_pub_alias(_Packet, _Channel) -> ok.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Check Pub ACL
|
||||
%% Check Pub Authorization
|
||||
|
||||
check_pub_acl(#mqtt_packet{variable = #mqtt_packet_publish{topic_name = Topic}},
|
||||
check_pub_authz(#mqtt_packet{variable = #mqtt_packet_publish{topic_name = Topic}},
|
||||
#channel{clientinfo = ClientInfo}) ->
|
||||
case is_acl_enabled(ClientInfo) andalso
|
||||
emqx_access_control:check_acl(ClientInfo, publish, Topic) of
|
||||
case is_authz_enabled(ClientInfo) andalso
|
||||
emqx_access_control:authorize(ClientInfo, publish, Topic) of
|
||||
false -> ok;
|
||||
allow -> ok;
|
||||
deny -> {error, ?RC_NOT_AUTHORIZED}
|
||||
|
@ -1423,24 +1436,24 @@ check_pub_caps(#mqtt_packet{header = #mqtt_packet_header{qos = QoS,
|
|||
emqx_mqtt_caps:check_pub(Zone, #{qos => QoS, retain => Retain, topic => Topic}).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Check Sub ACL
|
||||
%% Check Sub Authorization
|
||||
|
||||
check_sub_acls(TopicFilters, Channel) ->
|
||||
check_sub_acls(TopicFilters, Channel, []).
|
||||
check_sub_authzs(TopicFilters, Channel) ->
|
||||
check_sub_authzs(TopicFilters, Channel, []).
|
||||
|
||||
check_sub_acls([ TopicFilter = {Topic, _} | More] , Channel, Acc) ->
|
||||
case check_sub_acl(Topic, Channel) of
|
||||
check_sub_authzs([ TopicFilter = {Topic, _} | More] , Channel, Acc) ->
|
||||
case check_sub_authz(Topic, Channel) of
|
||||
allow ->
|
||||
check_sub_acls(More, Channel, [ {TopicFilter, 0} | Acc]);
|
||||
check_sub_authzs(More, Channel, [ {TopicFilter, 0} | Acc]);
|
||||
deny ->
|
||||
check_sub_acls(More, Channel, [ {TopicFilter, ?RC_NOT_AUTHORIZED} | Acc])
|
||||
check_sub_authzs(More, Channel, [ {TopicFilter, ?RC_NOT_AUTHORIZED} | Acc])
|
||||
end;
|
||||
check_sub_acls([], _Channel, Acc) ->
|
||||
check_sub_authzs([], _Channel, Acc) ->
|
||||
lists:reverse(Acc).
|
||||
|
||||
check_sub_acl(TopicFilter, #channel{clientinfo = ClientInfo}) ->
|
||||
case is_acl_enabled(ClientInfo) andalso
|
||||
emqx_access_control:check_acl(ClientInfo, subscribe, TopicFilter) of
|
||||
check_sub_authz(TopicFilter, #channel{clientinfo = ClientInfo}) ->
|
||||
case is_authz_enabled(ClientInfo) andalso
|
||||
emqx_access_control:authorize(ClientInfo, subscribe, TopicFilter) of
|
||||
false -> allow;
|
||||
Result -> Result
|
||||
end.
|
||||
|
@ -1464,13 +1477,14 @@ put_subid_in_subopts(_Properties, TopicFilters) -> TopicFilters.
|
|||
enrich_subopts(SubOpts, _Channel = ?IS_MQTT_V5) ->
|
||||
SubOpts;
|
||||
enrich_subopts(SubOpts, #channel{clientinfo = #{zone := Zone, is_bridge := IsBridge}}) ->
|
||||
NL = flag(emqx_zone:ignore_loop_deliver(Zone)),
|
||||
NL = flag(get_mqtt_conf(Zone, ignore_loop_deliver)),
|
||||
SubOpts#{rap => flag(IsBridge), nl => NL}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Enrich ConnAck Caps
|
||||
|
||||
enrich_connack_caps(AckProps, ?IS_MQTT_V5 = #channel{clientinfo = #{zone := Zone}}) ->
|
||||
enrich_connack_caps(AckProps, ?IS_MQTT_V5 = #channel{clientinfo = #{
|
||||
zone := Zone}}) ->
|
||||
#{max_packet_size := MaxPktSize,
|
||||
max_qos_allowed := MaxQoS,
|
||||
retain_available := Retain,
|
||||
|
@ -1500,8 +1514,8 @@ enrich_connack_caps(AckProps, _Channel) -> AckProps.
|
|||
%% Enrich server keepalive
|
||||
|
||||
enrich_server_keepalive(AckProps, #channel{clientinfo = #{zone := Zone}}) ->
|
||||
case emqx_zone:server_keepalive(Zone) of
|
||||
undefined -> AckProps;
|
||||
case get_mqtt_conf(Zone, server_keepalive) of
|
||||
disabled -> AckProps;
|
||||
Keepalive -> AckProps#{'Server-Keep-Alive' => Keepalive}
|
||||
end.
|
||||
|
||||
|
@ -1509,10 +1523,14 @@ enrich_server_keepalive(AckProps, #channel{clientinfo = #{zone := Zone}}) ->
|
|||
%% Enrich response information
|
||||
|
||||
enrich_response_information(AckProps, #channel{conninfo = #{conn_props := ConnProps},
|
||||
clientinfo = #{zone := Zone}}) ->
|
||||
clientinfo = #{zone := Zone}}) ->
|
||||
case emqx_mqtt_props:get('Request-Response-Information', ConnProps, 0) of
|
||||
0 -> AckProps;
|
||||
1 -> AckProps#{'Response-Information' => emqx_zone:response_information(Zone)}
|
||||
1 -> AckProps#{'Response-Information' =>
|
||||
case get_mqtt_conf(Zone, response_information, "") of
|
||||
"" -> undefined;
|
||||
RspInfo -> RspInfo
|
||||
end}
|
||||
end.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
|
@ -1544,7 +1562,7 @@ init_alias_maximum(#mqtt_packet_connect{proto_ver = ?MQTT_PROTO_V5,
|
|||
properties = Properties},
|
||||
#{zone := Zone} = _ClientInfo) ->
|
||||
#{outbound => emqx_mqtt_props:get('Topic-Alias-Maximum', Properties, 0),
|
||||
inbound => emqx_mqtt_caps:get_caps(Zone, max_topic_alias, ?MAX_TOPIC_AlIAS)
|
||||
inbound => maps:get(max_topic_alias, emqx_mqtt_caps:get_caps(Zone))
|
||||
};
|
||||
init_alias_maximum(_ConnPkt, _ClientInfo) -> undefined.
|
||||
|
||||
|
@ -1560,8 +1578,9 @@ ensure_keepalive(_AckProps, Channel = #channel{conninfo = ConnInfo}) ->
|
|||
ensure_keepalive_timer(maps:get(keepalive, ConnInfo), Channel).
|
||||
|
||||
ensure_keepalive_timer(0, Channel) -> Channel;
|
||||
ensure_keepalive_timer(disabled, Channel) -> Channel;
|
||||
ensure_keepalive_timer(Interval, Channel = #channel{clientinfo = #{zone := Zone}}) ->
|
||||
Backoff = emqx_zone:keepalive_backoff(Zone),
|
||||
Backoff = get_mqtt_conf(Zone, keepalive_backoff),
|
||||
Keepalive = emqx_keepalive:init(round(timer:seconds(Interval) * Backoff)),
|
||||
ensure_timer(alive_timer, Channel#channel{keepalive = Keepalive}).
|
||||
|
||||
|
@ -1596,16 +1615,14 @@ maybe_shutdown(Reason, Channel = #channel{conninfo = ConnInfo}) ->
|
|||
case maps:get(expiry_interval, ConnInfo) of
|
||||
?UINT_MAX -> {ok, Channel};
|
||||
I when I > 0 ->
|
||||
{ok, ensure_timer(expire_timer, timer:seconds(I), Channel)};
|
||||
{ok, ensure_timer(expire_timer, I, Channel)};
|
||||
_ -> shutdown(Reason, Channel)
|
||||
end.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Is ACL enabled?
|
||||
|
||||
-compile({inline, [is_acl_enabled/1]}).
|
||||
is_acl_enabled(#{zone := Zone, is_superuser := IsSuperuser}) ->
|
||||
(not IsSuperuser) andalso emqx_zone:enable_acl(Zone).
|
||||
%% Is Authorization enabled?
|
||||
is_authz_enabled(#{zone := Zone, is_superuser := IsSuperuser}) ->
|
||||
(not IsSuperuser) andalso emqx_config:get_zone_conf(Zone, [authorization, enable]).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Parse Topic Filters
|
||||
|
@ -1703,7 +1720,8 @@ shutdown(Reason, Reply, Packet, Channel) ->
|
|||
{shutdown, Reason, Reply, Packet, Channel}.
|
||||
|
||||
disconnect_and_shutdown(Reason, Reply, Channel = ?IS_MQTT_V5
|
||||
= #channel{conn_state = connected}) ->
|
||||
= #channel{conn_state = ConnState})
|
||||
when ConnState =:= connected orelse ConnState =:= reauthenticating ->
|
||||
shutdown(Reason, Reply, ?DISCONNECT_PACKET(reason_code(Reason)), Channel);
|
||||
|
||||
disconnect_and_shutdown(Reason, Reply, Channel) ->
|
||||
|
@ -1715,6 +1733,12 @@ sp(false) -> 0.
|
|||
flag(true) -> 1;
|
||||
flag(false) -> 0.
|
||||
|
||||
get_mqtt_conf(Zone, Key) ->
|
||||
emqx_config:get_zone_conf(Zone, [mqtt, Key]).
|
||||
|
||||
get_mqtt_conf(Zone, Key, Default) ->
|
||||
emqx_config:get_zone_conf(Zone, [mqtt, Key], Default).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% For CT tests
|
||||
%%--------------------------------------------------------------------
|
||||
|
@ -1722,4 +1746,3 @@ flag(false) -> 0.
|
|||
set_field(Name, Value, Channel) ->
|
||||
Pos = emqx_misc:index_of(Name, record_info(fields, channel)),
|
||||
setelement(Pos+1, Channel, Value).
|
||||
|
||||
|
|
|
@ -241,11 +241,31 @@ open_session(false, ClientInfo = #{clientid := ClientId}, ConnInfo) ->
|
|||
emqx_cm_locker:trans(ClientId, ResumeStart).
|
||||
|
||||
create_session(ClientInfo, ConnInfo) ->
|
||||
Session = emqx_session:init(ClientInfo, ConnInfo),
|
||||
Options = get_session_confs(ClientInfo, ConnInfo),
|
||||
Session = emqx_session:init(Options),
|
||||
ok = emqx_metrics:inc('session.created'),
|
||||
ok = emqx_hooks:run('session.created', [ClientInfo, emqx_session:info(Session)]),
|
||||
Session.
|
||||
|
||||
get_session_confs(#{zone := Zone}, #{receive_maximum := MaxInflight}) ->
|
||||
#{max_subscriptions => get_mqtt_conf(Zone, max_subscriptions),
|
||||
upgrade_qos => get_mqtt_conf(Zone, upgrade_qos),
|
||||
max_inflight => MaxInflight,
|
||||
retry_interval => get_mqtt_conf(Zone, retry_interval),
|
||||
await_rel_timeout => get_mqtt_conf(Zone, await_rel_timeout),
|
||||
mqueue => mqueue_confs(Zone)
|
||||
}.
|
||||
|
||||
mqueue_confs(Zone) ->
|
||||
#{max_len => get_mqtt_conf(Zone, max_mqueue_len),
|
||||
store_qos0 => get_mqtt_conf(Zone, mqueue_store_qos0),
|
||||
priorities => get_mqtt_conf(Zone, mqueue_priorities),
|
||||
default_priority => get_mqtt_conf(Zone, mqueue_default_priority)
|
||||
}.
|
||||
|
||||
get_mqtt_conf(Zone, Key) ->
|
||||
emqx_config:get_zone_conf(Zone, [mqtt, Key]).
|
||||
|
||||
%% @doc Try to takeover a session.
|
||||
-spec(takeover_session(emqx_types:clientid())
|
||||
-> {error, term()}
|
||||
|
|
|
@ -62,5 +62,5 @@ unlock(ClientId) ->
|
|||
|
||||
-spec(strategy() -> local | leader | quorum | all).
|
||||
strategy() ->
|
||||
emqx:get_env(session_locking_strategy, quorum).
|
||||
emqx_config:get([broker, session_locking_strategy]).
|
||||
|
||||
|
|
|
@ -48,7 +48,9 @@
|
|||
-define(TAB, emqx_channel_registry).
|
||||
-define(LOCK, {?MODULE, cleanup_down}).
|
||||
|
||||
-rlog_shard({?ROUTE_SHARD, ?TAB}).
|
||||
-define(CM_SHARD, emqx_cm_shard).
|
||||
|
||||
-rlog_shard({?CM_SHARD, ?TAB}).
|
||||
|
||||
-record(channel, {chid, pid}).
|
||||
|
||||
|
@ -64,7 +66,7 @@ start_link() ->
|
|||
%% @doc Is the global registry enabled?
|
||||
-spec(is_enabled() -> boolean()).
|
||||
is_enabled() ->
|
||||
emqx:get_env(enable_session_registry, true).
|
||||
emqx_config:get([broker, enable_session_registry]).
|
||||
|
||||
%% @doc Register a global channel.
|
||||
-spec(register_channel(emqx_types:clientid()
|
||||
|
@ -111,6 +113,7 @@ init([]) ->
|
|||
{storage_properties, [{ets, [{read_concurrency, true},
|
||||
{write_concurrency, true}]}]}]),
|
||||
ok = ekka_mnesia:copy_table(?TAB, ram_copies),
|
||||
ok = ekka_rlog:wait_for_shards([?CM_SHARD], infinity),
|
||||
ok = ekka:monitor(membership),
|
||||
{ok, #{}}.
|
||||
|
||||
|
@ -125,7 +128,7 @@ handle_cast(Msg, State) ->
|
|||
handle_info({membership, {mnesia, down, Node}}, State) ->
|
||||
global:trans({?LOCK, self()},
|
||||
fun() ->
|
||||
ekka_mnesia:transaction(?ROUTE_SHARD, fun cleanup_channels/1, [Node])
|
||||
ekka_mnesia:transaction(?CM_SHARD, fun cleanup_channels/1, [Node])
|
||||
end),
|
||||
{noreply, State};
|
||||
|
||||
|
|
|
@ -15,132 +15,271 @@
|
|||
%%--------------------------------------------------------------------
|
||||
-module(emqx_config).
|
||||
|
||||
-compile({no_auto_import, [get/0, get/1]}).
|
||||
-compile({no_auto_import, [get/0, get/1, put/2]}).
|
||||
|
||||
-export([ get/0
|
||||
, get/1
|
||||
-export([ init_load/2
|
||||
, read_override_conf/0
|
||||
, check_config/2
|
||||
, save_configs/4
|
||||
, save_to_app_env/1
|
||||
, save_to_config_map/2
|
||||
, save_to_override_conf/1
|
||||
]).
|
||||
|
||||
-export([get_root/1,
|
||||
get_root_raw/1]).
|
||||
|
||||
-export([ get/1
|
||||
, get/2
|
||||
, find/1
|
||||
, put/1
|
||||
, put/2
|
||||
]).
|
||||
|
||||
-export([ update_config/2
|
||||
-export([ get_zone_conf/2
|
||||
, get_zone_conf/3
|
||||
, put_zone_conf/3
|
||||
, find_zone_conf/2
|
||||
]).
|
||||
|
||||
%% raw configs is the config that is now parsed and tranlated by hocon schema
|
||||
-export([ get_raw/0
|
||||
, get_raw/1
|
||||
-export([ get_listener_conf/3
|
||||
, get_listener_conf/4
|
||||
, put_listener_conf/4
|
||||
, find_listener_conf/3
|
||||
]).
|
||||
|
||||
-export([ update/2
|
||||
, update/3
|
||||
, remove/1
|
||||
, remove/2
|
||||
]).
|
||||
|
||||
-export([ get_raw/1
|
||||
, get_raw/2
|
||||
, put_raw/1
|
||||
, put_raw/2
|
||||
]).
|
||||
|
||||
-export([ deep_get/2
|
||||
, deep_get/3
|
||||
, deep_put/3
|
||||
, safe_atom_key_map/1
|
||||
, unsafe_atom_key_map/1
|
||||
]).
|
||||
-define(CONF, fun(ROOT) -> {?MODULE, bin(ROOT)} end).
|
||||
-define(RAW_CONF, fun(ROOT) -> {?MODULE, raw, bin(ROOT)} end).
|
||||
-define(ZONE_CONF_PATH(ZONE, PATH), [zones, ZONE | PATH]).
|
||||
-define(LISTENER_CONF_PATH(ZONE, LISTENER, PATH), [zones, ZONE, listeners, LISTENER | PATH]).
|
||||
|
||||
-define(CONF, ?MODULE).
|
||||
-define(RAW_CONF, {?MODULE, raw}).
|
||||
|
||||
-export_type([update_request/0, raw_config/0, config_key/0, config_key_path/0]).
|
||||
-export_type([update_request/0, raw_config/0, config/0]).
|
||||
-type update_request() :: term().
|
||||
-type raw_config() :: hocon:config() | undefined.
|
||||
-type config_key() :: atom() | binary().
|
||||
-type config_key_path() :: [config_key()].
|
||||
%% raw_config() is the config that is NOT parsed and tranlated by hocon schema
|
||||
-type raw_config() :: #{binary() => term()} | undefined.
|
||||
%% config() is the config that is parsed and tranlated by hocon schema
|
||||
-type config() :: #{atom() => term()} | undefined.
|
||||
-type app_envs() :: [proplists:property()].
|
||||
|
||||
-spec get() -> map().
|
||||
get() ->
|
||||
persistent_term:get(?CONF, #{}).
|
||||
%% @doc For the given path, get root value enclosed in a single-key map.
|
||||
-spec get_root(emqx_map_lib:config_key_path()) -> map().
|
||||
get_root([RootName | _]) ->
|
||||
#{RootName => do_get(?CONF, [RootName], #{})}.
|
||||
|
||||
-spec get(config_key_path()) -> term().
|
||||
get(KeyPath) ->
|
||||
deep_get(KeyPath, get()).
|
||||
%% @doc For the given path, get raw root value enclosed in a single-key map.
|
||||
%% key is ensured to be binary.
|
||||
get_root_raw([RootName | _]) ->
|
||||
#{bin(RootName) => do_get(?RAW_CONF, [RootName], #{})}.
|
||||
|
||||
-spec get(config_key_path(), term()) -> term().
|
||||
get(KeyPath, Default) ->
|
||||
deep_get(KeyPath, get(), Default).
|
||||
%% @doc Get a config value for the given path.
|
||||
%% The path should at least include root config name.
|
||||
-spec get(emqx_map_lib:config_key_path()) -> term().
|
||||
get(KeyPath) -> do_get(?CONF, KeyPath).
|
||||
|
||||
-spec get(emqx_map_lib:config_key_path(), term()) -> term().
|
||||
get(KeyPath, Default) -> do_get(?CONF, KeyPath, Default).
|
||||
|
||||
-spec find(emqx_map_lib:config_key_path()) ->
|
||||
{ok, term()} | {not_found, emqx_map_lib:config_key_path(), term()}.
|
||||
find(KeyPath) ->
|
||||
emqx_map_lib:deep_find(KeyPath, get_root(KeyPath)).
|
||||
|
||||
-spec get_zone_conf(atom(), emqx_map_lib:config_key_path()) -> term().
|
||||
get_zone_conf(Zone, KeyPath) ->
|
||||
?MODULE:get(?ZONE_CONF_PATH(Zone, KeyPath)).
|
||||
|
||||
-spec get_zone_conf(atom(), emqx_map_lib:config_key_path(), term()) -> term().
|
||||
get_zone_conf(Zone, KeyPath, Default) ->
|
||||
?MODULE:get(?ZONE_CONF_PATH(Zone, KeyPath), Default).
|
||||
|
||||
-spec put_zone_conf(atom(), emqx_map_lib:config_key_path(), term()) -> ok.
|
||||
put_zone_conf(Zone, KeyPath, Conf) ->
|
||||
?MODULE:put(?ZONE_CONF_PATH(Zone, KeyPath), Conf).
|
||||
|
||||
-spec find_zone_conf(atom(), emqx_map_lib:config_key_path()) ->
|
||||
{ok, term()} | {not_found, emqx_map_lib:config_key_path(), term()}.
|
||||
find_zone_conf(Zone, KeyPath) ->
|
||||
find(?ZONE_CONF_PATH(Zone, KeyPath)).
|
||||
|
||||
-spec get_listener_conf(atom(), atom(), emqx_map_lib:config_key_path()) -> term().
|
||||
get_listener_conf(Zone, Listener, KeyPath) ->
|
||||
?MODULE:get(?LISTENER_CONF_PATH(Zone, Listener, KeyPath)).
|
||||
|
||||
-spec get_listener_conf(atom(), atom(), emqx_map_lib:config_key_path(), term()) -> term().
|
||||
get_listener_conf(Zone, Listener, KeyPath, Default) ->
|
||||
?MODULE:get(?LISTENER_CONF_PATH(Zone, Listener, KeyPath), Default).
|
||||
|
||||
-spec put_listener_conf(atom(), atom(), emqx_map_lib:config_key_path(), term()) -> ok.
|
||||
put_listener_conf(Zone, Listener, KeyPath, Conf) ->
|
||||
?MODULE:put(?LISTENER_CONF_PATH(Zone, Listener, KeyPath), Conf).
|
||||
|
||||
-spec find_listener_conf(atom(), atom(), emqx_map_lib:config_key_path()) ->
|
||||
{ok, term()} | {not_found, emqx_map_lib:config_key_path(), term()}.
|
||||
find_listener_conf(Zone, Listener, KeyPath) ->
|
||||
find(?LISTENER_CONF_PATH(Zone, Listener, KeyPath)).
|
||||
|
||||
-spec put(map()) -> ok.
|
||||
put(Config) ->
|
||||
persistent_term:put(?CONF, Config).
|
||||
maps:fold(fun(RootName, RootValue, _) ->
|
||||
?MODULE:put([RootName], RootValue)
|
||||
end, [], Config).
|
||||
|
||||
-spec put(config_key_path(), term()) -> ok.
|
||||
put(KeyPath, Config) ->
|
||||
put(deep_put(KeyPath, get(), Config)).
|
||||
-spec put(emqx_map_lib:config_key_path(), term()) -> ok.
|
||||
put(KeyPath, Config) -> do_put(?CONF, KeyPath, Config).
|
||||
|
||||
-spec update_config(config_key_path(), update_request()) ->
|
||||
-spec update(emqx_map_lib:config_key_path(), update_request()) ->
|
||||
ok | {error, term()}.
|
||||
update_config(ConfKeyPath, UpdateReq) ->
|
||||
emqx_config_handler:update_config(ConfKeyPath, UpdateReq, get_raw()).
|
||||
update(ConfKeyPath, UpdateReq) ->
|
||||
update(emqx_schema, ConfKeyPath, UpdateReq).
|
||||
|
||||
-spec get_raw() -> map().
|
||||
get_raw() ->
|
||||
persistent_term:get(?RAW_CONF, #{}).
|
||||
-spec update(module(), emqx_map_lib:config_key_path(), update_request()) ->
|
||||
ok | {error, term()}.
|
||||
update(SchemaModule, ConfKeyPath, UpdateReq) ->
|
||||
emqx_config_handler:update_config(SchemaModule, ConfKeyPath, UpdateReq).
|
||||
|
||||
-spec get_raw(config_key_path()) -> term().
|
||||
get_raw(KeyPath) ->
|
||||
deep_get(KeyPath, get_raw()).
|
||||
-spec remove(emqx_map_lib:config_key_path()) -> ok | {error, term()}.
|
||||
remove(ConfKeyPath) ->
|
||||
remove(emqx_schema, ConfKeyPath).
|
||||
|
||||
-spec get_raw(config_key_path(), term()) -> term().
|
||||
get_raw(KeyPath, Default) ->
|
||||
deep_get(KeyPath, get_raw(), Default).
|
||||
remove(SchemaModule, ConfKeyPath) ->
|
||||
emqx_config_handler:remove_config(SchemaModule, ConfKeyPath).
|
||||
|
||||
-spec get_raw(emqx_map_lib:config_key_path()) -> term().
|
||||
get_raw(KeyPath) -> do_get(?RAW_CONF, KeyPath).
|
||||
|
||||
-spec get_raw(emqx_map_lib:config_key_path(), term()) -> term().
|
||||
get_raw(KeyPath, Default) -> do_get(?RAW_CONF, KeyPath, Default).
|
||||
|
||||
-spec put_raw(map()) -> ok.
|
||||
put_raw(Config) ->
|
||||
persistent_term:put(?RAW_CONF, Config).
|
||||
maps:fold(fun(RootName, RootV, _) ->
|
||||
?MODULE:put_raw([RootName], RootV)
|
||||
end, [], hocon_schema:get_value([], Config)).
|
||||
|
||||
-spec put_raw(config_key_path(), term()) -> ok.
|
||||
put_raw(KeyPath, Config) ->
|
||||
put_raw(deep_put(KeyPath, get_raw(), Config)).
|
||||
-spec put_raw(emqx_map_lib:config_key_path(), term()) -> ok.
|
||||
put_raw(KeyPath, Config) -> do_put(?RAW_CONF, KeyPath, Config).
|
||||
|
||||
%%-----------------------------------------------------------------
|
||||
-dialyzer([{nowarn_function, [deep_get/2]}]).
|
||||
-spec deep_get(config_key_path(), map()) -> term().
|
||||
deep_get(ConfKeyPath, Map) ->
|
||||
do_deep_get(ConfKeyPath, Map, fun(KeyPath, Data) ->
|
||||
error({not_found, KeyPath, Data}) end).
|
||||
%%============================================================================
|
||||
%% Load/Update configs From/To files
|
||||
%%============================================================================
|
||||
|
||||
-spec deep_get(config_key_path(), map(), term()) -> term().
|
||||
deep_get(ConfKeyPath, Map, Default) ->
|
||||
do_deep_get(ConfKeyPath, Map, fun(_, _) -> Default end).
|
||||
|
||||
-spec deep_put(config_key_path(), map(), term()) -> map().
|
||||
deep_put([], Map, Config) when is_map(Map) ->
|
||||
Config;
|
||||
deep_put([Key | KeyPath], Map, Config) ->
|
||||
SubMap = deep_put(KeyPath, maps:get(Key, Map, #{}), Config),
|
||||
Map#{Key => SubMap}.
|
||||
|
||||
unsafe_atom_key_map(Map) ->
|
||||
covert_keys_to_atom(Map, fun(K) -> binary_to_atom(K, utf8) end).
|
||||
|
||||
safe_atom_key_map(Map) ->
|
||||
covert_keys_to_atom(Map, fun(K) -> binary_to_existing_atom(K, utf8) end).
|
||||
|
||||
%%---------------------------------------------------------------------------
|
||||
|
||||
-spec do_deep_get(config_key_path(), map(), fun((config_key(), term()) -> any())) -> term().
|
||||
do_deep_get([], Map, _) ->
|
||||
Map;
|
||||
do_deep_get([Key | KeyPath], Map, OnNotFound) when is_map(Map) ->
|
||||
case maps:find(Key, Map) of
|
||||
{ok, SubMap} -> do_deep_get(KeyPath, SubMap, OnNotFound);
|
||||
error -> OnNotFound(Key, Map)
|
||||
%% @doc Initial load of the given config files.
|
||||
%% NOTE: The order of the files is significant, configs from files orderd
|
||||
%% in the rear of the list overrides prior values.
|
||||
-spec init_load(module(), [string()] | binary() | hocon:config()) -> ok.
|
||||
init_load(SchemaModule, Conf) when is_list(Conf) orelse is_binary(Conf) ->
|
||||
ParseOptions = #{format => richmap},
|
||||
Parser = case is_binary(Conf) of
|
||||
true -> fun hocon:binary/2;
|
||||
false -> fun hocon:files/2
|
||||
end,
|
||||
case Parser(Conf, ParseOptions) of
|
||||
{ok, RawRichConf} ->
|
||||
init_load(SchemaModule, RawRichConf);
|
||||
{error, Reason} ->
|
||||
logger:error(#{msg => failed_to_load_hocon_conf,
|
||||
reason => Reason
|
||||
}),
|
||||
error(failed_to_load_hocon_conf)
|
||||
end;
|
||||
do_deep_get([Key | _KeyPath], Data, OnNotFound) ->
|
||||
OnNotFound(Key, Data).
|
||||
init_load(SchemaModule, RawRichConf) when is_map(RawRichConf) ->
|
||||
%% check with richmap for line numbers in error reports (future enhancement)
|
||||
Opts = #{return_plain => true,
|
||||
nullable => true
|
||||
},
|
||||
%% this call throws exception in case of check failure
|
||||
{_AppEnvs, CheckedConf} = hocon_schema:map_translate(SchemaModule, RawRichConf, Opts),
|
||||
ok = save_to_config_map(emqx_map_lib:unsafe_atom_key_map(CheckedConf),
|
||||
hocon_schema:richmap_to_map(RawRichConf)).
|
||||
|
||||
covert_keys_to_atom(BinKeyMap, Conv) when is_map(BinKeyMap) ->
|
||||
maps:fold(
|
||||
fun(K, V, Acc) when is_binary(K) ->
|
||||
Acc#{Conv(K) => covert_keys_to_atom(V, Conv)};
|
||||
(K, V, Acc) when is_atom(K) ->
|
||||
%% richmap keys
|
||||
Acc#{K => covert_keys_to_atom(V, Conv)}
|
||||
end, #{}, BinKeyMap);
|
||||
covert_keys_to_atom(ListV, Conv) when is_list(ListV) ->
|
||||
[covert_keys_to_atom(V, Conv) || V <- ListV];
|
||||
covert_keys_to_atom(Val, _) -> Val.
|
||||
-spec check_config(module(), raw_config()) -> {AppEnvs, CheckedConf}
|
||||
when AppEnvs :: app_envs(), CheckedConf :: config().
|
||||
check_config(SchemaModule, RawConf) ->
|
||||
Opts = #{return_plain => true,
|
||||
nullable => true,
|
||||
is_richmap => false
|
||||
},
|
||||
{AppEnvs, CheckedConf} =
|
||||
hocon_schema:map_translate(SchemaModule, RawConf, Opts),
|
||||
Conf = maps:with(maps:keys(RawConf), CheckedConf),
|
||||
{AppEnvs, emqx_map_lib:unsafe_atom_key_map(Conf)}.
|
||||
|
||||
-spec read_override_conf() -> raw_config().
|
||||
read_override_conf() ->
|
||||
load_hocon_file(emqx_override_conf_name(), map).
|
||||
|
||||
-spec save_configs(app_envs(), config(), raw_config(), raw_config()) -> ok | {error, term()}.
|
||||
save_configs(_AppEnvs, Conf, RawConf, OverrideConf) ->
|
||||
%% We may need also support hot config update for the apps that use application envs.
|
||||
%% If that is the case uncomment the following line to update the configs to app env
|
||||
%save_to_app_env(AppEnvs),
|
||||
save_to_config_map(Conf, RawConf),
|
||||
%% TODO: merge RawConf to OverrideConf can be done here
|
||||
save_to_override_conf(OverrideConf).
|
||||
|
||||
-spec save_to_app_env([tuple()]) -> ok.
|
||||
save_to_app_env(AppEnvs) ->
|
||||
lists:foreach(fun({AppName, Envs}) ->
|
||||
[application:set_env(AppName, Par, Val) || {Par, Val} <- Envs]
|
||||
end, AppEnvs).
|
||||
|
||||
-spec save_to_config_map(config(), raw_config()) -> ok.
|
||||
save_to_config_map(Conf, RawConf) ->
|
||||
?MODULE:put(Conf),
|
||||
?MODULE:put_raw(RawConf).
|
||||
|
||||
-spec save_to_override_conf(raw_config()) -> ok | {error, term()}.
|
||||
save_to_override_conf(RawConf) ->
|
||||
FileName = emqx_override_conf_name(),
|
||||
ok = filelib:ensure_dir(FileName),
|
||||
case file:write_file(FileName, jsx:prettify(jsx:encode(RawConf))) of
|
||||
ok -> ok;
|
||||
{error, Reason} ->
|
||||
logger:error("write to ~s failed, ~p", [FileName, Reason]),
|
||||
{error, Reason}
|
||||
end.
|
||||
|
||||
load_hocon_file(FileName, LoadType) ->
|
||||
case filelib:is_regular(FileName) of
|
||||
true ->
|
||||
{ok, Raw0} = hocon:load(FileName, #{format => LoadType}),
|
||||
Raw0;
|
||||
false -> #{}
|
||||
end.
|
||||
|
||||
emqx_override_conf_name() ->
|
||||
filename:join([?MODULE:get([node, data_dir]), "emqx_override.conf"]).
|
||||
|
||||
bin(Bin) when is_binary(Bin) -> Bin;
|
||||
bin(Atom) when is_atom(Atom) -> atom_to_binary(Atom, utf8).
|
||||
|
||||
do_get(PtKey, KeyPath) ->
|
||||
Ref = make_ref(),
|
||||
Res = do_get(PtKey, KeyPath, Ref),
|
||||
case Res =:= Ref of
|
||||
true -> error({config_not_found, KeyPath});
|
||||
false -> Res
|
||||
end.
|
||||
|
||||
do_get(PtKey, [RootName], Default) ->
|
||||
persistent_term:get(PtKey(RootName), Default);
|
||||
do_get(PtKey, [RootName | KeyPath], Default) ->
|
||||
RootV = persistent_term:get(PtKey(RootName), #{}),
|
||||
emqx_map_lib:deep_get(KeyPath, RootV, Default).
|
||||
|
||||
do_put(PtKey, [RootName | KeyPath], DeepValue) ->
|
||||
OldValue = do_get(PtKey, [RootName], #{}),
|
||||
NewValue = emqx_map_lib:deep_put(KeyPath, OldValue, DeepValue),
|
||||
persistent_term:put(PtKey(RootName), NewValue).
|
||||
|
|
|
@ -25,13 +25,10 @@
|
|||
-export([ start_link/0
|
||||
, add_handler/2
|
||||
, update_config/3
|
||||
, remove_config/2
|
||||
, merge_to_old_config/2
|
||||
]).
|
||||
|
||||
%% emqx_config_handler callbacks
|
||||
-export([ handle_update_config/2
|
||||
]).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([init/1,
|
||||
handle_call/3,
|
||||
|
@ -41,15 +38,21 @@
|
|||
code_change/3]).
|
||||
|
||||
-define(MOD, {mod}).
|
||||
-define(REMOVE_CONF, '$remove_config').
|
||||
|
||||
-type handler_name() :: module().
|
||||
-type handlers() :: #{emqx_config:config_key() => handlers(), ?MOD => handler_name()}.
|
||||
|
||||
-optional_callbacks([handle_update_config/2]).
|
||||
-optional_callbacks([ pre_config_update/2
|
||||
, post_config_update/3
|
||||
]).
|
||||
|
||||
-callback handle_update_config(emqx_config:update_request(), emqx_config:raw_config()) ->
|
||||
-callback pre_config_update(emqx_config:update_request(), emqx_config:raw_config()) ->
|
||||
emqx_config:update_request().
|
||||
|
||||
-callback post_config_update(emqx_config:update_request(), emqx_config:config(),
|
||||
emqx_config:config()) -> any().
|
||||
|
||||
-type state() :: #{
|
||||
handlers := handlers(),
|
||||
atom() => term()
|
||||
|
@ -58,11 +61,15 @@
|
|||
start_link() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, {}, []).
|
||||
|
||||
-spec update_config(emqx_config:config_key_path(), emqx_config:update_request(),
|
||||
emqx_config:raw_config()) ->
|
||||
-spec update_config(module(), emqx_config:config_key_path(), emqx_config:update_request()) ->
|
||||
ok | {error, term()}.
|
||||
update_config(ConfKeyPath, UpdateReq, RawConfig) ->
|
||||
gen_server:call(?MODULE, {update_config, ConfKeyPath, UpdateReq, RawConfig}).
|
||||
update_config(SchemaModule, ConfKeyPath, UpdateReq) when UpdateReq =/= ?REMOVE_CONF ->
|
||||
gen_server:call(?MODULE, {change_config, SchemaModule, ConfKeyPath, UpdateReq}).
|
||||
|
||||
-spec remove_config(module(), emqx_config:config_key_path()) ->
|
||||
ok | {error, term()}.
|
||||
remove_config(SchemaModule, ConfKeyPath) ->
|
||||
gen_server:call(?MODULE, {change_config, SchemaModule, ConfKeyPath, ?REMOVE_CONF}).
|
||||
|
||||
-spec add_handler(emqx_config:config_key_path(), handler_name()) -> ok.
|
||||
add_handler(ConfKeyPath, HandlerName) ->
|
||||
|
@ -72,26 +79,28 @@ add_handler(ConfKeyPath, HandlerName) ->
|
|||
|
||||
-spec init(term()) -> {ok, state()}.
|
||||
init(_) ->
|
||||
{ok, RawConf} = hocon:load(emqx_conf_name(), #{format => richmap}),
|
||||
{_MappedEnvs, Conf} = hocon_schema:map_translate(emqx_schema, RawConf, #{}),
|
||||
ok = save_config_to_emqx(to_plainmap(Conf), to_plainmap(RawConf)),
|
||||
{ok, #{handlers => #{?MOD => ?MODULE}}}.
|
||||
|
||||
handle_call({add_child, ConfKeyPath, HandlerName}, _From,
|
||||
State = #{handlers := Handlers}) ->
|
||||
{reply, ok, State#{handlers =>
|
||||
emqx_config:deep_put(ConfKeyPath, Handlers, #{?MOD => HandlerName})}};
|
||||
emqx_map_lib:deep_put(ConfKeyPath, Handlers, #{?MOD => HandlerName})}};
|
||||
|
||||
handle_call({update_config, ConfKeyPath, UpdateReq, RawConf}, _From,
|
||||
handle_call({change_config, SchemaModule, ConfKeyPath, UpdateReq}, _From,
|
||||
#{handlers := Handlers} = State) ->
|
||||
try {RootKeys, Conf} = do_update_config(ConfKeyPath, Handlers, RawConf, UpdateReq),
|
||||
{reply, save_configs(RootKeys, Conf), State}
|
||||
catch
|
||||
throw: Reason ->
|
||||
{reply, {error, Reason}, State};
|
||||
Error : Reason : ST ->
|
||||
?LOG(error, "update config failed: ~p", [{Error, Reason, ST}]),
|
||||
{reply, {error, Reason}, State}
|
||||
end;
|
||||
OldConf = emqx_config:get_root(ConfKeyPath),
|
||||
OldRawConf = emqx_config:get_root_raw(ConfKeyPath),
|
||||
Result = try
|
||||
{NewRawConf, OverrideConf} = process_upadate_request(ConfKeyPath, OldRawConf,
|
||||
Handlers, UpdateReq),
|
||||
{AppEnvs, CheckedConf} = emqx_config:check_config(SchemaModule, NewRawConf),
|
||||
_ = do_post_config_update(ConfKeyPath, Handlers, OldConf, CheckedConf, UpdateReq),
|
||||
emqx_config:save_configs(AppEnvs, CheckedConf, NewRawConf, OverrideConf)
|
||||
catch Error:Reason:ST ->
|
||||
?LOG(error, "change_config failed: ~p", [{Error, Reason, ST}]),
|
||||
{error, Reason}
|
||||
end,
|
||||
{reply, Result, State};
|
||||
|
||||
handle_call(_Request, _From, State) ->
|
||||
Reply = ok,
|
||||
|
@ -109,104 +118,67 @@ terminate(_Reason, _State) ->
|
|||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
do_update_config([], Handlers, OldConf, UpdateReq) ->
|
||||
call_handle_update_config(Handlers, OldConf, UpdateReq);
|
||||
do_update_config([ConfKey | ConfKeyPath], Handlers, OldConf, UpdateReq) ->
|
||||
process_upadate_request(ConfKeyPath, OldRawConf, _Handlers, ?REMOVE_CONF) ->
|
||||
BinKeyPath = bin_path(ConfKeyPath),
|
||||
NewRawConf = emqx_map_lib:deep_remove(BinKeyPath, OldRawConf),
|
||||
OverrideConf = emqx_map_lib:deep_remove(BinKeyPath, emqx_config:read_override_conf()),
|
||||
{NewRawConf, OverrideConf};
|
||||
process_upadate_request(ConfKeyPath, OldRawConf, Handlers, UpdateReq) ->
|
||||
NewRawConf = do_update_config(ConfKeyPath, Handlers, OldRawConf, UpdateReq),
|
||||
OverrideConf = update_override_config(NewRawConf),
|
||||
{NewRawConf, OverrideConf}.
|
||||
|
||||
do_update_config([], Handlers, OldRawConf, UpdateReq) ->
|
||||
call_pre_config_update(Handlers, OldRawConf, UpdateReq);
|
||||
do_update_config([ConfKey | ConfKeyPath], Handlers, OldRawConf, UpdateReq) ->
|
||||
SubOldRawConf = get_sub_config(bin(ConfKey), OldRawConf),
|
||||
SubHandlers = maps:get(ConfKey, Handlers, #{}),
|
||||
NewUpdateReq = do_update_config(ConfKeyPath, SubHandlers, SubOldRawConf, UpdateReq),
|
||||
call_pre_config_update(Handlers, OldRawConf, #{bin(ConfKey) => NewUpdateReq}).
|
||||
|
||||
do_post_config_update([], Handlers, OldConf, NewConf, UpdateReq) ->
|
||||
call_post_config_update(Handlers, OldConf, NewConf, UpdateReq);
|
||||
do_post_config_update([ConfKey | ConfKeyPath], Handlers, OldConf, NewConf, UpdateReq) ->
|
||||
SubOldConf = get_sub_config(ConfKey, OldConf),
|
||||
case maps:find(ConfKey, Handlers) of
|
||||
error -> throw({handler_not_found, ConfKey});
|
||||
{ok, SubHandlers} ->
|
||||
NewUpdateReq = do_update_config(ConfKeyPath, SubHandlers, SubOldConf, UpdateReq),
|
||||
call_handle_update_config(Handlers, OldConf, #{bin(ConfKey) => NewUpdateReq})
|
||||
end.
|
||||
SubNewConf = get_sub_config(ConfKey, NewConf),
|
||||
SubHandlers = maps:get(ConfKey, Handlers, #{}),
|
||||
_ = do_post_config_update(ConfKeyPath, SubHandlers, SubOldConf, SubNewConf, UpdateReq),
|
||||
call_post_config_update(Handlers, OldConf, NewConf, UpdateReq).
|
||||
|
||||
get_sub_config(_, undefined) ->
|
||||
undefined;
|
||||
get_sub_config(ConfKey, OldConf) when is_map(OldConf) ->
|
||||
maps:get(bin(ConfKey), OldConf, undefined);
|
||||
get_sub_config(_, OldConf) ->
|
||||
OldConf.
|
||||
get_sub_config(ConfKey, Conf) when is_map(Conf) ->
|
||||
maps:get(ConfKey, Conf, undefined);
|
||||
get_sub_config(_, _Conf) -> %% the Conf is a primitive
|
||||
undefined.
|
||||
|
||||
call_handle_update_config(Handlers, OldConf, UpdateReq) ->
|
||||
call_pre_config_update(Handlers, OldRawConf, UpdateReq) ->
|
||||
HandlerName = maps:get(?MOD, Handlers, undefined),
|
||||
case erlang:function_exported(HandlerName, handle_update_config, 2) of
|
||||
true -> HandlerName:handle_update_config(UpdateReq, OldConf);
|
||||
false -> UpdateReq %% the default behaviour is overwriting the old config
|
||||
case erlang:function_exported(HandlerName, pre_config_update, 2) of
|
||||
true -> HandlerName:pre_config_update(UpdateReq, OldRawConf);
|
||||
false -> merge_to_old_config(UpdateReq, OldRawConf)
|
||||
end.
|
||||
|
||||
%% callbacks for the top-level handler
|
||||
handle_update_config(UpdateReq, OldConf) ->
|
||||
FullRawConf = merge_to_old_config(UpdateReq, OldConf),
|
||||
{maps:keys(UpdateReq), FullRawConf}.
|
||||
|
||||
%% default callback of config handlers
|
||||
merge_to_old_config(UpdateReq, undefined) ->
|
||||
merge_to_old_config(UpdateReq, #{});
|
||||
merge_to_old_config(UpdateReq, RawConf) ->
|
||||
maps:merge(RawConf, UpdateReq).
|
||||
|
||||
%%============================================================================
|
||||
save_configs(RootKeys, RawConf) ->
|
||||
{_MappedEnvs, Conf} = hocon_schema:map_translate(emqx_schema, to_richmap(RawConf), #{}),
|
||||
%% We may need also support hot config update for the apps that use application envs.
|
||||
%% If so uncomment the following line to update the configs to application env
|
||||
%save_config_to_app_env(_MappedEnvs),
|
||||
save_config_to_emqx(to_plainmap(Conf), RawConf),
|
||||
save_config_to_disk(RootKeys, RawConf).
|
||||
|
||||
% save_config_to_app_env(MappedEnvs) ->
|
||||
% lists:foreach(fun({AppName, Envs}) ->
|
||||
% [application:set_env(AppName, Par, Val) || {Par, Val} <- Envs]
|
||||
% end, MappedEnvs).
|
||||
|
||||
save_config_to_emqx(Conf, RawConf) ->
|
||||
emqx_config:put(emqx_config:unsafe_atom_key_map(Conf)),
|
||||
emqx_config:put_raw(RawConf).
|
||||
|
||||
save_config_to_disk(RootKeys, Conf) ->
|
||||
FileName = emqx_override_conf_name(),
|
||||
OldConf = read_old_config(FileName),
|
||||
%% We don't save the overall config to file, but only the sub configs
|
||||
%% under RootKeys
|
||||
write_new_config(FileName,
|
||||
maps:merge(OldConf, maps:with(RootKeys, Conf))).
|
||||
|
||||
write_new_config(FileName, Conf) ->
|
||||
case file:write_file(FileName, jsx:prettify(jsx:encode(Conf))) of
|
||||
ok -> ok;
|
||||
{error, Reason} ->
|
||||
logger:error("write to ~s failed, ~p", [FileName, Reason]),
|
||||
{error, Reason}
|
||||
call_post_config_update(Handlers, OldConf, NewConf, UpdateReq) ->
|
||||
HandlerName = maps:get(?MOD, Handlers, undefined),
|
||||
case erlang:function_exported(HandlerName, post_config_update, 3) of
|
||||
true -> HandlerName:post_config_update(UpdateReq, NewConf, OldConf);
|
||||
false -> ok
|
||||
end.
|
||||
|
||||
read_old_config(FileName) ->
|
||||
case file:read_file(FileName) of
|
||||
{ok, Text} ->
|
||||
try jsx:decode(Text, [{return_maps, true}]) of
|
||||
Conf when is_map(Conf) -> Conf;
|
||||
_ -> #{}
|
||||
catch _Err : _Reason ->
|
||||
#{}
|
||||
end;
|
||||
_ -> #{}
|
||||
end.
|
||||
%% The default callback of config handlers
|
||||
%% the behaviour is overwriting the old config if:
|
||||
%% 1. the old config is undefined
|
||||
%% 2. either the old or the new config is not of map type
|
||||
%% the behaviour is merging the new the config to the old config if they are maps.
|
||||
merge_to_old_config(UpdateReq, RawConf) when is_map(UpdateReq), is_map(RawConf) ->
|
||||
maps:merge(RawConf, UpdateReq);
|
||||
merge_to_old_config(UpdateReq, _RawConf) ->
|
||||
UpdateReq.
|
||||
|
||||
emqx_conf_name() ->
|
||||
filename:join([etc_dir(), "emqx.conf"]).
|
||||
update_override_config(RawConf) ->
|
||||
OldConf = emqx_config:read_override_conf(),
|
||||
maps:merge(OldConf, RawConf).
|
||||
|
||||
emqx_override_conf_name() ->
|
||||
filename:join([emqx:get_env(data_dir), "emqx_override.conf"]).
|
||||
bin_path(ConfKeyPath) -> [bin(Key) || Key <- ConfKeyPath].
|
||||
|
||||
etc_dir() ->
|
||||
emqx:get_env(etc_dir).
|
||||
|
||||
to_richmap(Map) ->
|
||||
{ok, RichMap} = hocon:binary(jsx:encode(Map), #{format => richmap}),
|
||||
RichMap.
|
||||
|
||||
to_plainmap(RichMap) ->
|
||||
hocon_schema:richmap_to_map(RichMap).
|
||||
|
||||
bin(A) when is_atom(A) -> list_to_binary(atom_to_list(A));
|
||||
bin(B) when is_binary(B) -> B;
|
||||
bin(S) when is_list(S) -> list_to_binary(S).
|
||||
bin(A) when is_atom(A) -> atom_to_binary(A, utf8);
|
||||
bin(B) when is_binary(B) -> B.
|
||||
|
|
|
@ -55,8 +55,8 @@ cancel_alarms(Socket, Transport, Channel) ->
|
|||
end, ?ALL_ALARM_REASONS).
|
||||
|
||||
is_alarm_enabled(Channel) ->
|
||||
emqx_zone:get_env(emqx_channel:info(zone, Channel),
|
||||
conn_congestion_alarm_enabled, false).
|
||||
Zone = emqx_channel:info(zone, Channel),
|
||||
emqx_config:get_zone_conf(Zone, [conn_congestion, enable_alarm]).
|
||||
|
||||
alarm_congestion(Socket, Transport, Channel, Reason) ->
|
||||
case has_alarm_sent(Reason) of
|
||||
|
@ -68,8 +68,8 @@ alarm_congestion(Socket, Transport, Channel, Reason) ->
|
|||
|
||||
cancel_alarm_congestion(Socket, Transport, Channel, Reason) ->
|
||||
Zone = emqx_channel:info(zone, Channel),
|
||||
WontClearIn = emqx_zone:get_env(Zone, conn_congestion_min_alarm_sustain_duration,
|
||||
?WONT_CLEAR_IN),
|
||||
WontClearIn = emqx_config:get_zone_conf(Zone, [conn_congestion,
|
||||
min_alarm_sustain_duration]),
|
||||
case has_alarm_sent(Reason) andalso long_time_since_last_alarm(Reason, WontClearIn) of
|
||||
true -> do_cancel_alarm_congestion(Socket, Transport, Channel, Reason);
|
||||
false -> ok
|
||||
|
|
|
@ -83,8 +83,6 @@
|
|||
sockname :: emqx_types:peername(),
|
||||
%% Sock State
|
||||
sockstate :: emqx_types:sockstate(),
|
||||
%% The {active, N} option
|
||||
active_n :: pos_integer(),
|
||||
%% Limiter
|
||||
limiter :: maybe(emqx_limiter:limiter()),
|
||||
%% Limit Timer
|
||||
|
@ -102,13 +100,17 @@
|
|||
%% Idle Timeout
|
||||
idle_timeout :: integer(),
|
||||
%% Idle Timer
|
||||
idle_timer :: maybe(reference())
|
||||
idle_timer :: maybe(reference()),
|
||||
%% Zone name
|
||||
zone :: atom(),
|
||||
%% Listener Name
|
||||
listener :: atom()
|
||||
}).
|
||||
|
||||
-type(state() :: #state{}).
|
||||
|
||||
-define(ACTIVE_N, 100).
|
||||
-define(INFO_KEYS, [socktype, peername, sockname, sockstate, active_n]).
|
||||
-define(INFO_KEYS, [socktype, peername, sockname, sockstate]).
|
||||
-define(CONN_STATS, [recv_pkt, recv_msg, send_pkt, send_msg]).
|
||||
-define(SOCK_STATS, [recv_oct, recv_cnt, send_oct, send_cnt, send_pend]).
|
||||
|
||||
|
@ -134,7 +136,7 @@
|
|||
, system_code_change/4
|
||||
]}).
|
||||
|
||||
-spec(start_link(esockd:transport(), esockd:socket(), proplists:proplist())
|
||||
-spec(start_link(esockd:transport(), esockd:socket(), emqx_channel:opts())
|
||||
-> {ok, pid()}).
|
||||
start_link(Transport, Socket, Options) ->
|
||||
Args = [self(), Transport, Socket, Options],
|
||||
|
@ -165,8 +167,6 @@ info(sockname, #state{sockname = Sockname}) ->
|
|||
Sockname;
|
||||
info(sockstate, #state{sockstate = SockSt}) ->
|
||||
SockSt;
|
||||
info(active_n, #state{active_n = ActiveN}) ->
|
||||
ActiveN;
|
||||
info(stats_timer, #state{stats_timer = StatsTimer}) ->
|
||||
StatsTimer;
|
||||
info(limit_timer, #state{limit_timer = LimitTimer}) ->
|
||||
|
@ -243,7 +243,7 @@ init(Parent, Transport, RawSocket, Options) ->
|
|||
exit_on_sock_error(Reason)
|
||||
end.
|
||||
|
||||
init_state(Transport, Socket, Options) ->
|
||||
init_state(Transport, Socket, #{zone := Zone, listener := Listener} = Opts) ->
|
||||
{ok, Peername} = Transport:ensure_ok_or_exit(peername, [Socket]),
|
||||
{ok, Sockname} = Transport:ensure_ok_or_exit(sockname, [Socket]),
|
||||
Peercert = Transport:ensure_ok_or_exit(peercert, [Socket]),
|
||||
|
@ -253,26 +253,29 @@ init_state(Transport, Socket, Options) ->
|
|||
peercert => Peercert,
|
||||
conn_mod => ?MODULE
|
||||
},
|
||||
Zone = proplists:get_value(zone, Options),
|
||||
ActiveN = proplists:get_value(active_n, Options, ?ACTIVE_N),
|
||||
PubLimit = emqx_zone:publish_limit(Zone),
|
||||
BytesIn = proplists:get_value(rate_limit, Options),
|
||||
RateLimit = emqx_zone:ratelimit(Zone),
|
||||
Limiter = emqx_limiter:init(Zone, PubLimit, BytesIn, RateLimit),
|
||||
FrameOpts = emqx_zone:mqtt_frame_options(Zone),
|
||||
Limiter = emqx_limiter:init(Zone, undefined, undefined, []),
|
||||
FrameOpts = #{
|
||||
strict_mode => emqx_config:get_zone_conf(Zone, [mqtt, strict_mode]),
|
||||
max_size => emqx_config:get_zone_conf(Zone, [mqtt, max_packet_size])
|
||||
},
|
||||
ParseState = emqx_frame:initial_parse_state(FrameOpts),
|
||||
Serialize = emqx_frame:serialize_opts(),
|
||||
Channel = emqx_channel:init(ConnInfo, Options),
|
||||
GcState = emqx_zone:init_gc_state(Zone),
|
||||
StatsTimer = emqx_zone:stats_timer(Zone),
|
||||
IdleTimeout = emqx_zone:idle_timeout(Zone),
|
||||
Channel = emqx_channel:init(ConnInfo, Opts),
|
||||
GcState = case emqx_config:get_zone_conf(Zone, [force_gc]) of
|
||||
#{enable := false} -> undefined;
|
||||
GcPolicy -> emqx_gc:init(GcPolicy)
|
||||
end,
|
||||
StatsTimer = case emqx_config:get_zone_conf(Zone, [stats, enable]) of
|
||||
true -> undefined;
|
||||
false -> disabled
|
||||
end,
|
||||
IdleTimeout = emqx_channel:get_mqtt_conf(Zone, idle_timeout),
|
||||
IdleTimer = start_timer(IdleTimeout, idle_timeout),
|
||||
#state{transport = Transport,
|
||||
socket = Socket,
|
||||
peername = Peername,
|
||||
sockname = Sockname,
|
||||
sockstate = idle,
|
||||
active_n = ActiveN,
|
||||
limiter = Limiter,
|
||||
parse_state = ParseState,
|
||||
serialize = Serialize,
|
||||
|
@ -280,7 +283,9 @@ init_state(Transport, Socket, Options) ->
|
|||
gc_state = GcState,
|
||||
stats_timer = StatsTimer,
|
||||
idle_timeout = IdleTimeout,
|
||||
idle_timer = IdleTimer
|
||||
idle_timer = IdleTimer,
|
||||
zone = Zone,
|
||||
listener = Listener
|
||||
}.
|
||||
|
||||
run_loop(Parent, State = #state{transport = Transport,
|
||||
|
@ -288,8 +293,9 @@ run_loop(Parent, State = #state{transport = Transport,
|
|||
peername = Peername,
|
||||
channel = Channel}) ->
|
||||
emqx_logger:set_metadata_peername(esockd:format(Peername)),
|
||||
emqx_misc:tune_heap_size(emqx_zone:oom_policy(
|
||||
emqx_channel:info(zone, Channel))),
|
||||
ShutdownPolicy = emqx_config:get_zone_conf(emqx_channel:info(zone, Channel),
|
||||
[force_shutdown]),
|
||||
emqx_misc:tune_heap_size(ShutdownPolicy),
|
||||
case activate_socket(State) of
|
||||
{ok, NState} -> hibernate(Parent, NState);
|
||||
{error, Reason} ->
|
||||
|
@ -416,6 +422,13 @@ handle_msg({Inet, _Sock, Data}, State) when Inet == tcp; Inet == ssl ->
|
|||
ok = emqx_metrics:inc('bytes.received', Oct),
|
||||
parse_incoming(Data, State);
|
||||
|
||||
handle_msg({quic, Data, _Sock, _, _, _}, State) ->
|
||||
?LOG(debug, "RECV ~0p", [Data]),
|
||||
Oct = iolist_size(Data),
|
||||
inc_counter(incoming_bytes, Oct),
|
||||
ok = emqx_metrics:inc('bytes.received', Oct),
|
||||
parse_incoming(Data, State);
|
||||
|
||||
handle_msg({incoming, Packet = ?CONNECT_PACKET(ConnPkt)},
|
||||
State = #state{idle_timer = IdleTimer}) ->
|
||||
ok = emqx_misc:cancel_timer(IdleTimer),
|
||||
|
@ -440,7 +453,7 @@ handle_msg({Closed, _Sock}, State)
|
|||
handle_info({sock_closed, Closed}, close_socket(State));
|
||||
|
||||
handle_msg({Passive, _Sock}, State)
|
||||
when Passive == tcp_passive; Passive == ssl_passive ->
|
||||
when Passive == tcp_passive; Passive == ssl_passive; Passive =:= quic_passive ->
|
||||
%% In Stats
|
||||
Pubs = emqx_pd:reset_counter(incoming_pubs),
|
||||
Bytes = emqx_pd:reset_counter(incoming_bytes),
|
||||
|
@ -451,14 +464,15 @@ handle_msg({Passive, _Sock}, State)
|
|||
NState1 = check_oom(run_gc(InStats, NState)),
|
||||
handle_info(activate_socket, NState1);
|
||||
|
||||
handle_msg(Deliver = {deliver, _Topic, _Msg},
|
||||
#state{active_n = ActiveN} = State) ->
|
||||
handle_msg(Deliver = {deliver, _Topic, _Msg}, #state{zone = Zone,
|
||||
listener = Listener} = State) ->
|
||||
ActiveN = get_active_n(Zone, Listener),
|
||||
Delivers = [Deliver|emqx_misc:drain_deliver(ActiveN)],
|
||||
with_channel(handle_deliver, [Delivers], State);
|
||||
|
||||
%% Something sent
|
||||
handle_msg({inet_reply, _Sock, ok}, State = #state{active_n = ActiveN}) ->
|
||||
case emqx_pd:get_counter(outgoing_pubs) > ActiveN of
|
||||
handle_msg({inet_reply, _Sock, ok}, State = #state{zone = Zone, listener = Listener}) ->
|
||||
case emqx_pd:get_counter(outgoing_pubs) > get_active_n(Zone, Listener) of
|
||||
true ->
|
||||
Pubs = emqx_pd:reset_counter(outgoing_pubs),
|
||||
Bytes = emqx_pd:reset_counter(outgoing_bytes),
|
||||
|
@ -731,6 +745,15 @@ handle_info({sock_error, Reason}, State) ->
|
|||
end,
|
||||
handle_info({sock_closed, Reason}, close_socket(State));
|
||||
|
||||
handle_info({quic, peer_send_shutdown, _Stream}, State) ->
|
||||
handle_info({sock_closed, force}, close_socket(State));
|
||||
|
||||
handle_info({quic, closed, _Channel, ReasonFlag}, State) ->
|
||||
handle_info({sock_closed, ReasonFlag}, State);
|
||||
|
||||
handle_info({quic, closed, _Stream}, State) ->
|
||||
handle_info({sock_closed, force}, State);
|
||||
|
||||
handle_info(Info, State) ->
|
||||
with_channel(handle_info, [Info], State).
|
||||
|
||||
|
@ -778,15 +801,14 @@ run_gc(Stats, State = #state{gc_state = GcSt}) ->
|
|||
end.
|
||||
|
||||
check_oom(State = #state{channel = Channel}) ->
|
||||
Zone = emqx_channel:info(zone, Channel),
|
||||
OomPolicy = emqx_zone:oom_policy(Zone),
|
||||
?tp(debug, check_oom, #{policy => OomPolicy}),
|
||||
case ?ENABLED(OomPolicy) andalso emqx_misc:check_oom(OomPolicy) of
|
||||
ShutdownPolicy = emqx_config:get_zone_conf(
|
||||
emqx_channel:info(zone, Channel), [force_shutdown]),
|
||||
?tp(debug, check_oom, #{policy => ShutdownPolicy}),
|
||||
case emqx_misc:check_oom(ShutdownPolicy) of
|
||||
{shutdown, Reason} ->
|
||||
%% triggers terminate/2 callback immediately
|
||||
erlang:exit({shutdown, Reason});
|
||||
_Other ->
|
||||
ok
|
||||
_ -> ok
|
||||
end,
|
||||
State.
|
||||
|
||||
|
@ -798,10 +820,10 @@ activate_socket(State = #state{sockstate = closed}) ->
|
|||
{ok, State};
|
||||
activate_socket(State = #state{sockstate = blocked}) ->
|
||||
{ok, State};
|
||||
activate_socket(State = #state{transport = Transport,
|
||||
socket = Socket,
|
||||
active_n = N}) ->
|
||||
case Transport:setopts(Socket, [{active, N}]) of
|
||||
activate_socket(State = #state{transport = Transport, socket = Socket,
|
||||
zone = Zone, listener = Listener}) ->
|
||||
ActiveN = get_active_n(Zone, Listener),
|
||||
case Transport:setopts(Socket, [{active, ActiveN}]) of
|
||||
ok -> {ok, State#state{sockstate = running}};
|
||||
Error -> Error
|
||||
end.
|
||||
|
@ -882,3 +904,9 @@ get_state(Pid) ->
|
|||
State = sys:get_state(Pid),
|
||||
maps:from_list(lists:zip(record_info(fields, state),
|
||||
tl(tuple_to_list(State)))).
|
||||
|
||||
get_active_n(Zone, Listener) ->
|
||||
case emqx_config:get([zones, Zone, listeners, Listener, type]) of
|
||||
quic -> 100;
|
||||
_ -> emqx_config:get_listener_conf(Zone, Listener, [tcp, active_n])
|
||||
end.
|
||||
|
|
|
@ -45,16 +45,16 @@
|
|||
-define(FLAPPING_DURATION, 60000).
|
||||
-define(FLAPPING_BANNED_INTERVAL, 300000).
|
||||
-define(DEFAULT_DETECT_POLICY,
|
||||
#{threshold => ?FLAPPING_THRESHOLD,
|
||||
duration => ?FLAPPING_DURATION,
|
||||
banned_interval => ?FLAPPING_BANNED_INTERVAL
|
||||
#{max_count => ?FLAPPING_THRESHOLD,
|
||||
window_time => ?FLAPPING_DURATION,
|
||||
ban_time => ?FLAPPING_BANNED_INTERVAL
|
||||
}).
|
||||
|
||||
-record(flapping, {
|
||||
clientid :: emqx_types:clientid(),
|
||||
peerhost :: emqx_types:peerhost(),
|
||||
started_at :: pos_integer(),
|
||||
detect_cnt :: pos_integer()
|
||||
detect_cnt :: integer()
|
||||
}).
|
||||
|
||||
-opaque(flapping() :: #flapping{}).
|
||||
|
@ -69,33 +69,28 @@ stop() -> gen_server:stop(?MODULE).
|
|||
|
||||
%% @doc Detect flapping when a MQTT client disconnected.
|
||||
-spec(detect(emqx_types:clientinfo()) -> boolean()).
|
||||
detect(Client) -> detect(Client, get_policy()).
|
||||
|
||||
detect(#{clientid := ClientId, peerhost := PeerHost}, Policy = #{threshold := Threshold}) ->
|
||||
try ets:update_counter(?FLAPPING_TAB, ClientId, {#flapping.detect_cnt, 1}) of
|
||||
detect(#{clientid := ClientId, peerhost := PeerHost, zone := Zone}) ->
|
||||
Policy = #{max_count := Threshold} = get_policy(Zone),
|
||||
%% The initial flapping record sets the detect_cnt to 0.
|
||||
InitVal = #flapping{
|
||||
clientid = ClientId,
|
||||
peerhost = PeerHost,
|
||||
started_at = erlang:system_time(millisecond),
|
||||
detect_cnt = 0
|
||||
},
|
||||
case ets:update_counter(?FLAPPING_TAB, ClientId, {#flapping.detect_cnt, 1}, InitVal) of
|
||||
Cnt when Cnt < Threshold -> false;
|
||||
_Cnt -> case ets:take(?FLAPPING_TAB, ClientId) of
|
||||
[Flapping] ->
|
||||
ok = gen_server:cast(?MODULE, {detected, Flapping, Policy}),
|
||||
true;
|
||||
[] -> false
|
||||
end
|
||||
catch
|
||||
error:badarg ->
|
||||
%% Create a flapping record.
|
||||
Flapping = #flapping{clientid = ClientId,
|
||||
peerhost = PeerHost,
|
||||
started_at = erlang:system_time(millisecond),
|
||||
detect_cnt = 1
|
||||
},
|
||||
true = ets:insert(?FLAPPING_TAB, Flapping),
|
||||
false
|
||||
_Cnt ->
|
||||
case ets:take(?FLAPPING_TAB, ClientId) of
|
||||
[Flapping] ->
|
||||
ok = gen_server:cast(?MODULE, {detected, Flapping, Policy}),
|
||||
true;
|
||||
[] -> false
|
||||
end
|
||||
end.
|
||||
|
||||
-compile({inline, [get_policy/0, now_diff/1]}).
|
||||
|
||||
get_policy() ->
|
||||
emqx:get_env(flapping_detect_policy, ?DEFAULT_DETECT_POLICY).
|
||||
get_policy(Zone) ->
|
||||
emqx_config:get_zone_conf(Zone, [flapping_detect]).
|
||||
|
||||
now_diff(TS) -> erlang:system_time(millisecond) - TS.
|
||||
|
||||
|
@ -105,11 +100,12 @@ now_diff(TS) -> erlang:system_time(millisecond) - TS.
|
|||
|
||||
init([]) ->
|
||||
ok = emqx_tables:new(?FLAPPING_TAB, [public, set,
|
||||
{keypos, 2},
|
||||
{keypos, #flapping.clientid},
|
||||
{read_concurrency, true},
|
||||
{write_concurrency, true}
|
||||
]),
|
||||
{ok, ensure_timer(#{}), hibernate}.
|
||||
start_timers(),
|
||||
{ok, #{}, hibernate}.
|
||||
|
||||
handle_call(Req, _From, State) ->
|
||||
?LOG(error, "Unexpected call: ~p", [Req]),
|
||||
|
@ -119,17 +115,17 @@ handle_cast({detected, #flapping{clientid = ClientId,
|
|||
peerhost = PeerHost,
|
||||
started_at = StartedAt,
|
||||
detect_cnt = DetectCnt},
|
||||
#{duration := Duration, banned_interval := Interval}}, State) ->
|
||||
case now_diff(StartedAt) < Duration of
|
||||
#{window_time := WindTime, ban_time := Interval}}, State) ->
|
||||
case now_diff(StartedAt) < WindTime of
|
||||
true -> %% Flapping happened:(
|
||||
?LOG(error, "Flapping detected: ~s(~s) disconnected ~w times in ~wms",
|
||||
[ClientId, inet:ntoa(PeerHost), DetectCnt, Duration]),
|
||||
[ClientId, inet:ntoa(PeerHost), DetectCnt, WindTime]),
|
||||
Now = erlang:system_time(second),
|
||||
Banned = #banned{who = {clientid, ClientId},
|
||||
by = <<"flapping detector">>,
|
||||
reason = <<"flapping is detected">>,
|
||||
at = Now,
|
||||
until = Now + Interval},
|
||||
until = Now + (Interval div 1000)},
|
||||
emqx_banned:create(Banned);
|
||||
false ->
|
||||
?LOG(warning, "~s(~s) disconnected ~w times in ~wms",
|
||||
|
@ -141,11 +137,13 @@ handle_cast(Msg, State) ->
|
|||
?LOG(error, "Unexpected cast: ~p", [Msg]),
|
||||
{noreply, State}.
|
||||
|
||||
handle_info({timeout, TRef, expired_detecting}, State = #{expired_timer := TRef}) ->
|
||||
Timestamp = erlang:system_time(millisecond) - maps:get(duration, get_policy()),
|
||||
handle_info({timeout, _TRef, {garbage_collect, Zone}}, State) ->
|
||||
Timestamp = erlang:system_time(millisecond)
|
||||
- maps:get(window_time, get_policy(Zone)),
|
||||
MatchSpec = [{{'_', '_', '_', '$1', '_'},[{'<', '$1', Timestamp}], [true]}],
|
||||
ets:select_delete(?FLAPPING_TAB, MatchSpec),
|
||||
{noreply, ensure_timer(State), hibernate};
|
||||
start_timer(Zone),
|
||||
{noreply, State, hibernate};
|
||||
|
||||
handle_info(Info, State) ->
|
||||
?LOG(error, "Unexpected info: ~p", [Info]),
|
||||
|
@ -157,7 +155,11 @@ terminate(_Reason, _State) ->
|
|||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
ensure_timer(State) ->
|
||||
Timeout = maps:get(duration, get_policy()),
|
||||
TRef = emqx_misc:start_timer(Timeout, expired_detecting),
|
||||
State#{expired_timer => TRef}.
|
||||
start_timer(Zone) ->
|
||||
WindTime = maps:get(window_time, get_policy(Zone)),
|
||||
emqx_misc:start_timer(WindTime, {garbage_collect, Zone}).
|
||||
|
||||
start_timers() ->
|
||||
lists:foreach(fun({Zone, _ZoneConf}) ->
|
||||
start_timer(Zone)
|
||||
end, maps:to_list(emqx_config:get([zones], #{}))).
|
|
@ -81,11 +81,7 @@ initial_parse_state() ->
|
|||
|
||||
-spec(initial_parse_state(options()) -> {none, options()}).
|
||||
initial_parse_state(Options) when is_map(Options) ->
|
||||
?none(merge_opts(Options)).
|
||||
|
||||
%% @pivate
|
||||
merge_opts(Options) ->
|
||||
maps:merge(?DEFAULT_OPTIONS, Options).
|
||||
?none(maps:merge(?DEFAULT_OPTIONS, Options)).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Parse MQTT Frame
|
||||
|
@ -643,7 +639,7 @@ serialize_properties(Props) when is_map(Props) ->
|
|||
Bin = << <<(serialize_property(Prop, Val))/binary>> || {Prop, Val} <- maps:to_list(Props) >>,
|
||||
[serialize_variable_byte_integer(byte_size(Bin)), Bin].
|
||||
|
||||
serialize_property(_, undefined) ->
|
||||
serialize_property(_, Disabled) when Disabled =:= disabled; Disabled =:= undefined ->
|
||||
<<>>;
|
||||
serialize_property('Payload-Format-Indicator', Val) ->
|
||||
<<16#01, Val>>;
|
||||
|
|
|
@ -85,9 +85,9 @@ code_change(_OldVsn, State, _Extra) ->
|
|||
%%--------------------------------------------------------------------
|
||||
|
||||
ensure_timer(State) ->
|
||||
case emqx:get_env(global_gc_interval) of
|
||||
case emqx_config:get([node, global_gc_interval]) of
|
||||
undefined -> State;
|
||||
Interval -> TRef = emqx_misc:start_timer(timer:seconds(Interval), run),
|
||||
Interval -> TRef = emqx_misc:start_timer(Interval, run),
|
||||
State#{timer := TRef}
|
||||
end.
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
|
||||
-include("logger.hrl").
|
||||
-include("types.hrl").
|
||||
-include_lib("stdlib/include/ms_transform.hrl").
|
||||
|
||||
-logger_header("[Hooks]").
|
||||
|
||||
|
@ -32,6 +33,8 @@
|
|||
, add/3
|
||||
, add/4
|
||||
, put/2
|
||||
, put/3
|
||||
, put/4
|
||||
, del/2
|
||||
, run/2
|
||||
, run_fold/3
|
||||
|
@ -130,11 +133,25 @@ add(HookPoint, Action, Filter, Priority) when is_integer(Priority) ->
|
|||
|
||||
%% @doc Like add/2, it register a callback, discard 'already_exists' error.
|
||||
-spec(put(hookpoint(), action() | #callback{}) -> ok).
|
||||
put(HookPoint, Callback) ->
|
||||
put(HookPoint, Callback) when is_record(Callback, callback) ->
|
||||
case add(HookPoint, Callback) of
|
||||
ok -> ok;
|
||||
{error, already_exists} -> ok
|
||||
end.
|
||||
{error, already_exists} ->
|
||||
gen_server:call(?SERVER, {put, HookPoint, Callback}, infinity)
|
||||
end;
|
||||
put(HookPoint, Action) when is_function(Action); is_tuple(Action) ->
|
||||
?MODULE:put(HookPoint, #callback{action = Action, priority = 0}).
|
||||
|
||||
-spec(put(hookpoint(), action(), filter() | integer() | list()) -> ok).
|
||||
put(HookPoint, Action, {_M, _F, _A} = Filter) ->
|
||||
?MODULE:put(HookPoint, #callback{action = Action, filter = Filter, priority = 0});
|
||||
put(HookPoint, Action, Priority) when is_integer(Priority) ->
|
||||
?MODULE:put(HookPoint, #callback{action = Action, priority = Priority}).
|
||||
|
||||
-spec(put(hookpoint(), action(), filter(), integer()) -> ok).
|
||||
put(HookPoint, Action, Filter, Priority) when is_integer(Priority) ->
|
||||
?MODULE:put(HookPoint, #callback{action = Action, filter = Filter, priority = Priority}).
|
||||
|
||||
|
||||
%% @doc Unregister a callback.
|
||||
-spec(del(hookpoint(), action() | {module(), atom()}) -> ok).
|
||||
|
@ -215,15 +232,20 @@ init([]) ->
|
|||
ok = emqx_tables:new(?TAB, [{keypos, #hook.name}, {read_concurrency, true}]),
|
||||
{ok, #{}}.
|
||||
|
||||
handle_call({add, HookPoint, Callback = #callback{action = Action}}, _From, State) ->
|
||||
Reply = case lists:keymember(Action, #callback.action, Callbacks = lookup(HookPoint)) of
|
||||
true ->
|
||||
{error, already_exists};
|
||||
false ->
|
||||
insert_hook(HookPoint, add_callback(Callback, Callbacks))
|
||||
handle_call({add, HookPoint, Callback = #callback{action = {M, F, _}}}, _From, State) ->
|
||||
Reply = case lists:any(fun (#callback{action = {M0, F0, _}}) ->
|
||||
M0 =:= M andalso F0 =:= F
|
||||
end, Callbacks = lookup(HookPoint)) of
|
||||
true -> {error, already_exists};
|
||||
false -> insert_hook(HookPoint, add_callback(Callback, Callbacks))
|
||||
end,
|
||||
{reply, Reply, State};
|
||||
|
||||
handle_call({put, HookPoint, Callback = #callback{action = {M, F, _}}}, _From, State) ->
|
||||
Callbacks = del_callback({M, F}, lookup(HookPoint)),
|
||||
Reply = update_hook(HookPoint, add_callback(Callback, Callbacks)),
|
||||
{reply, Reply, State};
|
||||
|
||||
handle_call(Req, _From, State) ->
|
||||
?LOG(error, "Unexpected call: ~p", [Req]),
|
||||
{reply, ignored, State}.
|
||||
|
@ -257,6 +279,10 @@ code_change(_OldVsn, State, _Extra) ->
|
|||
|
||||
insert_hook(HookPoint, Callbacks) ->
|
||||
ets:insert(?TAB, #hook{name = HookPoint, callbacks = Callbacks}), ok.
|
||||
update_hook(HookPoint, Callbacks) ->
|
||||
Ms = ets:fun2ms(fun ({hook, K, V}) when K =:= HookPoint -> {hook, K, Callbacks} end),
|
||||
ets:select_replace(emqx_hooks, Ms),
|
||||
ok.
|
||||
|
||||
add_callback(C, Callbacks) ->
|
||||
add_callback(C, Callbacks, []).
|
||||
|
|
|
@ -27,14 +27,14 @@ start_link() ->
|
|||
|
||||
init([]) ->
|
||||
{ok, {{one_for_one, 10, 100},
|
||||
[child_spec(emqx_global_gc, worker),
|
||||
child_spec(emqx_pool_sup, supervisor),
|
||||
child_spec(emqx_hooks, worker),
|
||||
child_spec(emqx_stats, worker),
|
||||
child_spec(emqx_metrics, worker),
|
||||
child_spec(emqx_ctl, worker),
|
||||
child_spec(emqx_zone, worker),
|
||||
child_spec(emqx_config_handler, worker)
|
||||
%% always start emqx_config_handler first to load the emqx.conf to emqx_config
|
||||
[ child_spec(emqx_config_handler, worker)
|
||||
, child_spec(emqx_global_gc, worker)
|
||||
, child_spec(emqx_pool_sup, supervisor)
|
||||
, child_spec(emqx_hooks, worker)
|
||||
, child_spec(emqx_stats, worker)
|
||||
, child_spec(emqx_metrics, worker)
|
||||
, child_spec(emqx_ctl, worker)
|
||||
]}}.
|
||||
|
||||
child_spec(M, Type) ->
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
|
||||
-record(limiter, {
|
||||
%% Zone
|
||||
zone :: emqx_zone:zone(),
|
||||
zone :: atom(),
|
||||
%% Checkers
|
||||
checkers :: [checker()]
|
||||
}).
|
||||
|
@ -35,7 +35,7 @@
|
|||
-type(checker() :: #{ name := name()
|
||||
, capacity := non_neg_integer()
|
||||
, interval := non_neg_integer()
|
||||
, consumer := esockd_rate_limit:bucket() | emqx_zone:zone()
|
||||
, consumer := esockd_rate_limit:bucket() | atom()
|
||||
}).
|
||||
|
||||
-type(name() :: conn_bytes_in
|
||||
|
@ -59,7 +59,7 @@
|
|||
%% APIs
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
-spec(init(emqx_zone:zone(),
|
||||
-spec(init(atom(),
|
||||
maybe(esockd_rate_limit:config()),
|
||||
maybe(esockd_rate_limit:config()), policy())
|
||||
-> maybe(limiter())).
|
||||
|
@ -69,7 +69,7 @@ init(Zone, PubLimit, BytesIn, Specs) ->
|
|||
Filtered = maps:filter(fun(_, V) -> V /= undefined end, Merged),
|
||||
init(Zone, maps:to_list(Filtered)).
|
||||
|
||||
-spec(init(emqx_zone:zone(), policy()) -> maybe(limiter())).
|
||||
-spec(init(atom(), policy()) -> maybe(limiter())).
|
||||
init(_Zone, []) ->
|
||||
undefined;
|
||||
init(Zone, Specs) ->
|
||||
|
|
|
@ -20,97 +20,96 @@
|
|||
-include("emqx_mqtt.hrl").
|
||||
|
||||
%% APIs
|
||||
-export([ start/0
|
||||
, ensure_all_started/0
|
||||
-export([ list/0
|
||||
, start/0
|
||||
, restart/0
|
||||
, stop/0
|
||||
, is_running/1
|
||||
]).
|
||||
|
||||
-export([ start_listener/1
|
||||
, start_listener/3
|
||||
, stop_listener/1
|
||||
, stop_listener/3
|
||||
, restart_listener/1
|
||||
, restart_listener/3
|
||||
]).
|
||||
|
||||
-export([ find_id_by_listen_on/1
|
||||
, find_by_listen_on/1
|
||||
, find_by_id/1
|
||||
, identifier/1
|
||||
, format_listen_on/1
|
||||
]).
|
||||
-spec(list() -> [{ListenerId :: atom(), ListenerConf :: map()}]).
|
||||
list() ->
|
||||
Zones = maps:to_list(emqx_config:get([zones], #{})),
|
||||
lists:append([list(ZoneName, ZoneConf) || {ZoneName, ZoneConf} <- Zones]).
|
||||
|
||||
-type(listener() :: #{ name := binary()
|
||||
, proto := esockd:proto()
|
||||
, listen_on := esockd:listen_on()
|
||||
, opts := [esockd:option()]
|
||||
}).
|
||||
list(ZoneName, ZoneConf) ->
|
||||
Listeners = maps:to_list(maps:get(listeners, ZoneConf, #{})),
|
||||
[
|
||||
begin
|
||||
ListenerId = listener_id(ZoneName, LName),
|
||||
Running = is_running(ListenerId),
|
||||
Conf = merge_zone_and_listener_confs(ZoneConf, LConf),
|
||||
{ListenerId, maps:put(running, Running, Conf)}
|
||||
end
|
||||
|| {LName, LConf} <- Listeners].
|
||||
|
||||
%% @doc Find listener identifier by listen-on.
|
||||
%% Return empty string (binary) if listener is not found in config.
|
||||
-spec(find_id_by_listen_on(esockd:listen_on()) -> binary() | false).
|
||||
find_id_by_listen_on(ListenOn) ->
|
||||
case find_by_listen_on(ListenOn) of
|
||||
false -> false;
|
||||
L -> identifier(L)
|
||||
-spec is_running(ListenerId :: atom()) -> boolean() | {error, no_found}.
|
||||
is_running(ListenerId) ->
|
||||
Zones = maps:to_list(emqx_config:get([zones], #{})),
|
||||
Listeners = lists:append(
|
||||
[
|
||||
[{listener_id(ZoneName, LName),merge_zone_and_listener_confs(ZoneConf, LConf)}
|
||||
|| {LName, LConf} <- maps:to_list(maps:get(listeners, ZoneConf, #{}))]
|
||||
|| {ZoneName, ZoneConf} <- Zones]),
|
||||
case proplists:get_value(ListenerId, Listeners, undefined) of
|
||||
undefined ->
|
||||
{error, no_found};
|
||||
Conf ->
|
||||
is_running(ListenerId, Conf)
|
||||
end.
|
||||
|
||||
%% @doc Find listener by listen-on.
|
||||
%% Return 'false' if not found.
|
||||
-spec(find_by_listen_on(esockd:listen_on()) -> listener() | false).
|
||||
find_by_listen_on(ListenOn) ->
|
||||
find_by_listen_on(ListenOn, emqx:get_env(listeners, [])).
|
||||
is_running(ListenerId, #{type := tcp, bind := ListenOn})->
|
||||
try esockd:listener({ListenerId, ListenOn}) of
|
||||
Pid when is_pid(Pid)->
|
||||
true
|
||||
catch _:_ ->
|
||||
false
|
||||
end;
|
||||
|
||||
%% @doc Find listener by identifier.
|
||||
%% Return 'false' if not found.
|
||||
-spec(find_by_id(string() | binary()) -> listener() | false).
|
||||
find_by_id(Id) ->
|
||||
find_by_id(iolist_to_binary(Id), emqx:get_env(listeners, [])).
|
||||
is_running(ListenerId, #{type := ws})->
|
||||
try
|
||||
Info = ranch:info(ListenerId),
|
||||
proplists:get_value(status, Info) =:= running
|
||||
catch _:_ ->
|
||||
false
|
||||
end;
|
||||
|
||||
%% @doc Return the ID of the given listener.
|
||||
-spec identifier(listener()) -> binary().
|
||||
identifier(#{proto := Proto, name := Name}) ->
|
||||
identifier(Proto, Name).
|
||||
is_running(_ListenerId, #{type := quic})->
|
||||
%% TODO: quic support
|
||||
{error, no_found}.
|
||||
|
||||
%% @doc Start all listeners.
|
||||
-spec(start() -> ok).
|
||||
start() ->
|
||||
lists:foreach(fun start_listener/1, emqx:get_env(listeners, [])).
|
||||
foreach_listeners(fun start_listener/3).
|
||||
|
||||
%% @doc Ensure all configured listeners are started.
|
||||
%% Raise exception if any of them failed to start.
|
||||
-spec(ensure_all_started() -> ok).
|
||||
ensure_all_started() ->
|
||||
ensure_all_started(emqx:get_env(listeners, []), []).
|
||||
-spec start_listener(atom()) -> ok | {error, term()}.
|
||||
start_listener(ListenerId) ->
|
||||
apply_on_listener(ListenerId, fun start_listener/3).
|
||||
|
||||
ensure_all_started([], []) -> ok;
|
||||
ensure_all_started([], Failed) -> error(Failed);
|
||||
ensure_all_started([L | Rest], Results) ->
|
||||
#{proto := Proto, listen_on := ListenOn, opts := Options} = L,
|
||||
NewResults =
|
||||
case start_listener(Proto, ListenOn, Options) of
|
||||
{ok, _Pid} ->
|
||||
Results;
|
||||
{error, {already_started, _Pid}} ->
|
||||
Results;
|
||||
{error, Reason} ->
|
||||
[{identifier(L), Reason} | Results]
|
||||
end,
|
||||
ensure_all_started(Rest, NewResults).
|
||||
|
||||
%% @doc Format address:port for logging.
|
||||
-spec(format_listen_on(esockd:listen_on()) -> [char()]).
|
||||
format_listen_on(ListenOn) -> format(ListenOn).
|
||||
|
||||
-spec(start_listener(listener()) -> ok).
|
||||
start_listener(#{proto := Proto, name := Name, listen_on := ListenOn, opts := Options}) ->
|
||||
ID = identifier(Proto, Name),
|
||||
case start_listener(Proto, ListenOn, Options) of
|
||||
-spec start_listener(atom(), atom(), map()) -> ok | {error, term()}.
|
||||
start_listener(ZoneName, ListenerName, #{type := Type, bind := Bind} = Conf) ->
|
||||
case do_start_listener(ZoneName, ListenerName, Conf) of
|
||||
{ok, {skipped, Reason}} when Reason =:= listener_disabled;
|
||||
Reason =:= quic_app_missing ->
|
||||
console_print("- Skip - starting ~s listener ~s on ~s ~n due to ~p",
|
||||
[Type, listener_id(ZoneName, ListenerName), format(Bind), Reason]);
|
||||
{ok, _} ->
|
||||
console_print("Start ~s listener on ~s successfully.~n", [ID, format(ListenOn)]);
|
||||
console_print("Start ~s listener ~s on ~s successfully.~n",
|
||||
[Type, listener_id(ZoneName, ListenerName), format(Bind)]);
|
||||
{error, {already_started, Pid}} ->
|
||||
{error, {already_started, Pid}};
|
||||
{error, Reason} ->
|
||||
io:format(standard_error, "Failed to start mqtt listener ~s on ~s: ~0p~n",
|
||||
[ID, format(ListenOn), Reason]),
|
||||
io:format(standard_error, "Failed to start ~s listener ~s on ~s: ~0p~n",
|
||||
[Type, listener_id(ZoneName, ListenerName), format(Bind), Reason]),
|
||||
error(Reason)
|
||||
end.
|
||||
|
||||
|
@ -122,124 +121,136 @@ console_print(_Fmt, _Args) -> ok.
|
|||
-endif.
|
||||
|
||||
%% Start MQTT/TCP listener
|
||||
-spec(start_listener(esockd:proto(), esockd:listen_on(), [esockd:option()])
|
||||
-> {ok, pid()} | {error, term()}).
|
||||
start_listener(tcp, ListenOn, Options) ->
|
||||
start_mqtt_listener('mqtt:tcp', ListenOn, Options);
|
||||
|
||||
%% Start MQTT/TLS listener
|
||||
start_listener(Proto, ListenOn, Options) when Proto == ssl; Proto == tls ->
|
||||
start_mqtt_listener('mqtt:ssl', ListenOn, Options);
|
||||
-spec(do_start_listener(atom(), atom(), map())
|
||||
-> {ok, pid() | {skipped, atom()}} | {error, term()}).
|
||||
do_start_listener(_ZoneName, _ListenerName, #{enabled := false}) ->
|
||||
{ok, {skipped, listener_disabled}};
|
||||
do_start_listener(ZoneName, ListenerName, #{type := tcp, bind := ListenOn} = Opts) ->
|
||||
esockd:open(listener_id(ZoneName, ListenerName), ListenOn, merge_default(esockd_opts(Opts)),
|
||||
{emqx_connection, start_link,
|
||||
[#{zone => ZoneName, listener => ListenerName}]});
|
||||
|
||||
%% Start MQTT/WS listener
|
||||
start_listener(Proto, ListenOn, Options) when Proto == http; Proto == ws ->
|
||||
start_http_listener(fun cowboy:start_clear/3, 'mqtt:ws', ListenOn,
|
||||
ranch_opts(Options), ws_opts(Options));
|
||||
do_start_listener(ZoneName, ListenerName, #{type := ws, bind := ListenOn} = Opts) ->
|
||||
Id = listener_id(ZoneName, ListenerName),
|
||||
RanchOpts = ranch_opts(ListenOn, Opts),
|
||||
WsOpts = ws_opts(ZoneName, ListenerName, Opts),
|
||||
case is_ssl(Opts) of
|
||||
false ->
|
||||
cowboy:start_clear(Id, RanchOpts, WsOpts);
|
||||
true ->
|
||||
cowboy:start_tls(Id, RanchOpts, WsOpts)
|
||||
end;
|
||||
|
||||
%% Start MQTT/WSS listener
|
||||
start_listener(Proto, ListenOn, Options) when Proto == https; Proto == wss ->
|
||||
start_http_listener(fun cowboy:start_tls/3, 'mqtt:wss', ListenOn,
|
||||
ranch_opts(Options), ws_opts(Options)).
|
||||
|
||||
replace(Opts, Key, Value) -> [{Key, Value} | proplists:delete(Key, Opts)].
|
||||
|
||||
drop_tls13_for_old_otp(Options) ->
|
||||
case proplists:get_value(ssl_options, Options) of
|
||||
undefined -> Options;
|
||||
SslOpts ->
|
||||
SslOpts1 = emqx_tls_lib:drop_tls13_for_old_otp(SslOpts),
|
||||
replace(Options, ssl_options, SslOpts1)
|
||||
%% Start MQTT/QUIC listener
|
||||
do_start_listener(ZoneName, ListenerName, #{type := quic, bind := ListenOn} = Opts) ->
|
||||
case [ A || {quicer, _, _} = A<-application:which_applications() ] of
|
||||
[_] ->
|
||||
%% @fixme unsure why we need reopen lib and reopen config.
|
||||
quicer_nif:open_lib(),
|
||||
quicer_nif:reg_open(),
|
||||
DefAcceptors = erlang:system_info(schedulers_online) * 8,
|
||||
ListenOpts = [ {cert, maps:get(certfile, Opts)}
|
||||
, {key, maps:get(keyfile, Opts)}
|
||||
, {alpn, ["mqtt"]}
|
||||
, {conn_acceptors, maps:get(acceptors, Opts, DefAcceptors)}
|
||||
, {idle_timeout_ms, emqx_config:get_zone_conf(ZoneName, [mqtt, idle_timeout])}
|
||||
],
|
||||
ConnectionOpts = #{conn_callback => emqx_quic_connection
|
||||
, peer_unidi_stream_count => 1
|
||||
, peer_bidi_stream_count => 10
|
||||
, zone => ZoneName
|
||||
, listener => ListenerName
|
||||
},
|
||||
StreamOpts = [],
|
||||
quicer:start_listener(listener_id(ZoneName, ListenerName),
|
||||
port(ListenOn), {ListenOpts, ConnectionOpts, StreamOpts});
|
||||
[] ->
|
||||
{ok, {skipped, quic_app_missing}}
|
||||
end.
|
||||
|
||||
start_mqtt_listener(Name, ListenOn, Options0) ->
|
||||
Options = drop_tls13_for_old_otp(Options0),
|
||||
SockOpts = esockd:parse_opt(Options),
|
||||
esockd:open(Name, ListenOn, merge_default(SockOpts),
|
||||
{emqx_connection, start_link, [Options -- SockOpts]}).
|
||||
esockd_opts(Opts0) ->
|
||||
Opts1 = maps:with([acceptors, max_connections, proxy_protocol, proxy_protocol_timeout], Opts0),
|
||||
Opts2 = case emqx_map_lib:deep_get([rate_limit, max_conn_rate], Opts0) of
|
||||
infinity -> Opts1;
|
||||
Rate -> Opts1#{max_conn_rate => Rate}
|
||||
end,
|
||||
Opts3 = Opts2#{access_rules => esockd_access_rules(maps:get(access_rules, Opts0, []))},
|
||||
maps:to_list(case is_ssl(Opts0) of
|
||||
false ->
|
||||
Opts3#{tcp_options => tcp_opts(Opts0)};
|
||||
true ->
|
||||
Opts3#{ssl_options => ssl_opts(Opts0), tcp_options => tcp_opts(Opts0)}
|
||||
end).
|
||||
|
||||
start_http_listener(Start, Name, ListenOn, RanchOpts, ProtoOpts) ->
|
||||
Start(ws_name(Name, ListenOn), with_port(ListenOn, RanchOpts), ProtoOpts).
|
||||
|
||||
mqtt_path(Options) ->
|
||||
proplists:get_value(mqtt_path, Options, "/mqtt").
|
||||
|
||||
ws_opts(Options) ->
|
||||
WsPaths = [{mqtt_path(Options), emqx_ws_connection, Options}],
|
||||
ws_opts(ZoneName, ListenerName, Opts) ->
|
||||
WsPaths = [{maps:get(mqtt_path, Opts, "/mqtt"), emqx_ws_connection,
|
||||
#{zone => ZoneName, listener => ListenerName}}],
|
||||
Dispatch = cowboy_router:compile([{'_', WsPaths}]),
|
||||
ProxyProto = proplists:get_value(proxy_protocol, Options, false),
|
||||
ProxyProto = maps:get(proxy_protocol, Opts, false),
|
||||
#{env => #{dispatch => Dispatch}, proxy_header => ProxyProto}.
|
||||
|
||||
ranch_opts(Options0) ->
|
||||
Options = drop_tls13_for_old_otp(Options0),
|
||||
NumAcceptors = proplists:get_value(acceptors, Options, 4),
|
||||
MaxConnections = proplists:get_value(max_connections, Options, 1024),
|
||||
TcpOptions = proplists:get_value(tcp_options, Options, []),
|
||||
RanchOpts = #{num_acceptors => NumAcceptors,
|
||||
max_connections => MaxConnections,
|
||||
socket_opts => TcpOptions},
|
||||
case proplists:get_value(ssl_options, Options) of
|
||||
undefined -> RanchOpts;
|
||||
SslOptions -> RanchOpts#{socket_opts => TcpOptions ++ SslOptions}
|
||||
end.
|
||||
ranch_opts(ListenOn, Opts) ->
|
||||
NumAcceptors = maps:get(acceptors, Opts, 4),
|
||||
MaxConnections = maps:get(max_connections, Opts, 1024),
|
||||
SocketOpts = case is_ssl(Opts) of
|
||||
true -> tcp_opts(Opts) ++ proplists:delete(handshake_timeout, ssl_opts(Opts));
|
||||
false -> tcp_opts(Opts)
|
||||
end,
|
||||
#{num_acceptors => NumAcceptors,
|
||||
max_connections => MaxConnections,
|
||||
handshake_timeout => maps:get(handshake_timeout, Opts, 15000),
|
||||
socket_opts => ip_port(ListenOn) ++
|
||||
%% cowboy don't allow us to set 'reuseaddr'
|
||||
proplists:delete(reuseaddr, SocketOpts)}.
|
||||
|
||||
with_port(Port, Opts = #{socket_opts := SocketOption}) when is_integer(Port) ->
|
||||
Opts#{socket_opts => [{port, Port}| SocketOption]};
|
||||
with_port({Addr, Port}, Opts = #{socket_opts := SocketOption}) ->
|
||||
Opts#{socket_opts => [{ip, Addr}, {port, Port}| SocketOption]}.
|
||||
ip_port(Port) when is_integer(Port) ->
|
||||
[{port, Port}];
|
||||
ip_port({Addr, Port}) ->
|
||||
[{ip, Addr}, {port, Port}].
|
||||
|
||||
port(Port) when is_integer(Port) -> Port;
|
||||
port({_Addr, Port}) when is_integer(Port) -> Port.
|
||||
|
||||
esockd_access_rules(StrRules) ->
|
||||
Access = fun(S) ->
|
||||
[A, CIDR] = string:tokens(S, " "),
|
||||
{list_to_atom(A), case CIDR of "all" -> all; _ -> CIDR end}
|
||||
end,
|
||||
[Access(R) || R <- StrRules].
|
||||
|
||||
%% @doc Restart all listeners
|
||||
-spec(restart() -> ok).
|
||||
restart() ->
|
||||
lists:foreach(fun restart_listener/1, emqx:get_env(listeners, [])).
|
||||
foreach_listeners(fun restart_listener/3).
|
||||
|
||||
-spec(restart_listener(listener() | string() | binary()) -> ok | {error, any()}).
|
||||
restart_listener(#{proto := Proto, listen_on := ListenOn, opts := Options}) ->
|
||||
restart_listener(Proto, ListenOn, Options);
|
||||
restart_listener(Identifier) ->
|
||||
case emqx_listeners:find_by_id(Identifier) of
|
||||
false -> {error, {no_such_listener, Identifier}};
|
||||
Listener -> restart_listener(Listener)
|
||||
-spec(restart_listener(atom()) -> ok | {error, term()}).
|
||||
restart_listener(ListenerId) ->
|
||||
apply_on_listener(ListenerId, fun restart_listener/3).
|
||||
|
||||
-spec(restart_listener(atom(), atom(), map()) -> ok | {error, term()}).
|
||||
restart_listener(ZoneName, ListenerName, Conf) ->
|
||||
case stop_listener(ZoneName, ListenerName, Conf) of
|
||||
ok -> start_listener(ZoneName, ListenerName, Conf);
|
||||
Error -> Error
|
||||
end.
|
||||
|
||||
-spec(restart_listener(esockd:proto(), esockd:listen_on(), [esockd:option()]) ->
|
||||
ok | {error, any()}).
|
||||
restart_listener(tcp, ListenOn, _Options) ->
|
||||
esockd:reopen('mqtt:tcp', ListenOn);
|
||||
restart_listener(Proto, ListenOn, _Options) when Proto == ssl; Proto == tls ->
|
||||
esockd:reopen('mqtt:ssl', ListenOn);
|
||||
restart_listener(Proto, ListenOn, Options) when Proto == http; Proto == ws ->
|
||||
_ = cowboy:stop_listener(ws_name('mqtt:ws', ListenOn)),
|
||||
ok(start_listener(Proto, ListenOn, Options));
|
||||
restart_listener(Proto, ListenOn, Options) when Proto == https; Proto == wss ->
|
||||
_ = cowboy:stop_listener(ws_name('mqtt:wss', ListenOn)),
|
||||
ok(start_listener(Proto, ListenOn, Options));
|
||||
restart_listener(Proto, ListenOn, _Opts) ->
|
||||
esockd:reopen(Proto, ListenOn).
|
||||
|
||||
ok({ok, _}) -> ok;
|
||||
ok(Other) -> Other.
|
||||
|
||||
%% @doc Stop all listeners.
|
||||
-spec(stop() -> ok).
|
||||
stop() ->
|
||||
lists:foreach(fun stop_listener/1, emqx:get_env(listeners, [])).
|
||||
foreach_listeners(fun stop_listener/3).
|
||||
|
||||
-spec(stop_listener(listener()) -> ok | {error, term()}).
|
||||
stop_listener(#{proto := Proto, listen_on := ListenOn, opts := Opts}) ->
|
||||
stop_listener(Proto, ListenOn, Opts).
|
||||
-spec(stop_listener(atom()) -> ok | {error, term()}).
|
||||
stop_listener(ListenerId) ->
|
||||
apply_on_listener(ListenerId, fun stop_listener/3).
|
||||
|
||||
-spec(stop_listener(esockd:proto(), esockd:listen_on(), [esockd:option()])
|
||||
-> ok | {error, term()}).
|
||||
stop_listener(tcp, ListenOn, _Opts) ->
|
||||
esockd:close('mqtt:tcp', ListenOn);
|
||||
stop_listener(Proto, ListenOn, _Opts) when Proto == ssl; Proto == tls ->
|
||||
esockd:close('mqtt:ssl', ListenOn);
|
||||
stop_listener(Proto, ListenOn, _Opts) when Proto == http; Proto == ws ->
|
||||
cowboy:stop_listener(ws_name('mqtt:ws', ListenOn));
|
||||
stop_listener(Proto, ListenOn, _Opts) when Proto == https; Proto == wss ->
|
||||
cowboy:stop_listener(ws_name('mqtt:wss', ListenOn));
|
||||
stop_listener(Proto, ListenOn, _Opts) ->
|
||||
esockd:close(Proto, ListenOn).
|
||||
-spec(stop_listener(atom(), atom(), map()) -> ok | {error, term()}).
|
||||
stop_listener(ZoneName, ListenerName, #{type := tcp, bind := ListenOn}) ->
|
||||
esockd:close(listener_id(ZoneName, ListenerName), ListenOn);
|
||||
stop_listener(ZoneName, ListenerName, #{type := ws}) ->
|
||||
cowboy:stop_listener(listener_id(ZoneName, ListenerName));
|
||||
stop_listener(ZoneName, ListenerName, #{type := quic}) ->
|
||||
quicer:stop_listener(listener_id(ZoneName, ListenerName)).
|
||||
|
||||
merge_default(Options) ->
|
||||
case lists:keytake(tcp_options, 1, Options) of
|
||||
|
@ -256,23 +267,46 @@ format({Addr, Port}) when is_list(Addr) ->
|
|||
format({Addr, Port}) when is_tuple(Addr) ->
|
||||
io_lib:format("~s:~w", [inet:ntoa(Addr), Port]).
|
||||
|
||||
ws_name(Name, {_Addr, Port}) ->
|
||||
ws_name(Name, Port);
|
||||
ws_name(Name, Port) ->
|
||||
list_to_atom(lists:concat([Name, ":", Port])).
|
||||
listener_id(ZoneName, ListenerName) ->
|
||||
list_to_atom(lists:append([atom_to_list(ZoneName), ":", atom_to_list(ListenerName)])).
|
||||
|
||||
identifier(Proto, Name) when is_atom(Proto) ->
|
||||
identifier(atom_to_list(Proto), Name);
|
||||
identifier(Proto, Name) ->
|
||||
iolist_to_binary(["mqtt", ":", Proto, ":", Name]).
|
||||
|
||||
find_by_listen_on(_ListenOn, []) -> false;
|
||||
find_by_listen_on(ListenOn, [#{listen_on := ListenOn} = L | _]) -> L;
|
||||
find_by_listen_on(ListenOn, [_ | Rest]) -> find_by_listen_on(ListenOn, Rest).
|
||||
|
||||
find_by_id(_Id, []) -> false;
|
||||
find_by_id(Id, [L | Rest]) ->
|
||||
case identifier(L) =:= Id of
|
||||
true -> L;
|
||||
false -> find_by_id(Id, Rest)
|
||||
decode_listener_id(Id) ->
|
||||
case string:split(atom_to_list(Id), ":", leading) of
|
||||
[Zone, Listen] -> {list_to_atom(Zone), list_to_atom(Listen)};
|
||||
_ -> error({invalid_listener_id, Id})
|
||||
end.
|
||||
|
||||
ssl_opts(Opts) ->
|
||||
maps:to_list(
|
||||
emqx_tls_lib:drop_tls13_for_old_otp(
|
||||
maps:without([enable],
|
||||
maps:get(ssl, Opts, #{})))).
|
||||
|
||||
tcp_opts(Opts) ->
|
||||
maps:to_list(
|
||||
maps:without([active_n],
|
||||
maps:get(tcp, Opts, #{}))).
|
||||
|
||||
is_ssl(Opts) ->
|
||||
emqx_map_lib:deep_get([ssl, enable], Opts, false).
|
||||
|
||||
foreach_listeners(Do) ->
|
||||
lists:foreach(fun({ZoneName, ZoneConf}) ->
|
||||
lists:foreach(fun({LName, LConf}) ->
|
||||
Do(ZoneName, LName, merge_zone_and_listener_confs(ZoneConf, LConf))
|
||||
end, maps:to_list(maps:get(listeners, ZoneConf, #{})))
|
||||
end, maps:to_list(emqx_config:get([zones], #{}))).
|
||||
|
||||
%% merge the configs in zone and listeners in a manner that
|
||||
%% all config entries in the listener are prior to the ones in the zone.
|
||||
merge_zone_and_listener_confs(ZoneConf, ListenerConf) ->
|
||||
ConfsInZonesOnly = [listeners, overall_max_connections],
|
||||
BaseConf = maps:without(ConfsInZonesOnly, ZoneConf),
|
||||
emqx_map_lib:deep_merge(BaseConf, ListenerConf).
|
||||
|
||||
apply_on_listener(ListenerId, Do) ->
|
||||
{ZoneName, ListenerName} = decode_listener_id(ListenerId),
|
||||
case emqx_config:find_listener_conf(ZoneName, ListenerName, []) of
|
||||
{not_found, _, _} -> error({listener_config_not_found, ZoneName, ListenerName});
|
||||
{ok, Conf} -> Do(ZoneName, ListenerName, Conf)
|
||||
end.
|
||||
|
|
|
@ -0,0 +1,117 @@
|
|||
%%--------------------------------------------------------------------
|
||||
%% Copyright (c) 2020-2021 EMQ Technologies Co., Ltd. All Rights Reserved.
|
||||
%%
|
||||
%% Licensed under the Apache License, Version 2.0 (the "License");
|
||||
%% you may not use this file except in compliance with the License.
|
||||
%% You may obtain a copy of the License at
|
||||
%%
|
||||
%% http://www.apache.org/licenses/LICENSE-2.0
|
||||
%%
|
||||
%% Unless required by applicable law or agreed to in writing, software
|
||||
%% distributed under the License is distributed on an "AS IS" BASIS,
|
||||
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
%% See the License for the specific language governing permissions and
|
||||
%% limitations under the License.
|
||||
%%--------------------------------------------------------------------
|
||||
-module(emqx_map_lib).
|
||||
|
||||
-export([ deep_get/2
|
||||
, deep_get/3
|
||||
, deep_find/2
|
||||
, deep_put/3
|
||||
, deep_remove/2
|
||||
, deep_merge/2
|
||||
, safe_atom_key_map/1
|
||||
, unsafe_atom_key_map/1
|
||||
]).
|
||||
|
||||
-export_type([config_key/0, config_key_path/0]).
|
||||
-type config_key() :: atom() | binary().
|
||||
-type config_key_path() :: [config_key()].
|
||||
|
||||
%%-----------------------------------------------------------------
|
||||
-spec deep_get(config_key_path(), map()) -> term().
|
||||
deep_get(ConfKeyPath, Map) ->
|
||||
Ref = make_ref(),
|
||||
Res = deep_get(ConfKeyPath, Map, Ref),
|
||||
case Res =:= Ref of
|
||||
true -> error({config_not_found, ConfKeyPath});
|
||||
false -> Res
|
||||
end.
|
||||
|
||||
-spec deep_get(config_key_path(), map(), term()) -> term().
|
||||
deep_get(ConfKeyPath, Map, Default) ->
|
||||
case deep_find(ConfKeyPath, Map) of
|
||||
{not_found, _KeyPath, _Data} -> Default;
|
||||
{ok, Data} -> Data
|
||||
end.
|
||||
|
||||
-spec deep_find(config_key_path(), map()) ->
|
||||
{ok, term()} | {not_found, config_key_path(), term()}.
|
||||
deep_find([], Map) ->
|
||||
{ok, Map};
|
||||
deep_find([Key | KeyPath] = Path, Map) when is_map(Map) ->
|
||||
case maps:find(Key, Map) of
|
||||
{ok, SubMap} -> deep_find(KeyPath, SubMap);
|
||||
error -> {not_found, Path, Map}
|
||||
end;
|
||||
deep_find(_KeyPath, Data) ->
|
||||
{not_found, _KeyPath, Data}.
|
||||
|
||||
-spec deep_put(config_key_path(), map(), term()) -> map().
|
||||
deep_put([], Map, Config) when is_map(Map) ->
|
||||
Config;
|
||||
deep_put([], _Map, Config) -> %% not map, replace it
|
||||
Config;
|
||||
deep_put([Key | KeyPath], Map, Config) ->
|
||||
SubMap = deep_put(KeyPath, maps:get(Key, Map, #{}), Config),
|
||||
Map#{Key => SubMap}.
|
||||
|
||||
-spec deep_remove(config_key_path(), map()) -> map().
|
||||
deep_remove([], Map) ->
|
||||
Map;
|
||||
deep_remove([Key], Map) ->
|
||||
maps:remove(Key, Map);
|
||||
deep_remove([Key | KeyPath], Map) ->
|
||||
case maps:find(Key, Map) of
|
||||
{ok, SubMap} when is_map(SubMap) ->
|
||||
Map#{Key => deep_remove(KeyPath, SubMap)};
|
||||
{ok, _Val} -> Map;
|
||||
error -> Map
|
||||
end.
|
||||
|
||||
%% #{a => #{b => 3, c => 2}, d => 4}
|
||||
%% = deep_merge(#{a => #{b => 1, c => 2}, d => 4}, #{a => #{b => 3}}).
|
||||
-spec deep_merge(map(), map()) -> map().
|
||||
deep_merge(BaseMap, NewMap) ->
|
||||
NewKeys = maps:keys(NewMap) -- maps:keys(BaseMap),
|
||||
MergedBase = maps:fold(fun(K, V, Acc) ->
|
||||
case maps:find(K, NewMap) of
|
||||
error ->
|
||||
Acc#{K => V};
|
||||
{ok, NewV} when is_map(V), is_map(NewV) ->
|
||||
Acc#{K => deep_merge(V, NewV)};
|
||||
{ok, NewV} ->
|
||||
Acc#{K => NewV}
|
||||
end
|
||||
end, #{}, BaseMap),
|
||||
maps:merge(MergedBase, maps:with(NewKeys, NewMap)).
|
||||
|
||||
unsafe_atom_key_map(Map) ->
|
||||
covert_keys_to_atom(Map, fun(K) -> binary_to_atom(K, utf8) end).
|
||||
|
||||
safe_atom_key_map(Map) ->
|
||||
covert_keys_to_atom(Map, fun(K) -> binary_to_existing_atom(K, utf8) end).
|
||||
|
||||
%%---------------------------------------------------------------------------
|
||||
covert_keys_to_atom(BinKeyMap, Conv) when is_map(BinKeyMap) ->
|
||||
maps:fold(
|
||||
fun(K, V, Acc) when is_binary(K) ->
|
||||
Acc#{Conv(K) => covert_keys_to_atom(V, Conv)};
|
||||
(K, V, Acc) when is_atom(K) ->
|
||||
%% richmap keys
|
||||
Acc#{K => covert_keys_to_atom(V, Conv)}
|
||||
end, #{}, BinKeyMap);
|
||||
covert_keys_to_atom(ListV, Conv) when is_list(ListV) ->
|
||||
[covert_keys_to_atom(V, Conv) || V <- ListV];
|
||||
covert_keys_to_atom(Val, _) -> Val.
|
|
@ -172,7 +172,7 @@
|
|||
{counter, 'client.connected'},
|
||||
{counter, 'client.authenticate'},
|
||||
{counter, 'client.auth.anonymous'},
|
||||
{counter, 'client.check_acl'},
|
||||
{counter, 'client.authorize'},
|
||||
{counter, 'client.subscribe'},
|
||||
{counter, 'client.unsubscribe'},
|
||||
{counter, 'client.disconnected'}
|
||||
|
@ -563,7 +563,7 @@ reserved_idx('client.connected') -> 202;
|
|||
reserved_idx('client.authenticate') -> 203;
|
||||
reserved_idx('client.enhanced_authenticate') -> 204;
|
||||
reserved_idx('client.auth.anonymous') -> 205;
|
||||
reserved_idx('client.check_acl') -> 206;
|
||||
reserved_idx('client.authorize') -> 206;
|
||||
reserved_idx('client.subscribe') -> 207;
|
||||
reserved_idx('client.unsubscribe') -> 208;
|
||||
reserved_idx('client.disconnected') -> 209;
|
||||
|
|
|
@ -197,7 +197,8 @@ check_oom(Policy) ->
|
|||
check_oom(self(), Policy).
|
||||
|
||||
-spec(check_oom(pid(), emqx_types:oom_policy()) -> ok | {shutdown, term()}).
|
||||
check_oom(Pid, #{message_queue_len := MaxQLen,
|
||||
check_oom(_Pid, #{enable := false}) -> ok;
|
||||
check_oom(Pid, #{max_message_queue_len := MaxQLen,
|
||||
max_heap_size := MaxHeapSize}) ->
|
||||
case process_info(Pid, [message_queue_len, total_heap_size]) of
|
||||
undefined -> ok;
|
||||
|
@ -214,13 +215,26 @@ do_check_oom([{Val, Max, Reason}|Rest]) ->
|
|||
false -> do_check_oom(Rest)
|
||||
end.
|
||||
|
||||
tune_heap_size(#{max_heap_size := MaxHeapSize}) ->
|
||||
%% If set to zero, the limit is disabled.
|
||||
erlang:process_flag(max_heap_size, #{size => MaxHeapSize,
|
||||
kill => false,
|
||||
error_logger => true
|
||||
});
|
||||
tune_heap_size(undefined) -> ok.
|
||||
tune_heap_size(#{enable := false}) ->
|
||||
ok;
|
||||
%% If the max_heap_size is set to zero, the limit is disabled.
|
||||
tune_heap_size(#{max_heap_size := MaxHeapSize}) when MaxHeapSize > 0 ->
|
||||
MaxSize = case erlang:system_info(wordsize) of
|
||||
8 -> % arch_64
|
||||
(1 bsl 59) - 1;
|
||||
4 -> % arch_32
|
||||
(1 bsl 27) - 1
|
||||
end,
|
||||
OverflowedSize = case erlang:trunc(MaxHeapSize * 1.5) of
|
||||
SZ when SZ > MaxSize -> MaxSize;
|
||||
SZ -> SZ
|
||||
end,
|
||||
erlang:process_flag(max_heap_size, #{
|
||||
size => OverflowedSize,
|
||||
kill => true,
|
||||
error_logger => true
|
||||
}).
|
||||
|
||||
|
||||
-spec(proc_name(atom(), pos_integer()) -> atom()).
|
||||
proc_name(Mod, Id) ->
|
||||
|
|
|
@ -25,14 +25,8 @@
|
|||
]).
|
||||
|
||||
-export([ get_caps/1
|
||||
, get_caps/2
|
||||
, get_caps/3
|
||||
]).
|
||||
|
||||
-export([default_caps/0]).
|
||||
|
||||
-export([default/0]).
|
||||
|
||||
-export_type([caps/0]).
|
||||
|
||||
-type(caps() :: #{max_packet_size => integer(),
|
||||
|
@ -46,7 +40,7 @@
|
|||
shared_subscription => boolean()
|
||||
}).
|
||||
|
||||
-define(UNLIMITED, 0).
|
||||
-define(MAX_TOPIC_LEVELS, 65535).
|
||||
|
||||
-define(PUBCAP_KEYS, [max_topic_levels,
|
||||
max_qos_allowed,
|
||||
|
@ -62,7 +56,7 @@
|
|||
-define(DEFAULT_CAPS, #{max_packet_size => ?MAX_PACKET_SIZE,
|
||||
max_clientid_len => ?MAX_CLIENTID_LEN,
|
||||
max_topic_alias => ?MAX_TOPIC_AlIAS,
|
||||
max_topic_levels => ?UNLIMITED,
|
||||
max_topic_levels => ?MAX_TOPIC_LEVELS,
|
||||
max_qos_allowed => ?QOS_2,
|
||||
retain_available => true,
|
||||
wildcard_subscription => true,
|
||||
|
@ -81,7 +75,7 @@ check_pub(Zone, Flags) when is_map(Flags) ->
|
|||
Flags1#{topic_levels => emqx_topic:levels(Topic)};
|
||||
error ->
|
||||
Flags
|
||||
end, get_caps(Zone, publish)).
|
||||
end, maps:with(?PUBCAP_KEYS, get_caps(Zone))).
|
||||
|
||||
do_check_pub(#{topic_levels := Levels}, #{max_topic_levels := Limit})
|
||||
when Limit > 0, Levels > Limit ->
|
||||
|
@ -98,7 +92,7 @@ do_check_pub(_Flags, _Caps) -> ok.
|
|||
emqx_types:subopts())
|
||||
-> ok_or_error(emqx_types:reason_code())).
|
||||
check_sub(Zone, Topic, SubOpts) ->
|
||||
Caps = get_caps(Zone, subscribe),
|
||||
Caps = maps:with(?SUBCAP_KEYS, get_caps(Zone)),
|
||||
Flags = lists:foldl(
|
||||
fun(max_topic_levels, Map) ->
|
||||
Map#{topic_levels => emqx_topic:levels(Topic)};
|
||||
|
@ -119,42 +113,7 @@ do_check_sub(#{is_shared := true}, #{shared_subscription := false}) ->
|
|||
{error, ?RC_SHARED_SUBSCRIPTIONS_NOT_SUPPORTED};
|
||||
do_check_sub(_Flags, _Caps) -> ok.
|
||||
|
||||
default_caps() ->
|
||||
?DEFAULT_CAPS.
|
||||
|
||||
get_caps(Zone, Cap, Def) ->
|
||||
emqx_zone:get_env(Zone, Cap, Def).
|
||||
|
||||
get_caps(Zone, publish) ->
|
||||
with_env(Zone, '$mqtt_pub_caps',
|
||||
fun() ->
|
||||
filter_caps(?PUBCAP_KEYS, get_caps(Zone))
|
||||
end);
|
||||
|
||||
get_caps(Zone, subscribe) ->
|
||||
with_env(Zone, '$mqtt_sub_caps',
|
||||
fun() ->
|
||||
filter_caps(?SUBCAP_KEYS, get_caps(Zone))
|
||||
end).
|
||||
|
||||
get_caps(Zone) ->
|
||||
with_env(Zone, '$mqtt_caps',
|
||||
fun() ->
|
||||
maps:map(fun(Cap, Def) ->
|
||||
emqx_zone:get_env(Zone, Cap, Def)
|
||||
end, ?DEFAULT_CAPS)
|
||||
end).
|
||||
|
||||
filter_caps(Keys, Caps) ->
|
||||
maps:filter(fun(Key, _Val) -> lists:member(Key, Keys) end, Caps).
|
||||
|
||||
-spec(default() -> caps()).
|
||||
default() -> ?DEFAULT_CAPS.
|
||||
|
||||
with_env(Zone, Key, InitFun) ->
|
||||
case emqx_zone:get_env(Zone, Key) of
|
||||
undefined -> Caps = InitFun(),
|
||||
ok = emqx_zone:set_env(Zone, Key, Caps),
|
||||
Caps;
|
||||
ZoneCaps -> ZoneCaps
|
||||
end.
|
||||
lists:foldl(fun({K, V}, Acc) ->
|
||||
Acc#{K => emqx_config:get_zone_conf(Zone, [mqtt, K], V)}
|
||||
end, #{}, maps:to_list(?DEFAULT_CAPS)).
|
||||
|
|
|
@ -67,6 +67,8 @@
|
|||
, dropped/1
|
||||
]).
|
||||
|
||||
-define(NO_PRIORITY_TABLE, disabled).
|
||||
|
||||
-export_type([mqueue/0, options/0]).
|
||||
|
||||
-type(topic() :: emqx_topic:topic()).
|
||||
|
|
|
@ -22,15 +22,9 @@
|
|||
|
||||
-logger_header("[OS_MON]").
|
||||
|
||||
-export([start_link/1]).
|
||||
-export([start_link/0]).
|
||||
|
||||
-export([ get_cpu_check_interval/0
|
||||
, set_cpu_check_interval/1
|
||||
, get_cpu_high_watermark/0
|
||||
, set_cpu_high_watermark/1
|
||||
, get_cpu_low_watermark/0
|
||||
, set_cpu_low_watermark/1
|
||||
, get_mem_check_interval/0
|
||||
-export([ get_mem_check_interval/0
|
||||
, set_mem_check_interval/1
|
||||
, get_sysmem_high_watermark/0
|
||||
, set_sysmem_high_watermark/1
|
||||
|
@ -51,119 +45,76 @@
|
|||
|
||||
-define(OS_MON, ?MODULE).
|
||||
|
||||
start_link(Opts) ->
|
||||
gen_server:start_link({local, ?OS_MON}, ?MODULE, [Opts], []).
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?OS_MON}, ?MODULE, [], []).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% API
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
get_cpu_check_interval() ->
|
||||
call(get_cpu_check_interval).
|
||||
|
||||
set_cpu_check_interval(Seconds) ->
|
||||
call({set_cpu_check_interval, Seconds}).
|
||||
|
||||
get_cpu_high_watermark() ->
|
||||
call(get_cpu_high_watermark).
|
||||
|
||||
set_cpu_high_watermark(Float) ->
|
||||
call({set_cpu_high_watermark, Float}).
|
||||
|
||||
get_cpu_low_watermark() ->
|
||||
call(get_cpu_low_watermark).
|
||||
|
||||
set_cpu_low_watermark(Float) ->
|
||||
call({set_cpu_low_watermark, Float}).
|
||||
|
||||
get_mem_check_interval() ->
|
||||
memsup:get_check_interval() div 1000.
|
||||
memsup:get_check_interval().
|
||||
|
||||
set_mem_check_interval(Seconds) when Seconds < 60 ->
|
||||
set_mem_check_interval(Seconds) when Seconds < 60000 ->
|
||||
memsup:set_check_interval(1);
|
||||
set_mem_check_interval(Seconds) ->
|
||||
memsup:set_check_interval(Seconds div 60).
|
||||
memsup:set_check_interval(Seconds div 60000).
|
||||
|
||||
get_sysmem_high_watermark() ->
|
||||
memsup:get_sysmem_high_watermark().
|
||||
|
||||
set_sysmem_high_watermark(Float) ->
|
||||
memsup:set_sysmem_high_watermark(Float / 100).
|
||||
memsup:set_sysmem_high_watermark(Float).
|
||||
|
||||
get_procmem_high_watermark() ->
|
||||
memsup:get_procmem_high_watermark().
|
||||
|
||||
set_procmem_high_watermark(Float) ->
|
||||
memsup:set_procmem_high_watermark(Float / 100).
|
||||
|
||||
call(Req) ->
|
||||
gen_server:call(?OS_MON, Req, infinity).
|
||||
memsup:set_procmem_high_watermark(Float).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% gen_server callbacks
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
init([Opts]) ->
|
||||
set_mem_check_interval(proplists:get_value(mem_check_interval, Opts)),
|
||||
set_sysmem_high_watermark(proplists:get_value(sysmem_high_watermark, Opts)),
|
||||
set_procmem_high_watermark(proplists:get_value(procmem_high_watermark, Opts)),
|
||||
{ok, ensure_check_timer(#{cpu_high_watermark => proplists:get_value(cpu_high_watermark, Opts),
|
||||
cpu_low_watermark => proplists:get_value(cpu_low_watermark, Opts),
|
||||
cpu_check_interval => proplists:get_value(cpu_check_interval, Opts),
|
||||
timer => undefined})}.
|
||||
|
||||
handle_call(get_cpu_check_interval, _From, State) ->
|
||||
{reply, maps:get(cpu_check_interval, State, undefined), State};
|
||||
|
||||
handle_call({set_cpu_check_interval, Seconds}, _From, State) ->
|
||||
{reply, ok, State#{cpu_check_interval := Seconds}};
|
||||
|
||||
handle_call(get_cpu_high_watermark, _From, State) ->
|
||||
{reply, maps:get(cpu_high_watermark, State, undefined), State};
|
||||
|
||||
handle_call({set_cpu_high_watermark, Float}, _From, State) ->
|
||||
{reply, ok, State#{cpu_high_watermark := Float}};
|
||||
|
||||
handle_call(get_cpu_low_watermark, _From, State) ->
|
||||
{reply, maps:get(cpu_low_watermark, State, undefined), State};
|
||||
|
||||
handle_call({set_cpu_low_watermark, Float}, _From, State) ->
|
||||
{reply, ok, State#{cpu_low_watermark := Float}};
|
||||
init([]) ->
|
||||
Opts = emqx_config:get([sysmon, os]),
|
||||
set_mem_check_interval(maps:get(mem_check_interval, Opts)),
|
||||
set_sysmem_high_watermark(maps:get(sysmem_high_watermark, Opts)),
|
||||
set_procmem_high_watermark(maps:get(procmem_high_watermark, Opts)),
|
||||
_ = start_check_timer(),
|
||||
{ok, #{}}.
|
||||
|
||||
handle_call(Req, _From, State) ->
|
||||
?LOG(error, "Unexpected call: ~p", [Req]),
|
||||
{reply, ignored, State}.
|
||||
{reply, {error, {unexpected_call, Req}}, State}.
|
||||
|
||||
handle_cast(Msg, State) ->
|
||||
?LOG(error, "Unexpected cast: ~p", [Msg]),
|
||||
?LOG(error, "unexpected_cast_discarded: ~p", [Msg]),
|
||||
{noreply, State}.
|
||||
|
||||
handle_info({timeout, Timer, check}, State = #{timer := Timer,
|
||||
cpu_high_watermark := CPUHighWatermark,
|
||||
cpu_low_watermark := CPULowWatermark}) ->
|
||||
NState =
|
||||
case emqx_vm:cpu_util() of %% TODO: should be improved?
|
||||
0 ->
|
||||
State#{timer := undefined};
|
||||
handle_info({timeout, _Timer, check}, State) ->
|
||||
CPUHighWatermark = emqx_config:get([sysmon, os, cpu_high_watermark]) * 100,
|
||||
CPULowWatermark = emqx_config:get([sysmon, os, cpu_low_watermark]) * 100,
|
||||
_ = case emqx_vm:cpu_util() of %% TODO: should be improved?
|
||||
0 -> ok;
|
||||
Busy when Busy >= CPUHighWatermark ->
|
||||
emqx_alarm:activate(high_cpu_usage, #{usage => Busy,
|
||||
emqx_alarm:activate(high_cpu_usage, #{usage => io_lib:format("~p%", [Busy]),
|
||||
high_watermark => CPUHighWatermark,
|
||||
low_watermark => CPULowWatermark}),
|
||||
ensure_check_timer(State);
|
||||
start_check_timer();
|
||||
Busy when Busy =< CPULowWatermark ->
|
||||
emqx_alarm:deactivate(high_cpu_usage),
|
||||
ensure_check_timer(State);
|
||||
start_check_timer();
|
||||
_Busy ->
|
||||
ensure_check_timer(State)
|
||||
start_check_timer()
|
||||
end,
|
||||
{noreply, NState};
|
||||
{noreply, State};
|
||||
|
||||
handle_info(Info, State) ->
|
||||
?LOG(error, "unexpected info: ~p", [Info]),
|
||||
?LOG(info, "unexpected_info_discarded: ~p", [Info]),
|
||||
{noreply, State}.
|
||||
|
||||
terminate(_Reason, #{timer := Timer}) ->
|
||||
emqx_misc:cancel_timer(Timer).
|
||||
terminate(_Reason, _State) ->
|
||||
ok.
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
@ -172,8 +123,9 @@ code_change(_OldVsn, State, _Extra) ->
|
|||
%% Internal functions
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
ensure_check_timer(State = #{cpu_check_interval := Interval}) ->
|
||||
start_check_timer() ->
|
||||
Interval = emqx_config:get([sysmon, os, cpu_check_interval]),
|
||||
case erlang:system_info(system_architecture) of
|
||||
"x86_64-pc-linux-musl" -> State;
|
||||
_ -> State#{timer := emqx_misc:start_timer(timer:seconds(Interval), check)}
|
||||
"x86_64-pc-linux-musl" -> ok;
|
||||
_ -> emqx_misc:start_timer(Interval, check)
|
||||
end.
|
||||
|
|
|
@ -21,8 +21,6 @@
|
|||
|
||||
-logger_header("[Plugins]").
|
||||
|
||||
-export([init/0]).
|
||||
|
||||
-export([ load/0
|
||||
, load/1
|
||||
, unload/0
|
||||
|
@ -30,8 +28,6 @@
|
|||
, reload/1
|
||||
, list/0
|
||||
, find_plugin/1
|
||||
, generate_configs/1
|
||||
, apply_configs/1
|
||||
]).
|
||||
|
||||
-export([funlog/2]).
|
||||
|
@ -41,35 +37,14 @@
|
|||
-compile(nowarn_export_all).
|
||||
-endif.
|
||||
|
||||
-dialyzer({no_match, [ plugin_loaded/2
|
||||
, plugin_unloaded/2
|
||||
]}).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% APIs
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
%% @doc Init plugins' config
|
||||
-spec(init() -> ok).
|
||||
init() ->
|
||||
case emqx:get_env(plugins_etc_dir) of
|
||||
undefined -> ok;
|
||||
PluginsEtc ->
|
||||
CfgFiles = [filename:join(PluginsEtc, File) ||
|
||||
File <- filelib:wildcard("*.config", PluginsEtc)],
|
||||
lists:foreach(fun init_config/1, CfgFiles)
|
||||
end.
|
||||
|
||||
%% @doc Load all plugins when the broker started.
|
||||
-spec(load() -> ok | ignore | {error, term()}).
|
||||
load() ->
|
||||
ok = load_ext_plugins(emqx:get_env(expand_plugins_dir)),
|
||||
case emqx:get_env(plugins_loaded_file) of
|
||||
undefined -> ignore; %% No plugins available
|
||||
File ->
|
||||
_ = ensure_file(File),
|
||||
with_loaded_file(File, fun(Names) -> load_plugins(Names, false) end)
|
||||
end.
|
||||
ok = load_ext_plugins(emqx_config:get([plugins, expand_plugins_dir], undefined)).
|
||||
|
||||
%% @doc Load a Plugin
|
||||
-spec(load(atom()) -> ok | {error, term()}).
|
||||
|
@ -82,17 +57,13 @@ load(PluginName) when is_atom(PluginName) ->
|
|||
?LOG(notice, "Plugin ~s is already started", [PluginName]),
|
||||
{error, already_started};
|
||||
{_, false} ->
|
||||
load_plugin(PluginName, true)
|
||||
load_plugin(PluginName)
|
||||
end.
|
||||
|
||||
%% @doc Unload all plugins before broker stopped.
|
||||
-spec(unload() -> list() | {error, term()}).
|
||||
-spec(unload() -> ok).
|
||||
unload() ->
|
||||
case emqx:get_env(plugins_loaded_file) of
|
||||
undefined -> ignore;
|
||||
File ->
|
||||
with_loaded_file(File, fun stop_plugins/1)
|
||||
end.
|
||||
stop_plugins(list()).
|
||||
|
||||
%% @doc UnLoad a Plugin
|
||||
-spec(unload(atom()) -> ok | {error, term()}).
|
||||
|
@ -105,7 +76,7 @@ unload(PluginName) when is_atom(PluginName) ->
|
|||
?LOG(error, "Plugin ~s is not started", [PluginName]),
|
||||
{error, not_started};
|
||||
{_, _} ->
|
||||
unload_plugin(PluginName, true)
|
||||
unload_plugin(PluginName)
|
||||
end.
|
||||
|
||||
reload(PluginName) when is_atom(PluginName)->
|
||||
|
@ -126,8 +97,8 @@ reload(PluginName) when is_atom(PluginName)->
|
|||
-spec(list() -> [emqx_types:plugin()]).
|
||||
list() ->
|
||||
StartedApps = names(started_app),
|
||||
lists:map(fun({Name, _, [Type| _]}) ->
|
||||
Plugin = plugin(Name, Type),
|
||||
lists:map(fun({Name, _, _}) ->
|
||||
Plugin = plugin(Name),
|
||||
case lists:member(Name, StartedApps) of
|
||||
true -> Plugin#plugin{active = true};
|
||||
false -> Plugin
|
||||
|
@ -144,12 +115,6 @@ find_plugin(Name, Plugins) ->
|
|||
%% Internal functions
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
init_config(CfgFile) ->
|
||||
{ok, [AppsEnv]} = file:consult(CfgFile),
|
||||
lists:foreach(fun({App, Envs}) ->
|
||||
[application:set_env(App, Par, Val) || {Par, Val} <- Envs]
|
||||
end, AppsEnv).
|
||||
|
||||
%% load external plugins which are placed in etc/plugins dir
|
||||
load_ext_plugins(undefined) -> ok;
|
||||
load_ext_plugins(Dir) ->
|
||||
|
@ -173,15 +138,15 @@ load_ext_plugin(PluginDir) ->
|
|||
?LOG(alert, "plugin_app_file_not_found: ~s", [AppFile]),
|
||||
error({plugin_app_file_not_found, AppFile})
|
||||
end,
|
||||
ok = load_plugin_app(AppName, Ebin),
|
||||
try
|
||||
ok = generate_configs(AppName, PluginDir)
|
||||
catch
|
||||
throw : {conf_file_not_found, ConfFile} ->
|
||||
%% this is maybe a dependency of an external plugin
|
||||
?LOG(debug, "config_load_error_ignored for app=~p, path=~s", [AppName, ConfFile]),
|
||||
ok
|
||||
end.
|
||||
ok = load_plugin_app(AppName, Ebin).
|
||||
% try
|
||||
% ok = generate_configs(AppName, PluginDir)
|
||||
% catch
|
||||
% throw : {conf_file_not_found, ConfFile} ->
|
||||
% %% this is maybe a dependency of an external plugin
|
||||
% ?LOG(debug, "config_load_error_ignored for app=~p, path=~s", [AppName, ConfFile]),
|
||||
% ok
|
||||
% end.
|
||||
|
||||
load_plugin_app(AppName, Ebin) ->
|
||||
_ = code:add_patha(Ebin),
|
||||
|
@ -199,57 +164,24 @@ load_plugin_app(AppName, Ebin) ->
|
|||
{error, {already_loaded, _}} -> ok
|
||||
end.
|
||||
|
||||
ensure_file(File) ->
|
||||
case filelib:is_file(File) of false -> write_loaded([]); true -> ok end.
|
||||
|
||||
with_loaded_file(File, SuccFun) ->
|
||||
case read_loaded(File) of
|
||||
{ok, Names0} ->
|
||||
Names = filter_plugins(Names0),
|
||||
SuccFun(Names);
|
||||
{error, Error} ->
|
||||
?LOG(alert, "Failed to read: ~p, error: ~p", [File, Error]),
|
||||
{error, Error}
|
||||
end.
|
||||
|
||||
filter_plugins(Names) ->
|
||||
lists:filtermap(fun(Name1) when is_atom(Name1) -> {true, Name1};
|
||||
({Name1, true}) -> {true, Name1};
|
||||
({_Name1, false}) -> false
|
||||
end, Names).
|
||||
|
||||
load_plugins(Names, Persistent) ->
|
||||
Plugins = list(),
|
||||
NotFound = Names -- names(Plugins),
|
||||
case NotFound of
|
||||
[] -> ok;
|
||||
NotFound -> ?LOG(alert, "cannot_find_plugins: ~p", [NotFound])
|
||||
end,
|
||||
NeedToLoad = Names -- NotFound -- names(started_app),
|
||||
lists:foreach(fun(Name) ->
|
||||
Plugin = find_plugin(Name, Plugins),
|
||||
load_plugin(Plugin#plugin.name, Persistent)
|
||||
end, NeedToLoad).
|
||||
|
||||
%% Stop plugins
|
||||
stop_plugins(Names) ->
|
||||
_ = [stop_app(App) || App <- Names],
|
||||
stop_plugins(Plugins) ->
|
||||
_ = [stop_app(Plugin#plugin.name) || Plugin <- Plugins],
|
||||
ok.
|
||||
|
||||
plugin(AppName, Type) ->
|
||||
plugin(AppName) ->
|
||||
case application:get_all_key(AppName) of
|
||||
{ok, Attrs} ->
|
||||
Descr = proplists:get_value(description, Attrs, ""),
|
||||
#plugin{name = AppName, descr = Descr, type = plugin_type(Type)};
|
||||
#plugin{name = AppName, descr = Descr};
|
||||
undefined -> error({plugin_not_found, AppName})
|
||||
end.
|
||||
|
||||
load_plugin(Name, Persistent) ->
|
||||
load_plugin(Name) ->
|
||||
try
|
||||
ok = ?MODULE:generate_configs(Name),
|
||||
case load_app(Name) of
|
||||
ok ->
|
||||
start_app(Name, fun(App) -> plugin_loaded(App, Persistent) end);
|
||||
start_app(Name);
|
||||
{error, Error0} ->
|
||||
{error, Error0}
|
||||
end
|
||||
|
@ -268,22 +200,21 @@ load_app(App) ->
|
|||
{error, Error}
|
||||
end.
|
||||
|
||||
start_app(App, SuccFun) ->
|
||||
start_app(App) ->
|
||||
case application:ensure_all_started(App) of
|
||||
{ok, Started} ->
|
||||
?LOG(info, "Started plugins: ~p", [Started]),
|
||||
?LOG(info, "Load plugin ~s successfully", [App]),
|
||||
_ = SuccFun(App),
|
||||
ok;
|
||||
{error, {ErrApp, Reason}} ->
|
||||
?LOG(error, "Load plugin ~s failed, cannot start plugin ~s for ~0p", [App, ErrApp, Reason]),
|
||||
{error, {ErrApp, Reason}}
|
||||
end.
|
||||
|
||||
unload_plugin(App, Persistent) ->
|
||||
unload_plugin(App) ->
|
||||
case stop_app(App) of
|
||||
ok ->
|
||||
_ = plugin_unloaded(App, Persistent), ok;
|
||||
ok;
|
||||
{error, Reason} ->
|
||||
{error, Reason}
|
||||
end.
|
||||
|
@ -307,133 +238,5 @@ names(started_app) ->
|
|||
names(Plugins) ->
|
||||
[Name || #plugin{name = Name} <- Plugins].
|
||||
|
||||
plugin_loaded(_Name, false) ->
|
||||
ok;
|
||||
plugin_loaded(Name, true) ->
|
||||
case read_loaded() of
|
||||
{ok, Names} ->
|
||||
case lists:member(Name, Names) of
|
||||
false ->
|
||||
%% write file if plugin is loaded
|
||||
write_loaded(lists:append(Names, [{Name, true}]));
|
||||
true ->
|
||||
ignore
|
||||
end;
|
||||
{error, Error} ->
|
||||
?LOG(error, "Cannot read loaded plugins: ~p", [Error])
|
||||
end.
|
||||
|
||||
plugin_unloaded(_Name, false) ->
|
||||
ok;
|
||||
plugin_unloaded(Name, true) ->
|
||||
case read_loaded() of
|
||||
{ok, Names0} ->
|
||||
Names = filter_plugins(Names0),
|
||||
case lists:member(Name, Names) of
|
||||
true ->
|
||||
write_loaded(lists:delete(Name, Names));
|
||||
false ->
|
||||
?LOG(error, "Cannot find ~s in loaded_file", [Name])
|
||||
end;
|
||||
{error, Error} ->
|
||||
?LOG(error, "Cannot read loaded_plugins: ~p", [Error])
|
||||
end.
|
||||
|
||||
read_loaded() ->
|
||||
case emqx:get_env(plugins_loaded_file) of
|
||||
undefined -> {error, not_found};
|
||||
File -> read_loaded(File)
|
||||
end.
|
||||
|
||||
read_loaded(File) -> file:consult(File).
|
||||
|
||||
write_loaded(AppNames) ->
|
||||
FilePath = emqx:get_env(plugins_loaded_file),
|
||||
case file:write_file(FilePath, [io_lib:format("~p.~n", [Name]) || Name <- AppNames]) of
|
||||
ok -> ok;
|
||||
{error, Error} ->
|
||||
?LOG(error, "Write File ~p Error: ~p", [FilePath, Error]),
|
||||
{error, Error}
|
||||
end.
|
||||
|
||||
plugin_type(auth) -> auth;
|
||||
plugin_type(protocol) -> protocol;
|
||||
plugin_type(backend) -> backend;
|
||||
plugin_type(bridge) -> bridge;
|
||||
plugin_type(_) -> feature.
|
||||
|
||||
|
||||
funlog(Key, Value) ->
|
||||
?LOG(info, "~s = ~p", [string:join(Key, "."), Value]).
|
||||
|
||||
generate_configs(App) ->
|
||||
PluginConfDir = emqx:get_env(plugins_etc_dir),
|
||||
PluginSchemaDir = code:priv_dir(App),
|
||||
generate_configs(App, PluginConfDir, PluginSchemaDir).
|
||||
|
||||
generate_configs(App, PluginDir) ->
|
||||
PluginConfDir = filename:join([PluginDir, "etc"]),
|
||||
PluginSchemaDir = filename:join([PluginDir, "priv"]),
|
||||
generate_configs(App, PluginConfDir, PluginSchemaDir).
|
||||
|
||||
generate_configs(App, PluginConfDir, PluginSchemaDir) ->
|
||||
ConfigFile = filename:join([PluginConfDir, App]) ++ ".config",
|
||||
case filelib:is_file(ConfigFile) of
|
||||
true ->
|
||||
{ok, [Configs]} = file:consult(ConfigFile),
|
||||
apply_configs(Configs);
|
||||
false ->
|
||||
SchemaFile = filename:join([PluginSchemaDir, App]) ++ ".schema",
|
||||
case filelib:is_file(SchemaFile) of
|
||||
true ->
|
||||
AppsEnv = do_generate_configs(App),
|
||||
apply_configs(AppsEnv);
|
||||
false ->
|
||||
SchemaMod = lists:concat([App, "_schema"]),
|
||||
ConfName = filename:join([PluginConfDir, App]) ++ ".conf",
|
||||
SchemaFile1 = filename:join([code:lib_dir(App), "ebin", SchemaMod]) ++ ".beam",
|
||||
do_generate_hocon_configs(App, ConfName, SchemaFile1)
|
||||
end
|
||||
end.
|
||||
|
||||
do_generate_configs(App) ->
|
||||
Name1 = filename:join([emqx:get_env(plugins_etc_dir), App]) ++ ".conf",
|
||||
Name2 = filename:join([code:lib_dir(App), "etc", App]) ++ ".conf",
|
||||
ConfFile = case {filelib:is_file(Name1), filelib:is_file(Name2)} of
|
||||
{true, _} -> Name1;
|
||||
{false, true} -> Name2;
|
||||
{false, false} -> error({config_not_found, [Name1, Name2]})
|
||||
end,
|
||||
SchemaFile = filename:join([code:priv_dir(App), App]) ++ ".schema",
|
||||
case filelib:is_file(SchemaFile) of
|
||||
true ->
|
||||
Schema = cuttlefish_schema:files([SchemaFile]),
|
||||
Conf = cuttlefish_conf:file(ConfFile),
|
||||
cuttlefish_generator:map(Schema, Conf, undefined, fun ?MODULE:funlog/2);
|
||||
false ->
|
||||
error({schema_not_found, SchemaFile})
|
||||
end.
|
||||
|
||||
do_generate_hocon_configs(App, ConfName, SchemaFile) ->
|
||||
SchemaMod = lists:concat([App, "_schema"]),
|
||||
case {filelib:is_file(ConfName), filelib:is_file(SchemaFile)} of
|
||||
{true, true} ->
|
||||
{ok, RawConfig} = hocon:load(ConfName, #{format => richmap}),
|
||||
_ = hocon_schema:check(list_to_atom(SchemaMod), RawConfig, #{atom_key => true,
|
||||
return_plain => true}),
|
||||
ok;
|
||||
% emqx_config:update_config([App], Config);
|
||||
{true, false} ->
|
||||
error({schema_not_found, [SchemaFile]});
|
||||
{false, true} ->
|
||||
error({config_not_found, [ConfName]});
|
||||
{false, false} ->
|
||||
error({conf_and_schema_not_found, [ConfName, SchemaFile]})
|
||||
end.
|
||||
|
||||
apply_configs([]) ->
|
||||
ok;
|
||||
apply_configs([{App, Config} | More]) ->
|
||||
lists:foreach(fun({Key, _}) -> application:unset_env(App, Key) end, application:get_all_env(App)),
|
||||
lists:foreach(fun({Key, Val}) -> application:set_env(App, Key, Val) end, Config),
|
||||
apply_configs(More).
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
%%--------------------------------------------------------------------
|
||||
%% Copyright (c) 2017-2021 EMQ Technologies Co., Ltd. All Rights Reserved.
|
||||
%% Copyright (c) 2021 EMQ Technologies Co., Ltd. All Rights Reserved.
|
||||
%%
|
||||
%% Licensed under the Apache License, Version 2.0 (the "License");
|
||||
%% you may not use this file except in compliance with the License.
|
||||
|
@ -14,10 +14,11 @@
|
|||
%% limitations under the License.
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
-module(emqx_gen_mod).
|
||||
-module(emqx_quic_connection).
|
||||
|
||||
-callback(load(Opts :: any()) -> ok | {error, term()}).
|
||||
%% Callbacks
|
||||
-export([ new_conn/2
|
||||
]).
|
||||
|
||||
-callback(unload(State :: term()) -> term()).
|
||||
|
||||
-callback(description() -> any()).
|
||||
new_conn(Conn, {_L, COpts, _S}) when is_map(COpts) ->
|
||||
emqx_connection:start_link(emqx_quic_stream, Conn, COpts).
|
|
@ -0,0 +1,92 @@
|
|||
%%--------------------------------------------------------------------
|
||||
%% Copyright (c) 2021 EMQ Technologies Co., Ltd. All Rights Reserved.
|
||||
%%
|
||||
%% Licensed under the Apache License, Version 2.0 (the "License");
|
||||
%% you may not use this file except in compliance with the License.
|
||||
%% You may obtain a copy of the License at
|
||||
%%
|
||||
%% http://www.apache.org/licenses/LICENSE-2.0
|
||||
%%
|
||||
%% Unless required by applicable law or agreed to in writing, software
|
||||
%% distributed under the License is distributed on an "AS IS" BASIS,
|
||||
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
%% See the License for the specific language governing permissions and
|
||||
%% limitations under the License.
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
%% MQTT/QUIC Stream
|
||||
-module(emqx_quic_stream).
|
||||
|
||||
%% emqx transport Callbacks
|
||||
-export([ type/1
|
||||
, wait/1
|
||||
, getstat/2
|
||||
, fast_close/1
|
||||
, ensure_ok_or_exit/2
|
||||
, async_send/3
|
||||
, setopts/2
|
||||
, getopts/2
|
||||
, peername/1
|
||||
, sockname/1
|
||||
, peercert/1
|
||||
]).
|
||||
|
||||
wait(Conn) ->
|
||||
quicer:accept_stream(Conn, []).
|
||||
|
||||
type(_) ->
|
||||
quic.
|
||||
|
||||
peername(S) ->
|
||||
quicer:peername(S).
|
||||
|
||||
sockname(S) ->
|
||||
quicer:sockname(S).
|
||||
|
||||
peercert(_S) ->
|
||||
nossl.
|
||||
|
||||
getstat(Socket, Stats) ->
|
||||
case quicer:getstat(Socket, Stats) of
|
||||
{error, _} -> {error, closed};
|
||||
Res -> Res
|
||||
end.
|
||||
|
||||
setopts(Socket, Opts) ->
|
||||
lists:foreach(fun({Opt, V}) when is_atom(Opt) ->
|
||||
quicer:setopt(Socket, Opt, V);
|
||||
(Opt) when is_atom(Opt) ->
|
||||
quicer:setopt(Socket, Opt, true)
|
||||
end, Opts),
|
||||
ok.
|
||||
|
||||
getopts(_Socket, _Opts) ->
|
||||
%% @todo
|
||||
{ok, [{high_watermark, 0},
|
||||
{high_msgq_watermark, 0},
|
||||
{sndbuf, 0},
|
||||
{recbuf, 0},
|
||||
{buffer,80000}]}.
|
||||
|
||||
fast_close(Stream) ->
|
||||
%% Stream might be closed already.
|
||||
_ = quicer:async_close_stream(Stream),
|
||||
ok.
|
||||
|
||||
-spec(ensure_ok_or_exit(atom(), list(term())) -> term()).
|
||||
ensure_ok_or_exit(Fun, Args = [Sock|_]) when is_atom(Fun), is_list(Args) ->
|
||||
case erlang:apply(?MODULE, Fun, Args) of
|
||||
{error, Reason} when Reason =:= enotconn; Reason =:= closed ->
|
||||
fast_close(Sock),
|
||||
exit(normal);
|
||||
{error, Reason} ->
|
||||
fast_close(Sock),
|
||||
exit({shutdown, Reason});
|
||||
Result -> Result
|
||||
end.
|
||||
|
||||
async_send(Stream, Data, Options) when is_list(Data) ->
|
||||
async_send(Stream, iolist_to_binary(Data), Options);
|
||||
async_send(Stream, Data, _Options) when is_binary(Data) ->
|
||||
{ok, _Len} = quicer:send(Stream, Data),
|
||||
ok.
|
|
@ -170,16 +170,11 @@ frame_error(frame_too_large) -> ?RC_PACKET_TOO_LARGE;
|
|||
frame_error(_) -> ?RC_MALFORMED_PACKET.
|
||||
|
||||
connack_error(protocol_error) -> ?RC_PROTOCOL_ERROR;
|
||||
connack_error(client_identifier_not_valid) -> ?RC_CLIENT_IDENTIFIER_NOT_VALID;
|
||||
connack_error(bad_username_or_password) -> ?RC_BAD_USER_NAME_OR_PASSWORD;
|
||||
connack_error(bad_clientid_or_password) -> ?RC_BAD_USER_NAME_OR_PASSWORD;
|
||||
connack_error(username_or_password_undefined) -> ?RC_BAD_USER_NAME_OR_PASSWORD;
|
||||
connack_error(password_error) -> ?RC_BAD_USER_NAME_OR_PASSWORD;
|
||||
connack_error(not_authorized) -> ?RC_NOT_AUTHORIZED;
|
||||
connack_error(server_unavailable) -> ?RC_SERVER_UNAVAILABLE;
|
||||
connack_error(server_busy) -> ?RC_SERVER_BUSY;
|
||||
connack_error(banned) -> ?RC_BANNED;
|
||||
connack_error(bad_authentication_method) -> ?RC_BAD_AUTHENTICATION_METHOD;
|
||||
%% TODO: ???
|
||||
connack_error(_) -> ?RC_NOT_AUTHORIZED.
|
||||
connack_error(_) -> ?RC_UNSPECIFIED_ERROR.
|
||||
|
||||
|
|
|
@ -251,7 +251,7 @@ delete_trie_route(Route = #route{topic = Topic}) ->
|
|||
%% @private
|
||||
-spec(maybe_trans(function(), list(any())) -> ok | {error, term()}).
|
||||
maybe_trans(Fun, Args) ->
|
||||
case persistent_term:get(emqx_route_lock_type) of
|
||||
case emqx_config:get([broker, perf, route_lock_type]) of
|
||||
key ->
|
||||
trans(Fun, Args);
|
||||
global ->
|
||||
|
|
|
@ -33,11 +33,6 @@ init([]) ->
|
|||
shutdown => 5000,
|
||||
type => worker,
|
||||
modules => [emqx_router_helper]},
|
||||
|
||||
ok = persistent_term:put(emqx_route_lock_type,
|
||||
application:get_env(emqx, route_lock_type, key)
|
||||
),
|
||||
|
||||
%% Router pool
|
||||
RouterPool = emqx_pool_sup:spec([router_pool, hash,
|
||||
{emqx_router, start_link, []}]),
|
||||
|
|
|
@ -53,11 +53,9 @@ cast(Key, Node, Mod, Fun, Args) ->
|
|||
filter_result(?RPC:cast(rpc_node({Key, Node}), Mod, Fun, Args)).
|
||||
|
||||
rpc_node(Node) when is_atom(Node) ->
|
||||
ClientNum = application:get_env(gen_rpc, tcp_client_num, ?DefaultClientNum),
|
||||
{Node, rand:uniform(ClientNum)};
|
||||
{Node, rand:uniform(max_client_num())};
|
||||
rpc_node({Key, Node}) when is_atom(Node) ->
|
||||
ClientNum = application:get_env(gen_rpc, tcp_client_num, ?DefaultClientNum),
|
||||
{Node, erlang:phash2(Key, ClientNum) + 1}.
|
||||
{Node, erlang:phash2(Key, max_client_num()) + 1}.
|
||||
|
||||
rpc_nodes(Nodes) ->
|
||||
rpc_nodes(Nodes, []).
|
||||
|
@ -72,3 +70,6 @@ filter_result({Error, Reason})
|
|||
{badrpc, Reason};
|
||||
filter_result(Delivery) ->
|
||||
Delivery.
|
||||
|
||||
max_client_num() ->
|
||||
emqx_config:get([rpc, tcp_client_num], ?DefaultClientNum).
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -55,7 +55,7 @@
|
|||
-compile(nowarn_export_all).
|
||||
-endif.
|
||||
|
||||
-export([init/2]).
|
||||
-export([init/1]).
|
||||
|
||||
-export([ info/1
|
||||
, info/2
|
||||
|
@ -92,13 +92,11 @@
|
|||
|
||||
-export_type([session/0]).
|
||||
|
||||
-import(emqx_zone, [get_env/3]).
|
||||
|
||||
-record(session, {
|
||||
%% Client’s Subscriptions.
|
||||
subscriptions :: map(),
|
||||
%% Max subscriptions allowed
|
||||
max_subscriptions :: non_neg_integer(),
|
||||
max_subscriptions :: non_neg_integer() | infinity,
|
||||
%% Upgrade QoS?
|
||||
upgrade_qos :: boolean(),
|
||||
%% Client <- Broker: QoS1/2 messages sent to the client but
|
||||
|
@ -117,7 +115,7 @@
|
|||
%% have not been completely acknowledged
|
||||
awaiting_rel :: map(),
|
||||
%% Maximum number of awaiting QoS2 messages allowed
|
||||
max_awaiting_rel :: non_neg_integer(),
|
||||
max_awaiting_rel :: non_neg_integer() | infinity,
|
||||
%% Awaiting PUBREL Timeout (Unit: millsecond)
|
||||
await_rel_timeout :: timeout(),
|
||||
%% Created at
|
||||
|
@ -153,33 +151,39 @@
|
|||
|
||||
-define(DEFAULT_BATCH_N, 1000).
|
||||
|
||||
-type options() :: #{ max_subscriptions => non_neg_integer()
|
||||
, upgrade_qos => boolean()
|
||||
, retry_interval => timeout()
|
||||
, max_awaiting_rel => non_neg_integer() | infinity
|
||||
, await_rel_timeout => timeout()
|
||||
, max_inflight => integer()
|
||||
, mqueue => emqx_mqueue:options()
|
||||
}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Init a Session
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
-spec(init(emqx_types:clientinfo(), emqx_types:conninfo()) -> session()).
|
||||
init(#{zone := Zone}, #{receive_maximum := MaxInflight}) ->
|
||||
#session{max_subscriptions = get_env(Zone, max_subscriptions, 0),
|
||||
subscriptions = #{},
|
||||
upgrade_qos = get_env(Zone, upgrade_qos, false),
|
||||
inflight = emqx_inflight:new(MaxInflight),
|
||||
mqueue = init_mqueue(Zone),
|
||||
next_pkt_id = 1,
|
||||
retry_interval = timer:seconds(get_env(Zone, retry_interval, 0)),
|
||||
awaiting_rel = #{},
|
||||
max_awaiting_rel = get_env(Zone, max_awaiting_rel, 100),
|
||||
await_rel_timeout = timer:seconds(get_env(Zone, await_rel_timeout, 300)),
|
||||
created_at = erlang:system_time(millisecond)
|
||||
}.
|
||||
|
||||
%% @private init mq
|
||||
init_mqueue(Zone) ->
|
||||
emqx_mqueue:init(#{max_len => get_env(Zone, max_mqueue_len, 1000),
|
||||
store_qos0 => get_env(Zone, mqueue_store_qos0, true),
|
||||
priorities => get_env(Zone, mqueue_priorities, none),
|
||||
default_priority => get_env(Zone, mqueue_default_priority, lowest)
|
||||
}).
|
||||
-spec(init(options()) -> session()).
|
||||
init(Opts) ->
|
||||
MaxInflight = maps:get(max_inflight, Opts, 1),
|
||||
QueueOpts = maps:merge(
|
||||
#{max_len => 1000,
|
||||
store_qos0 => true
|
||||
}, maps:get(mqueue, Opts, #{})),
|
||||
#session{
|
||||
max_subscriptions = maps:get(max_subscriptions, Opts, infinity),
|
||||
subscriptions = #{},
|
||||
upgrade_qos = maps:get(upgrade_qos, Opts, false),
|
||||
inflight = emqx_inflight:new(MaxInflight),
|
||||
mqueue = emqx_mqueue:init(QueueOpts),
|
||||
next_pkt_id = 1,
|
||||
retry_interval = maps:get(retry_interval, Opts, 30000),
|
||||
awaiting_rel = #{},
|
||||
max_awaiting_rel = maps:get(max_awaiting_rel, Opts, 100),
|
||||
await_rel_timeout = maps:get(await_rel_timeout, Opts, 300000),
|
||||
created_at = erlang:system_time(millisecond)
|
||||
}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Info, Stats
|
||||
|
@ -207,7 +211,7 @@ info(inflight_cnt, #session{inflight = Inflight}) ->
|
|||
info(inflight_max, #session{inflight = Inflight}) ->
|
||||
emqx_inflight:max_size(Inflight);
|
||||
info(retry_interval, #session{retry_interval = Interval}) ->
|
||||
Interval div 1000;
|
||||
Interval;
|
||||
info(mqueue, #session{mqueue = MQueue}) ->
|
||||
MQueue;
|
||||
info(mqueue_len, #session{mqueue = MQueue}) ->
|
||||
|
@ -225,7 +229,7 @@ info(awaiting_rel_cnt, #session{awaiting_rel = AwaitingRel}) ->
|
|||
info(awaiting_rel_max, #session{max_awaiting_rel = Max}) ->
|
||||
Max;
|
||||
info(await_rel_timeout, #session{await_rel_timeout = Timeout}) ->
|
||||
Timeout div 1000;
|
||||
Timeout;
|
||||
info(created_at, #session{created_at = CreatedAt}) ->
|
||||
CreatedAt.
|
||||
|
||||
|
@ -253,7 +257,7 @@ subscribe(ClientInfo = #{clientid := ClientId}, TopicFilter, SubOpts,
|
|||
end.
|
||||
|
||||
-compile({inline, [is_subscriptions_full/1]}).
|
||||
is_subscriptions_full(#session{max_subscriptions = 0}) ->
|
||||
is_subscriptions_full(#session{max_subscriptions = infinity}) ->
|
||||
false;
|
||||
is_subscriptions_full(#session{subscriptions = Subs,
|
||||
max_subscriptions = MaxLimit}) ->
|
||||
|
@ -302,7 +306,7 @@ publish(_PacketId, Msg, Session) ->
|
|||
{ok, emqx_broker:publish(Msg), Session}.
|
||||
|
||||
-compile({inline, [is_awaiting_full/1]}).
|
||||
is_awaiting_full(#session{max_awaiting_rel = 0}) ->
|
||||
is_awaiting_full(#session{max_awaiting_rel = infinity}) ->
|
||||
false;
|
||||
is_awaiting_full(#session{awaiting_rel = AwaitingRel,
|
||||
max_awaiting_rel = MaxLimit}) ->
|
||||
|
@ -696,4 +700,3 @@ age(Now, Ts) -> Now - Ts.
|
|||
set_field(Name, Value, Session) ->
|
||||
Pos = emqx_misc:index_of(Name, record_info(fields, session)),
|
||||
setelement(Pos+1, Session, Value).
|
||||
|
||||
|
|
|
@ -77,6 +77,8 @@
|
|||
-define(NACK(Reason), {shared_sub_nack, Reason}).
|
||||
-define(NO_ACK, no_ack).
|
||||
|
||||
-rlog_shard({?SHARED_SUB_SHARD, ?TAB}).
|
||||
|
||||
-record(state, {pmon}).
|
||||
|
||||
-record(emqx_shared_subscription, {group, topic, subpid}).
|
||||
|
@ -135,11 +137,11 @@ dispatch(Group, Topic, Delivery = #delivery{message = Msg}, FailedSubs) ->
|
|||
|
||||
-spec(strategy() -> strategy()).
|
||||
strategy() ->
|
||||
emqx:get_env(shared_subscription_strategy, random).
|
||||
emqx_config:get([broker, shared_subscription_strategy]).
|
||||
|
||||
-spec(ack_enabled() -> boolean()).
|
||||
ack_enabled() ->
|
||||
emqx:get_env(shared_dispatch_ack_enabled, false).
|
||||
emqx_config:get([broker, shared_dispatch_ack_enabled]).
|
||||
|
||||
do_dispatch(SubPid, Topic, Msg, _Type) when SubPid =:= self() ->
|
||||
%% Deadlock otherwise
|
||||
|
@ -297,7 +299,7 @@ subscribers(Group, Topic) ->
|
|||
|
||||
init([]) ->
|
||||
{ok, _} = mnesia:subscribe({table, ?TAB, simple}),
|
||||
{atomic, PMon} = mnesia:transaction(fun init_monitors/0),
|
||||
{atomic, PMon} = ekka_mnesia:transaction(?SHARED_SUB_SHARD, fun init_monitors/0),
|
||||
ok = emqx_tables:new(?SHARED_SUBS, [protected, bag]),
|
||||
ok = emqx_tables:new(?ALIVE_SUBS, [protected, set, {read_concurrency, true}]),
|
||||
{ok, update_stats(#state{pmon = PMon})}.
|
||||
|
@ -309,7 +311,7 @@ init_monitors() ->
|
|||
end, emqx_pmon:new(), ?TAB).
|
||||
|
||||
handle_call({subscribe, Group, Topic, SubPid}, _From, State = #state{pmon = PMon}) ->
|
||||
mnesia:dirty_write(?TAB, record(Group, Topic, SubPid)),
|
||||
ekka_mnesia:dirty_write(?TAB, record(Group, Topic, SubPid)),
|
||||
case ets:member(?SHARED_SUBS, {Group, Topic}) of
|
||||
true -> ok;
|
||||
false -> ok = emqx_router:do_add_route(Topic, {Group, node()})
|
||||
|
@ -319,7 +321,7 @@ handle_call({subscribe, Group, Topic, SubPid}, _From, State = #state{pmon = PMon
|
|||
{reply, ok, update_stats(State#state{pmon = emqx_pmon:monitor(SubPid, PMon)})};
|
||||
|
||||
handle_call({unsubscribe, Group, Topic, SubPid}, _From, State) ->
|
||||
mnesia:dirty_delete_object(?TAB, record(Group, Topic, SubPid)),
|
||||
ekka_mnesia:dirty_delete_object(?TAB, record(Group, Topic, SubPid)),
|
||||
true = ets:delete_object(?SHARED_SUBS, {{Group, Topic}, SubPid}),
|
||||
delete_route_if_needed({Group, Topic}),
|
||||
{reply, ok, State};
|
||||
|
@ -373,7 +375,7 @@ cleanup_down(SubPid) ->
|
|||
?IS_LOCAL_PID(SubPid) orelse ets:delete(?ALIVE_SUBS, SubPid),
|
||||
lists:foreach(
|
||||
fun(Record = #emqx_shared_subscription{topic = Topic, group = Group}) ->
|
||||
ok = mnesia:dirty_delete_object(?TAB, Record),
|
||||
ok = ekka_mnesia:dirty_delete_object(?TAB, Record),
|
||||
true = ets:delete_object(?SHARED_SUBS, {{Group, Topic}, SubPid}),
|
||||
delete_route_if_needed({Group, Topic})
|
||||
end, mnesia:dirty_match_object(#emqx_shared_subscription{_ = '_', subpid = SubPid})).
|
||||
|
|
|
@ -32,8 +32,6 @@
|
|||
, uptime/0
|
||||
, datetime/0
|
||||
, sysdescr/0
|
||||
, sys_interval/0
|
||||
, sys_heatbeat_interval/0
|
||||
]).
|
||||
|
||||
-export([info/0]).
|
||||
|
@ -104,15 +102,11 @@ datetime() ->
|
|||
io_lib:format(
|
||||
"~4..0w-~2..0w-~2..0w ~2..0w:~2..0w:~2..0w", [Y, M, D, H, MM, S])).
|
||||
|
||||
%% @doc Get sys interval
|
||||
-spec(sys_interval() -> pos_integer()).
|
||||
sys_interval() ->
|
||||
emqx:get_env(broker_sys_interval, 60000).
|
||||
emqx_config:get([broker, sys_msg_interval]).
|
||||
|
||||
%% @doc Get sys heatbeat interval
|
||||
-spec(sys_heatbeat_interval() -> pos_integer()).
|
||||
sys_heatbeat_interval() ->
|
||||
emqx:get_env(broker_sys_heartbeat, 30000).
|
||||
emqx_config:get([broker, sys_heartbeat_interval]).
|
||||
|
||||
%% @doc Get sys info
|
||||
-spec(info() -> list(tuple())).
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
|
||||
-logger_header("[SYSMON]").
|
||||
|
||||
-export([start_link/1]).
|
||||
-export([start_link/0]).
|
||||
|
||||
%% compress unused warning
|
||||
-export([procinfo/1]).
|
||||
|
@ -37,25 +37,19 @@
|
|||
, code_change/3
|
||||
]).
|
||||
|
||||
-type(option() :: {long_gc, non_neg_integer()}
|
||||
| {long_schedule, non_neg_integer()}
|
||||
| {large_heap, non_neg_integer()}
|
||||
| {busy_port, boolean()}
|
||||
| {busy_dist_port, boolean()}).
|
||||
|
||||
-define(SYSMON, ?MODULE).
|
||||
|
||||
%% @doc Start the system monitor.
|
||||
-spec(start_link(list(option())) -> startlink_ret()).
|
||||
start_link(Opts) ->
|
||||
gen_server:start_link({local, ?SYSMON}, ?MODULE, [Opts], []).
|
||||
-spec(start_link() -> startlink_ret()).
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?SYSMON}, ?MODULE, [], []).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% gen_server callbacks
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
init([Opts]) ->
|
||||
_ = erlang:system_monitor(self(), parse_opt(Opts)),
|
||||
init([]) ->
|
||||
_ = erlang:system_monitor(self(), sysm_opts()),
|
||||
emqx_logger:set_proc_metadata(#{sysmon => true}),
|
||||
|
||||
%% Monitor cluster partition event
|
||||
|
@ -66,30 +60,28 @@ init([Opts]) ->
|
|||
start_timer(State) ->
|
||||
State#{timer := emqx_misc:start_timer(timer:seconds(2), reset)}.
|
||||
|
||||
parse_opt(Opts) ->
|
||||
parse_opt(Opts, []).
|
||||
parse_opt([], Acc) ->
|
||||
sysm_opts() ->
|
||||
sysm_opts(maps:to_list(emqx_config:get([sysmon, vm])), []).
|
||||
sysm_opts([], Acc) ->
|
||||
Acc;
|
||||
parse_opt([{long_gc, 0}|Opts], Acc) ->
|
||||
parse_opt(Opts, Acc);
|
||||
parse_opt([{long_gc, Ms}|Opts], Acc) when is_integer(Ms) ->
|
||||
parse_opt(Opts, [{long_gc, Ms}|Acc]);
|
||||
parse_opt([{long_schedule, 0}|Opts], Acc) ->
|
||||
parse_opt(Opts, Acc);
|
||||
parse_opt([{long_schedule, Ms}|Opts], Acc) when is_integer(Ms) ->
|
||||
parse_opt(Opts, [{long_schedule, Ms}|Acc]);
|
||||
parse_opt([{large_heap, Size}|Opts], Acc) when is_integer(Size) ->
|
||||
parse_opt(Opts, [{large_heap, Size}|Acc]);
|
||||
parse_opt([{busy_port, true}|Opts], Acc) ->
|
||||
parse_opt(Opts, [busy_port|Acc]);
|
||||
parse_opt([{busy_port, false}|Opts], Acc) ->
|
||||
parse_opt(Opts, Acc);
|
||||
parse_opt([{busy_dist_port, true}|Opts], Acc) ->
|
||||
parse_opt(Opts, [busy_dist_port|Acc]);
|
||||
parse_opt([{busy_dist_port, false}|Opts], Acc) ->
|
||||
parse_opt(Opts, Acc);
|
||||
parse_opt([_Opt|Opts], Acc) ->
|
||||
parse_opt(Opts, Acc).
|
||||
sysm_opts([{_, disabled}|Opts], Acc) ->
|
||||
sysm_opts(Opts, Acc);
|
||||
sysm_opts([{long_gc, Ms}|Opts], Acc) when is_integer(Ms) ->
|
||||
sysm_opts(Opts, [{long_gc, Ms}|Acc]);
|
||||
sysm_opts([{long_schedule, Ms}|Opts], Acc) when is_integer(Ms) ->
|
||||
sysm_opts(Opts, [{long_schedule, Ms}|Acc]);
|
||||
sysm_opts([{large_heap, Size}|Opts], Acc) when is_integer(Size) ->
|
||||
sysm_opts(Opts, [{large_heap, Size}|Acc]);
|
||||
sysm_opts([{busy_port, true}|Opts], Acc) ->
|
||||
sysm_opts(Opts, [busy_port|Acc]);
|
||||
sysm_opts([{busy_port, false}|Opts], Acc) ->
|
||||
sysm_opts(Opts, Acc);
|
||||
sysm_opts([{busy_dist_port, true}|Opts], Acc) ->
|
||||
sysm_opts(Opts, [busy_dist_port|Acc]);
|
||||
sysm_opts([{busy_dist_port, false}|Opts], Acc) ->
|
||||
sysm_opts(Opts, Acc);
|
||||
sysm_opts([_Opt|Opts], Acc) ->
|
||||
sysm_opts(Opts, Acc).
|
||||
|
||||
handle_call(Req, _From, State) ->
|
||||
?LOG(error, "Unexpected call: ~p", [Req]),
|
||||
|
|
|
@ -27,10 +27,10 @@ start_link() ->
|
|||
|
||||
init([]) ->
|
||||
Childs = [child_spec(emqx_sys),
|
||||
child_spec(emqx_alarm, [config(alarm)]),
|
||||
child_spec(emqx_sys_mon, [config(sysmon)]),
|
||||
child_spec(emqx_os_mon, [config(os_mon)]),
|
||||
child_spec(emqx_vm_mon, [config(vm_mon)])],
|
||||
child_spec(emqx_alarm),
|
||||
child_spec(emqx_sys_mon),
|
||||
child_spec(emqx_os_mon),
|
||||
child_spec(emqx_vm_mon)],
|
||||
{ok, {{one_for_one, 10, 100}, Childs}}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
|
@ -48,6 +48,3 @@ child_spec(Mod, Args) ->
|
|||
type => worker,
|
||||
modules => [Mod]
|
||||
}.
|
||||
|
||||
config(Name) -> emqx:get_env(Name, []).
|
||||
|
||||
|
|
|
@ -161,17 +161,16 @@ drop_tls13_for_old_otp(SslOpts) ->
|
|||
, "TLS_AES_128_CCM_8_SHA256"
|
||||
]).
|
||||
drop_tls13(SslOpts0) ->
|
||||
SslOpts1 = case proplists:get_value(versions, SslOpts0) of
|
||||
undefined -> SslOpts0;
|
||||
Vsns -> replace(SslOpts0, versions, Vsns -- ['tlsv1.3'])
|
||||
SslOpts1 = case maps:find(versions, SslOpts0) of
|
||||
error -> SslOpts0;
|
||||
{ok, Vsns} -> SslOpts0#{versions => (Vsns -- ['tlsv1.3'])}
|
||||
end,
|
||||
case proplists:get_value(ciphers, SslOpts1) of
|
||||
undefined -> SslOpts1;
|
||||
Ciphers -> replace(SslOpts1, ciphers, Ciphers -- ?TLSV13_EXCLUSIVE_CIPHERS)
|
||||
case maps:find(ciphers, SslOpts1) of
|
||||
error -> SslOpts1;
|
||||
{ok, Ciphers} ->
|
||||
SslOpts1#{ciphers => Ciphers -- ?TLSV13_EXCLUSIVE_CIPHERS}
|
||||
end.
|
||||
|
||||
replace(Opts, Key, Value) -> [{Key, Value} | proplists:delete(Key, Opts)].
|
||||
|
||||
-if(?OTP_RELEASE > 22).
|
||||
-ifdef(TEST).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
@ -181,13 +180,13 @@ drop_tls13_test() ->
|
|||
?assert(lists:member('tlsv1.3', Versions)),
|
||||
Ciphers = default_ciphers(),
|
||||
?assert(has_tlsv13_cipher(Ciphers)),
|
||||
Opts0 = [{versions, Versions}, {ciphers, Ciphers}, other, {bool, true}],
|
||||
Opts0 = #{versions => Versions, ciphers => Ciphers, other => true},
|
||||
Opts = drop_tls13(Opts0),
|
||||
?assertNot(lists:member('tlsv1.3', proplists:get_value(versions, Opts))),
|
||||
?assertNot(has_tlsv13_cipher(proplists:get_value(ciphers, Opts))).
|
||||
?assertNot(lists:member('tlsv1.3', maps:get(versions, Opts, undefined))),
|
||||
?assertNot(has_tlsv13_cipher(maps:get(ciphers, Opts, undefined))).
|
||||
|
||||
drop_tls13_no_versions_cipers_test() ->
|
||||
Opts0 = [other, {bool, true}],
|
||||
Opts0 = #{other => 0, bool => true},
|
||||
Opts = drop_tls13(Opts0),
|
||||
?_assertEqual(Opts0, Opts).
|
||||
|
||||
|
|
|
@ -28,14 +28,14 @@
|
|||
-export([ insert/1
|
||||
, match/1
|
||||
, delete/1
|
||||
, put_compaction_flag/1
|
||||
, put_default_compaction_flag/0
|
||||
]).
|
||||
|
||||
-export([ empty/0
|
||||
, lock_tables/0
|
||||
]).
|
||||
|
||||
-export([is_compact/0, set_compact/1]).
|
||||
|
||||
-ifdef(TEST).
|
||||
-compile(export_all).
|
||||
-compile(nowarn_export_all).
|
||||
|
@ -50,21 +50,12 @@
|
|||
, count = 0 :: non_neg_integer()
|
||||
}).
|
||||
|
||||
-define(IS_COMPACT, true).
|
||||
|
||||
-rlog_shard({?ROUTE_SHARD, ?TRIE}).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Mnesia bootstrap
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
put_compaction_flag(Bool) when is_boolean(Bool) ->
|
||||
_ = persistent_term:put({?MODULE, compaction}, Bool),
|
||||
ok.
|
||||
|
||||
put_default_compaction_flag() ->
|
||||
ok = put_compaction_flag(?IS_COMPACT).
|
||||
|
||||
%% @doc Create or replicate topics table.
|
||||
-spec(mnesia(boot | copy) -> ok).
|
||||
mnesia(boot) ->
|
||||
|
@ -279,16 +270,10 @@ match_compact([Word | Words], Prefix, IsWildcard, Acc0) ->
|
|||
lookup_topic(MlTopic).
|
||||
|
||||
is_compact() ->
|
||||
case persistent_term:get({?MODULE, compaction}, undefined) of
|
||||
undefined ->
|
||||
Default = ?IS_COMPACT,
|
||||
FromEnv = emqx:get_env(trie_compaction, Default),
|
||||
_ = put_compaction_flag(FromEnv),
|
||||
true = is_boolean(FromEnv),
|
||||
FromEnv;
|
||||
Value when is_boolean(Value) ->
|
||||
Value
|
||||
end.
|
||||
emqx_config:get([broker, perf, trie_compaction], true).
|
||||
|
||||
set_compact(Bool) ->
|
||||
emqx_config:put([broker, perf, trie_compaction], Bool).
|
||||
|
||||
-ifdef(TEST).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
@ -315,10 +300,11 @@ words(T) -> emqx_topic:words(T).
|
|||
|
||||
make_prefixes_t(Topic) -> make_prefixes(words(Topic)).
|
||||
|
||||
with_compact_flag(IsCmopact, F) ->
|
||||
put_compaction_flag(IsCmopact),
|
||||
with_compact_flag(IsCompact, F) ->
|
||||
OldV = is_compact(),
|
||||
set_compact(IsCompact),
|
||||
try F()
|
||||
after put_default_compaction_flag()
|
||||
after set_compact(OldV)
|
||||
end.
|
||||
|
||||
make_prefixes_test_() ->
|
||||
|
|
|
@ -94,14 +94,16 @@
|
|||
-type(ver() :: ?MQTT_PROTO_V3
|
||||
| ?MQTT_PROTO_V4
|
||||
| ?MQTT_PROTO_V5
|
||||
| non_neg_integer()).
|
||||
| non_neg_integer()
|
||||
| binary() % For lwm2m, mqtt-sn...
|
||||
).
|
||||
|
||||
-type(qos() :: ?QOS_0 | ?QOS_1 | ?QOS_2).
|
||||
-type(qos_name() :: qos0 | at_most_once |
|
||||
qos1 | at_least_once |
|
||||
qos2 | exactly_once).
|
||||
|
||||
-type(zone() :: emqx_zone:zone()).
|
||||
-type(zone() :: atom()).
|
||||
-type(pubsub() :: publish | subscribe).
|
||||
-type(topic() :: emqx_topic:topic()).
|
||||
-type(subid() :: binary() | atom()).
|
||||
|
@ -209,7 +211,8 @@
|
|||
-type(infos() :: #{atom() => term()}).
|
||||
-type(stats() :: [{atom(), term()}]).
|
||||
|
||||
-type(oom_policy() :: #{message_queue_len => non_neg_integer(),
|
||||
max_heap_size => non_neg_integer()
|
||||
-type(oom_policy() :: #{max_message_queue_len => non_neg_integer(),
|
||||
max_heap_size => non_neg_integer(),
|
||||
enable => boolean()
|
||||
}).
|
||||
|
||||
|
|
|
@ -21,15 +21,7 @@
|
|||
-include("logger.hrl").
|
||||
|
||||
%% APIs
|
||||
-export([start_link/1]).
|
||||
|
||||
-export([ get_check_interval/0
|
||||
, set_check_interval/1
|
||||
, get_process_high_watermark/0
|
||||
, set_process_high_watermark/1
|
||||
, get_process_low_watermark/0
|
||||
, set_process_low_watermark/1
|
||||
]).
|
||||
-export([start_link/0]).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([ init/1
|
||||
|
@ -42,61 +34,19 @@
|
|||
|
||||
-define(VM_MON, ?MODULE).
|
||||
|
||||
start_link(Opts) ->
|
||||
gen_server:start_link({local, ?VM_MON}, ?MODULE, [Opts], []).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% API
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
get_check_interval() ->
|
||||
call(get_check_interval).
|
||||
|
||||
set_check_interval(Seconds) ->
|
||||
call({set_check_interval, Seconds}).
|
||||
|
||||
get_process_high_watermark() ->
|
||||
call(get_process_high_watermark).
|
||||
|
||||
set_process_high_watermark(Float) ->
|
||||
call({set_process_high_watermark, Float}).
|
||||
|
||||
get_process_low_watermark() ->
|
||||
call(get_process_low_watermark).
|
||||
|
||||
set_process_low_watermark(Float) ->
|
||||
call({set_process_low_watermark, Float}).
|
||||
|
||||
call(Req) ->
|
||||
gen_server:call(?VM_MON, Req, infinity).
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?VM_MON}, ?MODULE, [], []).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% gen_server callbacks
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
init([Opts]) ->
|
||||
{ok, ensure_check_timer(#{check_interval => proplists:get_value(check_interval, Opts),
|
||||
process_high_watermark => proplists:get_value(process_high_watermark, Opts),
|
||||
process_low_watermark => proplists:get_value(process_low_watermark, Opts),
|
||||
timer => undefined})}.
|
||||
|
||||
handle_call(get_check_interval, _From, State) ->
|
||||
{reply, maps:get(check_interval, State, undefined), State};
|
||||
|
||||
handle_call({set_check_interval, Seconds}, _From, State) ->
|
||||
{reply, ok, State#{check_interval := Seconds}};
|
||||
|
||||
handle_call(get_process_high_watermark, _From, State) ->
|
||||
{reply, maps:get(process_high_watermark, State, undefined), State};
|
||||
|
||||
handle_call({set_process_high_watermark, Float}, _From, State) ->
|
||||
{reply, ok, State#{process_high_watermark := Float}};
|
||||
|
||||
handle_call(get_process_low_watermark, _From, State) ->
|
||||
{reply, maps:get(process_low_watermark, State, undefined), State};
|
||||
|
||||
handle_call({set_process_low_watermark, Float}, _From, State) ->
|
||||
{reply, ok, State#{process_low_watermark := Float}};
|
||||
init([]) ->
|
||||
start_check_timer(),
|
||||
{ok, #{}}.
|
||||
|
||||
handle_call(Req, _From, State) ->
|
||||
?LOG(error, "[VM_MON] Unexpected call: ~p", [Req]),
|
||||
|
@ -106,29 +56,30 @@ handle_cast(Msg, State) ->
|
|||
?LOG(error, "[VM_MON] Unexpected cast: ~p", [Msg]),
|
||||
{noreply, State}.
|
||||
|
||||
handle_info({timeout, Timer, check},
|
||||
State = #{timer := Timer,
|
||||
process_high_watermark := ProcHighWatermark,
|
||||
process_low_watermark := ProcLowWatermark}) ->
|
||||
handle_info({timeout, _Timer, check}, State) ->
|
||||
ProcHighWatermark = emqx_config:get([sysmon, vm, process_high_watermark]),
|
||||
ProcLowWatermark = emqx_config:get([sysmon, vm, process_low_watermark]),
|
||||
ProcessCount = erlang:system_info(process_count),
|
||||
case ProcessCount / erlang:system_info(process_limit) * 100 of
|
||||
case ProcessCount / erlang:system_info(process_limit) of
|
||||
Percent when Percent >= ProcHighWatermark ->
|
||||
emqx_alarm:activate(too_many_processes, #{usage => Percent,
|
||||
high_watermark => ProcHighWatermark,
|
||||
low_watermark => ProcLowWatermark});
|
||||
emqx_alarm:activate(too_many_processes, #{
|
||||
usage => io_lib:format("~p%", [Percent*100]),
|
||||
high_watermark => ProcHighWatermark,
|
||||
low_watermark => ProcLowWatermark});
|
||||
Percent when Percent < ProcLowWatermark ->
|
||||
emqx_alarm:deactivate(too_many_processes);
|
||||
_Precent ->
|
||||
ok
|
||||
end,
|
||||
{noreply, ensure_check_timer(State)};
|
||||
start_check_timer(),
|
||||
{noreply, State};
|
||||
|
||||
handle_info(Info, State) ->
|
||||
?LOG(error, "[VM_MON] Unexpected info: ~p", [Info]),
|
||||
{noreply, State}.
|
||||
|
||||
terminate(_Reason, #{timer := Timer}) ->
|
||||
emqx_misc:cancel_timer(Timer).
|
||||
terminate(_Reason, _State) ->
|
||||
ok.
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
@ -137,5 +88,6 @@ code_change(_OldVsn, State, _Extra) ->
|
|||
%% Internal functions
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
ensure_check_timer(State = #{check_interval := Interval}) ->
|
||||
State#{timer := emqx_misc:start_timer(timer:seconds(Interval), check)}.
|
||||
start_check_timer() ->
|
||||
Interval = emqx_config:get([sysmon, vm, process_check_interval]),
|
||||
emqx_misc:start_timer(Interval, check).
|
||||
|
|
|
@ -62,8 +62,6 @@
|
|||
sockname :: emqx_types:peername(),
|
||||
%% Sock state
|
||||
sockstate :: emqx_types:sockstate(),
|
||||
%% Simulate the active_n opt
|
||||
active_n :: pos_integer(),
|
||||
%% MQTT Piggyback
|
||||
mqtt_piggyback :: single | multiple,
|
||||
%% Limiter
|
||||
|
@ -85,7 +83,11 @@
|
|||
%% Idle Timeout
|
||||
idle_timeout :: timeout(),
|
||||
%% Idle Timer
|
||||
idle_timer :: maybe(reference())
|
||||
idle_timer :: maybe(reference()),
|
||||
%% Zone name
|
||||
zone :: atom(),
|
||||
%% Listener Name
|
||||
listener :: atom()
|
||||
}).
|
||||
|
||||
-type(state() :: #state{}).
|
||||
|
@ -93,7 +95,7 @@
|
|||
-type(ws_cmd() :: {active, boolean()}|close).
|
||||
|
||||
-define(ACTIVE_N, 100).
|
||||
-define(INFO_KEYS, [socktype, peername, sockname, sockstate, active_n]).
|
||||
-define(INFO_KEYS, [socktype, peername, sockname, sockstate]).
|
||||
-define(SOCK_STATS, [recv_oct, recv_cnt, send_oct, send_cnt]).
|
||||
-define(CONN_STATS, [recv_pkt, recv_msg, send_pkt, send_msg]).
|
||||
|
||||
|
@ -124,8 +126,6 @@ info(sockname, #state{sockname = Sockname}) ->
|
|||
Sockname;
|
||||
info(sockstate, #state{sockstate = SockSt}) ->
|
||||
SockSt;
|
||||
info(active_n, #state{active_n = ActiveN}) ->
|
||||
ActiveN;
|
||||
info(limiter, #state{limiter = Limiter}) ->
|
||||
maybe_apply(fun emqx_limiter:info/1, Limiter);
|
||||
info(channel, #state{channel = Channel}) ->
|
||||
|
@ -174,21 +174,13 @@ call(WsPid, Req, Timeout) when is_pid(WsPid) ->
|
|||
%% WebSocket callbacks
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
init(Req, Opts) ->
|
||||
init(Req, #{zone := Zone, listener := Listener} = Opts) ->
|
||||
%% WS Transport Idle Timeout
|
||||
IdleTimeout = proplists:get_value(idle_timeout, Opts, 7200000),
|
||||
DeflateOptions = maps:from_list(proplists:get_value(deflate_options, Opts, [])),
|
||||
MaxFrameSize = case proplists:get_value(max_frame_size, Opts, 0) of
|
||||
0 -> infinity;
|
||||
I -> I
|
||||
end,
|
||||
Compress = proplists:get_bool(compress, Opts),
|
||||
WsOpts = #{compress => Compress,
|
||||
deflate_opts => DeflateOptions,
|
||||
max_frame_size => MaxFrameSize,
|
||||
idle_timeout => IdleTimeout
|
||||
WsOpts = #{compress => get_ws_opts(Zone, Listener, compress),
|
||||
deflate_opts => get_ws_opts(Zone, Listener, deflate_opts),
|
||||
max_frame_size => get_ws_opts(Zone, Listener, max_frame_size),
|
||||
idle_timeout => get_ws_opts(Zone, Listener, idle_timeout)
|
||||
},
|
||||
|
||||
case check_origin_header(Req, Opts) of
|
||||
{error, Message} ->
|
||||
?LOG(error, "Invalid Origin Header ~p~n", [Message]),
|
||||
|
@ -196,18 +188,17 @@ init(Req, Opts) ->
|
|||
ok -> parse_sec_websocket_protocol(Req, Opts, WsOpts)
|
||||
end.
|
||||
|
||||
parse_sec_websocket_protocol(Req, Opts, WsOpts) ->
|
||||
FailIfNoSubprotocol = proplists:get_value(fail_if_no_subprotocol, Opts),
|
||||
parse_sec_websocket_protocol(Req, #{zone := Zone, listener := Listener} = Opts, WsOpts) ->
|
||||
case cowboy_req:parse_header(<<"sec-websocket-protocol">>, Req) of
|
||||
undefined ->
|
||||
case FailIfNoSubprotocol of
|
||||
case get_ws_opts(Zone, Listener, fail_if_no_subprotocol) of
|
||||
true ->
|
||||
{ok, cowboy_req:reply(400, Req), WsOpts};
|
||||
false ->
|
||||
{cowboy_websocket, Req, [Req, Opts], WsOpts}
|
||||
end;
|
||||
Subprotocols ->
|
||||
SupportedSubprotocols = proplists:get_value(supported_subprotocols, Opts),
|
||||
SupportedSubprotocols = get_ws_opts(Zone, Listener, supported_subprotocols),
|
||||
NSupportedSubprotocols = [list_to_binary(Subprotocol)
|
||||
|| Subprotocol <- SupportedSubprotocols],
|
||||
case pick_subprotocol(Subprotocols, NSupportedSubprotocols) of
|
||||
|
@ -231,31 +222,30 @@ pick_subprotocol([Subprotocol | Rest], SupportedSubprotocols) ->
|
|||
pick_subprotocol(Rest, SupportedSubprotocols)
|
||||
end.
|
||||
|
||||
parse_header_fun_origin(Req, Opts) ->
|
||||
parse_header_fun_origin(Req, #{zone := Zone, listener := Listener}) ->
|
||||
case cowboy_req:header(<<"origin">>, Req) of
|
||||
undefined ->
|
||||
case proplists:get_bool(allow_origin_absence, Opts) of
|
||||
case get_ws_opts(Zone, Listener, allow_origin_absence) of
|
||||
true -> ok;
|
||||
false -> {error, origin_header_cannot_be_absent}
|
||||
end;
|
||||
Value ->
|
||||
Origins = proplists:get_value(check_origins, Opts, []),
|
||||
case lists:member(Value, Origins) of
|
||||
case lists:member(Value, get_ws_opts(Zone, Listener, check_origins)) of
|
||||
true -> ok;
|
||||
false -> {origin_not_allowed, Value}
|
||||
end
|
||||
end.
|
||||
|
||||
check_origin_header(Req, Opts) ->
|
||||
case proplists:get_bool(check_origin_enable, Opts) of
|
||||
check_origin_header(Req, #{zone := Zone, listener := Listener} = Opts) ->
|
||||
case get_ws_opts(Zone, Listener, check_origin_enable) of
|
||||
true -> parse_header_fun_origin(Req, Opts);
|
||||
false -> ok
|
||||
end.
|
||||
|
||||
websocket_init([Req, Opts]) ->
|
||||
websocket_init([Req, #{zone := Zone, listener := Listener} = Opts]) ->
|
||||
{Peername, Peercert} =
|
||||
case proplists:get_bool(proxy_protocol, Opts)
|
||||
andalso maps:get(proxy_header, Req) of
|
||||
case emqx_config:get_listener_conf(Zone, Listener, [proxy_protocol]) andalso
|
||||
maps:get(proxy_header, Req) of
|
||||
#{src_address := SrcAddr, src_port := SrcPort, ssl := SSL} ->
|
||||
SourceName = {SrcAddr, SrcPort},
|
||||
%% Notice: Only CN is available in Proxy Protocol V2 additional info
|
||||
|
@ -266,7 +256,7 @@ websocket_init([Req, Opts]) ->
|
|||
{SourceName, SourceSSL};
|
||||
#{src_address := SrcAddr, src_port := SrcPort} ->
|
||||
SourceName = {SrcAddr, SrcPort},
|
||||
{SourceName , nossl};
|
||||
{SourceName, nossl};
|
||||
_ ->
|
||||
{get_peer(Req, Opts), cowboy_req:cert(Req)}
|
||||
end,
|
||||
|
@ -288,28 +278,35 @@ websocket_init([Req, Opts]) ->
|
|||
ws_cookie => WsCookie,
|
||||
conn_mod => ?MODULE
|
||||
},
|
||||
Zone = proplists:get_value(zone, Opts),
|
||||
PubLimit = emqx_zone:publish_limit(Zone),
|
||||
BytesIn = proplists:get_value(rate_limit, Opts),
|
||||
RateLimit = emqx_zone:ratelimit(Zone),
|
||||
Limiter = emqx_limiter:init(Zone, PubLimit, BytesIn, RateLimit),
|
||||
ActiveN = proplists:get_value(active_n, Opts, ?ACTIVE_N),
|
||||
MQTTPiggyback = proplists:get_value(mqtt_piggyback, Opts, multiple),
|
||||
FrameOpts = emqx_zone:mqtt_frame_options(Zone),
|
||||
Limiter = emqx_limiter:init(Zone, undefined, undefined, []),
|
||||
MQTTPiggyback = get_ws_opts(Zone, Listener, mqtt_piggyback),
|
||||
FrameOpts = #{
|
||||
strict_mode => emqx_config:get_zone_conf(Zone, [mqtt, strict_mode]),
|
||||
max_size => emqx_config:get_zone_conf(Zone, [mqtt, max_packet_size])
|
||||
},
|
||||
ParseState = emqx_frame:initial_parse_state(FrameOpts),
|
||||
Serialize = emqx_frame:serialize_opts(),
|
||||
Channel = emqx_channel:init(ConnInfo, Opts),
|
||||
GcState = emqx_zone:init_gc_state(Zone),
|
||||
StatsTimer = emqx_zone:stats_timer(Zone),
|
||||
GcState = case emqx_config:get_zone_conf(Zone, [force_gc]) of
|
||||
#{enable := false} -> undefined;
|
||||
GcPolicy -> emqx_gc:init(GcPolicy)
|
||||
end,
|
||||
StatsTimer = case emqx_config:get_zone_conf(Zone, [stats, enable]) of
|
||||
true -> undefined;
|
||||
false -> disabled
|
||||
end,
|
||||
%% MQTT Idle Timeout
|
||||
IdleTimeout = emqx_zone:idle_timeout(Zone),
|
||||
IdleTimeout = emqx_channel:get_mqtt_conf(Zone, idle_timeout),
|
||||
IdleTimer = start_timer(IdleTimeout, idle_timeout),
|
||||
emqx_misc:tune_heap_size(emqx_zone:oom_policy(Zone)),
|
||||
case emqx_config:get_zone_conf(emqx_channel:info(zone, Channel),
|
||||
[force_shutdown]) of
|
||||
#{enable := false} -> ok;
|
||||
ShutdownPolicy -> emqx_misc:tune_heap_size(ShutdownPolicy)
|
||||
end,
|
||||
emqx_logger:set_metadata_peername(esockd:format(Peername)),
|
||||
{ok, #state{peername = Peername,
|
||||
sockname = Sockname,
|
||||
sockstate = running,
|
||||
active_n = ActiveN,
|
||||
mqtt_piggyback = MQTTPiggyback,
|
||||
limiter = Limiter,
|
||||
parse_state = ParseState,
|
||||
|
@ -319,7 +316,9 @@ websocket_init([Req, Opts]) ->
|
|||
postponed = [],
|
||||
stats_timer = StatsTimer,
|
||||
idle_timeout = IdleTimeout,
|
||||
idle_timer = IdleTimer
|
||||
idle_timer = IdleTimer,
|
||||
zone = Zone,
|
||||
listener = Listener
|
||||
}, hibernate}.
|
||||
|
||||
websocket_handle({binary, Data}, State) when is_list(Data) ->
|
||||
|
@ -372,7 +371,8 @@ websocket_info({check_gc, Stats}, State) ->
|
|||
return(check_oom(run_gc(Stats, State)));
|
||||
|
||||
websocket_info(Deliver = {deliver, _Topic, _Msg},
|
||||
State = #state{active_n = ActiveN}) ->
|
||||
State = #state{zone = Zone, listener = Listener}) ->
|
||||
ActiveN = emqx_config:get_listener_conf(Zone, Listener, [tcp, active_n]),
|
||||
Delivers = [Deliver|emqx_misc:drain_deliver(ActiveN)],
|
||||
with_channel(handle_deliver, [Delivers], State);
|
||||
|
||||
|
@ -521,11 +521,16 @@ run_gc(Stats, State = #state{gc_state = GcSt}) ->
|
|||
end.
|
||||
|
||||
check_oom(State = #state{channel = Channel}) ->
|
||||
OomPolicy = emqx_zone:oom_policy(emqx_channel:info(zone, Channel)),
|
||||
case ?ENABLED(OomPolicy) andalso emqx_misc:check_oom(OomPolicy) of
|
||||
Shutdown = {shutdown, _Reason} ->
|
||||
postpone(Shutdown, State);
|
||||
_Other -> State
|
||||
ShutdownPolicy = emqx_config:get_zone_conf(
|
||||
emqx_channel:info(zone, Channel), [force_shutdown]),
|
||||
case ShutdownPolicy of
|
||||
#{enable := false} -> State;
|
||||
#{enable := true} ->
|
||||
case emqx_misc:check_oom(ShutdownPolicy) of
|
||||
Shutdown = {shutdown, _Reason} ->
|
||||
postpone(Shutdown, State);
|
||||
_Other -> State
|
||||
end
|
||||
end.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
|
@ -554,11 +559,12 @@ parse_incoming(Data, State = #state{parse_state = ParseState}) ->
|
|||
%% Handle incoming packet
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
handle_incoming(Packet, State = #state{active_n = ActiveN})
|
||||
handle_incoming(Packet, State = #state{zone = Zone, listener = Listener})
|
||||
when is_record(Packet, mqtt_packet) ->
|
||||
?LOG(debug, "RECV ~s", [emqx_packet:format(Packet)]),
|
||||
ok = inc_incoming_stats(Packet),
|
||||
NState = case emqx_pd:get_counter(incoming_pubs) > ActiveN of
|
||||
NState = case emqx_pd:get_counter(incoming_pubs) >
|
||||
emqx_config:get_listener_conf(Zone, Listener, [tcp, active_n]) of
|
||||
true -> postpone({cast, rate_limit}, State);
|
||||
false -> State
|
||||
end,
|
||||
|
@ -589,11 +595,13 @@ with_channel(Fun, Args, State = #state{channel = Channel}) ->
|
|||
%% Handle outgoing packets
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
handle_outgoing(Packets, State = #state{active_n = ActiveN, mqtt_piggyback = MQTTPiggyback}) ->
|
||||
handle_outgoing(Packets, State = #state{mqtt_piggyback = MQTTPiggyback,
|
||||
zone = Zone, listener = Listener}) ->
|
||||
IoData = lists:map(serialize_and_inc_stats_fun(State), Packets),
|
||||
Oct = iolist_size(IoData),
|
||||
ok = inc_sent_stats(length(Packets), Oct),
|
||||
NState = case emqx_pd:get_counter(outgoing_pubs) > ActiveN of
|
||||
NState = case emqx_pd:get_counter(outgoing_pubs) >
|
||||
emqx_config:get_listener_conf(Zone, Listener, [tcp, active_n]) of
|
||||
true ->
|
||||
Stats = #{cnt => emqx_pd:reset_counter(outgoing_pubs),
|
||||
oct => emqx_pd:reset_counter(outgoing_bytes)
|
||||
|
@ -742,9 +750,10 @@ classify([Event|More], Packets, Cmds, Events) ->
|
|||
|
||||
trigger(Event) -> erlang:send(self(), Event).
|
||||
|
||||
get_peer(Req, Opts) ->
|
||||
get_peer(Req, #{zone := Zone, listener := Listener}) ->
|
||||
{PeerAddr, PeerPort} = cowboy_req:peer(Req),
|
||||
AddrHeader = cowboy_req:header(proplists:get_value(proxy_address_header, Opts), Req, <<>>),
|
||||
AddrHeader = cowboy_req:header(
|
||||
get_ws_opts(Zone, Listener, proxy_address_header), Req, <<>>),
|
||||
ClientAddr = case string:tokens(binary_to_list(AddrHeader), ", ") of
|
||||
[] ->
|
||||
undefined;
|
||||
|
@ -757,7 +766,8 @@ get_peer(Req, Opts) ->
|
|||
_ ->
|
||||
PeerAddr
|
||||
end,
|
||||
PortHeader = cowboy_req:header(proplists:get_value(proxy_port_header, Opts), Req, <<>>),
|
||||
PortHeader = cowboy_req:header(
|
||||
get_ws_opts(Zone, Listener, proxy_port_header), Req, <<>>),
|
||||
ClientPort = case string:tokens(binary_to_list(PortHeader), ", ") of
|
||||
[] ->
|
||||
undefined;
|
||||
|
@ -778,3 +788,5 @@ set_field(Name, Value, State) ->
|
|||
Pos = emqx_misc:index_of(Name, record_info(fields, state)),
|
||||
setelement(Pos+1, State, Value).
|
||||
|
||||
get_ws_opts(Zone, Listener, Key) ->
|
||||
emqx_config:get_listener_conf(Zone, Listener, [websocket, Key]).
|
||||
|
|
|
@ -1,298 +0,0 @@
|
|||
%%--------------------------------------------------------------------
|
||||
%% Copyright (c) 2018-2021 EMQ Technologies Co., Ltd. All Rights Reserved.
|
||||
%%
|
||||
%% Licensed under the Apache License, Version 2.0 (the "License");
|
||||
%% you may not use this file except in compliance with the License.
|
||||
%% You may obtain a copy of the License at
|
||||
%%
|
||||
%% http://www.apache.org/licenses/LICENSE-2.0
|
||||
%%
|
||||
%% Unless required by applicable law or agreed to in writing, software
|
||||
%% distributed under the License is distributed on an "AS IS" BASIS,
|
||||
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
%% See the License for the specific language governing permissions and
|
||||
%% limitations under the License.
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
-module(emqx_zone).
|
||||
|
||||
-behaviour(gen_server).
|
||||
|
||||
-include("emqx.hrl").
|
||||
-include("emqx_mqtt.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("types.hrl").
|
||||
|
||||
-logger_header("[Zone]").
|
||||
|
||||
-compile({inline,
|
||||
[ idle_timeout/1
|
||||
, publish_limit/1
|
||||
, ratelimit/1
|
||||
, mqtt_frame_options/1
|
||||
, mqtt_strict_mode/1
|
||||
, max_packet_size/1
|
||||
, mountpoint/1
|
||||
, use_username_as_clientid/1
|
||||
, stats_timer/1
|
||||
, enable_stats/1
|
||||
, enable_acl/1
|
||||
, enable_ban/1
|
||||
, enable_flapping_detect/1
|
||||
, ignore_loop_deliver/1
|
||||
, server_keepalive/1
|
||||
, keepalive_backoff/1
|
||||
, max_inflight/1
|
||||
, session_expiry_interval/1
|
||||
, force_gc_policy/1
|
||||
, force_shutdown_policy/1
|
||||
, response_information/1
|
||||
, quota_policy/1
|
||||
, get_env/2
|
||||
, get_env/3
|
||||
]}).
|
||||
|
||||
%% APIs
|
||||
-export([start_link/0, stop/0]).
|
||||
|
||||
%% Zone Option API
|
||||
-export([ idle_timeout/1
|
||||
%% XXX: Dedeprecated at v4.2
|
||||
, publish_limit/1
|
||||
, ratelimit/1
|
||||
, mqtt_frame_options/1
|
||||
, mqtt_strict_mode/1
|
||||
, max_packet_size/1
|
||||
, mountpoint/1
|
||||
, use_username_as_clientid/1
|
||||
, stats_timer/1
|
||||
, enable_stats/1
|
||||
, enable_acl/1
|
||||
, enable_ban/1
|
||||
, enable_flapping_detect/1
|
||||
, ignore_loop_deliver/1
|
||||
, server_keepalive/1
|
||||
, keepalive_backoff/1
|
||||
, max_inflight/1
|
||||
, session_expiry_interval/1
|
||||
, force_gc_policy/1
|
||||
, force_shutdown_policy/1
|
||||
, response_information/1
|
||||
, quota_policy/1
|
||||
]).
|
||||
|
||||
-export([ init_gc_state/1
|
||||
, oom_policy/1
|
||||
]).
|
||||
|
||||
%% Zone API
|
||||
-export([ get_env/2
|
||||
, get_env/3
|
||||
, set_env/3
|
||||
, unset_env/2
|
||||
, unset_all_env/0
|
||||
]).
|
||||
|
||||
-export([force_reload/0]).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([ init/1
|
||||
, handle_call/3
|
||||
, handle_cast/2
|
||||
, handle_info/2
|
||||
, terminate/2
|
||||
, code_change/3
|
||||
]).
|
||||
|
||||
-import(emqx_misc, [maybe_apply/2]).
|
||||
|
||||
-export_type([zone/0]).
|
||||
|
||||
-type(zone() :: atom()).
|
||||
|
||||
-define(TAB, ?MODULE).
|
||||
-define(SERVER, ?MODULE).
|
||||
-define(DEFAULT_IDLE_TIMEOUT, 30000).
|
||||
-define(KEY(Zone, Key), {?MODULE, Zone, Key}).
|
||||
|
||||
-spec(start_link() -> startlink_ret()).
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
|
||||
|
||||
-spec(stop() -> ok).
|
||||
stop() ->
|
||||
gen_server:stop(?SERVER).
|
||||
|
||||
-spec(init_gc_state(zone()) -> maybe(emqx_gc:gc_state())).
|
||||
init_gc_state(Zone) ->
|
||||
maybe_apply(fun emqx_gc:init/1, force_gc_policy(Zone)).
|
||||
|
||||
-spec(oom_policy(zone()) -> emqx_types:oom_policy()).
|
||||
oom_policy(Zone) -> force_shutdown_policy(Zone).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Zone Options API
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
-spec(idle_timeout(zone()) -> pos_integer()).
|
||||
idle_timeout(Zone) ->
|
||||
get_env(Zone, idle_timeout, ?DEFAULT_IDLE_TIMEOUT).
|
||||
|
||||
-spec(publish_limit(zone()) -> maybe(esockd_rate_limit:config())).
|
||||
publish_limit(Zone) ->
|
||||
get_env(Zone, publish_limit).
|
||||
|
||||
-spec(ratelimit(zone()) -> [emqx_limiter:specs()]).
|
||||
ratelimit(Zone) ->
|
||||
get_env(Zone, ratelimit, []).
|
||||
|
||||
-spec(mqtt_frame_options(zone()) -> emqx_frame:options()).
|
||||
mqtt_frame_options(Zone) ->
|
||||
#{strict_mode => mqtt_strict_mode(Zone),
|
||||
max_size => max_packet_size(Zone)
|
||||
}.
|
||||
|
||||
-spec(mqtt_strict_mode(zone()) -> boolean()).
|
||||
mqtt_strict_mode(Zone) ->
|
||||
get_env(Zone, strict_mode, false).
|
||||
|
||||
-spec(max_packet_size(zone()) -> integer()).
|
||||
max_packet_size(Zone) ->
|
||||
get_env(Zone, max_packet_size, ?MAX_PACKET_SIZE).
|
||||
|
||||
-spec(mountpoint(zone()) -> maybe(emqx_mountpoint:mountpoint())).
|
||||
mountpoint(Zone) -> get_env(Zone, mountpoint).
|
||||
|
||||
-spec(use_username_as_clientid(zone()) -> boolean()).
|
||||
use_username_as_clientid(Zone) ->
|
||||
get_env(Zone, use_username_as_clientid, false).
|
||||
|
||||
-spec(stats_timer(zone()) -> undefined | disabled).
|
||||
stats_timer(Zone) ->
|
||||
case enable_stats(Zone) of true -> undefined; false -> disabled end.
|
||||
|
||||
-spec(enable_stats(zone()) -> boolean()).
|
||||
enable_stats(Zone) ->
|
||||
get_env(Zone, enable_stats, true).
|
||||
|
||||
-spec(enable_acl(zone()) -> boolean()).
|
||||
enable_acl(Zone) ->
|
||||
get_env(Zone, enable_acl, true).
|
||||
|
||||
-spec(enable_ban(zone()) -> boolean()).
|
||||
enable_ban(Zone) ->
|
||||
get_env(Zone, enable_ban, false).
|
||||
|
||||
-spec(enable_flapping_detect(zone()) -> boolean()).
|
||||
enable_flapping_detect(Zone) ->
|
||||
get_env(Zone, enable_flapping_detect, false).
|
||||
|
||||
-spec(ignore_loop_deliver(zone()) -> boolean()).
|
||||
ignore_loop_deliver(Zone) ->
|
||||
get_env(Zone, ignore_loop_deliver, false).
|
||||
|
||||
-spec(server_keepalive(zone()) -> maybe(pos_integer())).
|
||||
server_keepalive(Zone) ->
|
||||
get_env(Zone, server_keepalive).
|
||||
|
||||
-spec(keepalive_backoff(zone()) -> float()).
|
||||
keepalive_backoff(Zone) ->
|
||||
get_env(Zone, keepalive_backoff, 0.75).
|
||||
|
||||
-spec(max_inflight(zone()) -> 0..65535).
|
||||
max_inflight(Zone) ->
|
||||
get_env(Zone, max_inflight, 65535).
|
||||
|
||||
-spec(session_expiry_interval(zone()) -> non_neg_integer()).
|
||||
session_expiry_interval(Zone) ->
|
||||
get_env(Zone, session_expiry_interval, 0).
|
||||
|
||||
-spec(force_gc_policy(zone()) -> maybe(emqx_gc:opts())).
|
||||
force_gc_policy(Zone) ->
|
||||
get_env(Zone, force_gc_policy).
|
||||
|
||||
-spec(force_shutdown_policy(zone()) -> maybe(emqx_oom:opts())).
|
||||
force_shutdown_policy(Zone) ->
|
||||
get_env(Zone, force_shutdown_policy).
|
||||
|
||||
-spec(response_information(zone()) -> string()).
|
||||
response_information(Zone) ->
|
||||
get_env(Zone, response_information).
|
||||
|
||||
-spec(quota_policy(zone()) -> emqx_quota:policy()).
|
||||
quota_policy(Zone) ->
|
||||
get_env(Zone, quota, []).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% APIs
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
-spec(get_env(maybe(zone()), atom()) -> maybe(term())).
|
||||
get_env(undefined, Key) -> emqx:get_env(Key);
|
||||
get_env(Zone, Key) ->
|
||||
get_env(Zone, Key, undefined).
|
||||
|
||||
-spec(get_env(maybe(zone()), atom(), term()) -> maybe(term())).
|
||||
get_env(undefined, Key, Def) ->
|
||||
emqx:get_env(Key, Def);
|
||||
get_env(Zone, Key, Def) ->
|
||||
try persistent_term:get(?KEY(Zone, Key))
|
||||
catch error:badarg ->
|
||||
emqx:get_env(Key, Def)
|
||||
end.
|
||||
|
||||
-spec(set_env(zone(), atom(), term()) -> ok).
|
||||
set_env(Zone, Key, Val) ->
|
||||
persistent_term:put(?KEY(Zone, Key), Val).
|
||||
|
||||
-spec(unset_env(zone(), atom()) -> boolean()).
|
||||
unset_env(Zone, Key) ->
|
||||
persistent_term:erase(?KEY(Zone, Key)).
|
||||
|
||||
-spec(unset_all_env() -> ok).
|
||||
unset_all_env() ->
|
||||
[unset_env(Zone, Key) || {?KEY(Zone, Key), _Val} <- persistent_term:get()],
|
||||
ok.
|
||||
|
||||
-spec(force_reload() -> ok).
|
||||
force_reload() ->
|
||||
gen_server:call(?SERVER, force_reload).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% gen_server callbacks
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
init([]) ->
|
||||
_ = do_reload(),
|
||||
{ok, #{}}.
|
||||
|
||||
handle_call(force_reload, _From, State) ->
|
||||
_ = do_reload(),
|
||||
{reply, ok, State};
|
||||
|
||||
handle_call(Req, _From, State) ->
|
||||
?LOG(error, "Unexpected call: ~p", [Req]),
|
||||
{reply, ignored, State}.
|
||||
|
||||
handle_cast(Msg, State) ->
|
||||
?LOG(error, "Unexpected cast: ~p", [Msg]),
|
||||
{noreply, State}.
|
||||
|
||||
handle_info(Info, State) ->
|
||||
?LOG(error, "Unexpected info: ~p", [Info]),
|
||||
{noreply, State}.
|
||||
|
||||
terminate(_Reason, _State) ->
|
||||
ok.
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Internal functions
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
do_reload() ->
|
||||
[persistent_term:put(?KEY(Zone, Key), Val)
|
||||
|| {Zone, Opts} <- emqx:get_env(zones, []), {Key, Val} <- Opts].
|
||||
|
|
@ -51,14 +51,6 @@ t_stop_start(_) ->
|
|||
ok = emqx:shutdown(for_test),
|
||||
false = emqx:is_running(node()).
|
||||
|
||||
t_get_env(_) ->
|
||||
?assertEqual(undefined, emqx:get_env(undefined_key)),
|
||||
?assertEqual(default_value, emqx:get_env(undefined_key, default_value)),
|
||||
application:set_env(emqx, undefined_key, hello),
|
||||
?assertEqual(hello, emqx:get_env(undefined_key)),
|
||||
?assertEqual(hello, emqx:get_env(undefined_key, default_value)),
|
||||
application:unset_env(emqx, undefined_key).
|
||||
|
||||
t_emqx_pubsub_api(_) ->
|
||||
true = emqx:is_running(node()),
|
||||
{ok, C} = emqtt:start_link([{host, "localhost"}, {clientid, "myclient"}]),
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
%%--------------------------------------------------------------------
|
||||
%%
|
||||
%% [ACL](https://github.com/emqtt/emqttd/wiki/ACL)
|
||||
%% [Authorization](https://github.com/emqtt/emqttd/wiki/Authorization)
|
||||
%%
|
||||
%% -type who() :: all | binary() |
|
||||
%% {ipaddr, esockd_access:cidr()} |
|
||||
|
|
|
@ -33,43 +33,20 @@ end_per_suite(_Config) ->
|
|||
emqx_ct_helpers:stop_apps([]).
|
||||
|
||||
t_authenticate(_) ->
|
||||
emqx_zone:set_env(zone, allow_anonymous, false),
|
||||
?assertMatch({error, _}, emqx_access_control:authenticate(clientinfo())),
|
||||
emqx_zone:set_env(zone, allow_anonymous, true),
|
||||
?assertMatch({ok, _}, emqx_access_control:authenticate(clientinfo())).
|
||||
?assertMatch(ok, emqx_access_control:authenticate(clientinfo())).
|
||||
|
||||
t_check_acl(_) ->
|
||||
emqx_zone:set_env(zone, acl_nomatch, deny),
|
||||
application:set_env(emqx, enable_acl_cache, false),
|
||||
t_authorize(_) ->
|
||||
Publish = ?PUBLISH_PACKET(?QOS_0, <<"t">>, 1, <<"payload">>),
|
||||
?assertEqual(deny, emqx_access_control:check_acl(clientinfo(), Publish, <<"t">>)),
|
||||
|
||||
emqx_zone:set_env(zone, acl_nomatch, allow),
|
||||
application:set_env(emqx, enable_acl_cache, true),
|
||||
Publish = ?PUBLISH_PACKET(?QOS_0, <<"t">>, 1, <<"payload">>),
|
||||
?assertEqual(allow, emqx_access_control:check_acl(clientinfo(), Publish, <<"t">>)).
|
||||
|
||||
t_bypass_auth_plugins(_) ->
|
||||
ClientInfo = clientinfo(),
|
||||
emqx_zone:set_env(bypass_zone, allow_anonymous, true),
|
||||
emqx_zone:set_env(zone, allow_anonymous, false),
|
||||
emqx_zone:set_env(bypass_zone, bypass_auth_plugins, true),
|
||||
emqx:hook('client.authenticate',{?MODULE, auth_fun, []}),
|
||||
?assertMatch({ok, _}, emqx_access_control:authenticate(ClientInfo#{zone => bypass_zone})),
|
||||
?assertMatch({ok, _}, emqx_access_control:authenticate(ClientInfo)).
|
||||
?assertEqual(allow, emqx_access_control:authorize(clientinfo(), Publish, <<"t">>)).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Helper functions
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
auth_fun(#{zone := bypass_zone}, AuthRes) ->
|
||||
{stop, AuthRes#{auth_result => password_error}};
|
||||
auth_fun(#{zone := _}, AuthRes) ->
|
||||
{stop, AuthRes#{auth_result => success}}.
|
||||
|
||||
clientinfo() -> clientinfo(#{}).
|
||||
clientinfo(InitProps) ->
|
||||
maps:merge(#{zone => zone,
|
||||
maps:merge(#{zone => default,
|
||||
listener => mqtt_tcp,
|
||||
protocol => mqtt,
|
||||
peerhost => {127,0,0,1},
|
||||
clientid => <<"clientid">>,
|
||||
|
@ -79,3 +56,6 @@ clientinfo(InitProps) ->
|
|||
peercert => undefined,
|
||||
mountpoint => undefined
|
||||
}, InitProps).
|
||||
|
||||
toggle_auth(Bool) when is_boolean(Bool) ->
|
||||
emqx_config:put_zone_conf(default, [auth, enable], Bool).
|
||||
|
|
|
@ -27,27 +27,17 @@ all() -> emqx_ct:all(?MODULE).
|
|||
|
||||
init_per_testcase(t_size_limit, Config) ->
|
||||
emqx_ct_helpers:boot_modules(all),
|
||||
emqx_ct_helpers:start_apps([],
|
||||
fun(emqx) ->
|
||||
application:set_env(emqx, alarm, [{actions, [log,publish]},
|
||||
{size_limit, 2},
|
||||
{validity_period, 3600}]),
|
||||
ok;
|
||||
(_) ->
|
||||
ok
|
||||
end),
|
||||
emqx_ct_helpers:start_apps([]),
|
||||
emqx_config:update([alarm], #{
|
||||
<<"size_limit">> => 2
|
||||
}),
|
||||
Config;
|
||||
init_per_testcase(t_validity_period, Config) ->
|
||||
emqx_ct_helpers:boot_modules(all),
|
||||
emqx_ct_helpers:start_apps([],
|
||||
fun(emqx) ->
|
||||
application:set_env(emqx, alarm, [{actions, [log,publish]},
|
||||
{size_limit, 1000},
|
||||
{validity_period, 1}]),
|
||||
ok;
|
||||
(_) ->
|
||||
ok
|
||||
end),
|
||||
emqx_ct_helpers:start_apps([]),
|
||||
emqx_config:update([alarm], #{
|
||||
<<"validity_period">> => <<"1s">>
|
||||
}),
|
||||
Config;
|
||||
init_per_testcase(_, Config) ->
|
||||
emqx_ct_helpers:boot_modules(all),
|
||||
|
@ -89,7 +79,7 @@ t_size_limit(_) ->
|
|||
ok = emqx_alarm:activate(b),
|
||||
ok = emqx_alarm:deactivate(b),
|
||||
?assertNotEqual({error, not_found}, get_alarm(a, emqx_alarm:get_alarms(deactivated))),
|
||||
?assertNotEqual({error, not_found}, get_alarm(a, emqx_alarm:get_alarms(deactivated))),
|
||||
?assertNotEqual({error, not_found}, get_alarm(b, emqx_alarm:get_alarms(deactivated))),
|
||||
ok = emqx_alarm:activate(c),
|
||||
ok = emqx_alarm:deactivate(c),
|
||||
?assertNotEqual({error, not_found}, get_alarm(c, emqx_alarm:get_alarms(deactivated))),
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
%% limitations under the License.
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
-module(emqx_acl_cache_SUITE).
|
||||
-module(emqx_authz_cache_SUITE).
|
||||
|
||||
-compile(export_all).
|
||||
-compile(nowarn_export_all).
|
||||
|
@ -26,6 +26,7 @@ all() -> emqx_ct:all(?MODULE).
|
|||
init_per_suite(Config) ->
|
||||
emqx_ct_helpers:boot_modules(all),
|
||||
emqx_ct_helpers:start_apps([]),
|
||||
toggle_authz(true),
|
||||
Config.
|
||||
|
||||
end_per_suite(_Config) ->
|
||||
|
@ -35,7 +36,7 @@ end_per_suite(_Config) ->
|
|||
%% Test cases
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
t_clean_acl_cache(_) ->
|
||||
t_clean_authz_cache(_) ->
|
||||
{ok, Client} = emqtt:start_link([{clientid, <<"emqx_c">>}]),
|
||||
{ok, _} = emqtt:connect(Client),
|
||||
{ok, _, _} = emqtt:subscribe(Client, <<"t2">>, 0),
|
||||
|
@ -48,15 +49,14 @@ t_clean_acl_cache(_) ->
|
|||
lists:last(Pids);
|
||||
_ -> {error, not_found}
|
||||
end,
|
||||
Caches = gen_server:call(ClientPid, list_acl_cache),
|
||||
ct:log("acl caches: ~p", [Caches]),
|
||||
Caches = gen_server:call(ClientPid, list_authz_cache),
|
||||
ct:log("authz caches: ~p", [Caches]),
|
||||
?assert(length(Caches) > 0),
|
||||
erlang:send(ClientPid, clean_acl_cache),
|
||||
?assertEqual(0, length(gen_server:call(ClientPid, list_acl_cache))),
|
||||
erlang:send(ClientPid, clean_authz_cache),
|
||||
?assertEqual(0, length(gen_server:call(ClientPid, list_authz_cache))),
|
||||
emqtt:stop(Client).
|
||||
|
||||
|
||||
t_drain_acl_cache(_) ->
|
||||
t_drain_authz_cache(_) ->
|
||||
{ok, Client} = emqtt:start_link([{clientid, <<"emqx_c">>}]),
|
||||
{ok, _} = emqtt:connect(Client),
|
||||
{ok, _, _} = emqtt:subscribe(Client, <<"t2">>, 0),
|
||||
|
@ -69,80 +69,15 @@ t_drain_acl_cache(_) ->
|
|||
lists:last(Pids);
|
||||
_ -> {error, not_found}
|
||||
end,
|
||||
Caches = gen_server:call(ClientPid, list_acl_cache),
|
||||
ct:log("acl caches: ~p", [Caches]),
|
||||
Caches = gen_server:call(ClientPid, list_authz_cache),
|
||||
ct:log("authz caches: ~p", [Caches]),
|
||||
?assert(length(Caches) > 0),
|
||||
emqx_acl_cache:drain_cache(),
|
||||
?assertEqual(0, length(gen_server:call(ClientPid, list_acl_cache))),
|
||||
emqx_authz_cache:drain_cache(),
|
||||
?assertEqual(0, length(gen_server:call(ClientPid, list_authz_cache))),
|
||||
ct:sleep(100),
|
||||
{ok, _, _} = emqtt:subscribe(Client, <<"t2">>, 0),
|
||||
?assert(length(gen_server:call(ClientPid, list_acl_cache)) > 0),
|
||||
?assert(length(gen_server:call(ClientPid, list_authz_cache)) > 0),
|
||||
emqtt:stop(Client).
|
||||
|
||||
% optimize??
|
||||
t_reload_aclfile_and_cleanall(_Config) ->
|
||||
|
||||
RasieMsg = fun() -> Self = self(), #{puback => fun(Msg) -> Self ! {puback, Msg} end,
|
||||
disconnected => fun(_) -> ok end,
|
||||
publish => fun(_) -> ok end } end,
|
||||
|
||||
{ok, Client} = emqtt:start_link([{clientid, <<"emqx_c">>}, {proto_ver, v5},
|
||||
{msg_handler, RasieMsg()}]),
|
||||
{ok, _} = emqtt:connect(Client),
|
||||
|
||||
{ok, PktId} = emqtt:publish(Client, <<"t1">>, <<"{\"x\":1}">>, qos1),
|
||||
|
||||
%% Success publish to broker
|
||||
receive
|
||||
{puback, #{packet_id := PktId, reason_code := Rc}} ->
|
||||
?assertEqual(16#10, Rc);
|
||||
_ ->
|
||||
?assert(false)
|
||||
end,
|
||||
|
||||
%% Check acl cache list
|
||||
[ClientPid] = emqx_cm:lookup_channels(<<"emqx_c">>),
|
||||
?assert(length(gen_server:call(ClientPid, list_acl_cache)) > 0),
|
||||
emqtt:stop(Client).
|
||||
|
||||
%% @private
|
||||
testdir(DataPath) ->
|
||||
Ls = filename:split(DataPath),
|
||||
filename:join(lists:sublist(Ls, 1, length(Ls) - 1)).
|
||||
|
||||
% t_cache_k(_) ->
|
||||
% error('TODO').
|
||||
|
||||
% t_cache_v(_) ->
|
||||
% error('TODO').
|
||||
|
||||
% t_cleanup_acl_cache(_) ->
|
||||
% error('TODO').
|
||||
|
||||
% t_get_oldest_key(_) ->
|
||||
% error('TODO').
|
||||
|
||||
% t_get_newest_key(_) ->
|
||||
% error('TODO').
|
||||
|
||||
% t_get_cache_max_size(_) ->
|
||||
% error('TODO').
|
||||
|
||||
% t_get_cache_size(_) ->
|
||||
% error('TODO').
|
||||
|
||||
% t_dump_acl_cache(_) ->
|
||||
% error('TODO').
|
||||
|
||||
% t_empty_acl_cache(_) ->
|
||||
% error('TODO').
|
||||
|
||||
% t_put_acl_cache(_) ->
|
||||
% error('TODO').
|
||||
|
||||
% t_get_acl_cache(_) ->
|
||||
% error('TODO').
|
||||
|
||||
% t_is_enabled(_) ->
|
||||
% error('TODO').
|
||||
|
||||
toggle_authz(Bool) when is_boolean(Bool) ->
|
||||
emqx_config:put_zone_conf(default, [authorization, enable], Bool).
|
|
@ -14,20 +14,20 @@
|
|||
%% limitations under the License.
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
-module(emqx_acl_test_mod).
|
||||
-module(emqx_authz_test_mod).
|
||||
|
||||
%% ACL callbacks
|
||||
%% Authorization callbacks
|
||||
-export([ init/1
|
||||
, check_acl/2
|
||||
, authorize/2
|
||||
, description/0
|
||||
]).
|
||||
|
||||
init(AclOpts) ->
|
||||
{ok, AclOpts}.
|
||||
init(AuthzOpts) ->
|
||||
{ok, AuthzOpts}.
|
||||
|
||||
check_acl({_User, _PubSub, _Topic}, _State) ->
|
||||
authorize({_User, _PubSub, _Topic}, _State) ->
|
||||
allow.
|
||||
|
||||
description() ->
|
||||
"Test ACL Mod".
|
||||
"Test Authorization Mod".
|
||||
|
|
@ -42,19 +42,19 @@ end_per_suite(_Config) ->
|
|||
%%--------------------------------------------------------------------
|
||||
|
||||
t_stats_fun(_) ->
|
||||
?assertEqual(0, emqx_stats:getstat('subscribers.count')),
|
||||
?assertEqual(0, emqx_stats:getstat('subscriptions.count')),
|
||||
?assertEqual(0, emqx_stats:getstat('suboptions.count')),
|
||||
Subscribers = emqx_stats:getstat('subscribers.count'),
|
||||
Subscriptions = emqx_stats:getstat('subscriptions.count'),
|
||||
Subopts = emqx_stats:getstat('suboptions.count'),
|
||||
ok = emqx_broker:subscribe(<<"topic">>, <<"clientid">>),
|
||||
ok = emqx_broker:subscribe(<<"topic2">>, <<"clientid">>),
|
||||
emqx_broker:stats_fun(),
|
||||
ct:sleep(10),
|
||||
?assertEqual(2, emqx_stats:getstat('subscribers.count')),
|
||||
?assertEqual(2, emqx_stats:getstat('subscribers.max')),
|
||||
?assertEqual(2, emqx_stats:getstat('subscriptions.count')),
|
||||
?assertEqual(2, emqx_stats:getstat('subscriptions.max')),
|
||||
?assertEqual(2, emqx_stats:getstat('suboptions.count')),
|
||||
?assertEqual(2, emqx_stats:getstat('suboptions.max')).
|
||||
?assertEqual(Subscribers + 2, emqx_stats:getstat('subscribers.count')),
|
||||
?assertEqual(Subscribers + 2, emqx_stats:getstat('subscribers.max')),
|
||||
?assertEqual(Subscriptions + 2, emqx_stats:getstat('subscriptions.count')),
|
||||
?assertEqual(Subscriptions + 2, emqx_stats:getstat('subscriptions.max')),
|
||||
?assertEqual(Subopts + 2, emqx_stats:getstat('suboptions.count')),
|
||||
?assertEqual(Subopts + 2, emqx_stats:getstat('suboptions.max')).
|
||||
|
||||
t_subscribed(_) ->
|
||||
emqx_broker:subscribe(<<"topic">>),
|
||||
|
|
|
@ -24,7 +24,152 @@
|
|||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
|
||||
all() -> emqx_ct:all(?MODULE).
|
||||
all() ->
|
||||
emqx_ct:all(?MODULE).
|
||||
|
||||
mqtt_conf() ->
|
||||
#{await_rel_timeout => 300000,
|
||||
idle_timeout => 15000,
|
||||
ignore_loop_deliver => false,
|
||||
keepalive_backoff => 0.75,
|
||||
max_awaiting_rel => 100,
|
||||
max_clientid_len => 65535,
|
||||
max_inflight => 32,
|
||||
max_mqueue_len => 1000,
|
||||
max_packet_size => 1048576,
|
||||
max_qos_allowed => 2,
|
||||
max_subscriptions => infinity,
|
||||
max_topic_alias => 65535,
|
||||
max_topic_levels => 65535,
|
||||
mountpoint => <<>>,
|
||||
mqueue_default_priority => lowest,
|
||||
mqueue_priorities => #{},
|
||||
mqueue_store_qos0 => true,
|
||||
peer_cert_as_clientid => disabled,
|
||||
peer_cert_as_username => disabled,
|
||||
response_information => [],
|
||||
retain_available => true,
|
||||
retry_interval => 30000,
|
||||
server_keepalive => disabled,
|
||||
session_expiry_interval => 7200000,
|
||||
shared_subscription => true,
|
||||
strict_mode => false,
|
||||
upgrade_qos => false,
|
||||
use_username_as_clientid => false,
|
||||
wildcard_subscription => true}.
|
||||
|
||||
listener_mqtt_tcp_conf() ->
|
||||
#{acceptors => 16,
|
||||
access_rules => ["allow all"],
|
||||
bind => {{0,0,0,0},1883},
|
||||
max_connections => 1024000,
|
||||
proxy_protocol => false,
|
||||
proxy_protocol_timeout => 3000,
|
||||
rate_limit =>
|
||||
#{conn_bytes_in =>
|
||||
["100KB","10s"],
|
||||
conn_messages_in =>
|
||||
["100","10s"],
|
||||
max_conn_rate => 1000,
|
||||
quota =>
|
||||
#{conn_messages_routing => infinity,
|
||||
overall_messages_routing => infinity}},
|
||||
tcp =>
|
||||
#{active_n => 100,
|
||||
backlog => 1024,
|
||||
buffer => 4096,
|
||||
high_watermark => 1048576,
|
||||
send_timeout => 15000,
|
||||
send_timeout_close =>
|
||||
true},
|
||||
type => tcp}.
|
||||
|
||||
listener_mqtt_ws_conf() ->
|
||||
#{acceptors => 16,
|
||||
access_rules => ["allow all"],
|
||||
bind => {{0,0,0,0},8083},
|
||||
max_connections => 1024000,
|
||||
proxy_protocol => false,
|
||||
proxy_protocol_timeout => 3000,
|
||||
rate_limit =>
|
||||
#{conn_bytes_in =>
|
||||
["100KB","10s"],
|
||||
conn_messages_in =>
|
||||
["100","10s"],
|
||||
max_conn_rate => 1000,
|
||||
quota =>
|
||||
#{conn_messages_routing => infinity,
|
||||
overall_messages_routing => infinity}},
|
||||
tcp =>
|
||||
#{active_n => 100,
|
||||
backlog => 1024,
|
||||
buffer => 4096,
|
||||
high_watermark => 1048576,
|
||||
send_timeout => 15000,
|
||||
send_timeout_close =>
|
||||
true},
|
||||
type => ws,
|
||||
websocket =>
|
||||
#{allow_origin_absence =>
|
||||
true,
|
||||
check_origin_enable =>
|
||||
false,
|
||||
check_origins => [],
|
||||
compress => false,
|
||||
deflate_opts =>
|
||||
#{client_max_window_bits =>
|
||||
15,
|
||||
mem_level => 8,
|
||||
server_max_window_bits =>
|
||||
15},
|
||||
fail_if_no_subprotocol =>
|
||||
true,
|
||||
idle_timeout => 86400000,
|
||||
max_frame_size => infinity,
|
||||
mqtt_path => "/mqtt",
|
||||
mqtt_piggyback => multiple,
|
||||
proxy_address_header =>
|
||||
"x-forwarded-for",
|
||||
proxy_port_header =>
|
||||
"x-forwarded-port",
|
||||
supported_subprotocols =>
|
||||
["mqtt","mqtt-v3",
|
||||
"mqtt-v3.1.1",
|
||||
"mqtt-v5"]}}.
|
||||
|
||||
default_zone_conf() ->
|
||||
#{zones =>
|
||||
#{default =>
|
||||
#{ authorization => #{
|
||||
cache => #{enable => true,max_size => 32, ttl => 60000},
|
||||
deny_action => ignore,
|
||||
enable => false
|
||||
},
|
||||
auth => #{enable => false},
|
||||
overall_max_connections => infinity,
|
||||
stats => #{enable => true},
|
||||
conn_congestion =>
|
||||
#{enable_alarm => true, min_alarm_sustain_duration => 60000},
|
||||
flapping_detect =>
|
||||
#{ban_time => 300000,enable => false,
|
||||
max_count => 15,window_time => 60000},
|
||||
force_gc =>
|
||||
#{bytes => 16777216,count => 16000,
|
||||
enable => true},
|
||||
force_shutdown =>
|
||||
#{enable => true,
|
||||
max_heap_size => 4194304,
|
||||
max_message_queue_len => 1000},
|
||||
mqtt => mqtt_conf(),
|
||||
listeners =>
|
||||
#{mqtt_tcp => listener_mqtt_tcp_conf(),
|
||||
mqtt_ws => listener_mqtt_ws_conf()}
|
||||
}
|
||||
}
|
||||
}.
|
||||
|
||||
set_default_zone_conf() ->
|
||||
emqx_config:put(default_zone_conf()).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% CT Callbacks
|
||||
|
@ -36,8 +181,8 @@ init_per_suite(Config) ->
|
|||
%% Access Control Meck
|
||||
ok = meck:new(emqx_access_control, [passthrough, no_history, no_link]),
|
||||
ok = meck:expect(emqx_access_control, authenticate,
|
||||
fun(_) -> {ok, #{auth_result => success}} end),
|
||||
ok = meck:expect(emqx_access_control, check_acl, fun(_, _, _) -> allow end),
|
||||
fun(_) -> ok end),
|
||||
ok = meck:expect(emqx_access_control, authorize, fun(_, _, _) -> allow end),
|
||||
%% Broker Meck
|
||||
ok = meck:new(emqx_broker, [passthrough, no_history, no_link]),
|
||||
%% Hooks Meck
|
||||
|
@ -50,6 +195,9 @@ init_per_suite(Config) ->
|
|||
ok = meck:new(emqx_metrics, [passthrough, no_history, no_link]),
|
||||
ok = meck:expect(emqx_metrics, inc, fun(_) -> ok end),
|
||||
ok = meck:expect(emqx_metrics, inc, fun(_, _) -> ok end),
|
||||
%% Ban
|
||||
meck:new(emqx_banned, [passthrough, no_history, no_link]),
|
||||
ok = meck:expect(emqx_banned, check, fun(_ConnInfo) -> false end),
|
||||
Config.
|
||||
|
||||
end_per_suite(_Config) ->
|
||||
|
@ -58,15 +206,15 @@ end_per_suite(_Config) ->
|
|||
emqx_session,
|
||||
emqx_broker,
|
||||
emqx_hooks,
|
||||
emqx_cm
|
||||
emqx_cm,
|
||||
emqx_banned
|
||||
]).
|
||||
|
||||
init_per_testcase(_TestCase, Config) ->
|
||||
meck:new(emqx_zone, [passthrough, no_history, no_link]),
|
||||
set_default_zone_conf(),
|
||||
Config.
|
||||
|
||||
end_per_testcase(_TestCase, Config) ->
|
||||
meck:unload([emqx_zone]),
|
||||
Config.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
|
@ -83,7 +231,7 @@ t_chan_caps(_) ->
|
|||
#{max_clientid_len := 65535,
|
||||
max_qos_allowed := 2,
|
||||
max_topic_alias := 65535,
|
||||
max_topic_levels := 0,
|
||||
max_topic_levels := 65535,
|
||||
retain_available := true,
|
||||
shared_subscription := true,
|
||||
subscription_identifiers := true,
|
||||
|
@ -120,35 +268,40 @@ t_handle_in_unexpected_packet(_) ->
|
|||
{ok, [{outgoing, Packet}, {close, protocol_error}], Channel} =
|
||||
emqx_channel:handle_in(?PUBLISH_PACKET(?QOS_0), Channel).
|
||||
|
||||
t_handle_in_connect_auth_failed(_) ->
|
||||
ConnPkt = #mqtt_packet_connect{
|
||||
proto_name = <<"MQTT">>,
|
||||
proto_ver = ?MQTT_PROTO_V5,
|
||||
is_bridge = false,
|
||||
clean_start = true,
|
||||
keepalive = 30,
|
||||
properties = #{
|
||||
'Authentication-Method' => <<"failed_auth_method">>,
|
||||
'Authentication-Data' => <<"failed_auth_data">>
|
||||
},
|
||||
clientid = <<"clientid">>,
|
||||
username = <<"username">>
|
||||
},
|
||||
{shutdown, not_authorized, ?CONNACK_PACKET(?RC_NOT_AUTHORIZED), _} =
|
||||
emqx_channel:handle_in(?CONNECT_PACKET(ConnPkt), channel(#{conn_state => idle})).
|
||||
% t_handle_in_connect_auth_failed(_) ->
|
||||
% ConnPkt = #mqtt_packet_connect{
|
||||
% proto_name = <<"MQTT">>,
|
||||
% proto_ver = ?MQTT_PROTO_V5,
|
||||
% is_bridge = false,
|
||||
% clean_start = true,
|
||||
% keepalive = 30,
|
||||
% properties = #{
|
||||
% 'Authentication-Method' => <<"failed_auth_method">>,
|
||||
% 'Authentication-Data' => <<"failed_auth_data">>
|
||||
% },
|
||||
% clientid = <<"clientid">>,
|
||||
% username = <<"username">>
|
||||
% },
|
||||
% {shutdown, not_authorized, ?CONNACK_PACKET(?RC_NOT_AUTHORIZED), _} =
|
||||
% emqx_channel:handle_in(?CONNECT_PACKET(ConnPkt), channel(#{conn_state => idle})).
|
||||
|
||||
t_handle_in_continue_auth(_) ->
|
||||
Properties = #{
|
||||
'Authentication-Method' => <<"failed_auth_method">>,
|
||||
'Authentication-Data' => <<"failed_auth_data">>
|
||||
},
|
||||
{shutdown, bad_authentication_method, ?CONNACK_PACKET(?RC_BAD_AUTHENTICATION_METHOD), _} =
|
||||
emqx_channel:handle_in(?AUTH_PACKET(?RC_CONTINUE_AUTHENTICATION,Properties), channel()),
|
||||
{shutdown, not_authorized, ?CONNACK_PACKET(?RC_NOT_AUTHORIZED), _} =
|
||||
|
||||
Channel1 = channel(#{conn_state => connected}),
|
||||
{ok, [{outgoing, ?DISCONNECT_PACKET(?RC_PROTOCOL_ERROR)}, {close, protocol_error}], Channel1} =
|
||||
emqx_channel:handle_in(?AUTH_PACKET(?RC_CONTINUE_AUTHENTICATION, Properties), Channel1),
|
||||
|
||||
Channel2 = channel(#{conn_state => connecting}),
|
||||
ConnInfo = emqx_channel:info(conninfo, Channel2),
|
||||
Channel3 = emqx_channel:set_field(conninfo, ConnInfo#{conn_props => Properties}, Channel2),
|
||||
|
||||
{ok, [{event, connected}, {connack, ?CONNACK_PACKET(?RC_SUCCESS)}], _} =
|
||||
emqx_channel:handle_in(
|
||||
?AUTH_PACKET(?RC_CONTINUE_AUTHENTICATION,Properties),
|
||||
channel(#{conninfo => #{proto_ver => ?MQTT_PROTO_V5, conn_props => Properties}})
|
||||
).
|
||||
?AUTH_PACKET(?RC_CONTINUE_AUTHENTICATION, Properties), Channel3).
|
||||
|
||||
t_handle_in_re_auth(_) ->
|
||||
Properties = #{
|
||||
|
@ -167,10 +320,14 @@ t_handle_in_re_auth(_) ->
|
|||
?AUTH_PACKET(?RC_RE_AUTHENTICATE,Properties),
|
||||
channel(#{conninfo => #{proto_ver => ?MQTT_PROTO_V5, conn_props => undefined}})
|
||||
),
|
||||
{ok, [{outgoing, ?DISCONNECT_PACKET(?RC_NOT_AUTHORIZED)}, {close, not_authorized}], _} =
|
||||
|
||||
Channel1 = channel(),
|
||||
ConnInfo = emqx_channel:info(conninfo, Channel1),
|
||||
Channel2 = emqx_channel:set_field(conninfo, ConnInfo#{conn_props => Properties}, Channel1),
|
||||
|
||||
{ok, ?AUTH_PACKET(?RC_SUCCESS), _} =
|
||||
emqx_channel:handle_in(
|
||||
?AUTH_PACKET(?RC_RE_AUTHENTICATE,Properties),
|
||||
channel(#{conninfo => #{proto_ver => ?MQTT_PROTO_V5, conn_props => Properties}})
|
||||
?AUTH_PACKET(?RC_RE_AUTHENTICATE,Properties), Channel2
|
||||
).
|
||||
|
||||
t_handle_in_qos0_publish(_) ->
|
||||
|
@ -241,7 +398,7 @@ t_bad_receive_maximum(_) ->
|
|||
fun(true, _ClientInfo, _ConnInfo) ->
|
||||
{ok, #{session => session(), present => false}}
|
||||
end),
|
||||
ok = meck:expect(emqx_zone, response_information, fun(_) -> test end),
|
||||
emqx_config:put_zone_conf(default, [mqtt, response_information], test),
|
||||
C1 = channel(#{conn_state => idle}),
|
||||
{shutdown, protocol_error, _, _} =
|
||||
emqx_channel:handle_in(
|
||||
|
@ -254,8 +411,8 @@ t_override_client_receive_maximum(_) ->
|
|||
fun(true, _ClientInfo, _ConnInfo) ->
|
||||
{ok, #{session => session(), present => false}}
|
||||
end),
|
||||
ok = meck:expect(emqx_zone, response_information, fun(_) -> test end),
|
||||
ok = meck:expect(emqx_zone, max_inflight, fun(_) -> 0 end),
|
||||
emqx_config:put_zone_conf(default, [mqtt, response_information], test),
|
||||
emqx_config:put_zone_conf(default, [mqtt, max_inflight], 0),
|
||||
C1 = channel(#{conn_state => idle}),
|
||||
ClientCapacity = 2,
|
||||
{ok, [{event, connected}, _ConnAck], C2} =
|
||||
|
@ -346,8 +503,8 @@ t_handle_in_disconnect(_) ->
|
|||
|
||||
t_handle_in_auth(_) ->
|
||||
Channel = channel(#{conn_state => connected}),
|
||||
Packet = ?DISCONNECT_PACKET(?RC_IMPLEMENTATION_SPECIFIC_ERROR),
|
||||
{ok, [{outgoing, Packet}, {close, implementation_specific_error}], Channel} =
|
||||
Packet = ?DISCONNECT_PACKET(?RC_PROTOCOL_ERROR),
|
||||
{ok, [{outgoing, Packet}, {close, protocol_error}], Channel} =
|
||||
emqx_channel:handle_in(?AUTH_PACKET(), Channel).
|
||||
|
||||
t_handle_in_frame_error(_) ->
|
||||
|
@ -477,7 +634,7 @@ t_handle_deliver_nl(_) ->
|
|||
Channel = channel(#{clientinfo => ClientInfo, session => Session}),
|
||||
Msg = emqx_message:make(<<"clientid">>, ?QOS_1, <<"t1">>, <<"qos1">>),
|
||||
NMsg = emqx_message:set_flag(nl, Msg),
|
||||
{ok, Channel} = emqx_channel:handle_deliver([{deliver, <<"t1">>, NMsg}], Channel).
|
||||
{ok, _} = emqx_channel:handle_deliver([{deliver, <<"t1">>, NMsg}], Channel).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Test cases for handle_out
|
||||
|
@ -506,7 +663,7 @@ t_handle_out_connack_response_information(_) ->
|
|||
fun(true, _ClientInfo, _ConnInfo) ->
|
||||
{ok, #{session => session(), present => false}}
|
||||
end),
|
||||
ok = meck:expect(emqx_zone, response_information, fun(_) -> test end),
|
||||
emqx_config:put_zone_conf(default, [mqtt, response_information], test),
|
||||
IdleChannel = channel(#{conn_state => idle}),
|
||||
{ok, [{event, connected},
|
||||
{connack, ?CONNACK_PACKET(?RC_SUCCESS, 0, #{'Response-Information' := test})}],
|
||||
|
@ -520,7 +677,7 @@ t_handle_out_connack_not_response_information(_) ->
|
|||
fun(true, _ClientInfo, _ConnInfo) ->
|
||||
{ok, #{session => session(), present => false}}
|
||||
end),
|
||||
ok = meck:expect(emqx_zone, response_information, fun(_) -> test end),
|
||||
emqx_config:put_zone_conf(default, [mqtt, response_information], test),
|
||||
IdleChannel = channel(#{conn_state => idle}),
|
||||
{ok, [{event, connected}, {connack, ?CONNACK_PACKET(?RC_SUCCESS, 0, AckProps)}], _} =
|
||||
emqx_channel:handle_in(
|
||||
|
@ -660,11 +817,8 @@ t_enrich_conninfo(_) ->
|
|||
t_enrich_client(_) ->
|
||||
{ok, _ConnPkt, _Chan} = emqx_channel:enrich_client(connpkt(), channel()).
|
||||
|
||||
t_check_banned(_) ->
|
||||
ok = emqx_channel:check_banned(connpkt(), channel()).
|
||||
|
||||
t_auth_connect(_) ->
|
||||
{ok, _Chan} = emqx_channel:auth_connect(connpkt(), channel()).
|
||||
{ok, _, _Chan} = emqx_channel:authenticate(?CONNECT_PACKET(connpkt()), channel()).
|
||||
|
||||
t_process_alias(_) ->
|
||||
Publish = #mqtt_packet_publish{topic_name = <<>>, properties = #{'Topic-Alias' => 1}},
|
||||
|
@ -708,20 +862,20 @@ t_packing_alias(_) ->
|
|||
#mqtt_packet{variable = #mqtt_packet_publish{topic_name = <<"z">>}},
|
||||
channel())).
|
||||
|
||||
t_check_pub_acl(_) ->
|
||||
ok = meck:expect(emqx_zone, enable_acl, fun(_) -> true end),
|
||||
t_check_pub_authz(_) ->
|
||||
emqx_config:put_zone_conf(default, [authorization, enable], true),
|
||||
Publish = ?PUBLISH_PACKET(?QOS_0, <<"t">>, 1, <<"payload">>),
|
||||
ok = emqx_channel:check_pub_acl(Publish, channel()).
|
||||
ok = emqx_channel:check_pub_authz(Publish, channel()).
|
||||
|
||||
t_check_pub_alias(_) ->
|
||||
Publish = #mqtt_packet_publish{topic_name = <<>>, properties = #{'Topic-Alias' => 1}},
|
||||
Channel = emqx_channel:set_field(alias_maximum, #{inbound => 10}, channel()),
|
||||
ok = emqx_channel:check_pub_alias(#mqtt_packet{variable = Publish}, Channel).
|
||||
|
||||
t_check_sub_acls(_) ->
|
||||
ok = meck:expect(emqx_zone, enable_acl, fun(_) -> true end),
|
||||
t_check_sub_authzs(_) ->
|
||||
emqx_config:put_zone_conf(default, [authorization, enable], true),
|
||||
TopicFilter = {<<"t">>, ?DEFAULT_SUBOPTS},
|
||||
[{TopicFilter, 0}] = emqx_channel:check_sub_acls([TopicFilter], channel()).
|
||||
[{TopicFilter, 0}] = emqx_channel:check_sub_authzs([TopicFilter], channel()).
|
||||
|
||||
t_enrich_connack_caps(_) ->
|
||||
ok = meck:new(emqx_mqtt_caps, [passthrough, no_history]),
|
||||
|
@ -763,7 +917,7 @@ t_ws_cookie_init(_) ->
|
|||
conn_mod => emqx_ws_connection,
|
||||
ws_cookie => WsCookie
|
||||
},
|
||||
Channel = emqx_channel:init(ConnInfo, [{zone, zone}]),
|
||||
Channel = emqx_channel:init(ConnInfo, #{zone => default, listener => mqtt_tcp}),
|
||||
?assertMatch(#{ws_cookie := WsCookie}, emqx_channel:info(clientinfo, Channel)).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
|
@ -788,7 +942,7 @@ channel(InitFields) ->
|
|||
maps:fold(fun(Field, Value, Channel) ->
|
||||
emqx_channel:set_field(Field, Value, Channel)
|
||||
end,
|
||||
emqx_channel:init(ConnInfo, [{zone, zone}]),
|
||||
emqx_channel:init(ConnInfo, #{zone => default, listener => mqtt_tcp}),
|
||||
maps:merge(#{clientinfo => clientinfo(),
|
||||
session => session(),
|
||||
conn_state => connected
|
||||
|
@ -796,7 +950,8 @@ channel(InitFields) ->
|
|||
|
||||
clientinfo() -> clientinfo(#{}).
|
||||
clientinfo(InitProps) ->
|
||||
maps:merge(#{zone => zone,
|
||||
maps:merge(#{zone => default,
|
||||
listener => mqtt_tcp,
|
||||
protocol => mqtt,
|
||||
peerhost => {127,0,0,1},
|
||||
clientid => <<"clientid">>,
|
||||
|
@ -828,7 +983,7 @@ session(InitFields) when is_map(InitFields) ->
|
|||
maps:fold(fun(Field, Value, Session) ->
|
||||
emqx_session:set_field(Field, Value, Session)
|
||||
end,
|
||||
emqx_session:init(#{zone => channel}, #{receive_maximum => 0}),
|
||||
emqx_session:init(#{max_inflight => 0}),
|
||||
InitFields).
|
||||
|
||||
%% conn: 5/s; overall: 10/s
|
||||
|
|
|
@ -78,17 +78,14 @@ groups() ->
|
|||
|
||||
init_per_suite(Config) ->
|
||||
emqx_ct_helpers:boot_modules(all),
|
||||
emqx_ct_helpers:start_apps([], fun set_special_confs/1),
|
||||
emqx_ct_helpers:start_apps([]),
|
||||
emqx_config:put_listener_conf(default, mqtt_ssl, [ssl, verify], verify_peer),
|
||||
emqx_listeners:restart_listener('default:mqtt_ssl'),
|
||||
Config.
|
||||
|
||||
end_per_suite(_Config) ->
|
||||
emqx_ct_helpers:stop_apps([]).
|
||||
|
||||
set_special_confs(emqx) ->
|
||||
emqx_ct_helpers:change_emqx_opts(ssl_twoway, [{peer_cert_as_username, cn}]);
|
||||
set_special_confs(_) ->
|
||||
ok.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Test cases for MQTT v3
|
||||
%%--------------------------------------------------------------------
|
||||
|
@ -104,8 +101,7 @@ t_basic_v4(_Config) ->
|
|||
t_basic([{proto_ver, v4}]).
|
||||
|
||||
t_cm(_) ->
|
||||
IdleTimeout = emqx_zone:get_env(external, idle_timeout, 30000),
|
||||
emqx_zone:set_env(external, idle_timeout, 1000),
|
||||
emqx_config:put_zone_conf(default, [mqtt, idle_timeout], 1000),
|
||||
ClientId = <<"myclient">>,
|
||||
{ok, C} = emqtt:start_link([{clientid, ClientId}]),
|
||||
{ok, _} = emqtt:connect(C),
|
||||
|
@ -115,7 +111,7 @@ t_cm(_) ->
|
|||
ct:sleep(1200),
|
||||
Stats = emqx_cm:get_chan_stats(ClientId),
|
||||
?assertEqual(1, proplists:get_value(subscriptions_cnt, Stats)),
|
||||
emqx_zone:set_env(external, idle_timeout, IdleTimeout).
|
||||
emqx_config:put_zone_conf(default, [mqtt, idle_timeout], 15000).
|
||||
|
||||
t_cm_registry(_) ->
|
||||
Info = supervisor:which_children(emqx_cm_sup),
|
||||
|
@ -273,15 +269,13 @@ t_basic(_Opts) ->
|
|||
ok = emqtt:disconnect(C).
|
||||
|
||||
t_username_as_clientid(_) ->
|
||||
emqx_zone:set_env(external, use_username_as_clientid, true),
|
||||
emqx_config:put_zone_conf(default, [mqtt, use_username_as_clientid], true),
|
||||
Username = <<"usera">>,
|
||||
{ok, C} = emqtt:start_link([{username, Username}]),
|
||||
{ok, _} = emqtt:connect(C),
|
||||
#{clientinfo := #{clientid := Username}} = emqx_cm:get_chan_info(Username),
|
||||
emqtt:disconnect(C).
|
||||
|
||||
|
||||
|
||||
t_certcn_as_clientid_default_config_tls(_) ->
|
||||
tls_certcn_as_clientid(default).
|
||||
|
||||
|
@ -329,7 +323,7 @@ tls_certcn_as_clientid(TLSVsn) ->
|
|||
|
||||
tls_certcn_as_clientid(TLSVsn, RequiredTLSVsn) ->
|
||||
CN = <<"Client">>,
|
||||
emqx_zone:set_env(external, use_username_as_clientid, true),
|
||||
emqx_config:put_zone_conf(default, [mqtt, peer_cert_as_clientid], cn),
|
||||
SslConf = emqx_ct_helpers:client_ssl_twoway(TLSVsn),
|
||||
{ok, Client} = emqtt:start_link([{port, 8883}, {ssl, true}, {ssl_opts, SslConf}]),
|
||||
{ok, _} = emqtt:connect(Client),
|
||||
|
|
|
@ -89,7 +89,7 @@ t_open_session(_) ->
|
|||
ok = meck:expect(emqx_connection, call, fun(_, _) -> ok end),
|
||||
ok = meck:expect(emqx_connection, call, fun(_, _, _) -> ok end),
|
||||
|
||||
ClientInfo = #{zone => external,
|
||||
ClientInfo = #{zone => default, listener => mqtt_tcp,
|
||||
clientid => <<"clientid">>,
|
||||
username => <<"username">>,
|
||||
peerhost => {127,0,0,1}},
|
||||
|
@ -114,7 +114,7 @@ rand_client_id() ->
|
|||
|
||||
t_open_session_race_condition(_) ->
|
||||
ClientId = rand_client_id(),
|
||||
ClientInfo = #{zone => external,
|
||||
ClientInfo = #{zone => default, listener => mqtt_tcp,
|
||||
clientid => ClientId,
|
||||
username => <<"username">>,
|
||||
peerhost => {127,0,0,1}},
|
||||
|
|
|
@ -42,26 +42,26 @@ end_per_testcase(_TestCase, Config) ->
|
|||
Config.
|
||||
|
||||
t_is_enabled(_) ->
|
||||
application:set_env(emqx, enable_session_registry, false),
|
||||
emqx_config:put([broker, enable_session_registry], false),
|
||||
?assertEqual(false, emqx_cm_registry:is_enabled()),
|
||||
application:set_env(emqx, enable_session_registry, true),
|
||||
emqx_config:put([broker, enable_session_registry], true),
|
||||
?assertEqual(true, emqx_cm_registry:is_enabled()).
|
||||
|
||||
t_register_unregister_channel(_) ->
|
||||
ClientId = <<"clientid">>,
|
||||
application:set_env(emqx, enable_session_registry, false),
|
||||
emqx_config:put([broker, enable_session_registry], false),
|
||||
emqx_cm_registry:register_channel(ClientId),
|
||||
?assertEqual([], emqx_cm_registry:lookup_channels(ClientId)),
|
||||
|
||||
application:set_env(emqx, enable_session_registry, true),
|
||||
emqx_config:put([broker, enable_session_registry], true),
|
||||
emqx_cm_registry:register_channel(ClientId),
|
||||
?assertEqual([self()], emqx_cm_registry:lookup_channels(ClientId)),
|
||||
|
||||
application:set_env(emqx, enable_session_registry, false),
|
||||
emqx_config:put([broker, enable_session_registry], false),
|
||||
emqx_cm_registry:unregister_channel(ClientId),
|
||||
?assertEqual([self()], emqx_cm_registry:lookup_channels(ClientId)),
|
||||
|
||||
application:set_env(emqx, enable_session_registry, true),
|
||||
emqx_config:put([broker, enable_session_registry], true),
|
||||
emqx_cm_registry:unregister_channel(ClientId),
|
||||
?assertEqual([], emqx_cm_registry:lookup_channels(ClientId)).
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue