Merge remote-tracking branch 'origin/master' into merge-master-to-ee50-a
This commit is contained in:
commit
befc4acced
|
@ -121,34 +121,8 @@ jobs:
|
||||||
PGSQL_TAG: 13
|
PGSQL_TAG: 13
|
||||||
REDIS_TAG: 6
|
REDIS_TAG: 6
|
||||||
run: |
|
run: |
|
||||||
docker-compose \
|
./scripts/ct/run.sh --app ${{ matrix.app_name }}
|
||||||
-f .ci/docker-compose-file/docker-compose-mongo-single-tcp.yaml \
|
|
||||||
-f .ci/docker-compose-file/docker-compose-mongo-single-tls.yaml \
|
|
||||||
-f .ci/docker-compose-file/docker-compose-mongo-replicaset-tcp.yaml \
|
|
||||||
-f .ci/docker-compose-file/docker-compose-mongo-sharded-tcp.yaml \
|
|
||||||
-f .ci/docker-compose-file/docker-compose-mysql-tcp.yaml \
|
|
||||||
-f .ci/docker-compose-file/docker-compose-mysql-tls.yaml \
|
|
||||||
-f .ci/docker-compose-file/docker-compose-pgsql-tcp.yaml \
|
|
||||||
-f .ci/docker-compose-file/docker-compose-pgsql-tls.yaml \
|
|
||||||
-f .ci/docker-compose-file/docker-compose-redis-single-tcp.yaml \
|
|
||||||
-f .ci/docker-compose-file/docker-compose-redis-single-tls.yaml \
|
|
||||||
-f .ci/docker-compose-file/docker-compose-redis-sentinel-tcp.yaml \
|
|
||||||
-f .ci/docker-compose-file/docker-compose-redis-sentinel-tls.yaml \
|
|
||||||
-f .ci/docker-compose-file/docker-compose.yaml \
|
|
||||||
up -d --build
|
|
||||||
|
|
||||||
- name: wait some services to be fully up
|
|
||||||
run: |
|
|
||||||
docker wait mongosharded_client
|
|
||||||
docker wait mongo_rs_client
|
|
||||||
|
|
||||||
# produces <app-name>.coverdata
|
|
||||||
- name: run common test
|
|
||||||
working-directory: source
|
|
||||||
run: |
|
|
||||||
docker exec -i ${{ matrix.otp_release }} bash -c "git config --global --add safe.directory \"$GITHUB_WORKSPACE\" && make ${{ matrix.app_name }}-ct"
|
|
||||||
- uses: actions/upload-artifact@v1
|
- uses: actions/upload-artifact@v1
|
||||||
if: matrix.otp_release == 'erlang24'
|
|
||||||
with:
|
with:
|
||||||
name: coverdata
|
name: coverdata
|
||||||
path: source/_build/test/cover
|
path: source/_build/test/cover
|
||||||
|
|
|
@ -1,8 +1,17 @@
|
||||||
# 5.0.8
|
# 5.0.8
|
||||||
|
|
||||||
|
## Bug fixes
|
||||||
|
|
||||||
|
* Fix exhook `client.authorize` never being execauted. [#8780](https://github.com/emqx/emqx/pull/8780)
|
||||||
|
* Fix JWT plugin don't support non-integer timestamp claims. [#8867](https://github.com/emqx/emqx/pull/8867)
|
||||||
|
* Avoid publishing will message when client fails to auhtenticate. [#8887](https://github.com/emqx/emqx/pull/8887)
|
||||||
|
* Speed up dispatching of shared subscription messages in a cluster [#8893](https://github.com/emqx/emqx/pull/8893)
|
||||||
|
|
||||||
## Enhancements
|
## Enhancements
|
||||||
|
|
||||||
* change the `/gateway` API path to plural form. [#8823](https://github.com/emqx/emqx/pull/8823)
|
* Change the `/gateway` API path to plural form. [#8823](https://github.com/emqx/emqx/pull/8823)
|
||||||
|
* Remove `node.etc_dir` from emqx.conf, because it is never used.
|
||||||
|
Also allow user to customize the logging directory [#8892](https://github.com/emqx/emqx/pull/8892)
|
||||||
|
|
||||||
# 5.0.7
|
# 5.0.7
|
||||||
|
|
||||||
|
@ -11,6 +20,7 @@
|
||||||
* Remove `will_msg` (not used) field from the client API. [#8721](https://github.com/emqx/emqx/pull/8721)
|
* Remove `will_msg` (not used) field from the client API. [#8721](https://github.com/emqx/emqx/pull/8721)
|
||||||
* Fix `$queue` topic name error in management API return. [#8728](https://github.com/emqx/emqx/pull/8728)
|
* Fix `$queue` topic name error in management API return. [#8728](https://github.com/emqx/emqx/pull/8728)
|
||||||
* Fix race condition which may cause `client.connected` and `client.disconnected` out of order. [#8625](https://github.com/emqx/emqx/pull/8625)
|
* Fix race condition which may cause `client.connected` and `client.disconnected` out of order. [#8625](https://github.com/emqx/emqx/pull/8625)
|
||||||
|
* Fix quic listener default idle timeout's type. [#8826](https://github.com/emqx/emqx/pull/8826)
|
||||||
|
|
||||||
## Enhancements
|
## Enhancements
|
||||||
|
|
||||||
|
@ -32,6 +42,7 @@
|
||||||
|
|
||||||
## Enhancements
|
## Enhancements
|
||||||
|
|
||||||
|
* Add `bootstrap_users_file` configuration to add default Dashboard username list, which is only added when EMQX is first started.
|
||||||
* The license is now copied to all nodes in the cluster when it's reloaded. [#8598](https://github.com/emqx/emqx/pull/8598)
|
* The license is now copied to all nodes in the cluster when it's reloaded. [#8598](https://github.com/emqx/emqx/pull/8598)
|
||||||
* Added a HTTP API to manage licenses. [#8610](https://github.com/emqx/emqx/pull/8610)
|
* Added a HTTP API to manage licenses. [#8610](https://github.com/emqx/emqx/pull/8610)
|
||||||
* Updated `/nodes` API node_status from `Running/Stopped` to `running/stopped`. [#8642](https://github.com/emqx/emqx/pull/8642)
|
* Updated `/nodes` API node_status from `Running/Stopped` to `running/stopped`. [#8642](https://github.com/emqx/emqx/pull/8642)
|
||||||
|
|
5
Makefile
5
Makefile
|
@ -6,7 +6,7 @@ export EMQX_DEFAULT_BUILDER = ghcr.io/emqx/emqx-builder/5.0-17:1.13.4-24.2.1-1-d
|
||||||
export EMQX_DEFAULT_RUNNER = debian:11-slim
|
export EMQX_DEFAULT_RUNNER = debian:11-slim
|
||||||
export OTP_VSN ?= $(shell $(CURDIR)/scripts/get-otp-vsn.sh)
|
export OTP_VSN ?= $(shell $(CURDIR)/scripts/get-otp-vsn.sh)
|
||||||
export ELIXIR_VSN ?= $(shell $(CURDIR)/scripts/get-elixir-vsn.sh)
|
export ELIXIR_VSN ?= $(shell $(CURDIR)/scripts/get-elixir-vsn.sh)
|
||||||
export EMQX_DASHBOARD_VERSION ?= v1.0.7
|
export EMQX_DASHBOARD_VERSION ?= v1.0.8
|
||||||
export EMQX_EE_DASHBOARD_VERSION ?= e1.0.1-beta.2
|
export EMQX_EE_DASHBOARD_VERSION ?= e1.0.1-beta.2
|
||||||
export EMQX_REL_FORM ?= tgz
|
export EMQX_REL_FORM ?= tgz
|
||||||
export QUICER_DOWNLOAD_FROM_RELEASE = 1
|
export QUICER_DOWNLOAD_FROM_RELEASE = 1
|
||||||
|
@ -80,7 +80,6 @@ static_checks:
|
||||||
|
|
||||||
APPS=$(shell $(SCRIPTS)/find-apps.sh)
|
APPS=$(shell $(SCRIPTS)/find-apps.sh)
|
||||||
|
|
||||||
## app/name-ct targets are intended for local tests hence cover is not enabled
|
|
||||||
.PHONY: $(APPS:%=%-ct)
|
.PHONY: $(APPS:%=%-ct)
|
||||||
define gen-app-ct-target
|
define gen-app-ct-target
|
||||||
$1-ct: $(REBAR)
|
$1-ct: $(REBAR)
|
||||||
|
@ -132,7 +131,7 @@ $(REL_PROFILES:%=%): $(COMMON_DEPS)
|
||||||
clean: $(PROFILES:%=clean-%)
|
clean: $(PROFILES:%=clean-%)
|
||||||
$(PROFILES:%=clean-%):
|
$(PROFILES:%=clean-%):
|
||||||
@if [ -d _build/$(@:clean-%=%) ]; then \
|
@if [ -d _build/$(@:clean-%=%) ]; then \
|
||||||
rm rebar.lock \
|
rm -f rebar.lock; \
|
||||||
rm -rf _build/$(@:clean-%=%)/rel; \
|
rm -rf _build/$(@:clean-%=%)/rel; \
|
||||||
$(FIND) _build/$(@:clean-%=%) -name '*.beam' -o -name '*.so' -o -name '*.app' -o -name '*.appup' -o -name '*.o' -o -name '*.d' -type f | xargs rm -f; \
|
$(FIND) _build/$(@:clean-%=%) -name '*.beam' -o -name '*.so' -o -name '*.app' -o -name '*.appup' -o -name '*.o' -o -name '*.d' -type f | xargs rm -f; \
|
||||||
$(FIND) _build/$(@:clean-%=%) -type l -delete; \
|
$(FIND) _build/$(@:clean-%=%) -type l -delete; \
|
||||||
|
|
|
@ -40,6 +40,10 @@ docker run -d --name emqx-ee -p 1883:1883 -p 8081:8081 -p 8083:8083 -p 8084:8084
|
||||||
|
|
||||||
接下来请参考 [入门指南](https://www.emqx.io/docs/zh/v5.0/getting-started/getting-started.html#启动-emqx) 开启您的 EMQX 之旅。
|
接下来请参考 [入门指南](https://www.emqx.io/docs/zh/v5.0/getting-started/getting-started.html#启动-emqx) 开启您的 EMQX 之旅。
|
||||||
|
|
||||||
|
#### 在 Kubernetes 上运行 EMQX 集群
|
||||||
|
|
||||||
|
请参考 [EMQX Operator 文档](https://github.com/emqx/emqx-operator/blob/main/docs/zh_CN/getting-started/getting-started.md)。
|
||||||
|
|
||||||
#### 更多安装方式
|
#### 更多安装方式
|
||||||
|
|
||||||
您可以从 [www.emqx.io/zh/downloads](https://www.emqx.io/zh/downloads) 下载不同格式的 EMQX 安装包进行手动安装。
|
您可以从 [www.emqx.io/zh/downloads](https://www.emqx.io/zh/downloads) 下载不同格式的 EMQX 安装包进行手动安装。
|
||||||
|
|
75
README-RU.md
75
README-RU.md
|
@ -7,39 +7,84 @@
|
||||||
[](https://slack-invite.emqx.io/)
|
[](https://slack-invite.emqx.io/)
|
||||||
[](https://discord.gg/xYGf3fQnES)
|
[](https://discord.gg/xYGf3fQnES)
|
||||||
[](https://twitter.com/EMQTech)
|
[](https://twitter.com/EMQTech)
|
||||||
[](https://github.com/emqx/emqx/discussions)
|
|
||||||
[](https://www.youtube.com/channel/UC5FjR77ErAxvZENEWzQaO5Q)
|
[](https://www.youtube.com/channel/UC5FjR77ErAxvZENEWzQaO5Q)
|
||||||
|
|
||||||
[](https://www.emqx.com/en/careers)
|
|
||||||
|
|
||||||
[English](./README.md) | [简体中文](./README-CN.md) | [日本語](./README-JP.md) | русский
|
[English](./README.md) | [简体中文](./README-CN.md) | [日本語](./README-JP.md) | русский
|
||||||
|
|
||||||
*EMQX* — это масштабируемый, высоко доступный, распределённый MQTT брокер с полностью открытым кодом для интернета вещей, межмашинного взаимодействия и мобильных приложений, который поддерживает миллионы одновременных подключений.
|
*EMQX* — это самый масштабируемый и популярный высокопроизводительный MQTT брокер с полностью открытым кодом для интернета вещей, межмашинного взаимодействия и мобильных приложений. EMQX может поддерживать более чем 100 миллионов одновременных соединенией на одном кластере с задержкой в 1 миллисекунду, а также принимать и обрабабывать миллионы MQTT сообщений в секунду.
|
||||||
|
|
||||||
Начиная с релиза 3.0, брокер *EMQX* полностью поддерживает протокол MQTT версии 5.0, и обратно совместим с версиями 3.1 и 3.1.1, а также протоколами MQTT-SN, CoAP, LwM2M, WebSocket и STOMP. Начиная с релиза 3.0, брокер *EMQX* может масштабироваться до более чем 10 миллионов одновременных MQTT соединений на один кластер.
|
Мы [протестировали масштабируемость](https://www.emqx.com/en/blog/reaching-100m-mqtt-connections-with-emqx-5-0) EMQX v5.0 и подтвердили что брокер может поддерживать до 100 миллионов одновременных подключений устройств. Это является критически важной вехой для разработчиков IoT. EMQX 5.0 также поставляется с множеством интересных новых функций и значительными улучшениями производительности, включая более мощный [механизм правил](https://www.emqx.com/en/solutions/iot-rule-engine), улучшенное управление безопасностью, расширение базы данных Mria и многое другое для повышения масштабируемости приложений IoT.
|
||||||
|
|
||||||
- Полный список возможностей доступен по ссылке: [EMQX Release Notes](https://github.com/emqx/emqx/releases).
|
За последние несколько лет EMQX приобрел популярность среди IoT-компаний и используется более чем 20 000 пользователей по всему миру из более чем 50 стран, при этом по всему миру поддерживается более 100 миллионов подключений к IoT-устройствам.
|
||||||
- Более подробная информация доступна на нашем сайте: [EMQX homepage](https://www.emqx.io/).
|
|
||||||
|
|
||||||
## Установка
|
Для получения дополнительной информации, пожалуйста, посетите [домашнюю страницу EMQX](https://www.emqx.io/).
|
||||||
|
|
||||||
Брокер *EMQX* кросплатформенный, и поддерживает Linux, Unix, macOS и Windows. Он может работать на серверах с архитектурой x86_64 и устройствах на архитектуре ARM, таких как Raspberry Pi.
|
## Начало работы
|
||||||
|
|
||||||
Более подробная информация о запуске на Windows по ссылке: [Windows.md](./Windows.md)
|
#### EMQX Cloud
|
||||||
|
|
||||||
#### Установка EMQX с помощью Docker-образа
|
Самый простой способ запустить EMQX это развернуть его с помощью EMQX Cloud. Вы можете [попробовать EMQX Cloud бесплатно](https://www.emqx.com/en/signup?utm_source=github.com&utm_medium=referral&utm_campaign=emqx-readme-to-cloud&continue=https://cloud-intl.emqx.com/console/deployments/0?oper=new), данные кредитной карточки не требуются.
|
||||||
|
|
||||||
|
#### Установка EMQX с помощью Docker
|
||||||
|
|
||||||
```
|
```
|
||||||
docker run -d --name emqx -p 1883:1883 -p 8081:8081 -p 8083:8083 -p 8883:8883 -p 8084:8084 -p 18083:18083 emqx/emqx
|
docker run -d --name emqx -p 1883:1883 -p 8081:8081 -p 8083:8083 -p 8883:8883 -p 8084:8084 -p 18083:18083 emqx/emqx
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Установка бинарного пакета
|
Или запустите EMQX Enterprise со встроенной бессрочной лицензией на 10 соединений.
|
||||||
|
|
||||||
Сборки для различных операционных систем: [Загрузить EMQX](https://www.emqx.com/en/downloads).
|
```
|
||||||
|
docker run -d --name emqx-ee -p 1883:1883 -p 8081:8081 -p 8083:8083 -p 8084:8084 -p 8883:8883 -p 18083:18083 emqx/emqx-ee:latest
|
||||||
|
```
|
||||||
|
|
||||||
- [Установка на одном сервере](https://www.emqx.io/docs/en/latest/getting-started/install.html)
|
Чтобы ознакомиться с функциональностью EMQX, пожалуйста, следуйте [руководству по началу работы](https://www.emqx.io/docs/en/v5.0/getting-started/getting-started.html#start-emqx).
|
||||||
- [Установка на кластере](https://www.emqx.io/docs/en/latest/advanced/cluster.html)
|
|
||||||
|
|
||||||
|
#### Запуск кластера EMQX на kubernetes
|
||||||
|
|
||||||
|
[Документация по EMQX Operator](https://github.com/emqx/emqx-operator/blob/main/docs/en_US/getting-started/getting-started.md).
|
||||||
|
|
||||||
|
#### Дополнительные опции установки
|
||||||
|
|
||||||
|
Если вы предпочитаете устанавливать и управлять EMQX самостоятельно, вы можете загрузить последнюю версию с [www.emqx.io/downloads](https://www.emqx.io/downloads).
|
||||||
|
|
||||||
|
Смотрите также [EMQX installation documentation](https://www.emqx.io/docs/en/v5.0/deploy/install.html).
|
||||||
|
|
||||||
|
## Документация
|
||||||
|
|
||||||
|
[Документация EMQX](https://www.emqx.io/docs/en/latest/).
|
||||||
|
|
||||||
|
[Документация EMQX Enterprise](https://docs.emqx.com/en/).
|
||||||
|
|
||||||
|
## Участие в разработке
|
||||||
|
|
||||||
|
Пожалуйста, прочитайте [contributing.md](./CONTRIBUTING.md).
|
||||||
|
|
||||||
|
Для более организованных предложений по улучшению вы можете отправить pull requests в [EIP](https://github.com/emqx/eip).
|
||||||
|
|
||||||
|
## Присоединяйтесь к коммьюнити
|
||||||
|
|
||||||
|
- Подпишитесь на [@EMQTech on Twitter](https://twitter.com/EMQTech).
|
||||||
|
- Подключайтесь к [обсуждениям](https://github.com/emqx/emqx/discussions) на Github, если у вас есть какой-то вопрос.
|
||||||
|
- Присоединяйтесь к нашему [официальному Discord](https://discord.gg/xYGf3fQnES), чтобы поговорить с командой разработки.
|
||||||
|
- Подписывайтесь на канал [EMQX YouTube](https://www.youtube.com/channel/UC5FjR77ErAxvZENEWzQaO5Q).
|
||||||
|
|
||||||
|
## Дополнительные ресурсы
|
||||||
|
|
||||||
|
- [MQTT client programming](https://www.emqx.com/en/blog/tag/mqtt-client-programming)
|
||||||
|
|
||||||
|
Коллекция блогов, чтобы помочь разработчикам быстро начать работу с MQTT на PHP, Node.js, Python, Golang, и других языках программирования.
|
||||||
|
|
||||||
|
- [MQTT SDKs](https://www.emqx.com/en/mqtt-client-sdk)
|
||||||
|
|
||||||
|
Мы выбрали популярные SDK клиентов MQTT на различных языках программирования и предоставили примеры кода, которые помогут вам быстро понять, как использовать клиенты MQTT.
|
||||||
|
|
||||||
|
- [MQTT X](https://mqttx.app/)
|
||||||
|
|
||||||
|
Элегантный кроссплатформенный клиент MQTT 5.0, в виде десктопного приложения, приложения для командной строки и веб-приложения, чтобы помочь вам быстрее разрабатывать и отлаживать службы и приложения MQTT.
|
||||||
|
|
||||||
|
- [Internet of Vehicles](https://www.emqx.com/en/blog/category/internet-of-vehicles)
|
||||||
|
|
||||||
|
Создайте надежную, эффективную и специализированную для вашей индустрии платформу IoV на основе практического опыта EMQ, от теоретических знаний, таких как выбор протокола, до практических операций, таких как проектирование архитектуры платформы.
|
||||||
|
|
||||||
## Сборка из исходного кода
|
## Сборка из исходного кода
|
||||||
|
|
||||||
|
|
25
README.md
25
README.md
|
@ -41,6 +41,10 @@ docker run -d --name emqx-ee -p 1883:1883 -p 8081:8081 -p 8083:8083 -p 8084:8084
|
||||||
|
|
||||||
Next, please follow the [getting started guide](https://www.emqx.io/docs/en/v5.0/getting-started/getting-started.html#start-emqx) to tour the EMQX features.
|
Next, please follow the [getting started guide](https://www.emqx.io/docs/en/v5.0/getting-started/getting-started.html#start-emqx) to tour the EMQX features.
|
||||||
|
|
||||||
|
#### Run EMQX cluster on kubernetes
|
||||||
|
|
||||||
|
For details: [EMQX Operator](https://github.com/emqx/emqx-operator/blob/main/docs/en_US/getting-started/getting-started.md).
|
||||||
|
|
||||||
#### More installation options
|
#### More installation options
|
||||||
|
|
||||||
If you prefer to install and manage EMQX yourself, you can download the latest version from [www.emqx.io/downloads](https://www.emqx.io/downloads).
|
If you prefer to install and manage EMQX yourself, you can download the latest version from [www.emqx.io/downloads](https://www.emqx.io/downloads).
|
||||||
|
@ -106,6 +110,27 @@ make
|
||||||
_build/emqx/rel/emqx/bin/emqx console
|
_build/emqx/rel/emqx/bin/emqx console
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Building on Apple silicon (M1, M2)
|
||||||
|
|
||||||
|
Homebrew on Apple silicon [changed default location of it's home directory](https://github.com/Homebrew/brew/issues/9177) from `/usr/local` to `/opt/homebrew` and as a result a few things broke in the process.
|
||||||
|
|
||||||
|
Concerning EMQX, when you install `unixodbc` package (one of the dependencies) via Homebrew, and build Erlang/OTP with [kerl](https://github.com/kerl/kerl), kerl will not be able to find `unixodbc`.
|
||||||
|
|
||||||
|
Here is how to solve it:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
brew install unixodbc kerl
|
||||||
|
sudo ln -s $(realpath $(brew --prefix unixodbc)) /usr/local/odbc
|
||||||
|
export CC="/usr/bin/gcc -I$(brew --prefix unixodbc)/include"
|
||||||
|
export LDFLAGS="-L$(brew --prefix unixodbc)/lib"
|
||||||
|
kerl build 24.3
|
||||||
|
mkdir ~/.kerl/installations
|
||||||
|
kerl install 24.3 ~/.kerl/installations/24.3
|
||||||
|
. ~/.kerl/installations/24.3/activate
|
||||||
|
```
|
||||||
|
|
||||||
|
Then you can proceed with `make`.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
See [LICENSE](./LICENSE).
|
See [LICENSE](./LICENSE).
|
||||||
|
|
|
@ -32,7 +32,7 @@
|
||||||
%% `apps/emqx/src/bpapi/README.md'
|
%% `apps/emqx/src/bpapi/README.md'
|
||||||
|
|
||||||
%% Community edition
|
%% Community edition
|
||||||
-define(EMQX_RELEASE_CE, "5.0.6").
|
-define(EMQX_RELEASE_CE, "5.0.7").
|
||||||
|
|
||||||
%% Enterprise edition
|
%% Enterprise edition
|
||||||
-define(EMQX_RELEASE_EE, "5.0.0-beta.3").
|
-define(EMQX_RELEASE_EE, "5.0.0-beta.3").
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
{emqx_resource,1}.
|
{emqx_resource,1}.
|
||||||
{emqx_retainer,1}.
|
{emqx_retainer,1}.
|
||||||
{emqx_rule_engine,1}.
|
{emqx_rule_engine,1}.
|
||||||
|
{emqx_shared_sub,1}.
|
||||||
{emqx_slow_subs,1}.
|
{emqx_slow_subs,1}.
|
||||||
{emqx_statsd,1}.
|
{emqx_statsd,1}.
|
||||||
{emqx_telemetry,1}.
|
{emqx_telemetry,1}.
|
||||||
|
|
|
@ -69,9 +69,12 @@ start() ->
|
||||||
announce(emqx).
|
announce(emqx).
|
||||||
|
|
||||||
%% @doc Get maximum version of the backplane API supported by the node
|
%% @doc Get maximum version of the backplane API supported by the node
|
||||||
-spec supported_version(node(), api()) -> api_version().
|
-spec supported_version(node(), api()) -> api_version() | undefined.
|
||||||
supported_version(Node, API) ->
|
supported_version(Node, API) ->
|
||||||
ets:lookup_element(?TAB, {Node, API}, #?TAB.version).
|
case ets:lookup(?TAB, {Node, API}) of
|
||||||
|
[#?TAB{version = V}] -> V;
|
||||||
|
[] -> undefined
|
||||||
|
end.
|
||||||
|
|
||||||
%% @doc Get maximum version of the backplane API supported by the
|
%% @doc Get maximum version of the backplane API supported by the
|
||||||
%% entire cluster
|
%% entire cluster
|
||||||
|
|
|
@ -153,6 +153,8 @@ extract_mfa(?BACKEND(emqx_rpc, CallOrCast), [_Node, M, F, A]) ->
|
||||||
{call_or_cast(CallOrCast), M, F, A};
|
{call_or_cast(CallOrCast), M, F, A};
|
||||||
extract_mfa(?BACKEND(emqx_rpc, CallOrCast), [_Tag, _Node, M, F, A]) ->
|
extract_mfa(?BACKEND(emqx_rpc, CallOrCast), [_Tag, _Node, M, F, A]) ->
|
||||||
{call_or_cast(CallOrCast), M, F, A};
|
{call_or_cast(CallOrCast), M, F, A};
|
||||||
|
extract_mfa(?BACKEND(emqx_rpc, call), [_Tag, _Node, M, F, A, _Timeout]) ->
|
||||||
|
{call_or_cast(call), M, F, A};
|
||||||
%% (e)rpc:
|
%% (e)rpc:
|
||||||
extract_mfa(?BACKEND(rpc, multicall), [M, F, A]) ->
|
extract_mfa(?BACKEND(rpc, multicall), [M, F, A]) ->
|
||||||
{call_or_cast(multicall), M, F, A};
|
{call_or_cast(multicall), M, F, A};
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
{id, "emqx"},
|
{id, "emqx"},
|
||||||
{description, "EMQX Core"},
|
{description, "EMQX Core"},
|
||||||
% strict semver, bump manually!
|
% strict semver, bump manually!
|
||||||
{vsn, "5.0.7"},
|
{vsn, "5.0.8"},
|
||||||
{modules, []},
|
{modules, []},
|
||||||
{registered, []},
|
{registered, []},
|
||||||
{applications, [
|
{applications, [
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
-module(emqx_access_control).
|
-module(emqx_access_control).
|
||||||
|
|
||||||
-include("emqx.hrl").
|
-include("emqx.hrl").
|
||||||
|
-include("logger.hrl").
|
||||||
|
|
||||||
-export([
|
-export([
|
||||||
authenticate/1,
|
authenticate/1,
|
||||||
|
@ -70,9 +71,26 @@ check_authorization_cache(ClientInfo, PubSub, Topic) ->
|
||||||
|
|
||||||
do_authorize(ClientInfo, PubSub, Topic) ->
|
do_authorize(ClientInfo, PubSub, Topic) ->
|
||||||
NoMatch = emqx:get_config([authorization, no_match], allow),
|
NoMatch = emqx:get_config([authorization, no_match], allow),
|
||||||
case run_hooks('client.authorize', [ClientInfo, PubSub, Topic], NoMatch) of
|
Default = #{result => NoMatch, from => default},
|
||||||
allow -> allow;
|
case run_hooks('client.authorize', [ClientInfo, PubSub, Topic], Default) of
|
||||||
_Other -> deny
|
AuthzResult = #{result := Result} when Result == allow; Result == deny ->
|
||||||
|
From = maps:get(from, AuthzResult, unknown),
|
||||||
|
emqx:run_hook(
|
||||||
|
'client.check_authz_complete',
|
||||||
|
[ClientInfo, PubSub, Topic, Result, From]
|
||||||
|
),
|
||||||
|
Result;
|
||||||
|
Other ->
|
||||||
|
?SLOG(error, #{
|
||||||
|
msg => "unknown_authorization_return_format",
|
||||||
|
expected_example => "#{result => allow, from => default}",
|
||||||
|
got => Other
|
||||||
|
}),
|
||||||
|
emqx:run_hook(
|
||||||
|
'client.check_authz_complete',
|
||||||
|
[ClientInfo, PubSub, Topic, deny, unknown_return_format]
|
||||||
|
),
|
||||||
|
deny
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-compile({inline, [run_hooks/3]}).
|
-compile({inline, [run_hooks/3]}).
|
||||||
|
|
|
@ -22,6 +22,8 @@
|
||||||
-include("logger.hrl").
|
-include("logger.hrl").
|
||||||
-include("types.hrl").
|
-include("types.hrl").
|
||||||
|
|
||||||
|
-include_lib("snabbkaffe/include/snabbkaffe.hrl").
|
||||||
|
|
||||||
-ifdef(TEST).
|
-ifdef(TEST).
|
||||||
-compile(export_all).
|
-compile(export_all).
|
||||||
-compile(nowarn_export_all).
|
-compile(nowarn_export_all).
|
||||||
|
@ -1423,7 +1425,8 @@ interval(will_timer, #channel{will_msg = WillMsg}) ->
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
-spec terminate(any(), channel()) -> ok.
|
-spec terminate(any(), channel()) -> ok.
|
||||||
terminate(_, #channel{conn_state = idle}) ->
|
terminate(_, #channel{conn_state = idle} = _Channel) ->
|
||||||
|
?tp(channel_terminated, #{channel => _Channel}),
|
||||||
ok;
|
ok;
|
||||||
terminate(normal, Channel) ->
|
terminate(normal, Channel) ->
|
||||||
run_terminate_hook(normal, Channel);
|
run_terminate_hook(normal, Channel);
|
||||||
|
@ -1431,7 +1434,8 @@ terminate({shutdown, kicked}, Channel) ->
|
||||||
run_terminate_hook(kicked, Channel);
|
run_terminate_hook(kicked, Channel);
|
||||||
terminate({shutdown, Reason}, Channel) when
|
terminate({shutdown, Reason}, Channel) when
|
||||||
Reason =:= discarded;
|
Reason =:= discarded;
|
||||||
Reason =:= takenover
|
Reason =:= takenover;
|
||||||
|
Reason =:= not_authorized
|
||||||
->
|
->
|
||||||
run_terminate_hook(Reason, Channel);
|
run_terminate_hook(Reason, Channel);
|
||||||
terminate(Reason, Channel = #channel{will_msg = WillMsg}) ->
|
terminate(Reason, Channel = #channel{will_msg = WillMsg}) ->
|
||||||
|
@ -1452,9 +1456,11 @@ persist_if_session(#channel{session = Session} = Channel) ->
|
||||||
ok
|
ok
|
||||||
end.
|
end.
|
||||||
|
|
||||||
run_terminate_hook(_Reason, #channel{session = undefined}) ->
|
run_terminate_hook(_Reason, #channel{session = undefined} = _Channel) ->
|
||||||
|
?tp(channel_terminated, #{channel => _Channel}),
|
||||||
ok;
|
ok;
|
||||||
run_terminate_hook(Reason, #channel{clientinfo = ClientInfo, session = Session}) ->
|
run_terminate_hook(Reason, #channel{clientinfo = ClientInfo, session = Session} = _Channel) ->
|
||||||
|
?tp(channel_terminated, #{channel => _Channel}),
|
||||||
emqx_session:terminate(ClientInfo, Reason, Session).
|
emqx_session:terminate(ClientInfo, Reason, Session).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
-export([
|
-export([
|
||||||
call/4,
|
call/4,
|
||||||
call/5,
|
call/5,
|
||||||
|
call/6,
|
||||||
cast/4,
|
cast/4,
|
||||||
cast/5,
|
cast/5,
|
||||||
multicall/4,
|
multicall/4,
|
||||||
|
@ -78,6 +79,10 @@ call(Node, Mod, Fun, Args) ->
|
||||||
call(Key, Node, Mod, Fun, Args) ->
|
call(Key, Node, Mod, Fun, Args) ->
|
||||||
filter_result(gen_rpc:call(rpc_node({Key, Node}), Mod, Fun, Args)).
|
filter_result(gen_rpc:call(rpc_node({Key, Node}), Mod, Fun, Args)).
|
||||||
|
|
||||||
|
-spec call(term(), node(), module(), atom(), list(), timeout()) -> call_result().
|
||||||
|
call(Key, Node, Mod, Fun, Args, Timeout) ->
|
||||||
|
filter_result(gen_rpc:call(rpc_node({Key, Node}), Mod, Fun, Args, Timeout)).
|
||||||
|
|
||||||
-spec multicall([node()], module(), atom(), list()) -> multicall_result().
|
-spec multicall([node()], module(), atom(), list()) -> multicall_result().
|
||||||
multicall(Nodes, Mod, Fun, Args) ->
|
multicall(Nodes, Mod, Fun, Args) ->
|
||||||
gen_rpc:multicall(rpc_nodes(Nodes), Mod, Fun, Args).
|
gen_rpc:multicall(rpc_nodes(Nodes), Mod, Fun, Args).
|
||||||
|
|
|
@ -869,7 +869,7 @@ fields("mqtt_quic_listener") ->
|
||||||
sc(
|
sc(
|
||||||
duration_ms(),
|
duration_ms(),
|
||||||
#{
|
#{
|
||||||
default => "0",
|
default => 0,
|
||||||
desc => ?DESC(fields_mqtt_quic_listener_idle_timeout)
|
desc => ?DESC(fields_mqtt_quic_listener_idle_timeout)
|
||||||
}
|
}
|
||||||
)},
|
)},
|
||||||
|
|
|
@ -38,7 +38,8 @@
|
||||||
|
|
||||||
-export([
|
-export([
|
||||||
dispatch/3,
|
dispatch/3,
|
||||||
dispatch/4
|
dispatch/4,
|
||||||
|
do_dispatch_with_ack/4
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-export([
|
-export([
|
||||||
|
@ -170,30 +171,31 @@ do_dispatch(SubPid, _Group, Topic, Msg, _Type) when SubPid =:= self() ->
|
||||||
%% return either 'ok' (when everything is fine) or 'error'
|
%% return either 'ok' (when everything is fine) or 'error'
|
||||||
do_dispatch(SubPid, _Group, Topic, #message{qos = ?QOS_0} = Msg, _Type) ->
|
do_dispatch(SubPid, _Group, Topic, #message{qos = ?QOS_0} = Msg, _Type) ->
|
||||||
%% For QoS 0 message, send it as regular dispatch
|
%% For QoS 0 message, send it as regular dispatch
|
||||||
SubPid ! {deliver, Topic, Msg},
|
send(SubPid, Topic, {deliver, Topic, Msg});
|
||||||
ok;
|
|
||||||
do_dispatch(SubPid, _Group, Topic, Msg, retry) ->
|
do_dispatch(SubPid, _Group, Topic, Msg, retry) ->
|
||||||
%% Retry implies all subscribers nack:ed, send again without ack
|
%% Retry implies all subscribers nack:ed, send again without ack
|
||||||
SubPid ! {deliver, Topic, Msg},
|
send(SubPid, Topic, {deliver, Topic, Msg});
|
||||||
ok;
|
|
||||||
do_dispatch(SubPid, Group, Topic, Msg, fresh) ->
|
do_dispatch(SubPid, Group, Topic, Msg, fresh) ->
|
||||||
case ack_enabled() of
|
case ack_enabled() of
|
||||||
true ->
|
true ->
|
||||||
dispatch_with_ack(SubPid, Group, Topic, Msg);
|
%% FIXME: replace with `emqx_shared_sub_proto:dispatch_with_ack' in 5.2
|
||||||
|
do_dispatch_with_ack(SubPid, Group, Topic, Msg);
|
||||||
false ->
|
false ->
|
||||||
SubPid ! {deliver, Topic, Msg},
|
send(SubPid, Topic, {deliver, Topic, Msg})
|
||||||
ok
|
|
||||||
end.
|
end.
|
||||||
|
|
||||||
dispatch_with_ack(SubPid, Group, Topic, Msg) ->
|
-spec do_dispatch_with_ack(pid(), emqx_types:group(), emqx_types:topic(), emqx_types:message()) ->
|
||||||
|
ok | {error, _}.
|
||||||
|
do_dispatch_with_ack(SubPid, Group, Topic, Msg) ->
|
||||||
%% For QoS 1/2 message, expect an ack
|
%% For QoS 1/2 message, expect an ack
|
||||||
Ref = erlang:monitor(process, SubPid),
|
Ref = erlang:monitor(process, SubPid),
|
||||||
Sender = self(),
|
Sender = self(),
|
||||||
SubPid ! {deliver, Topic, with_group_ack(Msg, Group, Sender, Ref)},
|
%% FIXME: replace with regular send in 5.2
|
||||||
|
send(SubPid, Topic, {deliver, Topic, with_group_ack(Msg, Group, Sender, Ref)}),
|
||||||
Timeout =
|
Timeout =
|
||||||
case Msg#message.qos of
|
case Msg#message.qos of
|
||||||
?QOS_1 -> timer:seconds(?SHARED_SUB_QOS1_DISPATCH_TIMEOUT_SECONDS);
|
?QOS_2 -> infinity;
|
||||||
?QOS_2 -> infinity
|
_ -> timer:seconds(?SHARED_SUB_QOS1_DISPATCH_TIMEOUT_SECONDS)
|
||||||
end,
|
end,
|
||||||
try
|
try
|
||||||
receive
|
receive
|
||||||
|
@ -412,6 +414,17 @@ code_change(_OldVsn, State, _Extra) ->
|
||||||
%% Internal functions
|
%% Internal functions
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
send(Pid, Topic, Msg) ->
|
||||||
|
Node = node(Pid),
|
||||||
|
_ =
|
||||||
|
case Node =:= node() of
|
||||||
|
true ->
|
||||||
|
Pid ! Msg;
|
||||||
|
false ->
|
||||||
|
emqx_shared_sub_proto_v1:send(Node, Pid, Topic, Msg)
|
||||||
|
end,
|
||||||
|
ok.
|
||||||
|
|
||||||
maybe_insert_round_robin_count({Group, _Topic} = GroupTopic) ->
|
maybe_insert_round_robin_count({Group, _Topic} = GroupTopic) ->
|
||||||
strategy(Group) =:= round_robin_per_group andalso
|
strategy(Group) =:= round_robin_per_group andalso
|
||||||
ets:insert(?SHARED_SUBS_ROUND_ROBIN_COUNTER, {GroupTopic, 0}),
|
ets:insert(?SHARED_SUBS_ROUND_ROBIN_COUNTER, {GroupTopic, 0}),
|
||||||
|
|
|
@ -145,7 +145,8 @@ all_ciphers_set_cached() ->
|
||||||
case persistent_term:get(?FUNCTION_NAME, false) of
|
case persistent_term:get(?FUNCTION_NAME, false) of
|
||||||
false ->
|
false ->
|
||||||
S = sets:from_list(all_ciphers()),
|
S = sets:from_list(all_ciphers()),
|
||||||
persistent_term:put(?FUNCTION_NAME, S);
|
persistent_term:put(?FUNCTION_NAME, S),
|
||||||
|
S;
|
||||||
Set ->
|
Set ->
|
||||||
Set
|
Set
|
||||||
end.
|
end.
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Copyright (c) 2022 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_shared_sub_proto_v1).
|
||||||
|
|
||||||
|
-behaviour(emqx_bpapi).
|
||||||
|
|
||||||
|
-export([
|
||||||
|
introduced_in/0,
|
||||||
|
send/4,
|
||||||
|
dispatch_with_ack/5
|
||||||
|
]).
|
||||||
|
|
||||||
|
-include("bpapi.hrl").
|
||||||
|
|
||||||
|
%%================================================================================
|
||||||
|
%% behavior callbacks
|
||||||
|
%%================================================================================
|
||||||
|
|
||||||
|
introduced_in() ->
|
||||||
|
"5.0.8".
|
||||||
|
|
||||||
|
%%================================================================================
|
||||||
|
%% API funcions
|
||||||
|
%%================================================================================
|
||||||
|
|
||||||
|
-spec send(node(), pid(), emqx_types:topic(), term()) -> true.
|
||||||
|
send(Node, Pid, Topic, Msg) ->
|
||||||
|
emqx_rpc:cast(Topic, Node, erlang, send, [Pid, Msg]).
|
||||||
|
|
||||||
|
-spec dispatch_with_ack(
|
||||||
|
pid(), emqx_types:group(), emqx_types:topic(), emqx_types:message(), timeout()
|
||||||
|
) ->
|
||||||
|
ok | {error, _}.
|
||||||
|
dispatch_with_ack(Pid, Group, Topic, Msg, Timeout) ->
|
||||||
|
emqx_rpc:call(
|
||||||
|
Topic, node(Pid), emqx_shared_sub, do_dispatch_with_ack, [Pid, Group, Topic, Msg], Timeout
|
||||||
|
).
|
|
@ -40,7 +40,7 @@ end_per_suite(_Config) ->
|
||||||
t_max_supported_version(_Config) ->
|
t_max_supported_version(_Config) ->
|
||||||
?assertMatch(3, emqx_bpapi:supported_version('fake-node2@localhost', api2)),
|
?assertMatch(3, emqx_bpapi:supported_version('fake-node2@localhost', api2)),
|
||||||
?assertMatch(2, emqx_bpapi:supported_version(api2)),
|
?assertMatch(2, emqx_bpapi:supported_version(api2)),
|
||||||
?assertError(_, emqx_bpapi:supported_version('fake-node2@localhost', nonexistent_api)),
|
?assertMatch(undefined, emqx_bpapi:supported_version('fake-node2@localhost', nonexistent_api)),
|
||||||
?assertError(_, emqx_bpapi:supported_version(nonexistent_api)).
|
?assertError(_, emqx_bpapi:supported_version(nonexistent_api)).
|
||||||
|
|
||||||
t_announce(Config) ->
|
t_announce(Config) ->
|
||||||
|
|
|
@ -181,11 +181,15 @@ start_app(App, Handler) ->
|
||||||
app_conf_file(emqx_conf) -> "emqx.conf.all";
|
app_conf_file(emqx_conf) -> "emqx.conf.all";
|
||||||
app_conf_file(App) -> atom_to_list(App) ++ ".conf".
|
app_conf_file(App) -> atom_to_list(App) ++ ".conf".
|
||||||
|
|
||||||
%% TODO: get rid of cuttlefish
|
|
||||||
app_schema(App) ->
|
app_schema(App) ->
|
||||||
Mod = list_to_atom(atom_to_list(App) ++ "_schema"),
|
Mod = list_to_atom(atom_to_list(App) ++ "_schema"),
|
||||||
true = is_list(Mod:roots()),
|
try
|
||||||
Mod.
|
true = is_list(Mod:roots()),
|
||||||
|
Mod
|
||||||
|
catch
|
||||||
|
error:undef ->
|
||||||
|
no_schema
|
||||||
|
end.
|
||||||
|
|
||||||
mustache_vars(App) ->
|
mustache_vars(App) ->
|
||||||
[
|
[
|
||||||
|
@ -221,6 +225,8 @@ render_config_file(ConfigFile, Vars0) ->
|
||||||
ok = file:write_file(NewName, Targ),
|
ok = file:write_file(NewName, Targ),
|
||||||
NewName.
|
NewName.
|
||||||
|
|
||||||
|
read_schema_configs(no_schema, _ConfigFile) ->
|
||||||
|
ok;
|
||||||
read_schema_configs(Schema, ConfigFile) ->
|
read_schema_configs(Schema, ConfigFile) ->
|
||||||
NewConfig = generate_config(Schema, ConfigFile),
|
NewConfig = generate_config(Schema, ConfigFile),
|
||||||
lists:foreach(
|
lists:foreach(
|
||||||
|
|
|
@ -69,6 +69,7 @@ conninfo() ->
|
||||||
{conn_props, properties()},
|
{conn_props, properties()},
|
||||||
{connected, boolean()},
|
{connected, boolean()},
|
||||||
{connected_at, timestamp()},
|
{connected_at, timestamp()},
|
||||||
|
{disconnected_at, timestamp()},
|
||||||
{keepalive, range(0, 16#ffff)},
|
{keepalive, range(0, 16#ffff)},
|
||||||
{receive_maximum, non_neg_integer()},
|
{receive_maximum, non_neg_integer()},
|
||||||
{expiry_interval, non_neg_integer()}
|
{expiry_interval, non_neg_integer()}
|
||||||
|
|
|
@ -567,6 +567,59 @@ t_local(_) ->
|
||||||
?assertNotEqual(UsedSubPid1, UsedSubPid2),
|
?assertNotEqual(UsedSubPid1, UsedSubPid2),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
|
t_remote(_) ->
|
||||||
|
%% This testcase verifies dispatching of shared messages to the remote nodes via backplane API.
|
||||||
|
%%
|
||||||
|
%% In this testcase we start two EMQX nodes: local and remote.
|
||||||
|
%% A subscriber connects to the remote node.
|
||||||
|
%% A publisher connects to the local node and sends three messages with different QoS.
|
||||||
|
%% The test verifies that the remote side received all three messages.
|
||||||
|
ok = ensure_config(sticky, true),
|
||||||
|
GroupConfig = #{
|
||||||
|
<<"local_group">> => local,
|
||||||
|
<<"round_robin_group">> => round_robin,
|
||||||
|
<<"sticky_group">> => sticky
|
||||||
|
},
|
||||||
|
|
||||||
|
Node = start_slave('remote_shared_sub_testtesttest', 21999),
|
||||||
|
ok = ensure_group_config(GroupConfig),
|
||||||
|
ok = ensure_group_config(Node, GroupConfig),
|
||||||
|
|
||||||
|
Topic = <<"foo/bar">>,
|
||||||
|
ClientIdLocal = <<"ClientId1">>,
|
||||||
|
ClientIdRemote = <<"ClientId2">>,
|
||||||
|
|
||||||
|
{ok, ConnPidLocal} = emqtt:start_link([{clientid, ClientIdLocal}]),
|
||||||
|
{ok, ConnPidRemote} = emqtt:start_link([{clientid, ClientIdRemote}, {port, 21999}]),
|
||||||
|
|
||||||
|
try
|
||||||
|
{ok, ClientPidLocal} = emqtt:connect(ConnPidLocal),
|
||||||
|
{ok, ClientPidRemote} = emqtt:connect(ConnPidRemote),
|
||||||
|
|
||||||
|
emqtt:subscribe(ConnPidRemote, {<<"$share/remote_group/", Topic/binary>>, 0}),
|
||||||
|
|
||||||
|
ct:sleep(100),
|
||||||
|
|
||||||
|
Message1 = emqx_message:make(ClientPidLocal, 0, Topic, <<"hello1">>),
|
||||||
|
Message2 = emqx_message:make(ClientPidLocal, 1, Topic, <<"hello2">>),
|
||||||
|
Message3 = emqx_message:make(ClientPidLocal, 2, Topic, <<"hello3">>),
|
||||||
|
|
||||||
|
emqx:publish(Message1),
|
||||||
|
{true, UsedSubPid1} = last_message(<<"hello1">>, [ConnPidRemote]),
|
||||||
|
|
||||||
|
emqx:publish(Message2),
|
||||||
|
{true, UsedSubPid1} = last_message(<<"hello2">>, [ConnPidRemote]),
|
||||||
|
|
||||||
|
emqx:publish(Message3),
|
||||||
|
{true, UsedSubPid1} = last_message(<<"hello3">>, [ConnPidRemote]),
|
||||||
|
|
||||||
|
ok
|
||||||
|
after
|
||||||
|
emqtt:stop(ConnPidLocal),
|
||||||
|
emqtt:stop(ConnPidRemote),
|
||||||
|
stop_slave(Node)
|
||||||
|
end.
|
||||||
|
|
||||||
t_local_fallback(_) ->
|
t_local_fallback(_) ->
|
||||||
ok = ensure_group_config(#{
|
ok = ensure_group_config(#{
|
||||||
<<"local_group">> => local,
|
<<"local_group">> => local,
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
mongo
|
||||||
|
redis
|
||||||
|
mysql
|
||||||
|
pgsql
|
|
@ -1,7 +1,7 @@
|
||||||
%% -*- mode: erlang -*-
|
%% -*- mode: erlang -*-
|
||||||
{application, emqx_authn, [
|
{application, emqx_authn, [
|
||||||
{description, "EMQX Authentication"},
|
{description, "EMQX Authentication"},
|
||||||
{vsn, "0.1.5"},
|
{vsn, "0.1.6"},
|
||||||
{modules, []},
|
{modules, []},
|
||||||
{registered, [emqx_authn_sup, emqx_authn_registry]},
|
{registered, [emqx_authn_sup, emqx_authn_registry]},
|
||||||
{applications, [kernel, stdlib, emqx_resource, emqx_connector, ehttpc, epgsql, mysql, jose]},
|
{applications, [kernel, stdlib, emqx_resource, emqx_connector, ehttpc, epgsql, mysql, jose]},
|
||||||
|
|
|
@ -383,7 +383,7 @@ do_verify(JWT, [JWK | More], VerifyClaims) ->
|
||||||
try jose_jws:verify(JWK, JWT) of
|
try jose_jws:verify(JWK, JWT) of
|
||||||
{true, Payload, _JWT} ->
|
{true, Payload, _JWT} ->
|
||||||
Claims0 = emqx_json:decode(Payload, [return_maps]),
|
Claims0 = emqx_json:decode(Payload, [return_maps]),
|
||||||
Claims = try_convert_to_int(Claims0, [<<"exp">>, <<"iat">>, <<"nbf">>]),
|
Claims = try_convert_to_num(Claims0, [<<"exp">>, <<"iat">>, <<"nbf">>]),
|
||||||
case verify_claims(Claims, VerifyClaims) of
|
case verify_claims(Claims, VerifyClaims) of
|
||||||
ok ->
|
ok ->
|
||||||
{ok, Claims};
|
{ok, Claims};
|
||||||
|
@ -403,37 +403,37 @@ verify_claims(Claims, VerifyClaims0) ->
|
||||||
VerifyClaims =
|
VerifyClaims =
|
||||||
[
|
[
|
||||||
{<<"exp">>, fun(ExpireTime) ->
|
{<<"exp">>, fun(ExpireTime) ->
|
||||||
is_integer(ExpireTime) andalso Now < ExpireTime
|
is_number(ExpireTime) andalso Now < ExpireTime
|
||||||
end},
|
end},
|
||||||
{<<"iat">>, fun(IssueAt) ->
|
{<<"iat">>, fun(IssueAt) ->
|
||||||
is_integer(IssueAt) andalso IssueAt =< Now
|
is_number(IssueAt) andalso IssueAt =< Now
|
||||||
end},
|
end},
|
||||||
{<<"nbf">>, fun(NotBefore) ->
|
{<<"nbf">>, fun(NotBefore) ->
|
||||||
is_integer(NotBefore) andalso NotBefore =< Now
|
is_number(NotBefore) andalso NotBefore =< Now
|
||||||
end}
|
end}
|
||||||
] ++ VerifyClaims0,
|
] ++ VerifyClaims0,
|
||||||
do_verify_claims(Claims, VerifyClaims).
|
do_verify_claims(Claims, VerifyClaims).
|
||||||
|
|
||||||
try_convert_to_int(Claims, [Name | Names]) ->
|
try_convert_to_num(Claims, [Name | Names]) ->
|
||||||
case Claims of
|
case Claims of
|
||||||
#{Name := Value} ->
|
#{Name := Value} ->
|
||||||
case Value of
|
case Value of
|
||||||
Int when is_integer(Int) ->
|
Int when is_number(Int) ->
|
||||||
try_convert_to_int(Claims#{Name => Int}, Names);
|
try_convert_to_num(Claims#{Name => Int}, Names);
|
||||||
Bin when is_binary(Bin) ->
|
Bin when is_binary(Bin) ->
|
||||||
case string:to_integer(Bin) of
|
case binary_to_number(Bin) of
|
||||||
{Int, <<>>} ->
|
{ok, Num} ->
|
||||||
try_convert_to_int(Claims#{Name => Int}, Names);
|
try_convert_to_num(Claims#{Name => Num}, Names);
|
||||||
_ ->
|
_ ->
|
||||||
try_convert_to_int(Claims, Names)
|
try_convert_to_num(Claims, Names)
|
||||||
end;
|
end;
|
||||||
_ ->
|
_ ->
|
||||||
try_convert_to_int(Claims, Names)
|
try_convert_to_num(Claims, Names)
|
||||||
end;
|
end;
|
||||||
_ ->
|
_ ->
|
||||||
try_convert_to_int(Claims, Names)
|
try_convert_to_num(Claims, Names)
|
||||||
end;
|
end;
|
||||||
try_convert_to_int(Claims, []) ->
|
try_convert_to_num(Claims, []) ->
|
||||||
Claims.
|
Claims.
|
||||||
|
|
||||||
do_verify_claims(_Claims, []) ->
|
do_verify_claims(_Claims, []) ->
|
||||||
|
@ -519,3 +519,16 @@ to_binary(B) when is_binary(B) ->
|
||||||
B.
|
B.
|
||||||
|
|
||||||
sc(Type, Meta) -> hoconsc:mk(Type, Meta).
|
sc(Type, Meta) -> hoconsc:mk(Type, Meta).
|
||||||
|
|
||||||
|
binary_to_number(Bin) ->
|
||||||
|
try
|
||||||
|
{ok, erlang:binary_to_integer(Bin)}
|
||||||
|
catch
|
||||||
|
_:_ ->
|
||||||
|
try
|
||||||
|
{ok, erlang:binary_to_float(Bin)}
|
||||||
|
catch
|
||||||
|
_:_ ->
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
|
@ -0,0 +1,117 @@
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Copyright (c) 2022 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_authn_SUITE).
|
||||||
|
|
||||||
|
-compile(export_all).
|
||||||
|
-compile(nowarn_export_all).
|
||||||
|
|
||||||
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
-include_lib("common_test/include/ct.hrl").
|
||||||
|
-include_lib("snabbkaffe/include/snabbkaffe.hrl").
|
||||||
|
|
||||||
|
-include_lib("emqx/include/emqx.hrl").
|
||||||
|
-include_lib("emqx/include/emqx_mqtt.hrl").
|
||||||
|
|
||||||
|
%%=================================================================================
|
||||||
|
%% CT boilerplate
|
||||||
|
%%=================================================================================
|
||||||
|
|
||||||
|
all() ->
|
||||||
|
emqx_common_test_helpers:all(?MODULE).
|
||||||
|
|
||||||
|
init_per_suite(Config) ->
|
||||||
|
Config.
|
||||||
|
|
||||||
|
end_per_suite(_Config) ->
|
||||||
|
ok.
|
||||||
|
|
||||||
|
init_per_testcase(Case, Config) ->
|
||||||
|
?MODULE:Case({init, Config}).
|
||||||
|
|
||||||
|
end_per_testcase(Case, Config) ->
|
||||||
|
?MODULE:Case({'end', Config}).
|
||||||
|
|
||||||
|
%%=================================================================================
|
||||||
|
%% Helpers fns
|
||||||
|
%%=================================================================================
|
||||||
|
|
||||||
|
%%=================================================================================
|
||||||
|
%% Testcases
|
||||||
|
%%=================================================================================
|
||||||
|
|
||||||
|
t_will_message_connection_denied({init, Config}) ->
|
||||||
|
emqx_common_test_helpers:start_apps([emqx_conf, emqx_authn]),
|
||||||
|
mria:clear_table(emqx_authn_mnesia),
|
||||||
|
AuthnConfig = #{
|
||||||
|
<<"mechanism">> => <<"password_based">>,
|
||||||
|
<<"backend">> => <<"built_in_database">>,
|
||||||
|
<<"user_id_type">> => <<"clientid">>
|
||||||
|
},
|
||||||
|
Chain = 'mqtt:global',
|
||||||
|
emqx:update_config(
|
||||||
|
[authentication],
|
||||||
|
{create_authenticator, Chain, AuthnConfig}
|
||||||
|
),
|
||||||
|
User = #{user_id => <<"subscriber">>, password => <<"p">>},
|
||||||
|
AuthenticatorID = <<"password_based:built_in_database">>,
|
||||||
|
{ok, _} = emqx_authentication:add_user(
|
||||||
|
Chain,
|
||||||
|
AuthenticatorID,
|
||||||
|
User
|
||||||
|
),
|
||||||
|
Config;
|
||||||
|
t_will_message_connection_denied({'end', _Config}) ->
|
||||||
|
emqx:update_config(
|
||||||
|
[authentication],
|
||||||
|
{delete_authenticator, 'mqtt:global', <<"password_based:built_in_database">>}
|
||||||
|
),
|
||||||
|
emqx_common_test_helpers:stop_apps([emqx_authn, emqx_conf]),
|
||||||
|
mria:clear_table(emqx_authn_mnesia),
|
||||||
|
ok;
|
||||||
|
t_will_message_connection_denied(Config) when is_list(Config) ->
|
||||||
|
{ok, Subscriber} = emqtt:start_link([
|
||||||
|
{clientid, <<"subscriber">>},
|
||||||
|
{password, <<"p">>}
|
||||||
|
]),
|
||||||
|
{ok, _} = emqtt:connect(Subscriber),
|
||||||
|
{ok, _, [?RC_SUCCESS]} = emqtt:subscribe(Subscriber, <<"lwt">>),
|
||||||
|
|
||||||
|
process_flag(trap_exit, true),
|
||||||
|
|
||||||
|
{ok, Publisher} = emqtt:start_link([
|
||||||
|
{clientid, <<"publisher">>},
|
||||||
|
{will_topic, <<"lwt">>},
|
||||||
|
{will_payload, <<"should not be published">>}
|
||||||
|
]),
|
||||||
|
snabbkaffe:start_trace(),
|
||||||
|
?wait_async_action(
|
||||||
|
{error, _} = emqtt:connect(Publisher),
|
||||||
|
#{?snk_kind := channel_terminated}
|
||||||
|
),
|
||||||
|
snabbkaffe:stop(),
|
||||||
|
|
||||||
|
receive
|
||||||
|
{publish, #{
|
||||||
|
topic := <<"lwt">>,
|
||||||
|
payload := <<"should not be published">>
|
||||||
|
}} ->
|
||||||
|
ct:fail("should not publish will message")
|
||||||
|
after 0 ->
|
||||||
|
ok
|
||||||
|
end,
|
||||||
|
|
||||||
|
ok.
|
|
@ -408,7 +408,19 @@ t_verify_claims(_) ->
|
||||||
},
|
},
|
||||||
?assertMatch(
|
?assertMatch(
|
||||||
{ok, #{is_superuser := false}}, emqx_authn_jwt:authenticate(Credential4, State1)
|
{ok, #{is_superuser := false}}, emqx_authn_jwt:authenticate(Credential4, State1)
|
||||||
).
|
),
|
||||||
|
|
||||||
|
Payload5 = #{
|
||||||
|
<<"username">> => <<"myuser">>,
|
||||||
|
<<"foo">> => <<"myuser">>,
|
||||||
|
<<"exp">> => erlang:system_time(second) + 10.5
|
||||||
|
},
|
||||||
|
JWS5 = generate_jws('hmac-based', Payload5, Secret),
|
||||||
|
Credential5 = #{
|
||||||
|
username => <<"myuser">>,
|
||||||
|
password => JWS5
|
||||||
|
},
|
||||||
|
?assertMatch({ok, #{is_superuser := false}}, emqx_authn_jwt:authenticate(Credential5, State1)).
|
||||||
|
|
||||||
t_jwt_not_allow_empty_claim_name(_) ->
|
t_jwt_not_allow_empty_claim_name(_) ->
|
||||||
Request = #{
|
Request = #{
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
mongo
|
||||||
|
redis
|
||||||
|
mysql
|
||||||
|
pgsql
|
|
@ -49,7 +49,8 @@
|
||||||
|
|
||||||
-type default_result() :: allow | deny.
|
-type default_result() :: allow | deny.
|
||||||
|
|
||||||
-type authz_result() :: {stop, allow} | {ok, deny}.
|
-type authz_result_value() :: #{result := allow | deny, from => _}.
|
||||||
|
-type authz_result() :: {stop, authz_result_value()} | {ok, authz_result_value()} | ignore.
|
||||||
|
|
||||||
-type sources() :: [source()].
|
-type sources() :: [source()].
|
||||||
|
|
||||||
|
@ -319,7 +320,7 @@ authorize(
|
||||||
is_superuser => true
|
is_superuser => true
|
||||||
}),
|
}),
|
||||||
emqx_metrics:inc(?METRIC_SUPERUSER),
|
emqx_metrics:inc(?METRIC_SUPERUSER),
|
||||||
{stop, allow};
|
{stop, #{result => allow, from => superuser}};
|
||||||
false ->
|
false ->
|
||||||
authorize_non_superuser(Client, PubSub, Topic, DefaultResult, Sources)
|
authorize_non_superuser(Client, PubSub, Topic, DefaultResult, Sources)
|
||||||
end.
|
end.
|
||||||
|
@ -331,15 +332,11 @@ authorize_non_superuser(
|
||||||
} = Client,
|
} = Client,
|
||||||
PubSub,
|
PubSub,
|
||||||
Topic,
|
Topic,
|
||||||
DefaultResult,
|
_DefaultResult,
|
||||||
Sources
|
Sources
|
||||||
) ->
|
) ->
|
||||||
case do_authorize(Client, PubSub, Topic, sources_with_defaults(Sources)) of
|
case do_authorize(Client, PubSub, Topic, sources_with_defaults(Sources)) of
|
||||||
{{matched, allow}, AuthzSource} ->
|
{{matched, allow}, AuthzSource} ->
|
||||||
emqx:run_hook(
|
|
||||||
'client.check_authz_complete',
|
|
||||||
[Client, PubSub, Topic, allow, AuthzSource]
|
|
||||||
),
|
|
||||||
log_allowed(#{
|
log_allowed(#{
|
||||||
username => Username,
|
username => Username,
|
||||||
ipaddr => IpAddress,
|
ipaddr => IpAddress,
|
||||||
|
@ -348,12 +345,8 @@ authorize_non_superuser(
|
||||||
}),
|
}),
|
||||||
emqx_metrics_worker:inc(authz_metrics, AuthzSource, allow),
|
emqx_metrics_worker:inc(authz_metrics, AuthzSource, allow),
|
||||||
emqx_metrics:inc(?METRIC_ALLOW),
|
emqx_metrics:inc(?METRIC_ALLOW),
|
||||||
{stop, allow};
|
{stop, #{result => allow, from => AuthzSource}};
|
||||||
{{matched, deny}, AuthzSource} ->
|
{{matched, deny}, AuthzSource} ->
|
||||||
emqx:run_hook(
|
|
||||||
'client.check_authz_complete',
|
|
||||||
[Client, PubSub, Topic, deny, AuthzSource]
|
|
||||||
),
|
|
||||||
?SLOG(warning, #{
|
?SLOG(warning, #{
|
||||||
msg => "authorization_permission_denied",
|
msg => "authorization_permission_denied",
|
||||||
username => Username,
|
username => Username,
|
||||||
|
@ -363,12 +356,8 @@ authorize_non_superuser(
|
||||||
}),
|
}),
|
||||||
emqx_metrics_worker:inc(authz_metrics, AuthzSource, deny),
|
emqx_metrics_worker:inc(authz_metrics, AuthzSource, deny),
|
||||||
emqx_metrics:inc(?METRIC_DENY),
|
emqx_metrics:inc(?METRIC_DENY),
|
||||||
{stop, deny};
|
{stop, #{result => deny, from => AuthzSource}};
|
||||||
nomatch ->
|
nomatch ->
|
||||||
emqx:run_hook(
|
|
||||||
'client.check_authz_complete',
|
|
||||||
[Client, PubSub, Topic, DefaultResult, default]
|
|
||||||
),
|
|
||||||
?SLOG(info, #{
|
?SLOG(info, #{
|
||||||
msg => "authorization_failed_nomatch",
|
msg => "authorization_failed_nomatch",
|
||||||
username => Username,
|
username => Username,
|
||||||
|
@ -377,7 +366,7 @@ authorize_non_superuser(
|
||||||
reason => "no-match rule"
|
reason => "no-match rule"
|
||||||
}),
|
}),
|
||||||
emqx_metrics:inc(?METRIC_NOMATCH),
|
emqx_metrics:inc(?METRIC_NOMATCH),
|
||||||
{stop, DefaultResult}
|
ignore
|
||||||
end.
|
end.
|
||||||
|
|
||||||
log_allowed(Meta) ->
|
log_allowed(Meta) ->
|
||||||
|
|
|
@ -594,9 +594,10 @@ pick_bridges_by_id(Type, Name, BridgesAllNodes) ->
|
||||||
|
|
||||||
format_bridge_info([FirstBridge | _] = Bridges) ->
|
format_bridge_info([FirstBridge | _] = Bridges) ->
|
||||||
Res = maps:remove(node, FirstBridge),
|
Res = maps:remove(node, FirstBridge),
|
||||||
|
NRes = emqx_connector_ssl:drop_invalid_certs(Res),
|
||||||
NodeStatus = collect_status(Bridges),
|
NodeStatus = collect_status(Bridges),
|
||||||
NodeMetrics = collect_metrics(Bridges),
|
NodeMetrics = collect_metrics(Bridges),
|
||||||
Res#{
|
NRes#{
|
||||||
status => aggregate_status(NodeStatus),
|
status => aggregate_status(NodeStatus),
|
||||||
node_status => NodeStatus,
|
node_status => NodeStatus,
|
||||||
metrics => aggregate_metrics(NodeMetrics),
|
metrics => aggregate_metrics(NodeMetrics),
|
||||||
|
|
|
@ -12,7 +12,6 @@ node {
|
||||||
name = "emqx@127.0.0.1"
|
name = "emqx@127.0.0.1"
|
||||||
cookie = emqxsecretcookie
|
cookie = emqxsecretcookie
|
||||||
data_dir = "{{ platform_data_dir }}"
|
data_dir = "{{ platform_data_dir }}"
|
||||||
etc_dir = "{{ platform_etc_dir }}"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log {
|
log {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{application, emqx_conf, [
|
{application, emqx_conf, [
|
||||||
{description, "EMQX configuration management"},
|
{description, "EMQX configuration management"},
|
||||||
{vsn, "0.1.3"},
|
{vsn, "0.1.4"},
|
||||||
{registered, []},
|
{registered, []},
|
||||||
{mod, {emqx_conf_app, []}},
|
{mod, {emqx_conf_app, []}},
|
||||||
{applications, [kernel, stdlib]},
|
{applications, [kernel, stdlib]},
|
||||||
|
|
|
@ -535,14 +535,6 @@ fields("node") ->
|
||||||
desc => ?DESC(node_applications)
|
desc => ?DESC(node_applications)
|
||||||
}
|
}
|
||||||
)},
|
)},
|
||||||
{"etc_dir",
|
|
||||||
sc(
|
|
||||||
string(),
|
|
||||||
#{
|
|
||||||
desc => ?DESC(node_etc_dir),
|
|
||||||
'readOnly' => true
|
|
||||||
}
|
|
||||||
)},
|
|
||||||
{"cluster_call",
|
{"cluster_call",
|
||||||
sc(
|
sc(
|
||||||
?R_REF("cluster_call"),
|
?R_REF("cluster_call"),
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
mongo
|
||||||
|
redis
|
||||||
|
mysql
|
||||||
|
pgsql
|
|
@ -1,7 +1,7 @@
|
||||||
%% -*- mode: erlang -*-
|
%% -*- mode: erlang -*-
|
||||||
{application, emqx_connector, [
|
{application, emqx_connector, [
|
||||||
{description, "An OTP application"},
|
{description, "An OTP application"},
|
||||||
{vsn, "0.1.4"},
|
{vsn, "0.1.5"},
|
||||||
{registered, []},
|
{registered, []},
|
||||||
{mod, {emqx_connector_app, []}},
|
{mod, {emqx_connector_app, []}},
|
||||||
{applications, [
|
{applications, [
|
||||||
|
|
|
@ -18,27 +18,89 @@
|
||||||
|
|
||||||
-export([
|
-export([
|
||||||
convert_certs/2,
|
convert_certs/2,
|
||||||
|
drop_invalid_certs/1,
|
||||||
clear_certs/2
|
clear_certs/2
|
||||||
]).
|
]).
|
||||||
|
|
||||||
convert_certs(RltvDir, NewConfig) ->
|
%% TODO: rm `connector` case after `dev/ee5.0` merged into `master`.
|
||||||
NewSSL = map_get_oneof([<<"ssl">>, ssl], NewConfig, undefined),
|
%% The `connector` config layer will be removed.
|
||||||
case emqx_tls_lib:ensure_ssl_files(RltvDir, NewSSL) of
|
%% for bridges with `connector` field. i.e. `mqtt_source` and `mqtt_sink`
|
||||||
{ok, NewSSL1} ->
|
convert_certs(RltvDir, #{<<"connector">> := Connector} = Config) when
|
||||||
{ok, new_ssl_config(NewConfig, NewSSL1)};
|
is_map(Connector)
|
||||||
|
->
|
||||||
|
SSL = map_get_oneof([<<"ssl">>, ssl], Connector, undefined),
|
||||||
|
new_ssl_config(RltvDir, Config, SSL);
|
||||||
|
convert_certs(RltvDir, #{connector := Connector} = Config) when
|
||||||
|
is_map(Connector)
|
||||||
|
->
|
||||||
|
SSL = map_get_oneof([<<"ssl">>, ssl], Connector, undefined),
|
||||||
|
new_ssl_config(RltvDir, Config, SSL);
|
||||||
|
%% for bridges without `connector` field. i.e. webhook
|
||||||
|
convert_certs(RltvDir, #{<<"ssl">> := SSL} = Config) ->
|
||||||
|
new_ssl_config(RltvDir, Config, SSL);
|
||||||
|
convert_certs(RltvDir, #{ssl := SSL} = Config) ->
|
||||||
|
new_ssl_config(RltvDir, Config, SSL);
|
||||||
|
%% for bridges use connector name
|
||||||
|
convert_certs(_RltvDir, Config) ->
|
||||||
|
{ok, Config}.
|
||||||
|
|
||||||
|
clear_certs(RltvDir, #{<<"connector">> := Connector} = _Config) when
|
||||||
|
is_map(Connector)
|
||||||
|
->
|
||||||
|
OldSSL = map_get_oneof([<<"ssl">>, ssl], Connector, undefined),
|
||||||
|
ok = emqx_tls_lib:delete_ssl_files(RltvDir, undefined, OldSSL);
|
||||||
|
clear_certs(RltvDir, #{connector := Connector} = _Config) when
|
||||||
|
is_map(Connector)
|
||||||
|
->
|
||||||
|
OldSSL = map_get_oneof([<<"ssl">>, ssl], Connector, undefined),
|
||||||
|
ok = emqx_tls_lib:delete_ssl_files(RltvDir, undefined, OldSSL);
|
||||||
|
clear_certs(RltvDir, #{<<"ssl">> := OldSSL} = _Config) ->
|
||||||
|
ok = emqx_tls_lib:delete_ssl_files(RltvDir, undefined, OldSSL);
|
||||||
|
clear_certs(RltvDir, #{ssl := OldSSL} = _Config) ->
|
||||||
|
ok = emqx_tls_lib:delete_ssl_files(RltvDir, undefined, OldSSL);
|
||||||
|
clear_certs(_RltvDir, _) ->
|
||||||
|
ok.
|
||||||
|
|
||||||
|
drop_invalid_certs(#{<<"connector">> := Connector} = Config) when
|
||||||
|
is_map(Connector)
|
||||||
|
->
|
||||||
|
SSL = map_get_oneof([<<"ssl">>, ssl], Connector, undefined),
|
||||||
|
NewSSL = emqx_tls_lib:drop_invalid_certs(SSL),
|
||||||
|
new_ssl_config(Config, NewSSL);
|
||||||
|
drop_invalid_certs(#{connector := Connector} = Config) when
|
||||||
|
is_map(Connector)
|
||||||
|
->
|
||||||
|
SSL = map_get_oneof([<<"ssl">>, ssl], Connector, undefined),
|
||||||
|
NewSSL = emqx_tls_lib:drop_invalid_certs(SSL),
|
||||||
|
new_ssl_config(Config, NewSSL);
|
||||||
|
drop_invalid_certs(#{<<"ssl">> := SSL} = Config) ->
|
||||||
|
NewSSL = emqx_tls_lib:drop_invalid_certs(SSL),
|
||||||
|
new_ssl_config(Config, NewSSL);
|
||||||
|
drop_invalid_certs(#{ssl := SSL} = Config) ->
|
||||||
|
NewSSL = emqx_tls_lib:drop_invalid_certs(SSL),
|
||||||
|
new_ssl_config(Config, NewSSL);
|
||||||
|
%% for bridges use connector name
|
||||||
|
drop_invalid_certs(Config) ->
|
||||||
|
Config.
|
||||||
|
|
||||||
|
new_ssl_config(RltvDir, Config, SSL) ->
|
||||||
|
case emqx_tls_lib:ensure_ssl_files(RltvDir, SSL) of
|
||||||
|
{ok, NewSSL} ->
|
||||||
|
{ok, new_ssl_config(Config, NewSSL)};
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
||||||
{error, {bad_ssl_config, Reason}}
|
{error, {bad_ssl_config, Reason}}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
clear_certs(_RltvDir, undefined) ->
|
new_ssl_config(#{connector := Connector} = Config, NewSSL) ->
|
||||||
ok;
|
Config#{connector => Connector#{ssl => NewSSL}};
|
||||||
clear_certs(RltvDir, Config) ->
|
new_ssl_config(#{<<"connector">> := Connector} = Config, NewSSL) ->
|
||||||
OldSSL = map_get_oneof([<<"ssl">>, ssl], Config, undefined),
|
Config#{<<"connector">> => Connector#{<<"ssl">> => NewSSL}};
|
||||||
ok = emqx_tls_lib:delete_ssl_files(RltvDir, undefined, OldSSL).
|
new_ssl_config(#{ssl := _} = Config, NewSSL) ->
|
||||||
|
Config#{ssl => NewSSL};
|
||||||
new_ssl_config(Config, undefined) -> Config;
|
new_ssl_config(#{<<"ssl">> := _} = Config, NewSSL) ->
|
||||||
new_ssl_config(Config, #{<<"enable">> := _} = SSL) -> Config#{<<"ssl">> => SSL};
|
Config#{<<"ssl">> => NewSSL};
|
||||||
new_ssl_config(Config, #{enable := _} = SSL) -> Config#{ssl => SSL}.
|
new_ssl_config(Config, _NewSSL) ->
|
||||||
|
Config.
|
||||||
|
|
||||||
map_get_oneof([], _Map, Default) ->
|
map_get_oneof([], _Map, Default) ->
|
||||||
Default;
|
Default;
|
||||||
|
|
|
@ -101,7 +101,6 @@ fields("server_configs") ->
|
||||||
mk(
|
mk(
|
||||||
binary(),
|
binary(),
|
||||||
#{
|
#{
|
||||||
default => undefined,
|
|
||||||
desc => ?DESC("username")
|
desc => ?DESC("username")
|
||||||
}
|
}
|
||||||
)},
|
)},
|
||||||
|
@ -109,7 +108,6 @@ fields("server_configs") ->
|
||||||
mk(
|
mk(
|
||||||
binary(),
|
binary(),
|
||||||
#{
|
#{
|
||||||
default => undefined,
|
|
||||||
format => <<"password">>,
|
format => <<"password">>,
|
||||||
desc => ?DESC("password")
|
desc => ?DESC("password")
|
||||||
}
|
}
|
||||||
|
@ -261,7 +259,7 @@ fields("egress_remote") ->
|
||||||
mk(
|
mk(
|
||||||
binary(),
|
binary(),
|
||||||
#{
|
#{
|
||||||
required => true,
|
default => <<"${payload}">>,
|
||||||
desc => ?DESC("payload")
|
desc => ?DESC("payload")
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
execute(Req, Env) ->
|
execute(Req, Env) ->
|
||||||
case check_dispatch_ready(Env) of
|
case check_dispatch_ready(Env) of
|
||||||
true -> add_cors_flag(Req, Env);
|
true -> add_cors_flag(Req, Env);
|
||||||
false -> {stop, cowboy_req:reply(503, Req)}
|
false -> {stop, cowboy_req:reply(503, #{<<"retry-after">> => <<"15">>}, Req)}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
add_cors_flag(Req, Env) ->
|
add_cors_flag(Req, Env) ->
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
%% -*- mode: erlang -*-
|
%% -*- mode: erlang -*-
|
||||||
{application, emqx_exhook, [
|
{application, emqx_exhook, [
|
||||||
{description, "EMQX Extension for Hook"},
|
{description, "EMQX Extension for Hook"},
|
||||||
{vsn, "5.0.3"},
|
{vsn, "5.0.4"},
|
||||||
{modules, []},
|
{modules, []},
|
||||||
{registered, []},
|
{registered, []},
|
||||||
{mod, {emqx_exhook_app, []}},
|
{mod, {emqx_exhook_app, []}},
|
||||||
|
|
|
@ -133,7 +133,7 @@ on_client_authenticate(ClientInfo, AuthResult) ->
|
||||||
end.
|
end.
|
||||||
|
|
||||||
on_client_authorize(ClientInfo, PubSub, Topic, Result) ->
|
on_client_authorize(ClientInfo, PubSub, Topic, Result) ->
|
||||||
Bool = Result == allow,
|
Bool = maps:get(result, Result, deny) == allow,
|
||||||
Type =
|
Type =
|
||||||
case PubSub of
|
case PubSub of
|
||||||
publish -> 'PUBLISH';
|
publish -> 'PUBLISH';
|
||||||
|
@ -158,7 +158,7 @@ on_client_authorize(ClientInfo, PubSub, Topic, Result) ->
|
||||||
true -> allow;
|
true -> allow;
|
||||||
_ -> deny
|
_ -> deny
|
||||||
end,
|
end,
|
||||||
{StopOrOk, NResult};
|
{StopOrOk, #{result => NResult, from => exhook}};
|
||||||
_ ->
|
_ ->
|
||||||
{ok, Result}
|
{ok, Result}
|
||||||
end.
|
end.
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
|
|
||||||
-include_lib("eunit/include/eunit.hrl").
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
-include_lib("common_test/include/ct.hrl").
|
-include_lib("common_test/include/ct.hrl").
|
||||||
|
-include_lib("emqx/include/emqx_hooks.hrl").
|
||||||
|
|
||||||
-define(DEFAULT_CLUSTER_NAME_ATOM, emqxcl).
|
-define(DEFAULT_CLUSTER_NAME_ATOM, emqxcl).
|
||||||
|
|
||||||
|
@ -105,7 +106,10 @@ load_cfg(Cfg) ->
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
t_access_failed_if_no_server_running(Config) ->
|
t_access_failed_if_no_server_running(Config) ->
|
||||||
emqx_exhook_mgr:disable(<<"default">>),
|
meck:expect(emqx_metrics_worker, inc, fun(_, _, _) -> ok end),
|
||||||
|
meck:expect(emqx_metrics, inc, fun(_) -> ok end),
|
||||||
|
emqx_hooks:add('client.authorize', {emqx_authz, authorize, [[]]}, ?HP_AUTHZ),
|
||||||
|
|
||||||
ClientInfo = #{
|
ClientInfo = #{
|
||||||
clientid => <<"user-id-1">>,
|
clientid => <<"user-id-1">>,
|
||||||
username => <<"usera">>,
|
username => <<"usera">>,
|
||||||
|
@ -114,14 +118,35 @@ t_access_failed_if_no_server_running(Config) ->
|
||||||
protocol => mqtt,
|
protocol => mqtt,
|
||||||
mountpoint => undefined
|
mountpoint => undefined
|
||||||
},
|
},
|
||||||
|
?assertMatch(
|
||||||
|
allow,
|
||||||
|
emqx_access_control:authorize(
|
||||||
|
ClientInfo#{username => <<"gooduser">>},
|
||||||
|
publish,
|
||||||
|
<<"acl/1">>
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|
||||||
|
?assertMatch(
|
||||||
|
deny,
|
||||||
|
emqx_access_control:authorize(
|
||||||
|
ClientInfo#{username => <<"baduser">>},
|
||||||
|
publish,
|
||||||
|
<<"acl/2">>
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|
||||||
|
emqx_exhook_mgr:disable(<<"default">>),
|
||||||
?assertMatch(
|
?assertMatch(
|
||||||
{stop, {error, not_authorized}},
|
{stop, {error, not_authorized}},
|
||||||
emqx_exhook_handler:on_client_authenticate(ClientInfo, #{auth_result => success})
|
emqx_exhook_handler:on_client_authenticate(ClientInfo, #{auth_result => success})
|
||||||
),
|
),
|
||||||
|
|
||||||
?assertMatch(
|
?assertMatch(
|
||||||
{stop, deny},
|
{stop, #{result := deny, from := exhook}},
|
||||||
emqx_exhook_handler:on_client_authorize(ClientInfo, publish, <<"t/1">>, allow)
|
emqx_exhook_handler:on_client_authorize(ClientInfo, publish, <<"t/1">>, #{
|
||||||
|
result => allow, from => exhook
|
||||||
|
})
|
||||||
),
|
),
|
||||||
|
|
||||||
Message = emqx_message:make(<<"t/1">>, <<"abc">>),
|
Message = emqx_message:make(<<"t/1">>, <<"abc">>),
|
||||||
|
@ -130,6 +155,7 @@ t_access_failed_if_no_server_running(Config) ->
|
||||||
emqx_exhook_handler:on_message_publish(Message)
|
emqx_exhook_handler:on_message_publish(Message)
|
||||||
),
|
),
|
||||||
emqx_exhook_mgr:enable(<<"default">>),
|
emqx_exhook_mgr:enable(<<"default">>),
|
||||||
|
emqx_hooks:del('client.authorize', {emqx_authz, authorize}),
|
||||||
assert_get_basic_usage_info(Config).
|
assert_get_basic_usage_info(Config).
|
||||||
|
|
||||||
t_lookup(_) ->
|
t_lookup(_) ->
|
||||||
|
|
|
@ -133,9 +133,16 @@ prop_client_authenticate() ->
|
||||||
).
|
).
|
||||||
|
|
||||||
prop_client_authorize() ->
|
prop_client_authorize() ->
|
||||||
|
MkResult = fun(Result) -> #{result => Result, from => exhook} end,
|
||||||
?ALL(
|
?ALL(
|
||||||
{ClientInfo0, PubSub, Topic, Result, Meta},
|
{ClientInfo0, PubSub, Topic, Result, Meta},
|
||||||
{clientinfo(), oneof([publish, subscribe]), topic(), oneof([allow, deny]), request_meta()},
|
{
|
||||||
|
clientinfo(),
|
||||||
|
oneof([publish, subscribe]),
|
||||||
|
topic(),
|
||||||
|
oneof([MkResult(allow), MkResult(deny)]),
|
||||||
|
request_meta()
|
||||||
|
},
|
||||||
begin
|
begin
|
||||||
ClientInfo = inject_magic_into(username, ClientInfo0),
|
ClientInfo = inject_magic_into(username, ClientInfo0),
|
||||||
OutResult = emqx_hooks:run_fold(
|
OutResult = emqx_hooks:run_fold(
|
||||||
|
@ -145,9 +152,9 @@ prop_client_authorize() ->
|
||||||
),
|
),
|
||||||
ExpectedOutResult =
|
ExpectedOutResult =
|
||||||
case maps:get(username, ClientInfo) of
|
case maps:get(username, ClientInfo) of
|
||||||
<<"baduser">> -> deny;
|
<<"baduser">> -> MkResult(deny);
|
||||||
<<"gooduser">> -> allow;
|
<<"gooduser">> -> MkResult(allow);
|
||||||
<<"normaluser">> -> allow;
|
<<"normaluser">> -> MkResult(allow);
|
||||||
_ -> Result
|
_ -> Result
|
||||||
end,
|
end,
|
||||||
?assertEqual(ExpectedOutResult, OutResult),
|
?assertEqual(ExpectedOutResult, OutResult),
|
||||||
|
@ -544,7 +551,7 @@ subopts(SubOpts) ->
|
||||||
authresult_to_bool(AuthResult) ->
|
authresult_to_bool(AuthResult) ->
|
||||||
AuthResult == ok.
|
AuthResult == ok.
|
||||||
|
|
||||||
aclresult_to_bool(Result) ->
|
aclresult_to_bool(#{result := Result}) ->
|
||||||
Result == allow.
|
Result == allow.
|
||||||
|
|
||||||
pubsub_to_enum(publish) -> 'PUBLISH';
|
pubsub_to_enum(publish) -> 'PUBLISH';
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
%% -*- mode: erlang -*-
|
%% -*- mode: erlang -*-
|
||||||
{application, emqx_gateway, [
|
{application, emqx_gateway, [
|
||||||
{description, "The Gateway management application"},
|
{description, "The Gateway management application"},
|
||||||
{vsn, "0.1.4"},
|
{vsn, "0.1.5"},
|
||||||
{registered, []},
|
{registered, []},
|
||||||
{mod, {emqx_gateway_app, []}},
|
{mod, {emqx_gateway_app, []}},
|
||||||
{applications, [kernel, stdlib, grpc, emqx, emqx_authn]},
|
{applications, [kernel, stdlib, grpc, emqx, emqx_authn]},
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
{application, emqx_management, [
|
{application, emqx_management, [
|
||||||
{description, "EMQX Management API and CLI"},
|
{description, "EMQX Management API and CLI"},
|
||||||
% strict semver, bump manually!
|
% strict semver, bump manually!
|
||||||
{vsn, "5.0.4"},
|
{vsn, "5.0.5"},
|
||||||
{modules, []},
|
{modules, []},
|
||||||
{registered, [emqx_management_sup]},
|
{registered, [emqx_management_sup]},
|
||||||
{applications, [kernel, stdlib, emqx_plugins, minirest, emqx]},
|
{applications, [kernel, stdlib, emqx_plugins, minirest, emqx]},
|
||||||
|
|
|
@ -1,32 +0,0 @@
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% Copyright (c) 2020-2022 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_management_schema).
|
|
||||||
|
|
||||||
-include_lib("typerefl/include/types.hrl").
|
|
||||||
|
|
||||||
-behaviour(hocon_schema).
|
|
||||||
|
|
||||||
-export([
|
|
||||||
namespace/0,
|
|
||||||
roots/0,
|
|
||||||
fields/1
|
|
||||||
]).
|
|
||||||
|
|
||||||
namespace() -> management.
|
|
||||||
|
|
||||||
roots() -> [].
|
|
||||||
|
|
||||||
fields(_) -> [].
|
|
|
@ -33,18 +33,26 @@ init(Req0, State) ->
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
running_status() ->
|
running_status() ->
|
||||||
BrokerStatus =
|
case emqx_dashboard_listener:is_ready(timer:seconds(20)) of
|
||||||
case emqx:is_running() of
|
true ->
|
||||||
true ->
|
BrokerStatus = broker_status(),
|
||||||
started;
|
AppStatus = application_status(),
|
||||||
false ->
|
Body = io_lib:format("Node ~ts is ~ts~nemqx is ~ts", [node(), BrokerStatus, AppStatus]),
|
||||||
stopped
|
{200, #{<<"content-type">> => <<"text/plain">>}, list_to_binary(Body)};
|
||||||
end,
|
false ->
|
||||||
AppStatus =
|
{503, #{<<"retry-after">> => <<"15">>}, <<>>}
|
||||||
case lists:keysearch(emqx, 1, application:which_applications()) of
|
end.
|
||||||
false -> not_running;
|
|
||||||
{value, _Val} -> running
|
broker_status() ->
|
||||||
end,
|
case emqx:is_running() of
|
||||||
Status = io_lib:format("Node ~ts is ~ts~nemqx is ~ts", [node(), BrokerStatus, AppStatus]),
|
true ->
|
||||||
Body = list_to_binary(Status),
|
started;
|
||||||
{200, #{<<"content-type">> => <<"text/plain">>}, Body}.
|
false ->
|
||||||
|
stopped
|
||||||
|
end.
|
||||||
|
|
||||||
|
application_status() ->
|
||||||
|
case lists:keysearch(emqx, 1, application:which_applications()) of
|
||||||
|
false -> not_running;
|
||||||
|
{value, _Val} -> running
|
||||||
|
end.
|
||||||
|
|
|
@ -14,8 +14,8 @@ type: application
|
||||||
|
|
||||||
# This is the chart version. This version number should be incremented each time you make changes
|
# This is the chart version. This version number should be incremented each time you make changes
|
||||||
# to the chart and its templates, including the app version.
|
# to the chart and its templates, including the app version.
|
||||||
version: 5
|
version: 5.0.7
|
||||||
|
|
||||||
# This is the version number of the application being deployed. This version number should be
|
# This is the version number of the application being deployed. This version number should be
|
||||||
# incremented each time you make changes to the application.
|
# incremented each time you make changes to the application.
|
||||||
appVersion: latest
|
appVersion: 5.0.7
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
mongo
|
||||||
|
mongo_rs_sharded
|
|
@ -7,11 +7,15 @@ REL_VSN="{{ release_version }}"
|
||||||
ERTS_VSN="{{ erts_vsn }}"
|
ERTS_VSN="{{ erts_vsn }}"
|
||||||
ERL_OPTS="{{ erl_opts }}"
|
ERL_OPTS="{{ erl_opts }}"
|
||||||
RUNNER_BIN_DIR="{{ runner_bin_dir }}"
|
RUNNER_BIN_DIR="{{ runner_bin_dir }}"
|
||||||
RUNNER_LOG_DIR="{{ runner_log_dir }}"
|
|
||||||
RUNNER_LIB_DIR="{{ runner_lib_dir }}"
|
RUNNER_LIB_DIR="{{ runner_lib_dir }}"
|
||||||
|
IS_ELIXIR="${IS_ELIXIR:-{{ is_elixir }}}"
|
||||||
|
|
||||||
|
## Allow users to pre-set `RUNNER_LOG_DIR` because it only affects boot commands like `start` and `console`,
|
||||||
|
## but not other commands such as `ping` and `ctl`.
|
||||||
|
RUNNER_LOG_DIR="${RUNNER_LOG_DIR:-{{ runner_log_dir }}}"
|
||||||
|
|
||||||
EMQX_ETC_DIR="{{ emqx_etc_dir }}"
|
EMQX_ETC_DIR="{{ emqx_etc_dir }}"
|
||||||
RUNNER_USER="{{ runner_user }}"
|
RUNNER_USER="{{ runner_user }}"
|
||||||
IS_ELIXIR="${IS_ELIXIR:-{{ is_elixir }}}"
|
|
||||||
SCHEMA_MOD="{{ emqx_schema_mod }}"
|
SCHEMA_MOD="{{ emqx_schema_mod }}"
|
||||||
IS_ENTERPRISE="{{ is_enterprise }}"
|
IS_ENTERPRISE="{{ is_enterprise }}"
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,8 @@ parse_semver() {
|
||||||
echo "$1" | tr '.|-' ' '
|
echo "$1" | tr '.|-' ' '
|
||||||
}
|
}
|
||||||
|
|
||||||
while read -r app; do
|
APPS="$(./scripts/find-apps.sh)"
|
||||||
|
for app in ${APPS}; do
|
||||||
if [ "$app" != "emqx" ]; then
|
if [ "$app" != "emqx" ]; then
|
||||||
app_path="$app"
|
app_path="$app"
|
||||||
else
|
else
|
||||||
|
@ -54,7 +55,7 @@ while read -r app; do
|
||||||
bad_app_count=$(( bad_app_count + 1))
|
bad_app_count=$(( bad_app_count + 1))
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
done < <(./scripts/find-apps.sh)
|
done
|
||||||
|
|
||||||
if [ $bad_app_count -gt 0 ]; then
|
if [ $bad_app_count -gt 0 ]; then
|
||||||
exit 1
|
exit 1
|
||||||
|
|
|
@ -0,0 +1,116 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
## This script runs CT (and necessary dependencies) in docker container(s)
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# ensure dir
|
||||||
|
cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")/../.."
|
||||||
|
|
||||||
|
help() {
|
||||||
|
echo
|
||||||
|
echo "-h|--help: To display this usage info"
|
||||||
|
echo "--app lib_dir/app_name: Print apps in json"
|
||||||
|
echo "--console: Start EMQX in console mode"
|
||||||
|
}
|
||||||
|
|
||||||
|
WHICH_APP='novalue'
|
||||||
|
CONSOLE='no'
|
||||||
|
while [ "$#" -gt 0 ]; do
|
||||||
|
case $1 in
|
||||||
|
-h|--help)
|
||||||
|
help
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
--app)
|
||||||
|
WHICH_APP="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--console)
|
||||||
|
CONSOLE='yes'
|
||||||
|
shift 1
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "unknown option $1"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "${WHICH_APP}" = 'novalue' ]; then
|
||||||
|
echo "must provide --app arg"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
ERLANG_CONTAINER='erlang24'
|
||||||
|
DOCKER_CT_ENVS_FILE="${WHICH_APP}/docker-ct"
|
||||||
|
|
||||||
|
if [ -f "$DOCKER_CT_ENVS_FILE" ]; then
|
||||||
|
# shellcheck disable=SC2002
|
||||||
|
CT_DEPS="$(cat "$DOCKER_CT_ENVS_FILE" | xargs)"
|
||||||
|
fi
|
||||||
|
CT_DEPS="${ERLANG_CONTAINER} ${CT_DEPS}"
|
||||||
|
|
||||||
|
FILES=( )
|
||||||
|
|
||||||
|
for dep in ${CT_DEPS}; do
|
||||||
|
case "${dep}" in
|
||||||
|
erlang24)
|
||||||
|
FILES+=( '.ci/docker-compose-file/docker-compose.yaml' )
|
||||||
|
;;
|
||||||
|
mongo)
|
||||||
|
FILES+=( '.ci/docker-compose-file/docker-compose-mongo-single-tcp.yaml'
|
||||||
|
'.ci/docker-compose-file/docker-compose-mongo-single-tls.yaml' )
|
||||||
|
;;
|
||||||
|
mongo_rs_sharded)
|
||||||
|
FILES+=( '.ci/docker-compose-file/docker-compose-mongo-replicaset-tcp.yaml'
|
||||||
|
'.ci/docker-compose-file/docker-compose-mongo-sharded-tcp.yaml' )
|
||||||
|
;;
|
||||||
|
redis)
|
||||||
|
FILES+=( '.ci/docker-compose-file/docker-compose-redis-single-tcp.yaml'
|
||||||
|
'.ci/docker-compose-file/docker-compose-redis-single-tls.yaml'
|
||||||
|
'.ci/docker-compose-file/docker-compose-redis-sentinel-tcp.yaml'
|
||||||
|
'.ci/docker-compose-file/docker-compose-redis-sentinel-tls.yaml' )
|
||||||
|
;;
|
||||||
|
mysql)
|
||||||
|
FILES+=( '.ci/docker-compose-file/docker-compose-mysql-tcp.yaml'
|
||||||
|
'.ci/docker-compose-file/docker-compose-mysql-tls.yaml' )
|
||||||
|
;;
|
||||||
|
pgsql)
|
||||||
|
FILES+=( '.ci/docker-compose-file/docker-compose-pgsql-tcp.yaml'
|
||||||
|
'.ci/docker-compose-file/docker-compose-pgsql-tls.yaml' )
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "unknown_ct_dependency $dep"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
F_OPTIONS=""
|
||||||
|
|
||||||
|
for file in "${FILES[@]}"; do
|
||||||
|
F_OPTIONS="$F_OPTIONS -f $file"
|
||||||
|
done
|
||||||
|
|
||||||
|
# shellcheck disable=2086 # no quotes for F_OPTIONS
|
||||||
|
docker-compose $F_OPTIONS up -d --build
|
||||||
|
|
||||||
|
# /emqx is where the source dir is mounted to the Erlang container
|
||||||
|
# in .ci/docker-compose-file/docker-compose.yaml
|
||||||
|
TTY=''
|
||||||
|
if [[ -t 1 ]]; then
|
||||||
|
TTY='-t'
|
||||||
|
fi
|
||||||
|
docker exec -i $TTY "$ERLANG_CONTAINER" bash -c 'git config --global --add safe.directory /emqx'
|
||||||
|
|
||||||
|
if [ "$CONSOLE" = 'yes' ]; then
|
||||||
|
docker exec -i $TTY "$ERLANG_CONTAINER" bash -c "make run"
|
||||||
|
else
|
||||||
|
set +e
|
||||||
|
docker exec -i $TTY "$ERLANG_CONTAINER" bash -c "make ${WHICH_APP}-ct"
|
||||||
|
RESULT=$?
|
||||||
|
# shellcheck disable=2086 # no quotes for F_OPTIONS
|
||||||
|
docker-compose $F_OPTIONS down
|
||||||
|
exit $RESULT
|
||||||
|
fi
|
|
@ -1,5 +0,0 @@
|
||||||
# apps need docker-compose to run CT
|
|
||||||
apps/emqx_authn
|
|
||||||
apps/emqx_authz
|
|
||||||
apps/emqx_connector
|
|
||||||
lib-ee/emqx_ee_bridge
|
|
|
@ -50,24 +50,25 @@ find_app() {
|
||||||
|
|
||||||
CE="$(find_app 'apps')"
|
CE="$(find_app 'apps')"
|
||||||
EE="$(find_app 'lib-ee')"
|
EE="$(find_app 'lib-ee')"
|
||||||
|
APPS_ALL="$(echo -e "${CE}\n${EE}")"
|
||||||
|
|
||||||
if [ "$CT" = 'novalue' ]; then
|
if [ "$CT" = 'novalue' ]; then
|
||||||
echo -e "${CE}\n${EE}"
|
RESULT="${APPS_ALL}"
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
APPS_ALL="$(echo -e "${CE}\n${EE}")"
|
|
||||||
APPS_DOCKER_CT="$(grep -v -E '^#.*' scripts/docker-ct-apps)"
|
|
||||||
|
|
||||||
# shellcheck disable=SC2068
|
|
||||||
for app in ${APPS_DOCKER_CT[@]}; do
|
|
||||||
APPS_ALL=("${APPS_ALL[@]/$app}")
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ "$CT" = 'docker' ]; then
|
|
||||||
RESULT="${APPS_DOCKER_CT}"
|
|
||||||
else
|
else
|
||||||
RESULT="${APPS_ALL[*]}"
|
APPS_NORMAL_CT=( )
|
||||||
|
APPS_DOCKER_CT=( )
|
||||||
|
for app in ${APPS_ALL}; do
|
||||||
|
if [ -f "${app}/docker-ct" ]; then
|
||||||
|
APPS_DOCKER_CT+=("$app")
|
||||||
|
else
|
||||||
|
APPS_NORMAL_CT+=("$app")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [ "$CT" = 'docker' ]; then
|
||||||
|
RESULT="${APPS_DOCKER_CT[*]}"
|
||||||
|
else
|
||||||
|
RESULT="${APPS_NORMAL_CT[*]}"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$WANT_JSON" = 'yes' ]; then
|
if [ "$WANT_JSON" = 'yes' ]; then
|
||||||
|
|
Loading…
Reference in New Issue