Merge remote-tracking branch 'origin/main-v4.3' into main-v4.4
This commit is contained in:
commit
aa90177302
|
@ -0,0 +1,14 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -xe
|
||||||
|
|
||||||
|
cd "$EMQX_PATH"
|
||||||
|
|
||||||
|
rm -rf _build _upgrade_base
|
||||||
|
|
||||||
|
mkdir _upgrade_base
|
||||||
|
pushd _upgrade_base
|
||||||
|
wget "https://s3-us-west-2.amazonaws.com/packages.emqx/emqx-ce/v${EMQX_BASE}/emqx-ubuntu20.04-${EMQX_BASE}-amd64.zip"
|
||||||
|
popd
|
||||||
|
|
||||||
|
make emqx-zip
|
|
@ -0,0 +1,15 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -xe
|
||||||
|
|
||||||
|
mkdir -p "$TEST_PATH"
|
||||||
|
cd "$TEST_PATH"
|
||||||
|
|
||||||
|
cp ../"$EMQX_PATH"/_upgrade_base/*.zip ./
|
||||||
|
unzip ./*.zip
|
||||||
|
|
||||||
|
cp ../"$EMQX_PATH"/_packages/emqx/*.zip ./emqx/releases/
|
||||||
|
|
||||||
|
git clone --depth 1 https://github.com/terry-xiaoyu/one_more_emqx.git
|
||||||
|
|
||||||
|
./one_more_emqx/one_more_emqx.sh emqx2
|
|
@ -0,0 +1,17 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -xe
|
||||||
|
|
||||||
|
export EMQX_PATH="$1"
|
||||||
|
export EMQX_BASE="$2"
|
||||||
|
|
||||||
|
export TEST_PATH="emqx_test"
|
||||||
|
|
||||||
|
./build.sh
|
||||||
|
|
||||||
|
VERSION=$("$EMQX_PATH"/pkg-vsn.sh)
|
||||||
|
export VERSION
|
||||||
|
|
||||||
|
./prepare.sh
|
||||||
|
|
||||||
|
./test.sh
|
|
@ -0,0 +1,121 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
EMQX_ENDPOINT="http://localhost:8081/api/v4/acl"
|
||||||
|
EMQX2_ENDPOINT="http://localhost:8917/api/v4/acl"
|
||||||
|
|
||||||
|
function run() {
|
||||||
|
emqx="$1"
|
||||||
|
shift
|
||||||
|
|
||||||
|
echo "[$emqx]" "$@"
|
||||||
|
|
||||||
|
pushd "$TEST_PATH/$emqx"
|
||||||
|
"$@"
|
||||||
|
popd
|
||||||
|
}
|
||||||
|
|
||||||
|
function post_rule() {
|
||||||
|
endpoint="$1"
|
||||||
|
rule="$2"
|
||||||
|
echo -n "->($endpoint) "
|
||||||
|
curl -s -u admin:public -X POST "$endpoint" -d "$rule"
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
function verify_clientid_rule() {
|
||||||
|
endpoint="$1"
|
||||||
|
id="$2"
|
||||||
|
echo -n "<-($endpoint) "
|
||||||
|
curl -s -u admin:public "$endpoint/clientid/$id" | grep "$id" || (echo "verify rule for client $id failed" && return 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Run nodes
|
||||||
|
|
||||||
|
run emqx ./bin/emqx start
|
||||||
|
run emqx2 ./bin/emqx start
|
||||||
|
|
||||||
|
run emqx ./bin/emqx_ctl plugins load emqx_auth_mnesia
|
||||||
|
run emqx2 ./bin/emqx_ctl plugins load emqx_auth_mnesia
|
||||||
|
|
||||||
|
run emqx2 ./bin/emqx_ctl cluster join 'emqx@127.0.0.1'
|
||||||
|
|
||||||
|
# Add ACL rule to unupgraded EMQX nodes
|
||||||
|
|
||||||
|
post_rule "$EMQX_ENDPOINT" '{"clientid": "CLIENT1_A","topic": "t", "action": "pub", "access": "allow"}'
|
||||||
|
post_rule "$EMQX2_ENDPOINT" '{"clientid": "CLIENT1_B","topic": "t", "action": "pub", "access": "allow"}'
|
||||||
|
|
||||||
|
# Upgrade emqx2 node
|
||||||
|
|
||||||
|
run emqx2 ./bin/emqx install "$VERSION"
|
||||||
|
sleep 60
|
||||||
|
|
||||||
|
# Verify upgrade blocked
|
||||||
|
|
||||||
|
run emqx2 ./bin/emqx eval 'emqx_acl_mnesia_migrator:is_old_table_migrated().' | grep false || (echo "emqx2 shouldn't have migrated" && exit 1)
|
||||||
|
|
||||||
|
# Verify old rules on both nodes
|
||||||
|
|
||||||
|
verify_clientid_rule "$EMQX_ENDPOINT" 'CLIENT1_A'
|
||||||
|
verify_clientid_rule "$EMQX2_ENDPOINT" 'CLIENT1_A'
|
||||||
|
|
||||||
|
verify_clientid_rule "$EMQX_ENDPOINT" 'CLIENT1_B'
|
||||||
|
verify_clientid_rule "$EMQX2_ENDPOINT" 'CLIENT1_B'
|
||||||
|
|
||||||
|
# Add ACL on OLD and NEW node, verify on all nodes
|
||||||
|
|
||||||
|
post_rule "$EMQX_ENDPOINT" '{"clientid": "CLIENT2_A","topic": "t", "action": "pub", "access": "allow"}'
|
||||||
|
post_rule "$EMQX2_ENDPOINT" '{"clientid": "CLIENT2_B","topic": "t", "action": "pub", "access": "allow"}'
|
||||||
|
|
||||||
|
verify_clientid_rule "$EMQX_ENDPOINT" 'CLIENT2_A'
|
||||||
|
verify_clientid_rule "$EMQX2_ENDPOINT" 'CLIENT2_A'
|
||||||
|
|
||||||
|
verify_clientid_rule "$EMQX_ENDPOINT" 'CLIENT2_B'
|
||||||
|
verify_clientid_rule "$EMQX2_ENDPOINT" 'CLIENT2_B'
|
||||||
|
|
||||||
|
# Upgrade emqx node
|
||||||
|
|
||||||
|
run emqx ./bin/emqx install "$VERSION"
|
||||||
|
|
||||||
|
# Wait for upgrade
|
||||||
|
|
||||||
|
sleep 60
|
||||||
|
|
||||||
|
# Verify if upgrade occured
|
||||||
|
|
||||||
|
run emqx ./bin/emqx eval 'emqx_acl_mnesia_migrator:is_old_table_migrated().' | grep true || (echo "emqx should have migrated" && exit 1)
|
||||||
|
run emqx2 ./bin/emqx eval 'emqx_acl_mnesia_migrator:is_old_table_migrated().' | grep true || (echo "emqx2 should have migrated" && exit 1)
|
||||||
|
|
||||||
|
# Verify rules are kept
|
||||||
|
|
||||||
|
verify_clientid_rule "$EMQX_ENDPOINT" 'CLIENT1_A'
|
||||||
|
verify_clientid_rule "$EMQX2_ENDPOINT" 'CLIENT1_A'
|
||||||
|
|
||||||
|
verify_clientid_rule "$EMQX_ENDPOINT" 'CLIENT1_B'
|
||||||
|
verify_clientid_rule "$EMQX2_ENDPOINT" 'CLIENT1_B'
|
||||||
|
|
||||||
|
verify_clientid_rule "$EMQX_ENDPOINT" 'CLIENT2_A'
|
||||||
|
verify_clientid_rule "$EMQX2_ENDPOINT" 'CLIENT2_A'
|
||||||
|
|
||||||
|
verify_clientid_rule "$EMQX_ENDPOINT" 'CLIENT2_B'
|
||||||
|
verify_clientid_rule "$EMQX2_ENDPOINT" 'CLIENT2_B'
|
||||||
|
|
||||||
|
# Add ACL on OLD and NEW node, verify on all nodes
|
||||||
|
|
||||||
|
post_rule "$EMQX_ENDPOINT" '{"clientid": "CLIENT3_A","topic": "t", "action": "pub", "access": "allow"}'
|
||||||
|
post_rule "$EMQX2_ENDPOINT" '{"clientid": "CLIENT3_B","topic": "t", "action": "pub", "access": "allow"}'
|
||||||
|
|
||||||
|
verify_clientid_rule "$EMQX_ENDPOINT" 'CLIENT3_A'
|
||||||
|
verify_clientid_rule "$EMQX2_ENDPOINT" 'CLIENT3_A'
|
||||||
|
|
||||||
|
verify_clientid_rule "$EMQX_ENDPOINT" 'CLIENT3_B'
|
||||||
|
verify_clientid_rule "$EMQX2_ENDPOINT" 'CLIENT3_B'
|
||||||
|
|
||||||
|
# Stop nodes
|
||||||
|
|
||||||
|
run emqx ./bin/emqx stop
|
||||||
|
run emqx2 ./bin/emqx stop
|
||||||
|
|
||||||
|
echo "Success!"
|
||||||
|
|
|
@ -0,0 +1,99 @@
|
||||||
|
version: '3.9'
|
||||||
|
|
||||||
|
services:
|
||||||
|
haproxy:
|
||||||
|
container_name: haproxy
|
||||||
|
image: haproxy:2.3
|
||||||
|
depends_on:
|
||||||
|
- emqx1
|
||||||
|
- emqx2
|
||||||
|
volumes:
|
||||||
|
- ./haproxy/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg
|
||||||
|
- ../../etc/certs:/usr/local/etc/haproxy/certs
|
||||||
|
ports:
|
||||||
|
- "18083:18083"
|
||||||
|
# - "1883:1883"
|
||||||
|
# - "8883:8883"
|
||||||
|
# - "8083:8083"
|
||||||
|
# - "5683:5683/udp"
|
||||||
|
# - "9999:9999"
|
||||||
|
# - "8084:8084"
|
||||||
|
networks:
|
||||||
|
- emqx_bridge
|
||||||
|
working_dir: /usr/local/etc/haproxy
|
||||||
|
command:
|
||||||
|
- bash
|
||||||
|
- -c
|
||||||
|
- |
|
||||||
|
cat /usr/local/etc/haproxy/certs/cert.pem /usr/local/etc/haproxy/certs/key.pem > /usr/local/etc/haproxy/certs/emqx.pem
|
||||||
|
haproxy -f /usr/local/etc/haproxy/haproxy.cfg
|
||||||
|
|
||||||
|
emqx1:
|
||||||
|
restart: always
|
||||||
|
container_name: node1.emqx.io
|
||||||
|
image: $TARGET:$EMQX_TAG
|
||||||
|
env_file:
|
||||||
|
- conf.cluster.env
|
||||||
|
volumes:
|
||||||
|
- etc:/opt/emqx/etc
|
||||||
|
environment:
|
||||||
|
- "EMQX_HOST=node1.emqx.io"
|
||||||
|
ports:
|
||||||
|
- "11881:18083"
|
||||||
|
# - "1883:1883"
|
||||||
|
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
|
||||||
|
timeout: 25s
|
||||||
|
retries: 5
|
||||||
|
networks:
|
||||||
|
emqx_bridge:
|
||||||
|
aliases:
|
||||||
|
- node1.emqx.io
|
||||||
|
|
||||||
|
emqx2:
|
||||||
|
restart: always
|
||||||
|
container_name: node2.emqx.io
|
||||||
|
image: $TARGET:$EMQX_TAG
|
||||||
|
env_file:
|
||||||
|
- conf.cluster.env
|
||||||
|
volumes:
|
||||||
|
- etc:/opt/emqx/etc
|
||||||
|
environment:
|
||||||
|
- "EMQX_HOST=node2.emqx.io"
|
||||||
|
ports:
|
||||||
|
- "11882:18083"
|
||||||
|
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
|
||||||
|
timeout: 25s
|
||||||
|
retries: 5
|
||||||
|
networks:
|
||||||
|
emqx_bridge:
|
||||||
|
aliases:
|
||||||
|
- node2.emqx.io
|
||||||
|
volumes:
|
||||||
|
etc:
|
||||||
|
networks:
|
||||||
|
emqx_bridge:
|
||||||
|
driver: bridge
|
||||||
|
name: emqx_bridge
|
||||||
|
ipam:
|
||||||
|
driver: default
|
||||||
|
config:
|
||||||
|
- subnet: 172.100.239.0/24
|
||||||
|
gateway: 172.100.239.1
|
|
@ -27,6 +27,7 @@ services:
|
||||||
haproxy -f /usr/local/etc/haproxy/haproxy.cfg
|
haproxy -f /usr/local/etc/haproxy/haproxy.cfg
|
||||||
|
|
||||||
emqx1:
|
emqx1:
|
||||||
|
restart: always
|
||||||
container_name: node1.emqx.io
|
container_name: node1.emqx.io
|
||||||
image: $TARGET:$EMQX_TAG
|
image: $TARGET:$EMQX_TAG
|
||||||
env_file:
|
env_file:
|
||||||
|
@ -51,6 +52,7 @@ services:
|
||||||
- node1.emqx.io
|
- node1.emqx.io
|
||||||
|
|
||||||
emqx2:
|
emqx2:
|
||||||
|
restart: always
|
||||||
container_name: node2.emqx.io
|
container_name: node2.emqx.io
|
||||||
image: $TARGET:$EMQX_TAG
|
image: $TARGET:$EMQX_TAG
|
||||||
env_file:
|
env_file:
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
version: '3.9'
|
||||||
|
|
||||||
|
services:
|
||||||
|
web_server:
|
||||||
|
container_name: Tomcat
|
||||||
|
build:
|
||||||
|
context: ./http-service
|
||||||
|
image: web-server
|
||||||
|
networks:
|
||||||
|
- emqx_bridge
|
|
@ -0,0 +1,15 @@
|
||||||
|
FROM tomcat:10.0.5
|
||||||
|
|
||||||
|
RUN wget https://downloads.apache.org/maven/maven-3/3.6.3/binaries/apache-maven-3.6.3-bin.zip \
|
||||||
|
&& unzip apache-maven-3.6.3-bin.zip \
|
||||||
|
&& mv apache-maven-3.6.3 /opt/apache-maven-3.6.3/ \
|
||||||
|
&& ln -s /opt/apache-maven-3.6.3/ /opt/maven
|
||||||
|
ENV M2_HOME=/opt/maven
|
||||||
|
ENV M2=$M2_HOME/bin
|
||||||
|
ENV PATH=$M2:$PATH
|
||||||
|
COPY ./web-server /code
|
||||||
|
WORKDIR /code
|
||||||
|
RUN mvn package -Dmaven.skip.test=true
|
||||||
|
RUN mv ./target/emqx-web-0.0.1.war /usr/local/tomcat/webapps/emqx-web.war
|
||||||
|
EXPOSE 8080
|
||||||
|
CMD ["/usr/local/tomcat/bin/catalina.sh","run"]
|
|
@ -0,0 +1,65 @@
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>emqx-web</groupId>
|
||||||
|
<artifactId>emqx-web</artifactId>
|
||||||
|
<version>0.0.1</version>
|
||||||
|
<packaging>war</packaging>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>mysql</groupId>
|
||||||
|
<artifactId>mysql-connector-java</artifactId>
|
||||||
|
<version>8.0.16</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>commons-dbutils</groupId>
|
||||||
|
<artifactId>commons-dbutils</artifactId>
|
||||||
|
<version>1.7</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>commons-logging</groupId>
|
||||||
|
<artifactId>commons-logging</artifactId>
|
||||||
|
<version>1.2</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>commons-dbcp</groupId>
|
||||||
|
<artifactId>commons-dbcp</artifactId>
|
||||||
|
<version>1.4</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>commons-pool</groupId>
|
||||||
|
<artifactId>commons-pool</artifactId>
|
||||||
|
<version>1.6</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>jakarta.servlet</groupId>
|
||||||
|
<artifactId>jakarta.servlet-api</artifactId>
|
||||||
|
<version>5.0.0</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
<build>
|
||||||
|
<resources>
|
||||||
|
<resource>
|
||||||
|
<directory>src/main/reousrce</directory>
|
||||||
|
<excludes>
|
||||||
|
<exclude>**/*.java</exclude>
|
||||||
|
</excludes>
|
||||||
|
</resource>
|
||||||
|
</resources>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.8.1</version>
|
||||||
|
<configuration>
|
||||||
|
<source>1.8</source>
|
||||||
|
<target>1.8</target>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-war-plugin</artifactId>
|
||||||
|
<version>3.2.3</version>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
|
@ -0,0 +1,54 @@
|
||||||
|
package com.emqx.dao;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
import org.apache.commons.dbutils.QueryRunner;
|
||||||
|
import org.apache.commons.dbutils.handlers.ScalarHandler;
|
||||||
|
|
||||||
|
import com.emqx.util.EmqxDatabaseUtil;
|
||||||
|
|
||||||
|
public class AuthDAO {
|
||||||
|
|
||||||
|
public String getUserName(String userName) throws IOException, SQLException {
|
||||||
|
QueryRunner runner = new QueryRunner(EmqxDatabaseUtil.getDataSource());
|
||||||
|
String sql = "select password from http_user where username='"+userName+"'";
|
||||||
|
String password =runner.query(sql, new ScalarHandler<String>());
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getClient(String clientid) throws IOException, SQLException {
|
||||||
|
QueryRunner runner = new QueryRunner(EmqxDatabaseUtil.getDataSource());
|
||||||
|
String sql = "select password from http_user where clientid='"+clientid+"'";
|
||||||
|
String password =runner.query(sql, new ScalarHandler<String>());
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUserAccess(String userName) throws IOException, SQLException {
|
||||||
|
QueryRunner runner = new QueryRunner(EmqxDatabaseUtil.getDataSource());
|
||||||
|
String sql = "select access from http_acl where username='"+userName+"'";
|
||||||
|
String access =runner.query(sql, new ScalarHandler<String>());
|
||||||
|
return access;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUserTopic(String userName) throws IOException, SQLException {
|
||||||
|
QueryRunner runner = new QueryRunner(EmqxDatabaseUtil.getDataSource());
|
||||||
|
String sql = "select topic from http_acl where username='"+userName+"'";
|
||||||
|
String topic =runner.query(sql, new ScalarHandler<String>());
|
||||||
|
return topic;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getClientAccess(String clientid) throws IOException, SQLException {
|
||||||
|
QueryRunner runner = new QueryRunner(EmqxDatabaseUtil.getDataSource());
|
||||||
|
String sql = "select access from http_acl where clientid='"+clientid+"'";
|
||||||
|
String access =runner.query(sql, new ScalarHandler<String>());
|
||||||
|
return access;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getClientTopic(String clientid) throws IOException, SQLException {
|
||||||
|
QueryRunner runner = new QueryRunner(EmqxDatabaseUtil.getDataSource());
|
||||||
|
String sql = "select topic from http_acl where clientid='"+clientid+"'";
|
||||||
|
String topic =runner.query(sql, new ScalarHandler<String>());
|
||||||
|
return topic;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
package com.emqx.dao;
|
||||||
|
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
import org.apache.commons.dbcp.BasicDataSource;
|
||||||
|
import org.apache.commons.dbutils.QueryRunner;
|
||||||
|
import org.apache.commons.dbutils.handlers.ColumnListHandler;
|
||||||
|
import org.apache.commons.dbutils.handlers.ScalarHandler;
|
||||||
|
import org.apache.commons.dbutils.handlers.columns.StringColumnHandler;
|
||||||
|
|
||||||
|
|
||||||
|
public class DBUtilsTest {
|
||||||
|
|
||||||
|
public static void main(String args[]) throws FileNotFoundException, IOException, SQLException {
|
||||||
|
Properties property = new Properties();//流文件
|
||||||
|
|
||||||
|
property.load(DBUtilsTest.class.getClassLoader().getResourceAsStream("database.properties"));
|
||||||
|
|
||||||
|
BasicDataSource dataSource = new BasicDataSource();
|
||||||
|
dataSource.setDriverClassName(property.getProperty("jdbc.driver"));
|
||||||
|
dataSource.setUrl(property.getProperty("jdbc.url"));
|
||||||
|
dataSource.setUsername(property.getProperty("jdbc.username"));
|
||||||
|
dataSource.setPassword(property.getProperty("jdbc.password"));
|
||||||
|
|
||||||
|
// 初始化连接数 if(initialSize!=null)
|
||||||
|
//dataSource.setInitialSize(Integer.parseInt(initialSize));
|
||||||
|
|
||||||
|
// 最小空闲连接 if(minIdle!=null)
|
||||||
|
//dataSource.setMinIdle(Integer.parseInt(minIdle));
|
||||||
|
|
||||||
|
// 最大空闲连接 if(maxIdle!=null)
|
||||||
|
//dataSource.setMaxIdle(Integer.parseInt(maxIdle));
|
||||||
|
|
||||||
|
QueryRunner runner = new QueryRunner(dataSource);
|
||||||
|
String sql="select username from mqtt_user where id=1";
|
||||||
|
String result = runner.query(sql, new ScalarHandler<String>());
|
||||||
|
|
||||||
|
System.out.println(result);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,103 @@
|
||||||
|
package com.emqx.servlet;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
import com.emqx.dao.AuthDAO;
|
||||||
|
|
||||||
|
import jakarta.servlet.ServletException;
|
||||||
|
import jakarta.servlet.http.HttpServlet;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
public class AclServlet extends HttpServlet {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
doPost(req, resp);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||||
|
String clientid = req.getParameter("clientid");
|
||||||
|
String username = req.getParameter("username");
|
||||||
|
String access = req.getParameter("access");
|
||||||
|
String topic = req.getParameter("topic");
|
||||||
|
//String password = req.getParameter("password");
|
||||||
|
|
||||||
|
//step0: password is not null, or not pass.
|
||||||
|
|
||||||
|
AuthDAO dao = new AuthDAO();
|
||||||
|
try {
|
||||||
|
//step1: check username access&topic
|
||||||
|
if(username != null) {
|
||||||
|
String access_1 = dao.getUserAccess(username);
|
||||||
|
String topic_1 = dao.getUserTopic(username);
|
||||||
|
|
||||||
|
if(access.equals(access_1)) {
|
||||||
|
if(topic.equals(topic_1)) {
|
||||||
|
resp.setStatus(200);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if(clientid != null){
|
||||||
|
String access_2 = dao.getClientAccess(clientid);
|
||||||
|
String topic_2 = dao.getClientTopic(clientid);
|
||||||
|
if(access.equals(access_2)) {
|
||||||
|
if(topic.equals(topic_2)) {
|
||||||
|
resp.setStatus(200);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
resp.setStatus(400);
|
||||||
|
}
|
||||||
|
}else {
|
||||||
|
resp.setStatus(400);
|
||||||
|
}
|
||||||
|
}else {
|
||||||
|
resp.setStatus(400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}else {//step2.1: username password is not match, then check clientid password
|
||||||
|
if(clientid != null){
|
||||||
|
String access_3 = dao.getClientAccess(clientid);
|
||||||
|
String topic_3 = dao.getClientTopic(clientid);
|
||||||
|
if(access.equals(access_3)) {
|
||||||
|
if(topic.equals(topic_3)) {
|
||||||
|
resp.setStatus(200);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
resp.setStatus(400);
|
||||||
|
}
|
||||||
|
}else {
|
||||||
|
resp.setStatus(400);
|
||||||
|
}
|
||||||
|
}else {
|
||||||
|
resp.setStatus(400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}else {//step2.2: username is null, then check clientid password
|
||||||
|
if(clientid != null){
|
||||||
|
String access_4 = dao.getClientAccess(clientid);
|
||||||
|
String topic_4 = dao.getClientTopic(clientid);
|
||||||
|
if(access.equals(access_4)) {
|
||||||
|
if(topic.equals(topic_4)) {
|
||||||
|
resp.setStatus(200);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
resp.setStatus(400);
|
||||||
|
}
|
||||||
|
}else {
|
||||||
|
resp.setStatus(400);
|
||||||
|
}
|
||||||
|
}else {
|
||||||
|
resp.setStatus(400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
// TODO Auto-generated catch block
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
// TODO Auto-generated catch block
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
package com.emqx.servlet;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
import com.emqx.dao.AuthDAO;
|
||||||
|
|
||||||
|
import jakarta.servlet.ServletException;
|
||||||
|
import jakarta.servlet.http.HttpServlet;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
public class AuthServlet extends HttpServlet {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
doPost(req, resp);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||||
|
String clientid = req.getParameter("clientid");
|
||||||
|
String username =req.getParameter("username");
|
||||||
|
String password = req.getParameter("password");
|
||||||
|
|
||||||
|
//step0: password is not null, or not pass.
|
||||||
|
if(password == null) {
|
||||||
|
resp.setStatus(400);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
AuthDAO dao = new AuthDAO();
|
||||||
|
try {
|
||||||
|
//step1: check username password
|
||||||
|
if(username != null) {
|
||||||
|
String password_d = dao.getUserName(username);
|
||||||
|
|
||||||
|
if(password.equals(password_d)) {
|
||||||
|
resp.setStatus(200);
|
||||||
|
//200
|
||||||
|
}else {//step2.1: username password is not match, then check clientid password
|
||||||
|
if(clientid != null){
|
||||||
|
String password_c = dao.getClient(clientid);
|
||||||
|
if(password.equals(password_c)) {
|
||||||
|
resp.setStatus(200);
|
||||||
|
}else {
|
||||||
|
resp.setStatus(400);
|
||||||
|
}
|
||||||
|
}else {
|
||||||
|
resp.setStatus(400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}else {//step2.2: username is null, then check clientid password
|
||||||
|
if(clientid != null){
|
||||||
|
String password_c = dao.getClient(clientid);
|
||||||
|
if(password.equals(password_c)) {
|
||||||
|
resp.setStatus(200);
|
||||||
|
}else {
|
||||||
|
resp.setStatus(400);
|
||||||
|
}
|
||||||
|
}else {
|
||||||
|
resp.setStatus(400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
// TODO Auto-generated catch block
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
// TODO Auto-generated catch block
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
package com.emqx.util;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
|
import org.apache.commons.dbcp.BasicDataSource;
|
||||||
|
|
||||||
|
import com.emqx.dao.DBUtilsTest;
|
||||||
|
|
||||||
|
public class EmqxDatabaseUtil {
|
||||||
|
|
||||||
|
public static DataSource getDataSource() throws IOException {
|
||||||
|
Properties property = new Properties();// 流文件
|
||||||
|
|
||||||
|
property.load(EmqxDatabaseUtil.class.getClassLoader().getResourceAsStream("database.properties"));
|
||||||
|
|
||||||
|
BasicDataSource dataSource = new BasicDataSource();
|
||||||
|
dataSource.setDriverClassName(property.getProperty("jdbc.driver"));
|
||||||
|
dataSource.setUrl(property.getProperty("jdbc.url"));
|
||||||
|
dataSource.setUsername(property.getProperty("jdbc.username"));
|
||||||
|
dataSource.setPassword(property.getProperty("jdbc.password"));
|
||||||
|
|
||||||
|
return dataSource;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
jdbc.driver= com.mysql.jdbc.Driver
|
||||||
|
jdbc.url= jdbc:mysql://mysql_server:3306/mqtt
|
||||||
|
jdbc.username= root
|
||||||
|
jdbc.password= public
|
|
@ -0,0 +1,3 @@
|
||||||
|
Manifest-Version: 1.0
|
||||||
|
Class-Path:
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xmlns="http://JAVA.sun.com/xml/ns/javaee"
|
||||||
|
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
|
||||||
|
id="WebApp_ID" version="2.5">
|
||||||
|
<display-name>emqx-web</display-name>
|
||||||
|
<servlet>
|
||||||
|
<servlet-name>Auth</servlet-name>
|
||||||
|
<servlet-class>com.emqx.servlet.AuthServlet</servlet-class>
|
||||||
|
</servlet>
|
||||||
|
<servlet>
|
||||||
|
<servlet-name>Acl</servlet-name>
|
||||||
|
<servlet-class>com.emqx.servlet.AclServlet</servlet-class>
|
||||||
|
</servlet>
|
||||||
|
<servlet-mapping>
|
||||||
|
<servlet-name>Auth</servlet-name>
|
||||||
|
<url-pattern>/auth</url-pattern>
|
||||||
|
</servlet-mapping>
|
||||||
|
<servlet-mapping>
|
||||||
|
<servlet-name>Acl</servlet-name>
|
||||||
|
<url-pattern>/acl</url-pattern>
|
||||||
|
</servlet-mapping>
|
||||||
|
<welcome-file-list>
|
||||||
|
<welcome-file>index.html</welcome-file>
|
||||||
|
<welcome-file>index.htm</welcome-file>
|
||||||
|
<welcome-file>index.jsp</welcome-file>
|
||||||
|
<welcome-file>default.html</welcome-file>
|
||||||
|
<welcome-file>default.htm</welcome-file>
|
||||||
|
<welcome-file>default.jsp</welcome-file>
|
||||||
|
</welcome-file-list>
|
||||||
|
</web-app>
|
|
@ -0,0 +1,10 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>love</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
It's lucky, jiabanxiang.
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -23,10 +23,7 @@
|
||||||
?SH-PROMPT
|
?SH-PROMPT
|
||||||
|
|
||||||
!cd emqx
|
!cd emqx
|
||||||
!export EMQX_LOG__CONSOLE_HANDLER__ENABLE=true
|
!export EMQX_LOG__LEVEL=debug
|
||||||
!export EMQX_LOG__CONSOLE_HANDLER__LEVEL=debug
|
|
||||||
!export EMQX_LOG__PRIMARY_LEVEL=debug
|
|
||||||
!export EMQX_ZONES__DEFAULT__LISTENERS__MQTT_WSS__BIND="0.0.0.0:8085"
|
|
||||||
|
|
||||||
!./bin/emqx start
|
!./bin/emqx start
|
||||||
?EMQ X .* is started successfully!
|
?EMQ X .* is started successfully!
|
||||||
|
@ -39,9 +36,7 @@
|
||||||
?SH-PROMPT
|
?SH-PROMPT
|
||||||
!cd emqx2
|
!cd emqx2
|
||||||
|
|
||||||
!export EMQX_LOG__CONSOLE_HANDLER__ENABLE=true
|
!export EMQX_LOG__LEVEL=debug
|
||||||
!export EMQX_LOG__CONSOLE_HANDLER__LEVEL=debug
|
|
||||||
!export EMQX_LOG__PRIMARY_LEVEL=debug
|
|
||||||
|
|
||||||
!./bin/emqx start
|
!./bin/emqx start
|
||||||
?EMQ X .* is started successfully!
|
?EMQ X .* is started successfully!
|
||||||
|
|
|
@ -468,7 +468,7 @@ jobs:
|
||||||
-H "Authorization: token ${{ secrets.CI_GIT_TOKEN }}" \
|
-H "Authorization: token ${{ secrets.CI_GIT_TOKEN }}" \
|
||||||
-H "Accept: application/vnd.github.v3+json" \
|
-H "Accept: application/vnd.github.v3+json" \
|
||||||
-X POST \
|
-X POST \
|
||||||
-d "{\"ref\":\"v1.0.2\",\"inputs\":{\"version\": \"${{ env.version }}\", \"emqx_ee\": \"true\"}}" \
|
-d "{\"ref\":\"v1.0.3\",\"inputs\":{\"version\": \"${{ env.version }}\", \"emqx_ee\": \"true\"}}" \
|
||||||
"https://api.github.com/repos/emqx/emqx-ci-helper/actions/workflows/update_emqx_repos.yaml/dispatches"
|
"https://api.github.com/repos/emqx/emqx-ci-helper/actions/workflows/update_emqx_repos.yaml/dispatches"
|
||||||
- name: update repo.emqx.io
|
- name: update repo.emqx.io
|
||||||
if: github.event_name == 'release' && endsWith(github.repository, 'emqx') && matrix.profile == 'emqx'
|
if: github.event_name == 'release' && endsWith(github.repository, 'emqx') && matrix.profile == 'emqx'
|
||||||
|
@ -477,7 +477,7 @@ jobs:
|
||||||
-H "Authorization: token ${{ secrets.CI_GIT_TOKEN }}" \
|
-H "Authorization: token ${{ secrets.CI_GIT_TOKEN }}" \
|
||||||
-H "Accept: application/vnd.github.v3+json" \
|
-H "Accept: application/vnd.github.v3+json" \
|
||||||
-X POST \
|
-X POST \
|
||||||
-d "{\"ref\":\"v1.0.2\",\"inputs\":{\"version\": \"${{ env.version }}\", \"emqx_ce\": \"true\"}}" \
|
-d "{\"ref\":\"v1.0.3\",\"inputs\":{\"version\": \"${{ env.version }}\", \"emqx_ce\": \"true\"}}" \
|
||||||
"https://api.github.com/repos/emqx/emqx-ci-helper/actions/workflows/update_emqx_repos.yaml/dispatches"
|
"https://api.github.com/repos/emqx/emqx-ci-helper/actions/workflows/update_emqx_repos.yaml/dispatches"
|
||||||
- name: update homebrew packages
|
- name: update homebrew packages
|
||||||
if: github.event_name == 'release' && endsWith(github.repository, 'emqx') && matrix.profile == 'emqx'
|
if: github.event_name == 'release' && endsWith(github.repository, 'emqx') && matrix.profile == 'emqx'
|
||||||
|
@ -487,7 +487,7 @@ jobs:
|
||||||
-H "Authorization: token ${{ secrets.CI_GIT_TOKEN }}" \
|
-H "Authorization: token ${{ secrets.CI_GIT_TOKEN }}" \
|
||||||
-H "Accept: application/vnd.github.v3+json" \
|
-H "Accept: application/vnd.github.v3+json" \
|
||||||
-X POST \
|
-X POST \
|
||||||
-d "{\"ref\":\"v1.0.2\",\"inputs\":{\"version\": \"${{ env.version }}\"}}" \
|
-d "{\"ref\":\"v1.0.3\",\"inputs\":{\"version\": \"${{ env.version }}\"}}" \
|
||||||
"https://api.github.com/repos/emqx/emqx-ci-helper/actions/workflows/update_emqx_homebrew.yaml/dispatches"
|
"https://api.github.com/repos/emqx/emqx-ci-helper/actions/workflows/update_emqx_homebrew.yaml/dispatches"
|
||||||
fi
|
fi
|
||||||
- uses: geekyeggo/delete-artifact@v1
|
- uses: geekyeggo/delete-artifact@v1
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
name: ACL fix & migration integration tests
|
||||||
|
|
||||||
|
on: workflow_dispatch
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
container: emqx/build-env:erl23.2.7.2-emqx-2-ubuntu20.04
|
||||||
|
strategy:
|
||||||
|
fail-fast: true
|
||||||
|
env:
|
||||||
|
BASE_VERSION: "4.3.0"
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
path: emqx
|
||||||
|
- name: Prepare scripts
|
||||||
|
run: |
|
||||||
|
cp ./emqx/.ci/acl_migration_test/*.sh ./
|
||||||
|
- name: Run tests
|
||||||
|
run: |
|
||||||
|
./suite.sh emqx "$BASE_VERSION"
|
|
@ -0,0 +1,437 @@
|
||||||
|
name: Integration Test Suites
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "v4.*"
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- "main-v4.*"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
imgname: ${{ steps.build_docker.outputs.imgname}}
|
||||||
|
version: ${{ steps.build_docker.outputs.version}}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: build docker
|
||||||
|
id: build_docker
|
||||||
|
run: |
|
||||||
|
if [ -f EMQX_ENTERPRISE ]; then
|
||||||
|
echo "https://ci%40emqx.io:${{ secrets.CI_GIT_TOKEN }}@github.com" > $HOME/.git-credentials
|
||||||
|
git config --global credential.helper store
|
||||||
|
echo "${{ secrets.CI_GIT_TOKEN }}" >> scripts/git-token
|
||||||
|
make deps-emqx-ee
|
||||||
|
fi
|
||||||
|
make docker
|
||||||
|
echo "::set-output name=version::$(./pkg-vsn.sh)"
|
||||||
|
if [ -f EMQX_ENTERPRISE ]; then
|
||||||
|
echo "::set-output name=imgname::emqx-ee"
|
||||||
|
else
|
||||||
|
echo "::set-output name=imgname::emqx"
|
||||||
|
fi
|
||||||
|
- uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
name: emqx-docker-image-zip
|
||||||
|
path: _packages/${{ steps.build_docker.outputs.imgname }}/${{ steps.build_docker.outputs.imgname }}-docker-${{ steps.build_docker.outputs.version }}.zip
|
||||||
|
|
||||||
|
webhook:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
webhook_type:
|
||||||
|
- webhook_data_bridge
|
||||||
|
|
||||||
|
needs: build
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: actions/download-artifact@v2
|
||||||
|
with:
|
||||||
|
name: emqx-docker-image-zip
|
||||||
|
path: /tmp
|
||||||
|
- name: load docker image
|
||||||
|
env:
|
||||||
|
imgname: ${{ needs.build.outputs.imgname}}
|
||||||
|
version: ${{ needs.build.outputs.version }}
|
||||||
|
run: |
|
||||||
|
unzip -q /tmp/${imgname}-docker-${version}.zip -d /tmp
|
||||||
|
docker load < /tmp/${imgname}-docker-${version}
|
||||||
|
- name: docker compose up
|
||||||
|
timeout-minutes: 5
|
||||||
|
env:
|
||||||
|
TARGET: emqx/${{ needs.build.outputs.imgname }}
|
||||||
|
EMQX_TAG: ${{ needs.build.outputs.version }}
|
||||||
|
run: |
|
||||||
|
docker-compose \
|
||||||
|
-f .ci/docker-compose-file/docker-compose-emqx-cluster.yaml \
|
||||||
|
up -d --build
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
repository: emqx/emqx-svt-web-server
|
||||||
|
ref: web-server-1.0
|
||||||
|
path: emqx-svt-web-server
|
||||||
|
- uses: actions/download-artifact@v2
|
||||||
|
- name: run webserver in docker
|
||||||
|
run: |
|
||||||
|
cd ./emqx-svt-web-server/svtserver
|
||||||
|
mvn clean package
|
||||||
|
cd target
|
||||||
|
docker run --name webserver --network emqx_bridge -d -v $(pwd)/svtserver-0.0.1.jar:/webserver/svtserver-0.0.1.jar --workdir /webserver openjdk:8-jdk bash \
|
||||||
|
-c "java -jar svtserver-0.0.1.jar"
|
||||||
|
- name: wait docker compose up
|
||||||
|
timeout-minutes: 5
|
||||||
|
run: |
|
||||||
|
while [ "$(docker inspect -f '{{ .State.Health.Status}}' node1.emqx.io)" != "healthy" ] || [ "$(docker inspect -f '{{ .State.Health.Status}}' node2.emqx.io)" != "healthy" ]; do
|
||||||
|
echo "['$(date -u +"%y-%m-%dt%h:%m:%sz")']:waiting emqx";
|
||||||
|
sleep 5;
|
||||||
|
done
|
||||||
|
docker ps -a
|
||||||
|
echo HAPROXY_IP=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' haproxy) >> $GITHUB_ENV
|
||||||
|
echo WEB_IP=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' webserver) >> $GITHUB_ENV
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
repository: emqx/emqx-fvt
|
||||||
|
ref: integration_test_suites
|
||||||
|
path: scripts
|
||||||
|
- uses: actions/setup-java@v1
|
||||||
|
with:
|
||||||
|
java-version: '8.0.282' # The JDK version to make available on the path.
|
||||||
|
java-package: jdk # (jre, jdk, or jdk+fx) - defaults to jdk
|
||||||
|
architecture: x64 # (x64 or x86) - defaults to x64
|
||||||
|
- name: install jmeter
|
||||||
|
timeout-minutes: 10
|
||||||
|
env:
|
||||||
|
JMETER_VERSION: 5.3
|
||||||
|
run: |
|
||||||
|
wget --no-verbose --no-check-certificate -O /tmp/apache-jmeter.tgz https://downloads.apache.org/jmeter/binaries/apache-jmeter-$JMETER_VERSION.tgz
|
||||||
|
cd /tmp && tar -xvf apache-jmeter.tgz
|
||||||
|
echo "jmeter.save.saveservice.output_format=xml" >> /tmp/apache-jmeter-$JMETER_VERSION/user.properties
|
||||||
|
echo "jmeter.save.saveservice.response_data.on_error=true" >> /tmp/apache-jmeter-$JMETER_VERSION/user.properties
|
||||||
|
wget --no-verbose -O /tmp/apache-jmeter-$JMETER_VERSION/lib/ext/mqtt-xmeter-2.0.2-jar-with-dependencies.jar https://raw.githubusercontent.com/xmeter-net/mqtt-jmeter/master/Download/v2.0.2/mqtt-xmeter-2.0.2-jar-with-dependencies.jar
|
||||||
|
ln -s /tmp/apache-jmeter-$JMETER_VERSION /opt/jmeter
|
||||||
|
- name: run jmeter
|
||||||
|
run: |
|
||||||
|
/opt/jmeter/bin/jmeter.sh \
|
||||||
|
-Jjmeter.save.saveservice.output_format=xml -n \
|
||||||
|
-t scripts/.ci/automate-test-suite/${{ matrix.webhook_type }}.jmx \
|
||||||
|
-Demqx_ip=$HAPROXY_IP \
|
||||||
|
-Dweb_ip=$WEB_IP \
|
||||||
|
-l jmeter_logs/webhook_${{ matrix.webhook_type }}.jtl \
|
||||||
|
-j jmeter_logs/logs/webhook_${{ matrix.webhook_type }}.log
|
||||||
|
- name: check logs
|
||||||
|
run: |
|
||||||
|
if cat jmeter_logs/webhook_${{ matrix.webhook_type }}.jtl | grep -e '<failure>true</failure>' > /dev/null 2>&1; then
|
||||||
|
echo "check logs filed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
- uses: actions/upload-artifact@v1
|
||||||
|
if: always()
|
||||||
|
with:
|
||||||
|
name: jmeter_logs
|
||||||
|
path: ./jmeter_logs
|
||||||
|
|
||||||
|
mysql:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
mysql_tag:
|
||||||
|
- 5.7
|
||||||
|
- 8
|
||||||
|
mysql_type:
|
||||||
|
- mysql_auth_acl
|
||||||
|
|
||||||
|
needs: build
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: actions/download-artifact@v2
|
||||||
|
with:
|
||||||
|
name: emqx-docker-image-zip
|
||||||
|
path: /tmp
|
||||||
|
- name: load docker image
|
||||||
|
env:
|
||||||
|
imgname: ${{ needs.build.outputs.imgname }}
|
||||||
|
version: ${{ needs.build.outputs.version }}
|
||||||
|
run: |
|
||||||
|
unzip -q /tmp/${imgname}-docker-${version}.zip -d /tmp
|
||||||
|
docker load < /tmp/${imgname}-docker-${version}
|
||||||
|
- name: docker compose up
|
||||||
|
timeout-minutes: 5
|
||||||
|
env:
|
||||||
|
TARGET: emqx/${{ needs.build.outputs.imgname }}
|
||||||
|
EMQX_TAG: ${{ needs.build.outputs.version }}
|
||||||
|
MYSQL_TAG: ${{ matrix.mysql_tag }}
|
||||||
|
run: |
|
||||||
|
docker-compose \
|
||||||
|
-f .ci/docker-compose-file/docker-compose-emqx-cluster.yaml \
|
||||||
|
-f .ci/docker-compose-file/docker-compose-mysql-tls.yaml \
|
||||||
|
up -d --build
|
||||||
|
- name: wait docker compose up
|
||||||
|
timeout-minutes: 5
|
||||||
|
run: |
|
||||||
|
while [ "$(docker inspect -f '{{ .State.Health.Status}}' node1.emqx.io)" != "healthy" ] || [ "$(docker inspect -f '{{ .State.Health.Status}}' node2.emqx.io)" != "healthy" ]; do
|
||||||
|
echo "['$(date -u +"%y-%m-%dt%h:%m:%sz")']:waiting emqx";
|
||||||
|
sleep 5;
|
||||||
|
done
|
||||||
|
while [ $(docker ps -a --filter name=client --filter exited=0 | wc -l) \
|
||||||
|
!= $(docker ps -a --filter name=client | wc -l) ]; do
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
docker ps -a
|
||||||
|
echo HAPROXY_IP=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' haproxy) >> $GITHUB_ENV
|
||||||
|
echo MYSQL_IP=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' mysql) >> $GITHUB_ENV
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
repository: emqx/emqx-fvt
|
||||||
|
ref: integration_test_suites
|
||||||
|
path: scripts
|
||||||
|
- uses: actions/setup-java@v1
|
||||||
|
with:
|
||||||
|
java-version: '8.0.282' # The JDK version to make available on the path.
|
||||||
|
java-package: jdk # (jre, jdk, or jdk+fx) - defaults to jdk
|
||||||
|
architecture: x64 # (x64 or x86) - defaults to x64
|
||||||
|
- name: install jmeter
|
||||||
|
timeout-minutes: 10
|
||||||
|
env:
|
||||||
|
JMETER_VERSION: 5.3
|
||||||
|
run: |
|
||||||
|
wget --no-verbose --no-check-certificate -O /tmp/apache-jmeter.tgz https://downloads.apache.org/jmeter/binaries/apache-jmeter-$JMETER_VERSION.tgz
|
||||||
|
cd /tmp && tar -xvf apache-jmeter.tgz
|
||||||
|
echo "jmeter.save.saveservice.output_format=xml" >> /tmp/apache-jmeter-$JMETER_VERSION/user.properties
|
||||||
|
echo "jmeter.save.saveservice.response_data.on_error=true" >> /tmp/apache-jmeter-$JMETER_VERSION/user.properties
|
||||||
|
wget --no-verbose -O /tmp/apache-jmeter-$JMETER_VERSION/lib/ext/mqtt-xmeter-2.0.2-jar-with-dependencies.jar https://raw.githubusercontent.com/xmeter-net/mqtt-jmeter/master/Download/v2.0.2/mqtt-xmeter-2.0.2-jar-with-dependencies.jar
|
||||||
|
ln -s /tmp/apache-jmeter-$JMETER_VERSION /opt/jmeter
|
||||||
|
- name: install jmeter plugin
|
||||||
|
run: |
|
||||||
|
wget --no-verbose -O "/opt/jmeter/lib/mysql-connector-java-8.0.16.jar" https://repo1.maven.org/maven2/mysql/mysql-connector-java/8.0.16/mysql-connector-java-8.0.16.jar
|
||||||
|
- name: run jmeter
|
||||||
|
run: |
|
||||||
|
/opt/jmeter/bin/jmeter.sh \
|
||||||
|
-Jjmeter.save.saveservice.output_format=xml -n \
|
||||||
|
-t scripts/.ci/automate-test-suite/${{ matrix.mysql_type }}.jmx \
|
||||||
|
-Droute="apps/emqx_auth_mysql/test/emqx_auth_mysql_SUITE_data" \
|
||||||
|
-Dmysql_ip=$MYSQL_IP \
|
||||||
|
-Demqx_ip=$HAPROXY_IP \
|
||||||
|
-Ddbname="mqtt" \
|
||||||
|
-Dmysql_user="ssluser" \
|
||||||
|
-Ddb_user="root" \
|
||||||
|
-Dmysql_pwd="public" \
|
||||||
|
-Dconfig_path="/tmp/etc" \
|
||||||
|
-Ddocker_path=".ci/docker-compose-file" \
|
||||||
|
-l jmeter_logs/${{ matrix.mysql_type }}_${{ matrix.mysql_tag }}.jtl \
|
||||||
|
-j jmeter_logs/logs/${{ matrix.mysql_type }}_${{ matrix.mysql_tag }}.log
|
||||||
|
- name: check logs
|
||||||
|
run: |
|
||||||
|
if cat jmeter_logs/${{ matrix.mysql_type }}_${{ matrix.mysql_tag }}.jtl | grep -e '<failure>true</failure>' > /dev/null 2>&1; then
|
||||||
|
echo "check logs filed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
- uses: actions/upload-artifact@v1
|
||||||
|
if: always()
|
||||||
|
with:
|
||||||
|
name: jmeter_logs
|
||||||
|
path: ./jmeter_logs
|
||||||
|
|
||||||
|
|
||||||
|
postgresql:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
pgsql_type:
|
||||||
|
- pgsql_auth_acl
|
||||||
|
pgsql_tag:
|
||||||
|
- 9
|
||||||
|
- 10
|
||||||
|
- 11
|
||||||
|
- 12
|
||||||
|
- 13
|
||||||
|
|
||||||
|
needs: build
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: actions/download-artifact@v2
|
||||||
|
with:
|
||||||
|
name: emqx-docker-image-zip
|
||||||
|
path: /tmp
|
||||||
|
- name: load docker image
|
||||||
|
env:
|
||||||
|
imgname: ${{ needs.build.outputs.imgname }}
|
||||||
|
version: ${{ needs.build.outputs.version }}
|
||||||
|
run: |
|
||||||
|
unzip -q /tmp/${imgname}-docker-${version}.zip -d /tmp
|
||||||
|
docker load < /tmp/${imgname}-docker-${version}
|
||||||
|
- name: docker compose up
|
||||||
|
timeout-minutes: 5
|
||||||
|
env:
|
||||||
|
TARGET: emqx/${{ needs.build.outputs.imgname }}
|
||||||
|
EMQX_TAG: ${{ needs.build.outputs.version }}
|
||||||
|
PGSQL_TAG: ${{ matrix.pgsql_tag }}
|
||||||
|
run: |
|
||||||
|
docker-compose \
|
||||||
|
-f .ci/docker-compose-file/docker-compose-emqx-broker-cluster.yaml \
|
||||||
|
-f .ci/docker-compose-file/docker-compose-pgsql-tls.yaml \
|
||||||
|
up -d --build
|
||||||
|
- name: wait docker compose up
|
||||||
|
timeout-minutes: 5
|
||||||
|
run: |
|
||||||
|
while [ "$(docker inspect -f '{{ .State.Health.Status}}' node1.emqx.io)" != "healthy" ] || [ "$(docker inspect -f '{{ .State.Health.Status}}' node2.emqx.io)" != "healthy" ]; do
|
||||||
|
echo "['$(date -u +"%y-%m-%dt%h:%m:%sz")']:waiting emqx";
|
||||||
|
sleep 5;
|
||||||
|
done
|
||||||
|
docker ps -a
|
||||||
|
echo HAPROXY_IP=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' haproxy) >> $GITHUB_ENV
|
||||||
|
echo PGSQL_IP=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' pgsql) >> $GITHUB_ENV
|
||||||
|
echo CONFIG_PATH=$(docker inspect -f '{{ range .Mounts }}{{ if eq .Name "docker-compose-file_etc" }}{{ .Source }}{{ end }}{{ end }}' node1.emqx.io) >> $GITHUB_ENV
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
repository: emqx/emqx-fvt
|
||||||
|
ref: integration_test_suites
|
||||||
|
path: scripts
|
||||||
|
- uses: actions/setup-java@v1
|
||||||
|
with:
|
||||||
|
java-version: '8.0.282' # The JDK version to make available on the path.
|
||||||
|
java-package: jdk # (jre, jdk, or jdk+fx) - defaults to jdk
|
||||||
|
architecture: x64 # (x64 or x86) - defaults to x64
|
||||||
|
- name: install jmeter
|
||||||
|
timeout-minutes: 10
|
||||||
|
env:
|
||||||
|
JMETER_VERSION: 5.3
|
||||||
|
run: |
|
||||||
|
wget --no-verbose --no-check-certificate -O /tmp/apache-jmeter.tgz https://downloads.apache.org/jmeter/binaries/apache-jmeter-$JMETER_VERSION.tgz
|
||||||
|
cd /tmp && tar -xvf apache-jmeter.tgz
|
||||||
|
echo "jmeter.save.saveservice.output_format=xml" >> /tmp/apache-jmeter-$JMETER_VERSION/user.properties
|
||||||
|
echo "jmeter.save.saveservice.response_data.on_error=true" >> /tmp/apache-jmeter-$JMETER_VERSION/user.properties
|
||||||
|
wget --no-verbose -O /tmp/apache-jmeter-$JMETER_VERSION/lib/ext/mqtt-xmeter-2.0.2-jar-with-dependencies.jar https://raw.githubusercontent.com/xmeter-net/mqtt-jmeter/master/Download/v2.0.2/mqtt-xmeter-2.0.2-jar-with-dependencies.jar
|
||||||
|
ln -s /tmp/apache-jmeter-$JMETER_VERSION /opt/jmeter
|
||||||
|
- name: install jmeter plugin
|
||||||
|
run: |
|
||||||
|
wget --no-verbose -O "/opt/jmeter/lib/postgresql-42.2.18.jar" https://repo1.maven.org/maven2/org/postgresql/postgresql/42.2.18/postgresql-42.2.18.jar
|
||||||
|
- name: run jmeter
|
||||||
|
run: |
|
||||||
|
sudo /opt/jmeter/bin/jmeter.sh \
|
||||||
|
-Jjmeter.save.saveservice.output_format=xml -n \
|
||||||
|
-t scripts/.ci/automate-test-suite/${{ matrix.pgsql_type }}.jmx \
|
||||||
|
-Droute="apps/emqx_auth_pgsql/test/emqx_auth_pgsql_SUITE_data" \
|
||||||
|
-Dca_name="ca.pem" \
|
||||||
|
-Dkey_name="client-key.pem" \
|
||||||
|
-Dcert_name="client-cert.pem" \
|
||||||
|
-Ddb_ip=$PGSQL_IP \
|
||||||
|
-Dpgsql_ip=$PGSQL_IP \
|
||||||
|
-Demqx_ip=$HAPROXY_IP \
|
||||||
|
-Dpgsql_user="root" \
|
||||||
|
-Dpgsql_pwd="public" \
|
||||||
|
-Ddbname="mqtt" \
|
||||||
|
-Dpgsql_db="mqtt" \
|
||||||
|
-Dport="5432" \
|
||||||
|
-Dconfig_path=$CONFIG_PATH \
|
||||||
|
-Ddocker_path=".ci/docker-compose-file" \
|
||||||
|
-l jmeter_logs/${{ matrix.pgsql_type }}_${{ matrix.pgsql_tag }}.jtl \
|
||||||
|
-j jmeter_logs/logs/${{ matrix.pgsql_type }}_${{ matrix.pgsql_tag }}.log
|
||||||
|
- name: check logs
|
||||||
|
run: |
|
||||||
|
if cat jmeter_logs/${{ matrix.pgsql_type }}_${{ matrix.pgsql_tag }}.jtl | grep -e '<failure>true</failure>' > /dev/null 2>&1; then
|
||||||
|
echo "check logs filed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
- uses: actions/upload-artifact@v1
|
||||||
|
if: always()
|
||||||
|
with:
|
||||||
|
name: jmeter_logs
|
||||||
|
path: ./jmeter_logs
|
||||||
|
|
||||||
|
http:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
needs: build
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: actions/download-artifact@v2
|
||||||
|
with:
|
||||||
|
name: emqx-docker-image-zip
|
||||||
|
path: /tmp
|
||||||
|
- name: load docker image
|
||||||
|
env:
|
||||||
|
imgname: ${{ needs.build.outputs.imgname }}
|
||||||
|
version: ${{ needs.build.outputs.version }}
|
||||||
|
run: |
|
||||||
|
unzip -q /tmp/${imgname}-docker-${version}.zip -d /tmp
|
||||||
|
docker load < /tmp/${imgname}-docker-${version}
|
||||||
|
- name: docker compose up
|
||||||
|
timeout-minutes: 5
|
||||||
|
env:
|
||||||
|
TARGET: emqx/${{ needs.build.outputs.imgname }}
|
||||||
|
EMQX_TAG: ${{ needs.build.outputs.version }}
|
||||||
|
MYSQL_TAG: 8
|
||||||
|
run: |
|
||||||
|
docker-compose \
|
||||||
|
-f .ci/docker-compose-file/docker-compose-emqx-broker-cluster.yaml \
|
||||||
|
-f .ci/docker-compose-file/docker-compose-mysql-tcp.yaml \
|
||||||
|
-f .ci/docker-compose-file/docker-compose-enterprise-tomcat-tcp.yaml \
|
||||||
|
up -d --build
|
||||||
|
- name: wait docker compose up
|
||||||
|
timeout-minutes: 5
|
||||||
|
run: |
|
||||||
|
while [ "$(docker inspect -f '{{ .State.Health.Status}}' node1.emqx.io)" != "healthy" ] || [ "$(docker inspect -f '{{ .State.Health.Status}}' node2.emqx.io)" != "healthy" ]; do
|
||||||
|
echo "['$(date -u +"%y-%m-%dt%h:%m:%sz")']:waiting emqx";
|
||||||
|
sleep 5;
|
||||||
|
done
|
||||||
|
docker ps -a
|
||||||
|
echo HAPROXY_IP=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' haproxy) >> $GITHUB_ENV
|
||||||
|
echo HTTP_IP=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' Tomcat) >> $GITHUB_ENV
|
||||||
|
echo MYSQL_IP=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' mysql) >> $GITHUB_ENV
|
||||||
|
echo CONFIG_PATH=$(docker inspect -f '{{ range .Mounts }}{{ if eq .Name "docker-compose-file_etc" }}{{ .Source }}{{ end }}{{ end }}' node1.emqx.io) >> $GITHUB_ENV
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
repository: emqx/emqx-fvt
|
||||||
|
ref: integration_test_suites
|
||||||
|
path: scripts
|
||||||
|
- uses: actions/setup-java@v1
|
||||||
|
with:
|
||||||
|
java-version: '8.0.282' # The JDK version to make available on the path.
|
||||||
|
java-package: jdk # (jre, jdk, or jdk+fx) - defaults to jdk
|
||||||
|
architecture: x64 # (x64 or x86) - defaults to x64
|
||||||
|
- name: install jmeter
|
||||||
|
timeout-minutes: 10
|
||||||
|
env:
|
||||||
|
JMETER_VERSION: 5.3
|
||||||
|
run: |
|
||||||
|
wget --no-verbose --no-check-certificate -O /tmp/apache-jmeter.tgz https://downloads.apache.org/jmeter/binaries/apache-jmeter-$JMETER_VERSION.tgz
|
||||||
|
cd /tmp && tar -xvf apache-jmeter.tgz
|
||||||
|
echo "jmeter.save.saveservice.output_format=xml" >> /tmp/apache-jmeter-$JMETER_VERSION/user.properties
|
||||||
|
echo "jmeter.save.saveservice.response_data.on_error=true" >> /tmp/apache-jmeter-$JMETER_VERSION/user.properties
|
||||||
|
wget --no-verbose -O /tmp/apache-jmeter-$JMETER_VERSION/lib/ext/mqtt-xmeter-2.0.2-jar-with-dependencies.jar https://raw.githubusercontent.com/xmeter-net/mqtt-jmeter/master/Download/v2.0.2/mqtt-xmeter-2.0.2-jar-with-dependencies.jar
|
||||||
|
ln -s /tmp/apache-jmeter-$JMETER_VERSION /opt/jmeter
|
||||||
|
- name: install jmeter plugin
|
||||||
|
run: |
|
||||||
|
wget --no-verbose -O "/opt/jmeter/lib/mysql-connector-java-8.0.16.jar" https://repo1.maven.org/maven2/mysql/mysql-connector-java/8.0.16/mysql-connector-java-8.0.16.jar
|
||||||
|
- name: run jmeter
|
||||||
|
run: |
|
||||||
|
sudo /opt/jmeter/bin/jmeter.sh \
|
||||||
|
-Jjmeter.save.saveservice.output_format=xml -n \
|
||||||
|
-t scripts/.ci/automate-test-suite/http_auth_acl.jmx \
|
||||||
|
-Dmysql_ip=$MYSQL_IP \
|
||||||
|
-Demqx_ip=$HAPROXY_IP \
|
||||||
|
-Dweb_server_ip=$HTTP_IP \
|
||||||
|
-Dconfig_path=$CONFIG_PATH \
|
||||||
|
-Ddocker_path=".ci/docker-compose-file" \
|
||||||
|
-l jmeter_logs/http_auth_acl.jtl \
|
||||||
|
-j jmeter_logs/logs/http_auth_acl.log
|
||||||
|
- name: check logs
|
||||||
|
run: |
|
||||||
|
if cat jmeter_logs/http_auth_acl.jtl | grep -e '<failure>true</failure>' > /dev/null 2>&1; then
|
||||||
|
echo "check logs filed"
|
||||||
|
sudo cat /var/lib/docker/volumes/docker-compose-file_etc/_data/emqx.conf
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
- uses: actions/upload-artifact@v1
|
||||||
|
if: always()
|
||||||
|
with:
|
||||||
|
name: jmeter_logs
|
||||||
|
path: ./jmeter_logs
|
|
@ -1,21 +1,47 @@
|
||||||
-define(APP, emqx_auth_mnesia).
|
-define(APP, emqx_auth_mnesia).
|
||||||
|
|
||||||
-type(login():: {clientid, binary()}
|
-type(login() :: {clientid, binary()}
|
||||||
| {username, binary()}).
|
| {username, binary()}).
|
||||||
|
|
||||||
|
-type(acl_target() :: login() | all).
|
||||||
|
|
||||||
|
-type(acl_target_type() :: clientid | username | all).
|
||||||
|
|
||||||
|
-type(access():: allow | deny).
|
||||||
|
-type(action():: pub | sub).
|
||||||
|
-type(legacy_action():: action() | pubsub).
|
||||||
|
-type(created_at():: integer()).
|
||||||
|
|
||||||
-record(emqx_user, {
|
-record(emqx_user, {
|
||||||
login :: login(),
|
login :: login(),
|
||||||
password :: binary(),
|
password :: binary(),
|
||||||
created_at :: integer()
|
created_at :: created_at()
|
||||||
}).
|
}).
|
||||||
|
|
||||||
-record(emqx_acl, {
|
-define(ACL_TABLE, emqx_acl).
|
||||||
filter:: {login() | all, emqx_topic:topic()},
|
|
||||||
action :: pub | sub | pubsub,
|
-define(MIGRATION_MARK_KEY, emqx_acl2_migration_started).
|
||||||
access :: allow | deny,
|
|
||||||
created_at :: integer()
|
-record(?ACL_TABLE, {
|
||||||
|
filter :: {acl_target(), emqx_topic:topic()} | ?MIGRATION_MARK_KEY,
|
||||||
|
action :: legacy_action(),
|
||||||
|
access :: access(),
|
||||||
|
created_at :: created_at()
|
||||||
}).
|
}).
|
||||||
|
|
||||||
|
-define(MIGRATION_MARK_RECORD, #?ACL_TABLE{filter = ?MIGRATION_MARK_KEY, action = pub, access = deny, created_at = 0}).
|
||||||
|
|
||||||
|
-type(rule() :: {access(), action(), emqx_topic:topic(), created_at()}).
|
||||||
|
|
||||||
|
-define(ACL_TABLE2, emqx_acl2).
|
||||||
|
|
||||||
|
-record(?ACL_TABLE2, {
|
||||||
|
who :: acl_target(),
|
||||||
|
rules :: [ rule() ]
|
||||||
|
}).
|
||||||
|
|
||||||
|
-type(acl_record() :: {acl_target(), emqx_topic:topic(), action(), access(), created_at()}).
|
||||||
|
|
||||||
-record(auth_metrics, {
|
-record(auth_metrics, {
|
||||||
success = 'client.auth.success',
|
success = 'client.auth.success',
|
||||||
failure = 'client.auth.failure',
|
failure = 'client.auth.failure',
|
||||||
|
|
|
@ -18,24 +18,16 @@
|
||||||
|
|
||||||
-include("emqx_auth_mnesia.hrl").
|
-include("emqx_auth_mnesia.hrl").
|
||||||
|
|
||||||
-include_lib("stdlib/include/ms_transform.hrl").
|
|
||||||
|
|
||||||
-define(TABLE, emqx_acl).
|
|
||||||
|
|
||||||
%% ACL Callbacks
|
%% ACL Callbacks
|
||||||
-export([ init/0
|
-export([ init/0
|
||||||
, register_metrics/0
|
, register_metrics/0
|
||||||
, check_acl/5
|
, check_acl/5
|
||||||
, description/0
|
, description/0
|
||||||
]).
|
]).
|
||||||
|
|
||||||
init() ->
|
init() ->
|
||||||
ok = ekka_mnesia:create_table(emqx_acl, [
|
ok = emqx_acl_mnesia_db:create_table(),
|
||||||
{type, bag},
|
ok = emqx_acl_mnesia_db:create_table2().
|
||||||
{disc_copies, [node()]},
|
|
||||||
{attributes, record_info(fields, emqx_acl)},
|
|
||||||
{storage_properties, [{ets, [{read_concurrency, true}]}]}]),
|
|
||||||
ok = ekka_mnesia:copy_table(emqx_acl, disc_copies).
|
|
||||||
|
|
||||||
-spec(register_metrics() -> ok).
|
-spec(register_metrics() -> ok).
|
||||||
register_metrics() ->
|
register_metrics() ->
|
||||||
|
@ -46,12 +38,12 @@ check_acl(ClientInfo = #{ clientid := Clientid }, PubSub, Topic, _NoMatchAction,
|
||||||
|
|
||||||
Acls = case Username of
|
Acls = case Username of
|
||||||
undefined ->
|
undefined ->
|
||||||
emqx_acl_mnesia_cli:lookup_acl({clientid, Clientid}) ++
|
emqx_acl_mnesia_db:lookup_acl({clientid, Clientid}) ++
|
||||||
emqx_acl_mnesia_cli:lookup_acl(all);
|
emqx_acl_mnesia_db:lookup_acl(all);
|
||||||
_ ->
|
_ ->
|
||||||
emqx_acl_mnesia_cli:lookup_acl({clientid, Clientid}) ++
|
emqx_acl_mnesia_db:lookup_acl({clientid, Clientid}) ++
|
||||||
emqx_acl_mnesia_cli:lookup_acl({username, Username}) ++
|
emqx_acl_mnesia_db:lookup_acl({username, Username}) ++
|
||||||
emqx_acl_mnesia_cli:lookup_acl(all)
|
emqx_acl_mnesia_db:lookup_acl(all)
|
||||||
end,
|
end,
|
||||||
|
|
||||||
case match(ClientInfo, PubSub, Topic, Acls) of
|
case match(ClientInfo, PubSub, Topic, Acls) of
|
||||||
|
@ -83,7 +75,6 @@ match(ClientInfo, PubSub, Topic, [ {_, ACLTopic, Action, Access, _} | Acls]) ->
|
||||||
match_topic(ClientInfo, Topic, ACLTopic) when is_binary(Topic) ->
|
match_topic(ClientInfo, Topic, ACLTopic) when is_binary(Topic) ->
|
||||||
emqx_topic:match(Topic, feed_var(ClientInfo, ACLTopic)).
|
emqx_topic:match(Topic, feed_var(ClientInfo, ACLTopic)).
|
||||||
|
|
||||||
match_actions(_, pubsub) -> true;
|
|
||||||
match_actions(subscribe, sub) -> true;
|
match_actions(subscribe, sub) -> true;
|
||||||
match_actions(publish, pub) -> true;
|
match_actions(publish, pub) -> true;
|
||||||
match_actions(_, _) -> false.
|
match_actions(_, _) -> false.
|
||||||
|
|
|
@ -16,8 +16,6 @@
|
||||||
|
|
||||||
-module(emqx_acl_mnesia_api).
|
-module(emqx_acl_mnesia_api).
|
||||||
|
|
||||||
-include("emqx_auth_mnesia.hrl").
|
|
||||||
|
|
||||||
-include_lib("stdlib/include/ms_transform.hrl").
|
-include_lib("stdlib/include/ms_transform.hrl").
|
||||||
|
|
||||||
-import(proplists, [ get_value/2
|
-import(proplists, [ get_value/2
|
||||||
|
@ -99,26 +97,22 @@
|
||||||
]).
|
]).
|
||||||
|
|
||||||
list_clientid(_Bindings, Params) ->
|
list_clientid(_Bindings, Params) ->
|
||||||
MatchSpec = ets:fun2ms(
|
Table = emqx_acl_mnesia_db:login_acl_table(clientid),
|
||||||
fun({emqx_acl, {{clientid, Clientid}, Topic}, Action, Access, CreatedAt}) -> {{clientid,Clientid}, Topic, Action,Access, CreatedAt} end),
|
return({ok, emqx_auth_mnesia_api:paginate_qh(Table, count(Table), Params, fun emqx_acl_mnesia_db:comparing/2, fun format/1)}).
|
||||||
return({ok, emqx_auth_mnesia_api:paginate(emqx_acl, MatchSpec, Params, fun emqx_acl_mnesia_cli:comparing/2, fun format/1)}).
|
|
||||||
|
|
||||||
list_username(_Bindings, Params) ->
|
list_username(_Bindings, Params) ->
|
||||||
MatchSpec = ets:fun2ms(
|
Table = emqx_acl_mnesia_db:login_acl_table(username),
|
||||||
fun({emqx_acl, {{username, Username}, Topic}, Action, Access, CreatedAt}) -> {{username, Username}, Topic, Action,Access, CreatedAt} end),
|
return({ok, emqx_auth_mnesia_api:paginate_qh(Table, count(Table), Params, fun emqx_acl_mnesia_db:comparing/2, fun format/1)}).
|
||||||
return({ok, emqx_auth_mnesia_api:paginate(emqx_acl, MatchSpec, Params, fun emqx_acl_mnesia_cli:comparing/2, fun format/1)}).
|
|
||||||
|
|
||||||
list_all(_Bindings, Params) ->
|
list_all(_Bindings, Params) ->
|
||||||
MatchSpec = ets:fun2ms(
|
Table = emqx_acl_mnesia_db:login_acl_table(all),
|
||||||
fun({emqx_acl, {all, Topic}, Action, Access, CreatedAt}) -> {all, Topic, Action,Access, CreatedAt}end
|
return({ok, emqx_auth_mnesia_api:paginate_qh(Table, count(Table), Params, fun emqx_acl_mnesia_db:comparing/2, fun format/1)}).
|
||||||
),
|
|
||||||
return({ok, emqx_auth_mnesia_api:paginate(emqx_acl, MatchSpec, Params, fun emqx_acl_mnesia_cli:comparing/2, fun format/1)}).
|
|
||||||
|
|
||||||
|
|
||||||
lookup(#{clientid := Clientid}, _Params) ->
|
lookup(#{clientid := Clientid}, _Params) ->
|
||||||
return({ok, format(emqx_acl_mnesia_cli:lookup_acl({clientid, urldecode(Clientid)}))});
|
return({ok, format(emqx_acl_mnesia_db:lookup_acl({clientid, urldecode(Clientid)}))});
|
||||||
lookup(#{username := Username}, _Params) ->
|
lookup(#{username := Username}, _Params) ->
|
||||||
return({ok, format(emqx_acl_mnesia_cli:lookup_acl({username, urldecode(Username)}))}).
|
return({ok, format(emqx_acl_mnesia_db:lookup_acl({username, urldecode(Username)}))}).
|
||||||
|
|
||||||
add(_Bindings, Params) ->
|
add(_Bindings, Params) ->
|
||||||
[ P | _] = Params,
|
[ P | _] = Params,
|
||||||
|
@ -152,7 +146,7 @@ do_add(Params) ->
|
||||||
Access = get_value(<<"access">>, Params),
|
Access = get_value(<<"access">>, Params),
|
||||||
Re = case validate([login, topic, action, access], [Login, Topic, Action, Access]) of
|
Re = case validate([login, topic, action, access], [Login, Topic, Action, Access]) of
|
||||||
ok ->
|
ok ->
|
||||||
emqx_acl_mnesia_cli:add_acl(Login, Topic, erlang:binary_to_atom(Action, utf8), erlang:binary_to_atom(Access, utf8));
|
emqx_acl_mnesia_db:add_acl(Login, Topic, erlang:binary_to_atom(Action, utf8), erlang:binary_to_atom(Access, utf8));
|
||||||
Err -> Err
|
Err -> Err
|
||||||
end,
|
end,
|
||||||
maps:merge(#{topic => Topic,
|
maps:merge(#{topic => Topic,
|
||||||
|
@ -165,15 +159,19 @@ do_add(Params) ->
|
||||||
end).
|
end).
|
||||||
|
|
||||||
delete(#{clientid := Clientid, topic := Topic}, _) ->
|
delete(#{clientid := Clientid, topic := Topic}, _) ->
|
||||||
return(emqx_acl_mnesia_cli:remove_acl({clientid, urldecode(Clientid)}, urldecode(Topic)));
|
return(emqx_acl_mnesia_db:remove_acl({clientid, urldecode(Clientid)}, urldecode(Topic)));
|
||||||
delete(#{username := Username, topic := Topic}, _) ->
|
delete(#{username := Username, topic := Topic}, _) ->
|
||||||
return(emqx_acl_mnesia_cli:remove_acl({username, urldecode(Username)}, urldecode(Topic)));
|
return(emqx_acl_mnesia_db:remove_acl({username, urldecode(Username)}, urldecode(Topic)));
|
||||||
delete(#{topic := Topic}, _) ->
|
delete(#{topic := Topic}, _) ->
|
||||||
return(emqx_acl_mnesia_cli:remove_acl(all, urldecode(Topic))).
|
return(emqx_acl_mnesia_db:remove_acl(all, urldecode(Topic))).
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% Interval Funcs
|
%% Interval Funcs
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
count(QH) ->
|
||||||
|
qlc:fold(fun(_, Count) -> Count + 1 end, 0, QH).
|
||||||
|
|
||||||
format({{clientid, Clientid}, Topic, Action, Access, _CreatedAt}) ->
|
format({{clientid, Clientid}, Topic, Action, Access, _CreatedAt}) ->
|
||||||
#{clientid => Clientid, topic => Topic, action => Action, access => Access};
|
#{clientid => Clientid, topic => Topic, action => Action, access => Access};
|
||||||
format({{username, Username}, Topic, Action, Access, _CreatedAt}) ->
|
format({{username, Username}, Topic, Action, Access, _CreatedAt}) ->
|
||||||
|
|
|
@ -16,110 +16,28 @@
|
||||||
|
|
||||||
-module(emqx_acl_mnesia_cli).
|
-module(emqx_acl_mnesia_cli).
|
||||||
|
|
||||||
-include("emqx_auth_mnesia.hrl").
|
|
||||||
-include_lib("emqx/include/logger.hrl").
|
|
||||||
-include_lib("stdlib/include/ms_transform.hrl").
|
|
||||||
-define(TABLE, emqx_acl).
|
|
||||||
|
|
||||||
%% Acl APIs
|
|
||||||
-export([ add_acl/4
|
|
||||||
, lookup_acl/1
|
|
||||||
, all_acls/0
|
|
||||||
, all_acls/1
|
|
||||||
, remove_acl/2
|
|
||||||
]).
|
|
||||||
|
|
||||||
-export([cli/1]).
|
-export([cli/1]).
|
||||||
-export([comparing/2]).
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% Acl API
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
|
|
||||||
%% @doc Add Acls
|
|
||||||
-spec(add_acl(login() | all, emqx_topic:topic(), pub | sub | pubsub, allow | deny) ->
|
|
||||||
ok | {error, any()}).
|
|
||||||
add_acl(Login, Topic, Action, Access) ->
|
|
||||||
Filter = {Login, Topic},
|
|
||||||
Acl = #?TABLE{
|
|
||||||
filter = Filter,
|
|
||||||
action = Action,
|
|
||||||
access = Access,
|
|
||||||
created_at = erlang:system_time(millisecond)
|
|
||||||
},
|
|
||||||
ret(mnesia:transaction(
|
|
||||||
fun() ->
|
|
||||||
OldRecords = mnesia:wread({?TABLE, Filter}),
|
|
||||||
case Action of
|
|
||||||
pubsub ->
|
|
||||||
update_permission(pub, Acl, OldRecords),
|
|
||||||
update_permission(sub, Acl, OldRecords);
|
|
||||||
_ ->
|
|
||||||
update_permission(Action, Acl, OldRecords)
|
|
||||||
end
|
|
||||||
end)).
|
|
||||||
|
|
||||||
%% @doc Lookup acl by login
|
|
||||||
-spec(lookup_acl(login() | all) -> list()).
|
|
||||||
lookup_acl(undefined) -> [];
|
|
||||||
lookup_acl(Login) ->
|
|
||||||
MatchSpec = ets:fun2ms(fun({?TABLE, {Filter, ACLTopic}, Action, Access, CreatedAt})
|
|
||||||
when Filter =:= Login ->
|
|
||||||
{Filter, ACLTopic, Action, Access, CreatedAt}
|
|
||||||
end),
|
|
||||||
lists:sort(fun comparing/2, ets:select(?TABLE, MatchSpec)).
|
|
||||||
|
|
||||||
%% @doc Remove acl
|
|
||||||
-spec(remove_acl(login() | all, emqx_topic:topic()) -> ok | {error, any()}).
|
|
||||||
remove_acl(Login, Topic) ->
|
|
||||||
ret(mnesia:transaction(fun mnesia:delete/1, [{?TABLE, {Login, Topic}}])).
|
|
||||||
|
|
||||||
%% @doc All logins
|
|
||||||
-spec(all_acls() -> list()).
|
|
||||||
all_acls() ->
|
|
||||||
all_acls(clientid) ++
|
|
||||||
all_acls(username) ++
|
|
||||||
all_acls(all).
|
|
||||||
|
|
||||||
all_acls(clientid) ->
|
|
||||||
MatchSpec = ets:fun2ms(
|
|
||||||
fun({?TABLE, {{clientid, Clientid}, Topic}, Action, Access, CreatedAt}) ->
|
|
||||||
{{clientid, Clientid}, Topic, Action, Access, CreatedAt}
|
|
||||||
end),
|
|
||||||
lists:sort(fun comparing/2, ets:select(?TABLE, MatchSpec));
|
|
||||||
all_acls(username) ->
|
|
||||||
MatchSpec = ets:fun2ms(
|
|
||||||
fun({?TABLE, {{username, Username}, Topic}, Action, Access, CreatedAt}) ->
|
|
||||||
{{username, Username}, Topic, Action, Access, CreatedAt}
|
|
||||||
end),
|
|
||||||
lists:sort(fun comparing/2, ets:select(?TABLE, MatchSpec));
|
|
||||||
all_acls(all) ->
|
|
||||||
MatchSpec = ets:fun2ms(
|
|
||||||
fun({?TABLE, {all, Topic}, Action, Access, CreatedAt}) ->
|
|
||||||
{all, Topic, Action, Access, CreatedAt}
|
|
||||||
end
|
|
||||||
),
|
|
||||||
lists:sort(fun comparing/2, ets:select(?TABLE, MatchSpec)).
|
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% ACL Cli
|
%% ACL Cli
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
cli(["list"]) ->
|
cli(["list"]) ->
|
||||||
[print_acl(Acl) || Acl <- all_acls()];
|
[print_acl(Acl) || Acl <- emqx_acl_mnesia_db:all_acls()];
|
||||||
|
|
||||||
cli(["list", "clientid"]) ->
|
cli(["list", "clientid"]) ->
|
||||||
[print_acl(Acl) || Acl <- all_acls(clientid)];
|
[print_acl(Acl) || Acl <- emqx_acl_mnesia_db:all_acls(clientid)];
|
||||||
|
|
||||||
cli(["list", "username"]) ->
|
cli(["list", "username"]) ->
|
||||||
[print_acl(Acl) || Acl <- all_acls(username)];
|
[print_acl(Acl) || Acl <- emqx_acl_mnesia_db:all_acls(username)];
|
||||||
|
|
||||||
cli(["list", "_all"]) ->
|
cli(["list", "_all"]) ->
|
||||||
[print_acl(Acl) || Acl <- all_acls(all)];
|
[print_acl(Acl) || Acl <- emqx_acl_mnesia_db:all_acls(all)];
|
||||||
|
|
||||||
cli(["add", "clientid", Clientid, Topic, Action, Access]) ->
|
cli(["add", "clientid", Clientid, Topic, Action, Access]) ->
|
||||||
case validate(action, Action) andalso validate(access, Access) of
|
case validate(action, Action) andalso validate(access, Access) of
|
||||||
true ->
|
true ->
|
||||||
case add_acl(
|
case emqx_acl_mnesia_db:add_acl(
|
||||||
{clientid, iolist_to_binary(Clientid)},
|
{clientid, iolist_to_binary(Clientid)},
|
||||||
iolist_to_binary(Topic),
|
iolist_to_binary(Topic),
|
||||||
list_to_existing_atom(Action),
|
list_to_existing_atom(Action),
|
||||||
|
@ -135,7 +53,7 @@ cli(["add", "clientid", Clientid, Topic, Action, Access]) ->
|
||||||
cli(["add", "username", Username, Topic, Action, Access]) ->
|
cli(["add", "username", Username, Topic, Action, Access]) ->
|
||||||
case validate(action, Action) andalso validate(access, Access) of
|
case validate(action, Action) andalso validate(access, Access) of
|
||||||
true ->
|
true ->
|
||||||
case add_acl(
|
case emqx_acl_mnesia_db:add_acl(
|
||||||
{username, iolist_to_binary(Username)},
|
{username, iolist_to_binary(Username)},
|
||||||
iolist_to_binary(Topic),
|
iolist_to_binary(Topic),
|
||||||
list_to_existing_atom(Action),
|
list_to_existing_atom(Action),
|
||||||
|
@ -151,7 +69,7 @@ cli(["add", "username", Username, Topic, Action, Access]) ->
|
||||||
cli(["add", "_all", Topic, Action, Access]) ->
|
cli(["add", "_all", Topic, Action, Access]) ->
|
||||||
case validate(action, Action) andalso validate(access, Access) of
|
case validate(action, Action) andalso validate(access, Access) of
|
||||||
true ->
|
true ->
|
||||||
case add_acl(
|
case emqx_acl_mnesia_db:add_acl(
|
||||||
all,
|
all,
|
||||||
iolist_to_binary(Topic),
|
iolist_to_binary(Topic),
|
||||||
list_to_existing_atom(Action),
|
list_to_existing_atom(Action),
|
||||||
|
@ -165,16 +83,16 @@ cli(["add", "_all", Topic, Action, Access]) ->
|
||||||
end;
|
end;
|
||||||
|
|
||||||
cli(["show", "clientid", Clientid]) ->
|
cli(["show", "clientid", Clientid]) ->
|
||||||
[print_acl(Acl) || Acl <- lookup_acl({clientid, iolist_to_binary(Clientid)})];
|
[print_acl(Acl) || Acl <- emqx_acl_mnesia_db:lookup_acl({clientid, iolist_to_binary(Clientid)})];
|
||||||
|
|
||||||
cli(["show", "username", Username]) ->
|
cli(["show", "username", Username]) ->
|
||||||
[print_acl(Acl) || Acl <- lookup_acl({username, iolist_to_binary(Username)})];
|
[print_acl(Acl) || Acl <- emqx_acl_mnesia_db:lookup_acl({username, iolist_to_binary(Username)})];
|
||||||
|
|
||||||
cli(["del", "clientid", Clientid, Topic])->
|
cli(["del", "clientid", Clientid, Topic])->
|
||||||
cli(["delete", "clientid", Clientid, Topic]);
|
cli(["delete", "clientid", Clientid, Topic]);
|
||||||
|
|
||||||
cli(["delete", "clientid", Clientid, Topic])->
|
cli(["delete", "clientid", Clientid, Topic])->
|
||||||
case remove_acl({clientid, iolist_to_binary(Clientid)}, iolist_to_binary(Topic)) of
|
case emqx_acl_mnesia_db:remove_acl({clientid, iolist_to_binary(Clientid)}, iolist_to_binary(Topic)) of
|
||||||
ok -> emqx_ctl:print("ok~n");
|
ok -> emqx_ctl:print("ok~n");
|
||||||
{error, Reason} -> emqx_ctl:print("Error: ~p~n", [Reason])
|
{error, Reason} -> emqx_ctl:print("Error: ~p~n", [Reason])
|
||||||
end;
|
end;
|
||||||
|
@ -183,7 +101,7 @@ cli(["del", "username", Username, Topic])->
|
||||||
cli(["delete", "username", Username, Topic]);
|
cli(["delete", "username", Username, Topic]);
|
||||||
|
|
||||||
cli(["delete", "username", Username, Topic])->
|
cli(["delete", "username", Username, Topic])->
|
||||||
case remove_acl({username, iolist_to_binary(Username)}, iolist_to_binary(Topic)) of
|
case emqx_acl_mnesia_db:remove_acl({username, iolist_to_binary(Username)}, iolist_to_binary(Topic)) of
|
||||||
ok -> emqx_ctl:print("ok~n");
|
ok -> emqx_ctl:print("ok~n");
|
||||||
{error, Reason} -> emqx_ctl:print("Error: ~p~n", [Reason])
|
{error, Reason} -> emqx_ctl:print("Error: ~p~n", [Reason])
|
||||||
end;
|
end;
|
||||||
|
@ -192,7 +110,7 @@ cli(["del", "_all", Topic])->
|
||||||
cli(["delete", "_all", Topic]);
|
cli(["delete", "_all", Topic]);
|
||||||
|
|
||||||
cli(["delete", "_all", Topic])->
|
cli(["delete", "_all", Topic])->
|
||||||
case remove_acl(all, iolist_to_binary(Topic)) of
|
case emqx_acl_mnesia_db:remove_acl(all, iolist_to_binary(Topic)) of
|
||||||
ok -> emqx_ctl:print("ok~n");
|
ok -> emqx_ctl:print("ok~n");
|
||||||
{error, Reason} -> emqx_ctl:print("Error: ~p~n", [Reason])
|
{error, Reason} -> emqx_ctl:print("Error: ~p~n", [Reason])
|
||||||
end;
|
end;
|
||||||
|
@ -215,13 +133,6 @@ cli(_) ->
|
||||||
%% Internal functions
|
%% Internal functions
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
comparing({_, _, _, _, CreatedAt1},
|
|
||||||
{_, _, _, _, CreatedAt2}) ->
|
|
||||||
CreatedAt1 >= CreatedAt2.
|
|
||||||
|
|
||||||
ret({atomic, ok}) -> ok;
|
|
||||||
ret({aborted, Error}) -> {error, Error}.
|
|
||||||
|
|
||||||
validate(action, "pub") -> true;
|
validate(action, "pub") -> true;
|
||||||
validate(action, "sub") -> true;
|
validate(action, "sub") -> true;
|
||||||
validate(action, "pubsub") -> true;
|
validate(action, "pubsub") -> true;
|
||||||
|
@ -244,27 +155,3 @@ print_acl({all, Topic, Action, Access, _}) ->
|
||||||
"Acl($all topic = ~p action = ~p access = ~p)~n",
|
"Acl($all topic = ~p action = ~p access = ~p)~n",
|
||||||
[Topic, Action, Access]
|
[Topic, Action, Access]
|
||||||
).
|
).
|
||||||
|
|
||||||
update_permission(Action, Acl0, OldRecords) ->
|
|
||||||
Acl = Acl0 #?TABLE{action = Action},
|
|
||||||
maybe_delete_shadowed_records(Action, OldRecords),
|
|
||||||
mnesia:write(Acl).
|
|
||||||
|
|
||||||
maybe_delete_shadowed_records(_, []) ->
|
|
||||||
ok;
|
|
||||||
maybe_delete_shadowed_records(Action1, [Rec = #emqx_acl{action = Action2} | Rest]) ->
|
|
||||||
if Action1 =:= Action2 ->
|
|
||||||
ok = mnesia:delete_object(Rec);
|
|
||||||
Action2 =:= pubsub ->
|
|
||||||
%% Perform migration from the old data format on the
|
|
||||||
%% fly. This is needed only for the enterprise version,
|
|
||||||
%% delete this branch on 5.0
|
|
||||||
mnesia:delete_object(Rec),
|
|
||||||
mnesia:write(Rec#?TABLE{action = other_action(Action1)});
|
|
||||||
true ->
|
|
||||||
ok
|
|
||||||
end,
|
|
||||||
maybe_delete_shadowed_records(Action1, Rest).
|
|
||||||
|
|
||||||
other_action(pub) -> sub;
|
|
||||||
other_action(sub) -> pub.
|
|
||||||
|
|
|
@ -0,0 +1,339 @@
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% 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_acl_mnesia_db).
|
||||||
|
|
||||||
|
-include("emqx_auth_mnesia.hrl").
|
||||||
|
-include_lib("stdlib/include/ms_transform.hrl").
|
||||||
|
-include_lib("stdlib/include/qlc.hrl").
|
||||||
|
|
||||||
|
%% ACL APIs
|
||||||
|
-export([ create_table/0
|
||||||
|
, create_table2/0
|
||||||
|
]).
|
||||||
|
|
||||||
|
-export([ add_acl/4
|
||||||
|
, lookup_acl/1
|
||||||
|
, all_acls_export/0
|
||||||
|
, all_acls/0
|
||||||
|
, all_acls/1
|
||||||
|
, remove_acl/2
|
||||||
|
, merge_acl_records/3
|
||||||
|
, login_acl_table/1
|
||||||
|
, is_migration_started/0
|
||||||
|
]).
|
||||||
|
|
||||||
|
-export([comparing/2]).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% ACL API
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
%% @doc Create table `emqx_acl` of old format rules
|
||||||
|
-spec(create_table() -> ok).
|
||||||
|
create_table() ->
|
||||||
|
ok = ekka_mnesia:create_table(?ACL_TABLE, [
|
||||||
|
{type, bag},
|
||||||
|
{disc_copies, [node()]},
|
||||||
|
{attributes, record_info(fields, ?ACL_TABLE)},
|
||||||
|
{storage_properties, [{ets, [{read_concurrency, true}]}]}]),
|
||||||
|
ok = ekka_mnesia:copy_table(?ACL_TABLE, disc_copies).
|
||||||
|
|
||||||
|
%% @doc Create table `emqx_acl2` of new format rules
|
||||||
|
-spec(create_table2() -> ok).
|
||||||
|
create_table2() ->
|
||||||
|
ok = ekka_mnesia:create_table(?ACL_TABLE2, [
|
||||||
|
{type, ordered_set},
|
||||||
|
{disc_copies, [node()]},
|
||||||
|
{attributes, record_info(fields, ?ACL_TABLE2)},
|
||||||
|
{storage_properties, [{ets, [{read_concurrency, true}]}]}]),
|
||||||
|
ok = ekka_mnesia:copy_table(?ACL_TABLE2, disc_copies).
|
||||||
|
|
||||||
|
%% @doc Add Acls
|
||||||
|
-spec(add_acl(acl_target(), emqx_topic:topic(), legacy_action(), access()) ->
|
||||||
|
ok | {error, any()}).
|
||||||
|
add_acl(Login, Topic, Action, Access) ->
|
||||||
|
ret(mnesia:transaction(fun() ->
|
||||||
|
case is_migration_started() of
|
||||||
|
true -> add_acl_new(Login, Topic, Action, Access);
|
||||||
|
false -> add_acl_old(Login, Topic, Action, Access)
|
||||||
|
end
|
||||||
|
end)).
|
||||||
|
|
||||||
|
%% @doc Lookup acl by login
|
||||||
|
-spec(lookup_acl(acl_target()) -> list(acl_record())).
|
||||||
|
lookup_acl(undefined) -> [];
|
||||||
|
lookup_acl(Login) ->
|
||||||
|
% After migration to ?ACL_TABLE2, ?ACL_TABLE never has any rules. This lookup should be removed later.
|
||||||
|
MatchSpec = ets:fun2ms(fun(#?ACL_TABLE{filter = {Filter, _}} = Rec)
|
||||||
|
when Filter =:= Login -> Rec
|
||||||
|
end),
|
||||||
|
OldRecs = ets:select(?ACL_TABLE, MatchSpec),
|
||||||
|
|
||||||
|
NewAcls = ets:lookup(?ACL_TABLE2, Login),
|
||||||
|
MergedAcl = merge_acl_records(Login, OldRecs, NewAcls),
|
||||||
|
lists:sort(fun comparing/2, acl_to_list(MergedAcl)).
|
||||||
|
|
||||||
|
%% @doc Remove ACL
|
||||||
|
-spec remove_acl(acl_target(), emqx_topic:topic()) -> ok | {error, any()}.
|
||||||
|
remove_acl(Login, Topic) ->
|
||||||
|
ret(mnesia:transaction(fun() ->
|
||||||
|
mnesia:delete({?ACL_TABLE, {Login, Topic}}),
|
||||||
|
case mnesia:wread({?ACL_TABLE2, Login}) of
|
||||||
|
[] -> ok;
|
||||||
|
[#?ACL_TABLE2{rules = Rules} = Acl] ->
|
||||||
|
case delete_topic_rules(Topic, Rules) of
|
||||||
|
[] -> mnesia:delete({?ACL_TABLE2, Login});
|
||||||
|
[_ | _] = RemainingRules ->
|
||||||
|
mnesia:write(Acl#?ACL_TABLE2{rules = RemainingRules})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)).
|
||||||
|
|
||||||
|
%% @doc All ACL rules
|
||||||
|
-spec(all_acls() -> list(acl_record())).
|
||||||
|
all_acls() ->
|
||||||
|
all_acls(username) ++
|
||||||
|
all_acls(clientid) ++
|
||||||
|
all_acls(all).
|
||||||
|
|
||||||
|
%% @doc All ACL rules of specified type
|
||||||
|
-spec(all_acls(acl_target_type()) -> list(acl_record())).
|
||||||
|
all_acls(AclTargetType) ->
|
||||||
|
lists:sort(fun comparing/2, qlc:eval(login_acl_table(AclTargetType))).
|
||||||
|
|
||||||
|
%% @doc All ACL rules fetched transactionally
|
||||||
|
-spec(all_acls_export() -> list(acl_record())).
|
||||||
|
all_acls_export() ->
|
||||||
|
AclTargetTypes = [username, clientid, all],
|
||||||
|
MatchSpecNew = lists:flatmap(fun login_match_spec_new/1, AclTargetTypes),
|
||||||
|
MatchSpecOld = lists:flatmap(fun login_match_spec_old/1, AclTargetTypes),
|
||||||
|
|
||||||
|
{atomic, Records} = mnesia:transaction(
|
||||||
|
fun() ->
|
||||||
|
QH = acl_table(MatchSpecNew, MatchSpecOld, fun mnesia:table/2, fun lookup_mnesia/2),
|
||||||
|
qlc:eval(QH)
|
||||||
|
end),
|
||||||
|
Records.
|
||||||
|
|
||||||
|
%% @doc QLC table of logins matching spec
|
||||||
|
-spec(login_acl_table(acl_target_type()) -> qlc:query_handle()).
|
||||||
|
login_acl_table(AclTargetType) ->
|
||||||
|
MatchSpecNew = login_match_spec_new(AclTargetType),
|
||||||
|
MatchSpecOld = login_match_spec_old(AclTargetType),
|
||||||
|
acl_table(MatchSpecNew, MatchSpecOld, fun ets:table/2, fun lookup_ets/2).
|
||||||
|
|
||||||
|
%% @doc Combine old `emqx_acl` ACL records with a new `emqx_acl2` ACL record for a given login
|
||||||
|
-spec(merge_acl_records(acl_target(), [#?ACL_TABLE{}], [#?ACL_TABLE2{}]) -> #?ACL_TABLE2{}).
|
||||||
|
merge_acl_records(Login, OldRecs, Acls) ->
|
||||||
|
OldRules = old_recs_to_rules(OldRecs),
|
||||||
|
NewRules = case Acls of
|
||||||
|
[] -> [];
|
||||||
|
[#?ACL_TABLE2{rules = Rules}] -> Rules
|
||||||
|
end,
|
||||||
|
#?ACL_TABLE2{who = Login, rules = merge_rules(NewRules, OldRules)}.
|
||||||
|
|
||||||
|
%% @doc Checks if background migration of ACL rules from `emqx_acl` to `emqx_acl2` format started.
|
||||||
|
%% Should be run in transaction
|
||||||
|
-spec(is_migration_started() -> boolean()).
|
||||||
|
is_migration_started() ->
|
||||||
|
case mnesia:read({?ACL_TABLE, ?MIGRATION_MARK_KEY}) of
|
||||||
|
[?MIGRATION_MARK_RECORD | _] -> true;
|
||||||
|
[] -> false
|
||||||
|
end.
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Internal functions
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
add_acl_new(Login, Topic, Action, Access) ->
|
||||||
|
Rule = {Access, Action, Topic, erlang:system_time(millisecond)},
|
||||||
|
Rules = normalize_rule(Rule),
|
||||||
|
OldAcl = mnesia:wread({?ACL_TABLE2, Login}),
|
||||||
|
NewAcl = case OldAcl of
|
||||||
|
[#?ACL_TABLE2{rules = OldRules} = Acl] ->
|
||||||
|
Acl#?ACL_TABLE2{rules = merge_rules(Rules, OldRules)};
|
||||||
|
[] ->
|
||||||
|
#?ACL_TABLE2{who = Login, rules = Rules}
|
||||||
|
end,
|
||||||
|
mnesia:write(NewAcl).
|
||||||
|
|
||||||
|
add_acl_old(Login, Topic, Action, Access) ->
|
||||||
|
Filter = {Login, Topic},
|
||||||
|
Acl = #?ACL_TABLE{
|
||||||
|
filter = Filter,
|
||||||
|
action = Action,
|
||||||
|
access = Access,
|
||||||
|
created_at = erlang:system_time(millisecond)
|
||||||
|
},
|
||||||
|
OldRecords = mnesia:wread({?ACL_TABLE, Filter}),
|
||||||
|
case Action of
|
||||||
|
pubsub ->
|
||||||
|
update_permission(pub, Acl, OldRecords),
|
||||||
|
update_permission(sub, Acl, OldRecords);
|
||||||
|
_ ->
|
||||||
|
update_permission(Action, Acl, OldRecords)
|
||||||
|
end.
|
||||||
|
|
||||||
|
old_recs_to_rules(OldRecs) ->
|
||||||
|
lists:flatmap(fun old_rec_to_rules/1, OldRecs).
|
||||||
|
|
||||||
|
old_rec_to_rules(#?ACL_TABLE{filter = {_, Topic}, action = Action, access = Access, created_at = CreatedAt}) ->
|
||||||
|
normalize_rule({Access, Action, Topic, CreatedAt}).
|
||||||
|
|
||||||
|
normalize_rule({Access, pubsub, Topic, CreatedAt}) ->
|
||||||
|
[{Access, pub, Topic, CreatedAt}, {Access, sub, Topic, CreatedAt}];
|
||||||
|
normalize_rule({Access, Action, Topic, CreatedAt}) ->
|
||||||
|
[{Access, Action, Topic, CreatedAt}].
|
||||||
|
|
||||||
|
merge_rules([], OldRules) -> OldRules;
|
||||||
|
merge_rules([NewRule | RestNewRules], OldRules) ->
|
||||||
|
merge_rules(RestNewRules, merge_rule(NewRule, OldRules)).
|
||||||
|
|
||||||
|
merge_rule({_, Action, Topic, _ } = NewRule, OldRules) ->
|
||||||
|
[NewRule | lists:filter(
|
||||||
|
fun({_, OldAction, OldTopic, _}) ->
|
||||||
|
{Action, Topic} =/= {OldAction, OldTopic}
|
||||||
|
end, OldRules)].
|
||||||
|
|
||||||
|
acl_to_list(#?ACL_TABLE2{who = Login, rules = Rules}) ->
|
||||||
|
[{Login, Topic, Action, Access, CreatedAt} || {Access, Action, Topic, CreatedAt} <- Rules].
|
||||||
|
|
||||||
|
delete_topic_rules(Topic, Rules) ->
|
||||||
|
[Rule || {_, _, T, _} = Rule <- Rules, T =/= Topic].
|
||||||
|
|
||||||
|
comparing({_, _, _, _, CreatedAt} = Rec1,
|
||||||
|
{_, _, _, _, CreatedAt} = Rec2) ->
|
||||||
|
Rec1 >= Rec2;
|
||||||
|
|
||||||
|
comparing({_, _, _, _, CreatedAt1},
|
||||||
|
{_, _, _, _, CreatedAt2}) ->
|
||||||
|
CreatedAt1 >= CreatedAt2.
|
||||||
|
|
||||||
|
login_match_spec_old(all) ->
|
||||||
|
ets:fun2ms(fun(#?ACL_TABLE{filter = {all, _}} = Record) ->
|
||||||
|
Record
|
||||||
|
end);
|
||||||
|
|
||||||
|
login_match_spec_old(Type) when (Type =:= username) or (Type =:= clientid) ->
|
||||||
|
ets:fun2ms(fun(#?ACL_TABLE{filter = {{RecordType, _}, _}} = Record)
|
||||||
|
when RecordType =:= Type -> Record
|
||||||
|
end).
|
||||||
|
|
||||||
|
login_match_spec_new(all) ->
|
||||||
|
ets:fun2ms(fun(#?ACL_TABLE2{who = all} = Record) ->
|
||||||
|
Record
|
||||||
|
end);
|
||||||
|
|
||||||
|
login_match_spec_new(Type) when (Type =:= username) or (Type =:= clientid) ->
|
||||||
|
ets:fun2ms(fun(#?ACL_TABLE2{who = {RecordType, _}} = Record)
|
||||||
|
when RecordType =:= Type -> Record
|
||||||
|
end).
|
||||||
|
|
||||||
|
acl_table(MatchSpecNew, MatchSpecOld, TableFun, LookupFun) ->
|
||||||
|
TraverseFun =
|
||||||
|
fun() ->
|
||||||
|
CursorNew =
|
||||||
|
qlc:cursor(
|
||||||
|
TableFun(?ACL_TABLE2, [{traverse, {select, MatchSpecNew}}])),
|
||||||
|
CursorOld =
|
||||||
|
qlc:cursor(
|
||||||
|
TableFun(?ACL_TABLE, [{traverse, {select, MatchSpecOld}}])),
|
||||||
|
traverse_new(CursorNew, CursorOld, #{}, LookupFun)
|
||||||
|
end,
|
||||||
|
|
||||||
|
qlc:table(TraverseFun, []).
|
||||||
|
|
||||||
|
|
||||||
|
% These are traverse funs for qlc table created by `acl_table/4`.
|
||||||
|
% Traversing consumes memory: it collects logins present in `?ACL_TABLE` and
|
||||||
|
% at the same time having rules in `?ACL_TABLE2`.
|
||||||
|
% Such records appear if ACLs are inserted before migration started.
|
||||||
|
% After migration, number of such logins is zero, so traversing starts working in
|
||||||
|
% constant memory.
|
||||||
|
|
||||||
|
traverse_new(CursorNew, CursorOld, FoundKeys, LookupFun) ->
|
||||||
|
Acls = qlc:next_answers(CursorNew, 1),
|
||||||
|
case Acls of
|
||||||
|
[] ->
|
||||||
|
qlc:delete_cursor(CursorNew),
|
||||||
|
traverse_old(CursorOld, FoundKeys);
|
||||||
|
[#?ACL_TABLE2{who = Login, rules = Rules} = Acl] ->
|
||||||
|
Keys = lists:usort([{Login, Topic} || {_, _, Topic, _} <- Rules]),
|
||||||
|
OldRecs = lists:flatmap(fun(Key) -> LookupFun(?ACL_TABLE, Key) end, Keys),
|
||||||
|
MergedAcl = merge_acl_records(Login, OldRecs, [Acl]),
|
||||||
|
NewFoundKeys =
|
||||||
|
lists:foldl(fun(#?ACL_TABLE{filter = Key}, Found) -> maps:put(Key, true, Found) end,
|
||||||
|
FoundKeys,
|
||||||
|
OldRecs),
|
||||||
|
case acl_to_list(MergedAcl) of
|
||||||
|
[] ->
|
||||||
|
traverse_new(CursorNew, CursorOld, NewFoundKeys, LookupFun);
|
||||||
|
List ->
|
||||||
|
List ++ fun() -> traverse_new(CursorNew, CursorOld, NewFoundKeys, LookupFun) end
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
|
traverse_old(CursorOld, FoundKeys) ->
|
||||||
|
OldAcls = qlc:next_answers(CursorOld),
|
||||||
|
case OldAcls of
|
||||||
|
[] ->
|
||||||
|
qlc:delete_cursor(CursorOld),
|
||||||
|
[];
|
||||||
|
_ ->
|
||||||
|
Records = [ {Login, Topic, Action, Access, CreatedAt}
|
||||||
|
|| #?ACL_TABLE{filter = {Login, Topic}, action = LegacyAction, access = Access, created_at = CreatedAt} <- OldAcls,
|
||||||
|
{_, Action, _, _} <- normalize_rule({Access, LegacyAction, Topic, CreatedAt}),
|
||||||
|
not maps:is_key({Login, Topic}, FoundKeys)
|
||||||
|
],
|
||||||
|
case Records of
|
||||||
|
[] -> traverse_old(CursorOld, FoundKeys);
|
||||||
|
List -> List ++ fun() -> traverse_old(CursorOld, FoundKeys) end
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
|
lookup_mnesia(Tab, Key) ->
|
||||||
|
mnesia:read({Tab, Key}).
|
||||||
|
|
||||||
|
lookup_ets(Tab, Key) ->
|
||||||
|
ets:lookup(Tab, Key).
|
||||||
|
|
||||||
|
update_permission(Action, Acl0, OldRecords) ->
|
||||||
|
Acl = Acl0 #?ACL_TABLE{action = Action},
|
||||||
|
maybe_delete_shadowed_records(Action, OldRecords),
|
||||||
|
mnesia:write(Acl).
|
||||||
|
|
||||||
|
maybe_delete_shadowed_records(_, []) ->
|
||||||
|
ok;
|
||||||
|
maybe_delete_shadowed_records(Action1, [Rec = #emqx_acl{action = Action2} | Rest]) ->
|
||||||
|
if Action1 =:= Action2 ->
|
||||||
|
ok = mnesia:delete_object(Rec);
|
||||||
|
Action2 =:= pubsub ->
|
||||||
|
%% Perform migration from the old data format on the
|
||||||
|
%% fly. This is needed only for the enterprise version,
|
||||||
|
%% delete this branch on 5.0
|
||||||
|
mnesia:delete_object(Rec),
|
||||||
|
mnesia:write(Rec#?ACL_TABLE{action = other_action(Action1)});
|
||||||
|
true ->
|
||||||
|
ok
|
||||||
|
end,
|
||||||
|
maybe_delete_shadowed_records(Action1, Rest).
|
||||||
|
|
||||||
|
other_action(pub) -> sub;
|
||||||
|
other_action(sub) -> pub.
|
||||||
|
|
||||||
|
ret({atomic, ok}) -> ok;
|
||||||
|
ret({aborted, Error}) -> {error, Error}.
|
|
@ -0,0 +1,215 @@
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% 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_acl_mnesia_migrator).
|
||||||
|
|
||||||
|
-include("emqx_auth_mnesia.hrl").
|
||||||
|
-include_lib("emqx/include/logger.hrl").
|
||||||
|
-include_lib("snabbkaffe/include/snabbkaffe.hrl").
|
||||||
|
|
||||||
|
-behaviour(gen_statem).
|
||||||
|
|
||||||
|
-define(CHECK_ALL_NODES_INTERVAL, 60000).
|
||||||
|
|
||||||
|
-type(migration_delay_reason() :: old_nodes | bad_nodes).
|
||||||
|
|
||||||
|
-export([
|
||||||
|
callback_mode/0,
|
||||||
|
init/1
|
||||||
|
]).
|
||||||
|
|
||||||
|
-export([
|
||||||
|
waiting_all_nodes/3,
|
||||||
|
checking_old_table/3,
|
||||||
|
migrating/3
|
||||||
|
]).
|
||||||
|
|
||||||
|
-export([
|
||||||
|
start_link/0,
|
||||||
|
start_link/1,
|
||||||
|
start_supervised/0,
|
||||||
|
stop_supervised/0,
|
||||||
|
migrate_records/0,
|
||||||
|
is_migrating_on_node/1,
|
||||||
|
is_old_table_migrated/0
|
||||||
|
]).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% External interface
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
start_link() ->
|
||||||
|
start_link(?MODULE).
|
||||||
|
|
||||||
|
start_link(Name) when is_atom(Name) ->
|
||||||
|
start_link(#{
|
||||||
|
name => Name
|
||||||
|
});
|
||||||
|
|
||||||
|
start_link(#{name := Name} = Opts) ->
|
||||||
|
gen_statem:start_link({local, Name}, ?MODULE, Opts, []).
|
||||||
|
|
||||||
|
start_supervised() ->
|
||||||
|
try
|
||||||
|
{ok, _} = supervisor:restart_child(emqx_auth_mnesia_sup, ?MODULE),
|
||||||
|
ok
|
||||||
|
catch
|
||||||
|
exit:{noproc, _} -> ok
|
||||||
|
end.
|
||||||
|
|
||||||
|
stop_supervised() ->
|
||||||
|
try
|
||||||
|
ok = supervisor:terminate_child(emqx_auth_mnesia_sup, ?MODULE),
|
||||||
|
ok = supervisor:delete_child(emqx_auth_mnesia_sup, ?MODULE)
|
||||||
|
catch
|
||||||
|
exit:{noproc, _} -> ok
|
||||||
|
end.
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% gen_statem callbacks
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
callback_mode() -> state_functions.
|
||||||
|
|
||||||
|
init(Opts) ->
|
||||||
|
ok = emqx_acl_mnesia_db:create_table(),
|
||||||
|
ok = emqx_acl_mnesia_db:create_table2(),
|
||||||
|
Name = maps:get(name, Opts, ?MODULE),
|
||||||
|
CheckNodesInterval = maps:get(check_nodes_interval, Opts, ?CHECK_ALL_NODES_INTERVAL),
|
||||||
|
GetNodes = maps:get(get_nodes, Opts, fun all_nodes/0),
|
||||||
|
Data =
|
||||||
|
#{name => Name,
|
||||||
|
check_nodes_interval => CheckNodesInterval,
|
||||||
|
get_nodes => GetNodes},
|
||||||
|
{ok, waiting_all_nodes, Data, [{state_timeout, 0, check_nodes}]}.
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% state callbacks
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
waiting_all_nodes(state_timeout, check_nodes, Data) ->
|
||||||
|
#{name := Name, check_nodes_interval := CheckNodesInterval, get_nodes := GetNodes} = Data,
|
||||||
|
case is_all_nodes_migrating(Name, GetNodes()) of
|
||||||
|
true ->
|
||||||
|
?tp(info, emqx_acl_mnesia_migrator_check_old_table, #{}),
|
||||||
|
{next_state, checking_old_table, Data, [{next_event, internal, check_old_table}]};
|
||||||
|
{false, Reason, Nodes} ->
|
||||||
|
?tp(info,
|
||||||
|
emqx_acl_mnesia_migrator_bad_nodes_delay,
|
||||||
|
#{delay => CheckNodesInterval,
|
||||||
|
reason => Reason,
|
||||||
|
name => Name,
|
||||||
|
nodes => Nodes}),
|
||||||
|
{keep_state_and_data, [{state_timeout, CheckNodesInterval, check_nodes}]}
|
||||||
|
end.
|
||||||
|
|
||||||
|
checking_old_table(internal, check_old_table, Data) ->
|
||||||
|
case is_old_table_migrated() of
|
||||||
|
true ->
|
||||||
|
?tp(info, emqx_acl_mnesia_migrator_finish, #{}),
|
||||||
|
{next_state, finished, Data, [{hibernate, true}]};
|
||||||
|
false ->
|
||||||
|
?tp(info, emqx_acl_mnesia_migrator_start_migration, #{}),
|
||||||
|
{next_state, migrating, Data, [{next_event, internal, start_migration}]}
|
||||||
|
end.
|
||||||
|
|
||||||
|
migrating(internal, start_migration, Data) ->
|
||||||
|
ok = migrate_records(),
|
||||||
|
{next_state, checking_old_table, Data, [{next_event, internal, check_old_table}]}.
|
||||||
|
|
||||||
|
%% @doc Returns `true` if migration is started in the local node, otherwise crash.
|
||||||
|
-spec(is_migrating_on_node(atom()) -> true).
|
||||||
|
is_migrating_on_node(Name) ->
|
||||||
|
true = is_pid(erlang:whereis(Name)).
|
||||||
|
|
||||||
|
%% @doc Run migration of records
|
||||||
|
-spec(migrate_records() -> ok).
|
||||||
|
migrate_records() ->
|
||||||
|
ok = add_migration_mark(),
|
||||||
|
Key = peek_record(),
|
||||||
|
do_migrate_records(Key).
|
||||||
|
|
||||||
|
%% @doc Run migration of records
|
||||||
|
-spec(is_all_nodes_migrating(atom(), list(node())) -> true | {false, migration_delay_reason(), list(node())}).
|
||||||
|
is_all_nodes_migrating(Name, Nodes) ->
|
||||||
|
case rpc:multicall(Nodes, ?MODULE, is_migrating_on_node, [Name]) of
|
||||||
|
{Results, []} ->
|
||||||
|
OldNodes = [ Node || {Node, Result} <- lists:zip(Nodes, Results), Result =/= true ],
|
||||||
|
case OldNodes of
|
||||||
|
[] -> true;
|
||||||
|
_ -> {false, old_nodes, OldNodes}
|
||||||
|
end;
|
||||||
|
{_, [_BadNode | _] = BadNodes} ->
|
||||||
|
{false, bad_nodes, BadNodes}
|
||||||
|
end.
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Internal functions
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
all_nodes() ->
|
||||||
|
ekka_mnesia:cluster_nodes(all).
|
||||||
|
|
||||||
|
is_old_table_migrated() ->
|
||||||
|
Result =
|
||||||
|
mnesia:transaction(fun() ->
|
||||||
|
case mnesia:first(?ACL_TABLE) of
|
||||||
|
?MIGRATION_MARK_KEY ->
|
||||||
|
case mnesia:next(?ACL_TABLE, ?MIGRATION_MARK_KEY) of
|
||||||
|
'$end_of_table' -> true;
|
||||||
|
_OtherKey -> false
|
||||||
|
end;
|
||||||
|
'$end_of_table' -> false;
|
||||||
|
_OtherKey -> false
|
||||||
|
end
|
||||||
|
end),
|
||||||
|
case Result of
|
||||||
|
{atomic, true} ->
|
||||||
|
true;
|
||||||
|
_ ->
|
||||||
|
false
|
||||||
|
end.
|
||||||
|
|
||||||
|
add_migration_mark() ->
|
||||||
|
{atomic, ok} = mnesia:transaction(fun() -> mnesia:write(?MIGRATION_MARK_RECORD) end),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
peek_record() ->
|
||||||
|
Key = mnesia:dirty_first(?ACL_TABLE),
|
||||||
|
case Key of
|
||||||
|
?MIGRATION_MARK_KEY ->
|
||||||
|
mnesia:dirty_next(?ACL_TABLE, Key);
|
||||||
|
_ -> Key
|
||||||
|
end.
|
||||||
|
|
||||||
|
do_migrate_records('$end_of_table') -> ok;
|
||||||
|
do_migrate_records({_Login, _Topic} = Key) ->
|
||||||
|
?tp(emqx_acl_mnesia_migrator_record_selected, #{key => Key}),
|
||||||
|
_ = mnesia:transaction(fun migrate_one_record/1, [Key]),
|
||||||
|
do_migrate_records(peek_record()).
|
||||||
|
|
||||||
|
migrate_one_record({Login, _Topic} = Key) ->
|
||||||
|
case mnesia:wread({?ACL_TABLE, Key}) of
|
||||||
|
[] ->
|
||||||
|
?tp(emqx_acl_mnesia_migrator_record_missed, #{key => Key}),
|
||||||
|
record_missing;
|
||||||
|
OldRecs ->
|
||||||
|
Acls = mnesia:wread({?ACL_TABLE2, Login}),
|
||||||
|
UpdatedAcl = emqx_acl_mnesia_db:merge_acl_records(Login, OldRecs, Acls),
|
||||||
|
ok = mnesia:write(UpdatedAcl),
|
||||||
|
ok = mnesia:delete({?ACL_TABLE, Key}),
|
||||||
|
?tp(emqx_acl_mnesia_migrator_record_migrated, #{key => Key})
|
||||||
|
end.
|
|
@ -1,6 +1,6 @@
|
||||||
{application, emqx_auth_mnesia,
|
{application, emqx_auth_mnesia,
|
||||||
[{description, "EMQ X Authentication with Mnesia"},
|
[{description, "EMQ X Authentication with Mnesia"},
|
||||||
{vsn, "4.3.3"}, % strict semver, bump manually
|
{vsn, "4.3.4"}, % strict semver, bump manually
|
||||||
{modules, []},
|
{modules, []},
|
||||||
{registered, []},
|
{registered, []},
|
||||||
{applications, [kernel,stdlib,mnesia]},
|
{applications, [kernel,stdlib,mnesia]},
|
||||||
|
|
|
@ -1,22 +1,31 @@
|
||||||
%% -*- mode: erlang -*-
|
%% -*- mode: erlang -*-
|
||||||
{VSN,
|
{VSN,
|
||||||
[{"4.3.2",
|
[
|
||||||
[{load_module,emqx_acl_mnesia_api,brutal_purge,soft_purge,[]},
|
{<<"4.3.[0-3]">>, [
|
||||||
{load_module,emqx_auth_mnesia_api,brutal_purge,soft_purge,[]}]},
|
{add_module,emqx_acl_mnesia_db},
|
||||||
{"4.3.1",
|
{add_module,emqx_acl_mnesia_migrator, [emqx_acl_mnesia_db]},
|
||||||
[{load_module,emqx_acl_mnesia_api,brutal_purge,soft_purge,[]},
|
{update, emqx_auth_mnesia_sup, supervisor},
|
||||||
{load_module,emqx_auth_mnesia_api,brutal_purge,soft_purge,[]}]},
|
{apply, {emqx_acl_mnesia_migrator, start_supervised, []}},
|
||||||
{"4.3.0",
|
{load_module,emqx_auth_mnesia_api, brutal_purge,soft_purge,[]},
|
||||||
[{load_module,emqx_acl_mnesia_api,brutal_purge,soft_purge,[]},
|
{load_module,emqx_acl_mnesia, brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_auth_mnesia_api,brutal_purge,soft_purge,[]}]},
|
{load_module,emqx_acl_mnesia_api, brutal_purge,soft_purge,[]},
|
||||||
{<<".*">>,[]}],
|
{load_module,emqx_acl_mnesia_cli, brutal_purge,soft_purge,[]}
|
||||||
[{"4.3.2",
|
]},
|
||||||
[{load_module,emqx_acl_mnesia_api,brutal_purge,soft_purge,[]},
|
{<<".*">>, [
|
||||||
{load_module,emqx_auth_mnesia_api,brutal_purge,soft_purge,[]}]},
|
]}
|
||||||
{"4.3.1",
|
],
|
||||||
[{load_module,emqx_acl_mnesia_api,brutal_purge,soft_purge,[]},
|
[
|
||||||
{load_module,emqx_auth_mnesia_api,brutal_purge,soft_purge,[]}]},
|
{<<"4.3.[0-3]">>, [
|
||||||
{"4.3.0",
|
{apply, {emqx_acl_mnesia_migrator, stop_supervised, []}},
|
||||||
[{load_module,emqx_acl_mnesia_api,brutal_purge,soft_purge,[]},
|
{update, emqx_auth_mnesia_sup, supervisor},
|
||||||
{load_module,emqx_auth_mnesia_api,brutal_purge,soft_purge,[]}]},
|
{load_module,emqx_acl_mnesia_cli, brutal_purge,soft_purge,[]},
|
||||||
{<<".*">>,[]}]}.
|
{load_module,emqx_acl_mnesia_api, brutal_purge,soft_purge,[]},
|
||||||
|
{load_module,emqx_auth_mnesia_api, brutal_purge,soft_purge,[]},
|
||||||
|
{load_module,emqx_acl_mnesia, brutal_purge,soft_purge,[]},
|
||||||
|
{delete_module,emqx_acl_mnesia_migrator},
|
||||||
|
{delete_module,emqx_acl_mnesia_db}
|
||||||
|
]},
|
||||||
|
{<<".*">>, [
|
||||||
|
]}
|
||||||
|
]
|
||||||
|
}.
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
|
|
||||||
-import(proplists, [get_value/2]).
|
-import(proplists, [get_value/2]).
|
||||||
-import(minirest, [return/1]).
|
-import(minirest, [return/1]).
|
||||||
-export([paginate/5]).
|
-export([paginate_qh/5]).
|
||||||
|
|
||||||
-export([ list_clientid/2
|
-export([ list_clientid/2
|
||||||
, lookup_clientid/2
|
, lookup_clientid/2
|
||||||
|
@ -212,9 +212,12 @@ delete_username(#{username := Username}, _) ->
|
||||||
%% Paging Query
|
%% Paging Query
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
|
|
||||||
paginate(Tables, MatchSpec, Params, ComparingFun, RowFun) ->
|
paginate(Table, MatchSpec, Params, ComparingFun, RowFun) ->
|
||||||
Qh = query_handle(Tables, MatchSpec),
|
Qh = query_handle(Table, MatchSpec),
|
||||||
Count = count(Tables, MatchSpec),
|
Count = count(Table, MatchSpec),
|
||||||
|
paginate_qh(Qh, Count, Params, ComparingFun, RowFun).
|
||||||
|
|
||||||
|
paginate_qh(Qh, Count, Params, ComparingFun, RowFun) ->
|
||||||
Page = page(Params),
|
Page = page(Params),
|
||||||
Limit = limit(Params),
|
Limit = limit(Params),
|
||||||
Cursor = qlc:cursor(Qh),
|
Cursor = qlc:cursor(Qh),
|
||||||
|
@ -231,24 +234,12 @@ paginate(Tables, MatchSpec, Params, ComparingFun, RowFun) ->
|
||||||
|
|
||||||
query_handle(Table, MatchSpec) when is_atom(Table) ->
|
query_handle(Table, MatchSpec) when is_atom(Table) ->
|
||||||
Options = {traverse, {select, MatchSpec}},
|
Options = {traverse, {select, MatchSpec}},
|
||||||
qlc:q([R|| R <- ets:table(Table, Options)]);
|
qlc:q([R || R <- ets:table(Table, Options)]).
|
||||||
query_handle([Table], MatchSpec) when is_atom(Table) ->
|
|
||||||
Options = {traverse, {select, MatchSpec}},
|
|
||||||
qlc:q([R|| R <- ets:table(Table, Options)]);
|
|
||||||
query_handle(Tables, MatchSpec) ->
|
|
||||||
Options = {traverse, {select, MatchSpec}},
|
|
||||||
qlc:append([qlc:q([E || E <- ets:table(T, Options)]) || T <- Tables]).
|
|
||||||
|
|
||||||
count(Table, MatchSpec) when is_atom(Table) ->
|
count(Table, MatchSpec) when is_atom(Table) ->
|
||||||
[{MatchPattern, Where, _Re}] = MatchSpec,
|
[{MatchPattern, Where, _Re}] = MatchSpec,
|
||||||
NMatchSpec = [{MatchPattern, Where, [true]}],
|
NMatchSpec = [{MatchPattern, Where, [true]}],
|
||||||
ets:select_count(Table, NMatchSpec);
|
ets:select_count(Table, NMatchSpec).
|
||||||
count([Table], MatchSpec) when is_atom(Table) ->
|
|
||||||
[{MatchPattern, Where, _Re}] = MatchSpec,
|
|
||||||
NMatchSpec = [{MatchPattern, Where, [true]}],
|
|
||||||
ets:select_count(Table, NMatchSpec);
|
|
||||||
count(Tables, MatchSpec) ->
|
|
||||||
lists:sum([count(T, MatchSpec) || T <- Tables]).
|
|
||||||
|
|
||||||
page(Params) ->
|
page(Params) ->
|
||||||
binary_to_integer(proplists:get_value(<<"_page">>, Params, <<"1">>)).
|
binary_to_integer(proplists:get_value(<<"_page">>, Params, <<"1">>)).
|
||||||
|
|
|
@ -33,4 +33,16 @@ start_link() ->
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
init([]) ->
|
init([]) ->
|
||||||
{ok, {{one_for_one, 10, 100}, []}}.
|
{ok, {{one_for_one, 10, 100}, [
|
||||||
|
child_spec(emqx_acl_mnesia_migrator, worker, [])
|
||||||
|
]}}.
|
||||||
|
|
||||||
|
child_spec(M, worker, Args) ->
|
||||||
|
#{id => M,
|
||||||
|
start => {M, start_link, Args},
|
||||||
|
restart => permanent,
|
||||||
|
shutdown => 5000,
|
||||||
|
type => worker,
|
||||||
|
modules => [M]
|
||||||
|
}.
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
-include("emqx_auth_mnesia.hrl").
|
-include("emqx_auth_mnesia.hrl").
|
||||||
-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("snabbkaffe/include/snabbkaffe.hrl").
|
||||||
|
|
||||||
-import(emqx_ct_http, [ request_api/3
|
-import(emqx_ct_http, [ request_api/3
|
||||||
, request_api/5
|
, request_api/5
|
||||||
|
@ -39,10 +40,15 @@ all() ->
|
||||||
emqx_ct:all(?MODULE).
|
emqx_ct:all(?MODULE).
|
||||||
|
|
||||||
groups() ->
|
groups() ->
|
||||||
[].
|
[{async_migration_tests, [sequence], [
|
||||||
|
t_old_and_new_acl_migration_by_migrator,
|
||||||
|
t_old_and_new_acl_migration_repeated_by_migrator,
|
||||||
|
t_migration_concurrency
|
||||||
|
]}].
|
||||||
|
|
||||||
init_per_suite(Config) ->
|
init_per_suite(Config) ->
|
||||||
emqx_ct_helpers:start_apps([emqx_modules, emqx_management, emqx_auth_mnesia], fun set_special_configs/1),
|
emqx_ct_helpers:start_apps([emqx_modules, emqx_management, emqx_auth_mnesia], fun set_special_configs/1),
|
||||||
|
supervisor:terminate_child(emqx_auth_mnesia_sup, emqx_acl_mnesia_migrator),
|
||||||
create_default_app(),
|
create_default_app(),
|
||||||
Config.
|
Config.
|
||||||
|
|
||||||
|
@ -50,14 +56,32 @@ end_per_suite(_Config) ->
|
||||||
delete_default_app(),
|
delete_default_app(),
|
||||||
emqx_ct_helpers:stop_apps([emqx_modules, emqx_management, emqx_auth_mnesia]).
|
emqx_ct_helpers:stop_apps([emqx_modules, emqx_management, emqx_auth_mnesia]).
|
||||||
|
|
||||||
init_per_testcase(t_check_acl_as_clientid, Config) ->
|
init_per_testcase_clean(_, Config) ->
|
||||||
|
mnesia:clear_table(?ACL_TABLE),
|
||||||
|
mnesia:clear_table(?ACL_TABLE2),
|
||||||
|
Config.
|
||||||
|
|
||||||
|
init_per_testcase_emqx_hook(t_check_acl_as_clientid, Config) ->
|
||||||
emqx:hook('client.check_acl', fun emqx_acl_mnesia:check_acl/5, [#{key_as => clientid}]),
|
emqx:hook('client.check_acl', fun emqx_acl_mnesia:check_acl/5, [#{key_as => clientid}]),
|
||||||
Config;
|
Config;
|
||||||
|
init_per_testcase_emqx_hook(_, Config) ->
|
||||||
init_per_testcase(_, Config) ->
|
|
||||||
emqx:hook('client.check_acl', fun emqx_acl_mnesia:check_acl/5, [#{key_as => username}]),
|
emqx:hook('client.check_acl', fun emqx_acl_mnesia:check_acl/5, [#{key_as => username}]),
|
||||||
Config.
|
Config.
|
||||||
|
|
||||||
|
init_per_testcase_migration(t_management_before_migration, Config) ->
|
||||||
|
Config;
|
||||||
|
init_per_testcase_migration(_, Config) ->
|
||||||
|
emqx_acl_mnesia_migrator:migrate_records(),
|
||||||
|
Config.
|
||||||
|
|
||||||
|
init_per_testcase(Case, Config) ->
|
||||||
|
PerTestInitializers = [
|
||||||
|
fun init_per_testcase_clean/2,
|
||||||
|
fun init_per_testcase_migration/2,
|
||||||
|
fun init_per_testcase_emqx_hook/2
|
||||||
|
],
|
||||||
|
lists:foldl(fun(Init, Conf) -> Init(Case, Conf) end, Config, PerTestInitializers).
|
||||||
|
|
||||||
end_per_testcase(_, Config) ->
|
end_per_testcase(_, Config) ->
|
||||||
emqx:unhook('client.check_acl', fun emqx_acl_mnesia:check_acl/5),
|
emqx:unhook('client.check_acl', fun emqx_acl_mnesia:check_acl/5),
|
||||||
Config.
|
Config.
|
||||||
|
@ -76,25 +100,34 @@ set_special_configs(_App) ->
|
||||||
%% Testcases
|
%% Testcases
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
|
|
||||||
t_management(_Config) ->
|
t_management_before_migration(_Config) ->
|
||||||
clean_all_acls(),
|
{atomic, IsStarted} = mnesia:transaction(fun emqx_acl_mnesia_db:is_migration_started/0),
|
||||||
?assertEqual("Acl with Mnesia", emqx_acl_mnesia:description()),
|
?assertNot(IsStarted),
|
||||||
?assertEqual([], emqx_acl_mnesia_cli:all_acls()),
|
run_acl_tests().
|
||||||
|
|
||||||
ok = emqx_acl_mnesia_cli:add_acl({clientid, <<"test_clientid">>}, <<"topic/%c">>, sub, allow),
|
t_management_after_migration(_Config) ->
|
||||||
ok = emqx_acl_mnesia_cli:add_acl({clientid, <<"test_clientid">>}, <<"topic/+">>, pub, deny),
|
{atomic, IsStarted} = mnesia:transaction(fun emqx_acl_mnesia_db:is_migration_started/0),
|
||||||
ok = emqx_acl_mnesia_cli:add_acl({username, <<"test_username">>}, <<"topic/%u">>, sub, deny),
|
?assert(IsStarted),
|
||||||
ok = emqx_acl_mnesia_cli:add_acl({username, <<"test_username">>}, <<"topic/+">>, pub, allow),
|
run_acl_tests().
|
||||||
ok = emqx_acl_mnesia_cli:add_acl(all, <<"#">>, pubsub, deny),
|
|
||||||
|
run_acl_tests() ->
|
||||||
|
?assertEqual("Acl with Mnesia", emqx_acl_mnesia:description()),
|
||||||
|
?assertEqual([], emqx_acl_mnesia_db:all_acls()),
|
||||||
|
|
||||||
|
ok = emqx_acl_mnesia_db:add_acl({clientid, <<"test_clientid">>}, <<"topic/%c">>, sub, allow),
|
||||||
|
ok = emqx_acl_mnesia_db:add_acl({clientid, <<"test_clientid">>}, <<"topic/+">>, pub, deny),
|
||||||
|
ok = emqx_acl_mnesia_db:add_acl({username, <<"test_username">>}, <<"topic/%u">>, sub, deny),
|
||||||
|
ok = emqx_acl_mnesia_db:add_acl({username, <<"test_username">>}, <<"topic/+">>, pub, allow),
|
||||||
|
ok = emqx_acl_mnesia_db:add_acl(all, <<"#">>, pubsub, deny),
|
||||||
%% Sleeps below are needed to hide the race condition between
|
%% Sleeps below are needed to hide the race condition between
|
||||||
%% mnesia and ets dirty select in check_acl, that make this test
|
%% mnesia and ets dirty select in check_acl, that make this test
|
||||||
%% flaky
|
%% flaky
|
||||||
timer:sleep(100),
|
timer:sleep(100),
|
||||||
|
|
||||||
?assertEqual(2, length(emqx_acl_mnesia_cli:lookup_acl({clientid, <<"test_clientid">>}))),
|
?assertEqual(2, length(emqx_acl_mnesia_db:lookup_acl({clientid, <<"test_clientid">>}))),
|
||||||
?assertEqual(2, length(emqx_acl_mnesia_cli:lookup_acl({username, <<"test_username">>}))),
|
?assertEqual(2, length(emqx_acl_mnesia_db:lookup_acl({username, <<"test_username">>}))),
|
||||||
?assertEqual(2, length(emqx_acl_mnesia_cli:lookup_acl(all))),
|
?assertEqual(2, length(emqx_acl_mnesia_db:lookup_acl(all))),
|
||||||
?assertEqual(6, length(emqx_acl_mnesia_cli:all_acls())),
|
?assertEqual(6, length(emqx_acl_mnesia_db:all_acls())),
|
||||||
|
|
||||||
User1 = #{zone => external, clientid => <<"test_clientid">>},
|
User1 = #{zone => external, clientid => <<"test_clientid">>},
|
||||||
User2 = #{zone => external, clientid => <<"no_exist">>, username => <<"test_username">>},
|
User2 = #{zone => external, clientid => <<"no_exist">>, username => <<"test_username">>},
|
||||||
|
@ -110,30 +143,30 @@ t_management(_Config) ->
|
||||||
deny = emqx_access_control:check_acl(User3, publish, <<"topic/A/B">>),
|
deny = emqx_access_control:check_acl(User3, publish, <<"topic/A/B">>),
|
||||||
|
|
||||||
%% Test merging of pubsub capability:
|
%% Test merging of pubsub capability:
|
||||||
ok = emqx_acl_mnesia_cli:add_acl({clientid, <<"test_clientid">>}, <<"topic/mix">>, pubsub, deny),
|
ok = emqx_acl_mnesia_db:add_acl({clientid, <<"test_clientid">>}, <<"topic/mix">>, pubsub, deny),
|
||||||
timer:sleep(100),
|
timer:sleep(100),
|
||||||
deny = emqx_access_control:check_acl(User1, subscribe, <<"topic/mix">>),
|
deny = emqx_access_control:check_acl(User1, subscribe, <<"topic/mix">>),
|
||||||
deny = emqx_access_control:check_acl(User1, publish, <<"topic/mix">>),
|
deny = emqx_access_control:check_acl(User1, publish, <<"topic/mix">>),
|
||||||
ok = emqx_acl_mnesia_cli:add_acl({clientid, <<"test_clientid">>}, <<"topic/mix">>, pub, allow),
|
ok = emqx_acl_mnesia_db:add_acl({clientid, <<"test_clientid">>}, <<"topic/mix">>, pub, allow),
|
||||||
timer:sleep(100),
|
timer:sleep(100),
|
||||||
deny = emqx_access_control:check_acl(User1, subscribe, <<"topic/mix">>),
|
deny = emqx_access_control:check_acl(User1, subscribe, <<"topic/mix">>),
|
||||||
allow = emqx_access_control:check_acl(User1, publish, <<"topic/mix">>),
|
allow = emqx_access_control:check_acl(User1, publish, <<"topic/mix">>),
|
||||||
ok = emqx_acl_mnesia_cli:add_acl({clientid, <<"test_clientid">>}, <<"topic/mix">>, pubsub, allow),
|
ok = emqx_acl_mnesia_db:add_acl({clientid, <<"test_clientid">>}, <<"topic/mix">>, pubsub, allow),
|
||||||
timer:sleep(100),
|
timer:sleep(100),
|
||||||
allow = emqx_access_control:check_acl(User1, subscribe, <<"topic/mix">>),
|
allow = emqx_access_control:check_acl(User1, subscribe, <<"topic/mix">>),
|
||||||
allow = emqx_access_control:check_acl(User1, publish, <<"topic/mix">>),
|
allow = emqx_access_control:check_acl(User1, publish, <<"topic/mix">>),
|
||||||
ok = emqx_acl_mnesia_cli:add_acl({clientid, <<"test_clientid">>}, <<"topic/mix">>, sub, deny),
|
ok = emqx_acl_mnesia_db:add_acl({clientid, <<"test_clientid">>}, <<"topic/mix">>, sub, deny),
|
||||||
timer:sleep(100),
|
timer:sleep(100),
|
||||||
deny = emqx_access_control:check_acl(User1, subscribe, <<"topic/mix">>),
|
deny = emqx_access_control:check_acl(User1, subscribe, <<"topic/mix">>),
|
||||||
allow = emqx_access_control:check_acl(User1, publish, <<"topic/mix">>),
|
allow = emqx_access_control:check_acl(User1, publish, <<"topic/mix">>),
|
||||||
ok = emqx_acl_mnesia_cli:add_acl({clientid, <<"test_clientid">>}, <<"topic/mix">>, pub, deny),
|
ok = emqx_acl_mnesia_db:add_acl({clientid, <<"test_clientid">>}, <<"topic/mix">>, pub, deny),
|
||||||
timer:sleep(100),
|
timer:sleep(100),
|
||||||
deny = emqx_access_control:check_acl(User1, subscribe, <<"topic/mix">>),
|
deny = emqx_access_control:check_acl(User1, subscribe, <<"topic/mix">>),
|
||||||
deny = emqx_access_control:check_acl(User1, publish, <<"topic/mix">>),
|
deny = emqx_access_control:check_acl(User1, publish, <<"topic/mix">>),
|
||||||
|
|
||||||
%% Test implicit migration of pubsub to pub and sub:
|
%% Test implicit migration of pubsub to pub and sub:
|
||||||
ok = emqx_acl_mnesia_cli:remove_acl({clientid, <<"test_clientid">>}, <<"topic/mix">>),
|
ok = emqx_acl_mnesia_db:remove_acl({clientid, <<"test_clientid">>}, <<"topic/mix">>),
|
||||||
ok = mnesia:dirty_write(#emqx_acl{
|
ok = mnesia:dirty_write(#?ACL_TABLE{
|
||||||
filter = {{clientid, <<"test_clientid">>}, <<"topic/mix">>},
|
filter = {{clientid, <<"test_clientid">>}, <<"topic/mix">>},
|
||||||
action = pubsub,
|
action = pubsub,
|
||||||
access = allow,
|
access = allow,
|
||||||
|
@ -142,24 +175,130 @@ t_management(_Config) ->
|
||||||
timer:sleep(100),
|
timer:sleep(100),
|
||||||
allow = emqx_access_control:check_acl(User1, subscribe, <<"topic/mix">>),
|
allow = emqx_access_control:check_acl(User1, subscribe, <<"topic/mix">>),
|
||||||
allow = emqx_access_control:check_acl(User1, publish, <<"topic/mix">>),
|
allow = emqx_access_control:check_acl(User1, publish, <<"topic/mix">>),
|
||||||
ok = emqx_acl_mnesia_cli:add_acl({clientid, <<"test_clientid">>}, <<"topic/mix">>, pub, deny),
|
ok = emqx_acl_mnesia_db:add_acl({clientid, <<"test_clientid">>}, <<"topic/mix">>, pub, deny),
|
||||||
timer:sleep(100),
|
timer:sleep(100),
|
||||||
allow = emqx_access_control:check_acl(User1, subscribe, <<"topic/mix">>),
|
allow = emqx_access_control:check_acl(User1, subscribe, <<"topic/mix">>),
|
||||||
deny = emqx_access_control:check_acl(User1, publish, <<"topic/mix">>),
|
deny = emqx_access_control:check_acl(User1, publish, <<"topic/mix">>),
|
||||||
ok = emqx_acl_mnesia_cli:add_acl({clientid, <<"test_clientid">>}, <<"topic/mix">>, sub, deny),
|
ok = emqx_acl_mnesia_db:add_acl({clientid, <<"test_clientid">>}, <<"topic/mix">>, sub, deny),
|
||||||
timer:sleep(100),
|
timer:sleep(100),
|
||||||
deny = emqx_access_control:check_acl(User1, subscribe, <<"topic/mix">>),
|
deny = emqx_access_control:check_acl(User1, subscribe, <<"topic/mix">>),
|
||||||
deny = emqx_access_control:check_acl(User1, publish, <<"topic/mix">>),
|
deny = emqx_access_control:check_acl(User1, publish, <<"topic/mix">>),
|
||||||
|
|
||||||
ok = emqx_acl_mnesia_cli:remove_acl({clientid, <<"test_clientid">>}, <<"topic/%c">>),
|
ok = emqx_acl_mnesia_db:remove_acl({clientid, <<"test_clientid">>}, <<"topic/%c">>),
|
||||||
ok = emqx_acl_mnesia_cli:remove_acl({clientid, <<"test_clientid">>}, <<"topic/+">>),
|
ok = emqx_acl_mnesia_db:remove_acl({clientid, <<"test_clientid">>}, <<"topic/+">>),
|
||||||
ok = emqx_acl_mnesia_cli:remove_acl({clientid, <<"test_clientid">>}, <<"topic/mix">>),
|
ok = emqx_acl_mnesia_db:remove_acl({clientid, <<"test_clientid">>}, <<"topic/mix">>),
|
||||||
ok = emqx_acl_mnesia_cli:remove_acl({username, <<"test_username">>}, <<"topic/%u">>),
|
ok = emqx_acl_mnesia_db:remove_acl({username, <<"test_username">>}, <<"topic/%u">>),
|
||||||
ok = emqx_acl_mnesia_cli:remove_acl({username, <<"test_username">>}, <<"topic/+">>),
|
ok = emqx_acl_mnesia_db:remove_acl({username, <<"test_username">>}, <<"topic/+">>),
|
||||||
ok = emqx_acl_mnesia_cli:remove_acl(all, <<"#">>),
|
ok = emqx_acl_mnesia_db:remove_acl(all, <<"#">>),
|
||||||
timer:sleep(100),
|
timer:sleep(100),
|
||||||
|
|
||||||
?assertEqual([], emqx_acl_mnesia_cli:all_acls()).
|
?assertEqual([], emqx_acl_mnesia_db:all_acls()).
|
||||||
|
|
||||||
|
t_old_and_new_acl_combination(_Config) ->
|
||||||
|
create_conflicting_records(),
|
||||||
|
|
||||||
|
?assertEqual(combined_conflicting_records(), emqx_acl_mnesia_db:all_acls()),
|
||||||
|
?assertEqual(
|
||||||
|
lists:usort(combined_conflicting_records()),
|
||||||
|
lists:usort(emqx_acl_mnesia_db:all_acls_export())).
|
||||||
|
|
||||||
|
t_old_and_new_acl_migration(_Config) ->
|
||||||
|
create_conflicting_records(),
|
||||||
|
emqx_acl_mnesia_migrator:migrate_records(),
|
||||||
|
|
||||||
|
?assertEqual(combined_conflicting_records(), emqx_acl_mnesia_db:all_acls()),
|
||||||
|
?assertEqual(
|
||||||
|
lists:usort(combined_conflicting_records()),
|
||||||
|
lists:usort(emqx_acl_mnesia_db:all_acls_export())),
|
||||||
|
|
||||||
|
% check that old table is not popoulated anymore
|
||||||
|
ok = emqx_acl_mnesia_db:add_acl({clientid, <<"test_clientid">>}, <<"topic/%c">>, sub, allow),
|
||||||
|
?assert(emqx_acl_mnesia_migrator:is_old_table_migrated()).
|
||||||
|
|
||||||
|
|
||||||
|
t_migration_concurrency(_Config) ->
|
||||||
|
Key = {{clientid,<<"client6">>}, <<"t">>},
|
||||||
|
Record = #?ACL_TABLE{filter = Key, action = pubsub, access = deny, created_at = 0},
|
||||||
|
{atomic, ok} = mnesia:transaction(fun mnesia:write/1, [Record]),
|
||||||
|
|
||||||
|
LockWaitAndDelete =
|
||||||
|
fun() ->
|
||||||
|
[_Rec] = mnesia:wread({?ACL_TABLE, Key}),
|
||||||
|
{{Pid, Ref}, _} =
|
||||||
|
?wait_async_action(spawn_monitor(fun emqx_acl_mnesia_migrator:migrate_records/0),
|
||||||
|
#{?snk_kind := emqx_acl_mnesia_migrator_record_selected},
|
||||||
|
1000),
|
||||||
|
mnesia:delete({?ACL_TABLE, Key}),
|
||||||
|
{Pid, Ref}
|
||||||
|
end,
|
||||||
|
|
||||||
|
?check_trace(
|
||||||
|
begin
|
||||||
|
{atomic, {Pid, Ref}} = mnesia:transaction(LockWaitAndDelete),
|
||||||
|
receive {'DOWN', Ref, process, Pid, _} -> ok end
|
||||||
|
end,
|
||||||
|
fun(_, Trace) ->
|
||||||
|
?assertMatch([_], ?of_kind(emqx_acl_mnesia_migrator_record_missed, Trace))
|
||||||
|
end),
|
||||||
|
|
||||||
|
?assert(emqx_acl_mnesia_migrator:is_old_table_migrated()),
|
||||||
|
?assertEqual([], emqx_acl_mnesia_db:all_acls()).
|
||||||
|
|
||||||
|
|
||||||
|
t_old_and_new_acl_migration_by_migrator(_Config) ->
|
||||||
|
create_conflicting_records(),
|
||||||
|
|
||||||
|
meck:new(fake_nodes, [non_strict]),
|
||||||
|
meck:expect(fake_nodes, all, fun() -> [node(), 'somebadnode@127.0.0.1'] end),
|
||||||
|
|
||||||
|
?check_trace(
|
||||||
|
begin
|
||||||
|
% check all nodes every 30 ms
|
||||||
|
{ok, _} = emqx_acl_mnesia_migrator:start_link(#{
|
||||||
|
name => ct_migrator,
|
||||||
|
check_nodes_interval => 30,
|
||||||
|
get_nodes => fun fake_nodes:all/0
|
||||||
|
}),
|
||||||
|
timer:sleep(100)
|
||||||
|
end,
|
||||||
|
fun(_, Trace) ->
|
||||||
|
?assertEqual([], ?of_kind(emqx_acl_mnesia_migrator_start_migration, Trace))
|
||||||
|
end),
|
||||||
|
|
||||||
|
?check_trace(
|
||||||
|
begin
|
||||||
|
meck:expect(fake_nodes, all, fun() -> [node()] end),
|
||||||
|
timer:sleep(100)
|
||||||
|
end,
|
||||||
|
fun(_, Trace) ->
|
||||||
|
?assertMatch([_], ?of_kind(emqx_acl_mnesia_migrator_finish, Trace))
|
||||||
|
end),
|
||||||
|
|
||||||
|
meck:unload(fake_nodes),
|
||||||
|
|
||||||
|
?assertEqual(combined_conflicting_records(), emqx_acl_mnesia_db:all_acls()),
|
||||||
|
?assert(emqx_acl_mnesia_migrator:is_old_table_migrated()).
|
||||||
|
|
||||||
|
t_old_and_new_acl_migration_repeated_by_migrator(_Config) ->
|
||||||
|
create_conflicting_records(),
|
||||||
|
emqx_acl_mnesia_migrator:migrate_records(),
|
||||||
|
|
||||||
|
?check_trace(
|
||||||
|
begin
|
||||||
|
{ok, _} = emqx_acl_mnesia_migrator:start_link(ct_migrator),
|
||||||
|
timer:sleep(100)
|
||||||
|
end,
|
||||||
|
fun(_, Trace) ->
|
||||||
|
?assertEqual([], ?of_kind(emqx_acl_mnesia_migrator_start_migration, Trace)),
|
||||||
|
?assertMatch([_], ?of_kind(emqx_acl_mnesia_migrator_finish, Trace))
|
||||||
|
end).
|
||||||
|
|
||||||
|
t_start_stop_supervised(_Config) ->
|
||||||
|
?assertEqual(undefined, whereis(emqx_acl_mnesia_migrator)),
|
||||||
|
ok = emqx_acl_mnesia_migrator:start_supervised(),
|
||||||
|
?assert(is_pid(whereis(emqx_acl_mnesia_migrator))),
|
||||||
|
ok = emqx_acl_mnesia_migrator:stop_supervised(),
|
||||||
|
?assertEqual(undefined, whereis(emqx_acl_mnesia_migrator)).
|
||||||
|
|
||||||
t_acl_cli(_Config) ->
|
t_acl_cli(_Config) ->
|
||||||
meck:new(emqx_ctl, [non_strict, passthrough]),
|
meck:new(emqx_ctl, [non_strict, passthrough]),
|
||||||
|
@ -168,8 +307,6 @@ t_acl_cli(_Config) ->
|
||||||
meck:expect(emqx_ctl, usage, fun(Usages) -> emqx_ctl:format_usage(Usages) end),
|
meck:expect(emqx_ctl, usage, fun(Usages) -> emqx_ctl:format_usage(Usages) end),
|
||||||
meck:expect(emqx_ctl, usage, fun(Cmd, Descr) -> emqx_ctl:format_usage(Cmd, Descr) end),
|
meck:expect(emqx_ctl, usage, fun(Cmd, Descr) -> emqx_ctl:format_usage(Cmd, Descr) end),
|
||||||
|
|
||||||
clean_all_acls(),
|
|
||||||
|
|
||||||
?assertEqual(0, length(emqx_acl_mnesia_cli:cli(["list"]))),
|
?assertEqual(0, length(emqx_acl_mnesia_cli:cli(["list"]))),
|
||||||
|
|
||||||
emqx_acl_mnesia_cli:cli(["add", "clientid", "test_clientid", "topic/A", "pub", "deny"]),
|
emqx_acl_mnesia_cli:cli(["add", "clientid", "test_clientid", "topic/A", "pub", "deny"]),
|
||||||
|
@ -202,8 +339,6 @@ t_acl_cli(_Config) ->
|
||||||
meck:unload(emqx_ctl).
|
meck:unload(emqx_ctl).
|
||||||
|
|
||||||
t_rest_api(_Config) ->
|
t_rest_api(_Config) ->
|
||||||
clean_all_acls(),
|
|
||||||
|
|
||||||
Params1 = [#{<<"clientid">> => <<"test_clientid">>,
|
Params1 = [#{<<"clientid">> => <<"test_clientid">>,
|
||||||
<<"topic">> => <<"topic/A">>,
|
<<"topic">> => <<"topic/A">>,
|
||||||
<<"action">> => <<"pub">>,
|
<<"action">> => <<"pub">>,
|
||||||
|
@ -273,13 +408,24 @@ t_rest_api(_Config) ->
|
||||||
{ok, Res3} = request_http_rest_list(["$all"]),
|
{ok, Res3} = request_http_rest_list(["$all"]),
|
||||||
?assertMatch([], get_http_data(Res3)).
|
?assertMatch([], get_http_data(Res3)).
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
%% Helpers
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
clean_all_acls() ->
|
create_conflicting_records() ->
|
||||||
[ mnesia:dirty_delete({emqx_acl, Login})
|
Records = [
|
||||||
|| Login <- mnesia:dirty_all_keys(emqx_acl)].
|
#?ACL_TABLE{filter = {{clientid,<<"client6">>}, <<"t">>}, action = pubsub, access = deny, created_at = 0},
|
||||||
|
#?ACL_TABLE{filter = {{clientid,<<"client5">>}, <<"t">>}, action = pubsub, access = deny, created_at = 1},
|
||||||
|
#?ACL_TABLE2{who = {clientid,<<"client5">>}, rules = [{allow, sub, <<"t">>, 2}]}
|
||||||
|
],
|
||||||
|
mnesia:transaction(fun() -> lists:foreach(fun mnesia:write/1, Records) end).
|
||||||
|
|
||||||
|
|
||||||
|
combined_conflicting_records() ->
|
||||||
|
% pubsub's are split, ACL_TABLE2 rules shadow ACL_TABLE rules
|
||||||
|
[
|
||||||
|
{{clientid,<<"client5">>},<<"t">>,sub,allow,2},
|
||||||
|
{{clientid,<<"client5">>},<<"t">>,pub,deny,1},
|
||||||
|
{{clientid,<<"client6">>},<<"t">>,sub,deny,0},
|
||||||
|
{{clientid,<<"client6">>},<<"t">>,pub,deny,0}
|
||||||
|
].
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% HTTP Request
|
%% HTTP Request
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{deps,
|
{deps,
|
||||||
%% NOTE: mind poolboy version when updating mongodb-erlang version
|
%% NOTE: mind poolboy version when updating mongodb-erlang version
|
||||||
[{mongodb, {git,"https://github.com/emqx/mongodb-erlang", {tag, "v3.0.7"}}},
|
[{mongodb, {git,"https://github.com/emqx/mongodb-erlang", {tag, "v3.0.10"}}},
|
||||||
%% mongodb-erlang uses a special fork https://github.com/comtihon/poolboy.git
|
%% mongodb-erlang uses a special fork https://github.com/comtihon/poolboy.git
|
||||||
%% (which has overflow_ttl feature added).
|
%% (which has overflow_ttl feature added).
|
||||||
%% However, it references `{branch, "master}` (commit 9c06a9a on 2021-04-07).
|
%% However, it references `{branch, "master}` (commit 9c06a9a on 2021-04-07).
|
||||||
|
|
|
@ -1,23 +1,23 @@
|
||||||
-----BEGIN CERTIFICATE-----
|
-----BEGIN CERTIFICATE-----
|
||||||
MIID1zCCAb8CCQC/+qKgZd+m/DANBgkqhkiG9w0BAQsFADA1MRMwEQYDVQQKDApS
|
MIID1zCCAb8CCQC/+qKgZd+m/jANBgkqhkiG9w0BAQsFADA1MRMwEQYDVQQKDApS
|
||||||
ZWRpcyBUZXN0MR4wHAYDVQQDDBVDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMjAx
|
ZWRpcyBUZXN0MR4wHAYDVQQDDBVDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMjEx
|
||||||
MDI5MDEzNDE2WhcNMjExMDI5MDEzNDE2WjAmMRMwEQYDVQQKDApSZWRpcyBUZXN0
|
MTAxMDgwMDU1WhcNMzExMDMwMDgwMDU1WjAmMRMwEQYDVQQKDApSZWRpcyBUZXN0
|
||||||
MQ8wDQYDVQQDDAZTZXJ2ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
|
MQ8wDQYDVQQDDAZTZXJ2ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
|
||||||
AQDSs3bQ9sYi2AhFuHU75Ryk1HHSgfzA6pQAJilmJdTy0s5vyiWe1HQJaWkMcS5V
|
AQDSs3bQ9sYi2AhFuHU75Ryk1HHSgfzA6pQAJilmJdTy0s5vyiWe1HQJaWkMcS5V
|
||||||
GVzGMK+c+OBqtXtDDninL3betg1YPMjSCOjPMOTC1H9K7+effwf7Iwpnw9Zro8mb
|
GVzGMK+c+OBqtXtDDninL3betg1YPMjSCOjPMOTC1H9K7+effwf7Iwpnw9Zro8mb
|
||||||
TEmMslIYhhcDedzT9Owli4QAgbgTn4l1BYuKX9CLrrKFtnr21miKu3ydViy9q7T1
|
TEmMslIYhhcDedzT9Owli4QAgbgTn4l1BYuKX9CLrrKFtnr21miKu3ydViy9q7T1
|
||||||
pib3eigvAyk7X2fadHFArGEttsXrD6cetPPkSF/1OLWNlqzUKXzhSyrBXzO44Kks
|
pib3eigvAyk7X2fadHFArGEttsXrD6cetPPkSF/1OLWNlqzUKXzhSyrBXzO44Kks
|
||||||
fwR/EpTiES9g4dNOL2wvKS/YE1fNKhiCENrNxTXQo1l0yOdm2+MeyOeHFzRuS0b/
|
fwR/EpTiES9g4dNOL2wvKS/YE1fNKhiCENrNxTXQo1l0yOdm2+MeyOeHFzRuS0b/
|
||||||
+uGDFOPPi04KXeO6dQ5olBCPAgMBAAEwDQYJKoZIhvcNAQELBQADggIBADn0E2vG
|
+uGDFOPPi04KXeO6dQ5olBCPAgMBAAEwDQYJKoZIhvcNAQELBQADggIBALRSylnk
|
||||||
iQWe8/I7VbBdPhPNupVNcLvew10eIHxY2g5vSruCSVRQTgk8itVMRmDQxbb7gdDW
|
JJhEFRniuQ+H1kbfZlVOqnSqGkm38r8X76dnYRZfkFlxVzU2q5HPnSdiL9D3JrrH
|
||||||
jnCRbxykxbLjM9iCRljnOCsIcTi7qO7JRl8niV8dtEpPOs9lZxEdNXjIV1iZoWf3
|
P7wIA5zjr76zK7GPJjkRExRZy5sTLSpsGp7YIGAZ19J3KsDVHSOvPTl38c6L217a
|
||||||
arBbPQSyQZvTQHG6qbFnyCdMMyyXGGvEPGQDaBiKH+Ko1qeAbCi0zupChYvxmtZ8
|
YzPeQL5IrrW55URmA5PZFu3lsm9z7CNguw1wn2pCNNB+r/cRl4iELehZJT891CQe
|
||||||
hSTPlMFezDT9bKoNY0pkJSELfokEPU/Pn6Lz/NVbdzmCMjVa/xmF3s31g+DGhz95
|
nV9a1YfHY/DkDoMnmrKqmeYdvje8n1uSqTnIV/wNiASU36ztxxD8ZmwprxxbjLSs
|
||||||
4AyOnCr6o0aydPVVV3pB/BCezNXPUxpp53BG0w/K2f2DnKYCvGvJbqDAaJ8bG/J1
|
aBjBvsR/eBHbWrz2W1dc5ppgGLuCkiEKmh6/IWX/ZQqyBCzZkmFNiTs8QiLtmoC4
|
||||||
EFSOmwobdwVxJz3KNubmo1qJ6xOl/YT7yyqPRQRM1SY8nZW+YcoJSZjOe8wJVlob
|
2bXkPVSyq5wT7eisGbRdcY9vGDtoW/WZOmFVA4XEDVx8M9fb4speHwoHRuTfWsA0
|
||||||
d0bOwN1C3HQwomyMWes187bEQP6Y36HuEbR1fK8yIOzGsGDKRFAFwQwMgw2M91lr
|
6Y8P9XpYjG2tQoPpxrZaRshZ+SiHWPov7cAvY34szFePfTWR8gzbL6SgpDz30ceh
|
||||||
EJIP5NRD3OZRuiYDiVfVhDZDaNahrAMZUcPCgeCAwc4YG6Gp2sDtdorOl4kIJYWE
|
XIuTArOMQMhfWHn3NaOc6hlkRsoviNhc5IXR9VjIdaNJCamEoLVNWZsvHJCUiP10
|
||||||
BbBZ0Jplq9+g6ciu5ChjAW8iFl0Ae5U24MxPGXnrxiRF4WWxLeZMVLXLDvlPqReD
|
yx+9/0a9vI6G+i8oKQ+eKJsfP8Ikoiolf7vU6M+/1kF+sSMxGjFwkMCxLgZB67+a
|
||||||
CHII5ifyvGEt5+RhqtZC/L+HimL+5wQgOlntqhUdLb6yWRz7YW37PFMnUXU3MXe9
|
m9kw83sVfykWLQ3eRwhdBz0/JiiYtDbbtyqgs3kPhJs9SGZUhDc/7R0lTWf4zxoJ
|
||||||
uY7m73ZLluXiLojcZxU2+cx89u5FOJxrYtrj
|
l3y7pn/3nJvYrGX7uCBbWPUuqWeHVM9Ip6AZ
|
||||||
-----END CERTIFICATE-----
|
-----END CERTIFICATE-----
|
||||||
|
|
|
@ -146,4 +146,4 @@ lwm2m.dtls.ciphers = ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES256-GCM-SHA384,E
|
||||||
## Note that 'lwm2m.dtls.ciphers' and 'lwm2m.dtls.psk_ciphers' cannot
|
## Note that 'lwm2m.dtls.ciphers' and 'lwm2m.dtls.psk_ciphers' cannot
|
||||||
## be configured at the same time.
|
## be configured at the same time.
|
||||||
## See 'https://tools.ietf.org/html/rfc4279#section-2'.
|
## See 'https://tools.ietf.org/html/rfc4279#section-2'.
|
||||||
#lwm2m.dtls.psk_ciphers = PSK-AES128-CBC-SHA,PSK-AES256-CBC-SHA,PSK-3DES-EDE-CBC-SHA,PSK-RC4-SHA
|
#lwm2m.dtls.psk_ciphers = RSA-PSK-AES256-GCM-SHA384,RSA-PSK-AES256-CBC-SHA384,RSA-PSK-AES128-GCM-SHA256,RSA-PSK-AES128-CBC-SHA256,RSA-PSK-AES256-CBC-SHA,RSA-PSK-AES128-CBC-SHA
|
||||||
|
|
|
@ -185,7 +185,7 @@ end}.
|
||||||
OldCert = cuttlefish:conf_get("lwm2m.certfile", Conf, undefined),
|
OldCert = cuttlefish:conf_get("lwm2m.certfile", Conf, undefined),
|
||||||
|
|
||||||
%% Ciphers
|
%% Ciphers
|
||||||
SplitFun = fun(undefined) -> undefined; (S) -> string:tokens(S, ",") end,
|
SplitFun = fun(undefined) -> []; (S) -> string:tokens(S, ",") end,
|
||||||
Ciphers =
|
Ciphers =
|
||||||
case cuttlefish:conf_get("lwm2m.dtls.ciphers", Conf, undefined) of
|
case cuttlefish:conf_get("lwm2m.dtls.ciphers", Conf, undefined) of
|
||||||
undefined ->
|
undefined ->
|
||||||
|
@ -198,16 +198,17 @@ end}.
|
||||||
undefined ->
|
undefined ->
|
||||||
[];
|
[];
|
||||||
C2 ->
|
C2 ->
|
||||||
Psk = lists:map(fun("PSK-AES128-CBC-SHA") -> {psk, aes_128_cbc, sha};
|
Psk = lists:map(fun("PSK-AES128-CBC-SHA") -> "RSA-PSK-AES128-CBC-SHA";
|
||||||
("PSK-AES256-CBC-SHA") -> {psk, aes_256_cbc, sha};
|
("PSK-AES256-CBC-SHA") -> "RSA-PSK-AES256-CBC-SHA";
|
||||||
("PSK-3DES-EDE-CBC-SHA") -> {psk, '3des_ede_cbc', sha};
|
("PSK-3DES-EDE-CBC-SHA") -> "RSA-PSK-3DES-EDE-CBC-SHA";
|
||||||
("PSK-RC4-SHA") -> {psk, rc4_128, sha}
|
("PSK-RC4-SHA") -> "RSA-PSK-RC4-SHA";
|
||||||
end, SplitFun(C2)),
|
(Suite) -> Suite
|
||||||
|
end, SplitFun(C2)),
|
||||||
[{ciphers, Psk}, {user_lookup_fun, {fun emqx_psk:lookup/3, <<>>}}]
|
[{ciphers, Psk}, {user_lookup_fun, {fun emqx_psk:lookup/3, <<>>}}]
|
||||||
end,
|
end,
|
||||||
Ciphers /= []
|
Ciphers /= []
|
||||||
andalso PskCiphers /= []
|
andalso PskCiphers /= []
|
||||||
andalso cuttlefish:invalid("The 'lwm2m.dtls.ciphers' and 'lwm2m.dtls.psk_ciphers' cannot exist simultaneously."),
|
andalso cuttlefish:invalid("The 'lwm2m.dtls.ciphers' and 'lwm2m.dtls.psk_ciphers' cannot coexist"),
|
||||||
|
|
||||||
NCiphers = Ciphers ++ PskCiphers,
|
NCiphers = Ciphers ++ PskCiphers,
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{application,emqx_lwm2m,
|
{application,emqx_lwm2m,
|
||||||
[{description,"EMQ X LwM2M Gateway"},
|
[{description,"EMQ X LwM2M Gateway"},
|
||||||
{vsn, "4.3.3"}, % strict semver, bump manually!
|
{vsn, "4.3.4"}, % strict semver, bump manually!
|
||||||
{modules,[]},
|
{modules,[]},
|
||||||
{registered,[emqx_lwm2m_sup]},
|
{registered,[emqx_lwm2m_sup]},
|
||||||
{applications,[kernel,stdlib,lwm2m_coap]},
|
{applications,[kernel,stdlib,lwm2m_coap]},
|
||||||
|
|
|
@ -1,19 +1,21 @@
|
||||||
%% -*-: erlang -*-
|
%% -*-: erlang -*-
|
||||||
{"4.3.3",
|
{"4.3.4",
|
||||||
[
|
[
|
||||||
{<<"4.3.[0-1]">>, [
|
{<<"4\\.3\\.[0-1]">>, [
|
||||||
{restart_application, emqx_lwm2m}
|
{restart_application, emqx_lwm2m}
|
||||||
]},
|
]},
|
||||||
{"4.3.2", [
|
{"4.3.2", [
|
||||||
{load_module, emqx_lwm2m_message, brutal_purge, soft_purge, []}
|
{load_module, emqx_lwm2m_message, brutal_purge, soft_purge, []}
|
||||||
]}
|
]},
|
||||||
|
{"4.3.3", []} %% only config change
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
{<<"4.3.[0-1]">>, [
|
{<<"4\\.3\\.[0-1]">>, [
|
||||||
{restart_application, emqx_lwm2m}
|
{restart_application, emqx_lwm2m}
|
||||||
]},
|
]},
|
||||||
{"4.3.2", [
|
{"4.3.2", [
|
||||||
{load_module, emqx_lwm2m_message, brutal_purge, soft_purge, []}
|
{load_module, emqx_lwm2m_message, brutal_purge, soft_purge, []}
|
||||||
]}
|
]},
|
||||||
|
{"4.3.3", []} %% only config change
|
||||||
]
|
]
|
||||||
}.
|
}.
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{application, emqx_management,
|
{application, emqx_management,
|
||||||
[{description, "EMQ X Management API and CLI"},
|
[{description, "EMQ X Management API and CLI"},
|
||||||
{vsn, "4.3.7"}, % strict semver, bump manually!
|
{vsn, "4.3.8"}, % strict semver, bump manually!
|
||||||
{modules, []},
|
{modules, []},
|
||||||
{registered, [emqx_management_sup]},
|
{registered, [emqx_management_sup]},
|
||||||
{applications, [kernel,stdlib,minirest]},
|
{applications, [kernel,stdlib,minirest]},
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
%% -*- mode: erlang -*-
|
%% -*- mode: erlang -*-
|
||||||
{VSN,
|
{VSN,
|
||||||
[ {<<"4.3.[0-9]">>,
|
[ {<<"4\\.3\\.[0-7]+">>,
|
||||||
[ {apply,{minirest,stop_http,['http:management']}},
|
[ {apply,{minirest,stop_http,['http:management']}},
|
||||||
{apply,{minirest,stop_http,['https:management']}},
|
{apply,{minirest,stop_http,['https:management']}},
|
||||||
{restart_application, emqx_management}
|
{restart_application, emqx_management}
|
||||||
]},
|
]},
|
||||||
{<<".*">>, []}
|
{<<".*">>, []}
|
||||||
],
|
],
|
||||||
[ {<<"4.3.[0-9]">>,
|
[ {<<"4\\.3\\.[0-7]+">>,
|
||||||
[ {apply,{minirest,stop_http,['http:management']}},
|
[ {apply,{minirest,stop_http,['http:management']}},
|
||||||
{apply,{minirest,stop_http,['https:management']}},
|
{apply,{minirest,stop_http,['https:management']}},
|
||||||
{restart_application, emqx_management}
|
{restart_application, emqx_management}
|
||||||
|
|
|
@ -158,7 +158,7 @@ do_subscribe(ClientId, Topics, QoS) ->
|
||||||
_ -> ok
|
_ -> ok
|
||||||
end.
|
end.
|
||||||
|
|
||||||
do_publish(ClientId, _Topics, _Qos, _Retain, _Payload) when not is_binary(ClientId) ->
|
do_publish(ClientId, _Topics, _Qos, _Retain, _Payload) when not (is_binary(ClientId) or (ClientId =:= undefined)) ->
|
||||||
{ok, ?ERROR8, <<"bad clientid: must be string">>};
|
{ok, ?ERROR8, <<"bad clientid: must be string">>};
|
||||||
do_publish(_ClientId, [], _Qos, _Retain, _Payload) ->
|
do_publish(_ClientId, [], _Qos, _Retain, _Payload) ->
|
||||||
{ok, ?ERROR15, bad_topic};
|
{ok, ?ERROR15, bad_topic};
|
||||||
|
|
|
@ -191,10 +191,8 @@ clients(["show", ClientId]) ->
|
||||||
if_client(ClientId, fun print/1);
|
if_client(ClientId, fun print/1);
|
||||||
|
|
||||||
clients(["kick", ClientId]) ->
|
clients(["kick", ClientId]) ->
|
||||||
case emqx_cm:kick_session(bin(ClientId)) of
|
ok = emqx_cm:kick_session(bin(ClientId)),
|
||||||
ok -> emqx_ctl:print("ok~n");
|
emqx_ctl:print("ok~n");
|
||||||
_ -> emqx_ctl:print("Not Found.~n")
|
|
||||||
end;
|
|
||||||
|
|
||||||
clients(_) ->
|
clients(_) ->
|
||||||
emqx_ctl:usage([{"clients list", "List all clients"},
|
emqx_ctl:usage([{"clients list", "List all clients"},
|
||||||
|
|
|
@ -118,18 +118,18 @@ export_auth_mnesia() ->
|
||||||
end.
|
end.
|
||||||
|
|
||||||
export_acl_mnesia() ->
|
export_acl_mnesia() ->
|
||||||
case ets:info(emqx_acl) of
|
case ets:info(emqx_acl2) of
|
||||||
undefined -> [];
|
undefined -> [];
|
||||||
_ ->
|
_ ->
|
||||||
lists:map(fun({_, Filter, Action, Access, CreatedAt}) ->
|
lists:map(fun({Login, Topic, Action, Access, CreatedAt}) ->
|
||||||
Filter1 = case Filter of
|
Filter1 = case Login of
|
||||||
{{Type, TypeValue}, Topic} ->
|
{Type, TypeValue} ->
|
||||||
[{type, Type}, {type_value, TypeValue}, {topic, Topic}];
|
[{type, Type}, {type_value, TypeValue}, {topic, Topic}];
|
||||||
{Type, Topic} ->
|
Type ->
|
||||||
[{type, Type}, {topic, Topic}]
|
[{type, Type}, {topic, Topic}]
|
||||||
end,
|
end,
|
||||||
Filter1 ++ [{action, Action}, {access, Access}, {created_at, CreatedAt}]
|
Filter1 ++ [{action, Action}, {access, Access}, {created_at, CreatedAt}]
|
||||||
end, ets:tab2list(emqx_acl))
|
end, emqx_acl_mnesia_db:all_acls_export())
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-ifdef(EMQX_ENTERPRISE).
|
-ifdef(EMQX_ENTERPRISE).
|
||||||
|
@ -473,10 +473,9 @@ do_import_auth_mnesia(Auths) ->
|
||||||
end.
|
end.
|
||||||
|
|
||||||
do_import_acl_mnesia_by_old_data(Acls) ->
|
do_import_acl_mnesia_by_old_data(Acls) ->
|
||||||
case ets:info(emqx_acl) of
|
case ets:info(emqx_acl2) of
|
||||||
undefined -> ok;
|
undefined -> ok;
|
||||||
_ ->
|
_ ->
|
||||||
CreatedAt = erlang:system_time(millisecond),
|
|
||||||
lists:foreach(fun(#{<<"login">> := Login,
|
lists:foreach(fun(#{<<"login">> := Login,
|
||||||
<<"topic">> := Topic,
|
<<"topic">> := Topic,
|
||||||
<<"allow">> := Allow,
|
<<"allow">> := Allow,
|
||||||
|
@ -485,11 +484,11 @@ do_import_acl_mnesia_by_old_data(Acls) ->
|
||||||
true -> allow;
|
true -> allow;
|
||||||
false -> deny
|
false -> deny
|
||||||
end,
|
end,
|
||||||
mnesia:dirty_write({emqx_acl, {{get_old_type(), Login}, Topic}, any_to_atom(Action), Allow1, CreatedAt})
|
emqx_acl_mnesia_db:add_acl({get_old_type(), Login}, Topic, any_to_atom(Action), Allow1)
|
||||||
end, Acls)
|
end, Acls)
|
||||||
end.
|
end.
|
||||||
do_import_acl_mnesia(Acls) ->
|
do_import_acl_mnesia(Acls) ->
|
||||||
case ets:info(emqx_acl) of
|
case ets:info(emqx_acl2) of
|
||||||
undefined -> ok;
|
undefined -> ok;
|
||||||
_ ->
|
_ ->
|
||||||
lists:foreach(fun(Map = #{<<"action">> := Action,
|
lists:foreach(fun(Map = #{<<"action">> := Action,
|
||||||
|
@ -501,7 +500,7 @@ do_import_acl_mnesia(Acls) ->
|
||||||
Value ->
|
Value ->
|
||||||
{any_to_atom(maps:get(<<"type">>, Map)), Value}
|
{any_to_atom(maps:get(<<"type">>, Map)), Value}
|
||||||
end,
|
end,
|
||||||
emqx_acl_mnesia_cli:add_acl(Login, Topic, any_to_atom(Action), any_to_atom(Access))
|
emqx_acl_mnesia_db:add_acl(Login, Topic, any_to_atom(Action), any_to_atom(Access))
|
||||||
end, Acls)
|
end, Acls)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,7 @@ matrix() ->
|
||||||
, Version <- ["v4.2.10", "v4.1.5"]].
|
, Version <- ["v4.2.10", "v4.1.5"]].
|
||||||
|
|
||||||
all() ->
|
all() ->
|
||||||
[t_import_4_0, t_import_4_1, t_import_4_2].
|
[t_import_4_0, t_import_4_1, t_import_4_2, t_export_import].
|
||||||
|
|
||||||
groups() ->
|
groups() ->
|
||||||
[{username, [], cases()}, {clientid, [], cases()}].
|
[{username, [], cases()}, {clientid, [], cases()}].
|
||||||
|
@ -52,7 +52,8 @@ init_per_testcase(_, Config) ->
|
||||||
Config.
|
Config.
|
||||||
|
|
||||||
end_per_testcase(_, _Config) ->
|
end_per_testcase(_, _Config) ->
|
||||||
{atomic,ok} = mnesia:clear_table(emqx_acl),
|
{atomic,ok} = mnesia:clear_table(?ACL_TABLE),
|
||||||
|
{atomic,ok} = mnesia:clear_table(?ACL_TABLE2),
|
||||||
{atomic,ok} = mnesia:clear_table(emqx_user),
|
{atomic,ok} = mnesia:clear_table(emqx_user),
|
||||||
ok.
|
ok.
|
||||||
-ifdef(EMQX_ENTERPRISE).
|
-ifdef(EMQX_ENTERPRISE).
|
||||||
|
@ -138,25 +139,50 @@ t_import_4_2(Config) ->
|
||||||
test_import(clientid, {<<"client_for_test">>, <<"public">>}),
|
test_import(clientid, {<<"client_for_test">>, <<"public">>}),
|
||||||
test_import(username, {<<"user_for_test">>, <<"public">>}),
|
test_import(username, {<<"user_for_test">>, <<"public">>}),
|
||||||
|
|
||||||
?assertMatch([#emqx_acl{
|
?assertMatch([
|
||||||
filter = {{Type,<<"emqx_c">>}, <<"Topic/A">>},
|
{{username, <<"emqx_c">>}, <<"Topic/A">>, pub, allow, _},
|
||||||
action = pub,
|
{{username, <<"emqx_c">>}, <<"Topic/A">>, sub, allow, _}
|
||||||
access = allow
|
],
|
||||||
},
|
lists:sort(emqx_acl_mnesia_db:all_acls())).
|
||||||
#emqx_acl{
|
|
||||||
filter = {{Type,<<"emqx_c">>}, <<"Topic/A">>},
|
|
||||||
action = sub,
|
|
||||||
access = allow
|
|
||||||
}],
|
|
||||||
lists:sort(ets:tab2list(emqx_acl))).
|
|
||||||
-endif.
|
-endif.
|
||||||
|
|
||||||
|
t_export_import(_Config) ->
|
||||||
|
emqx_acl_mnesia_migrator:migrate_records(),
|
||||||
|
|
||||||
|
Records = [
|
||||||
|
#?ACL_TABLE2{who = {clientid,<<"client1">>}, rules = [{allow, sub, <<"t1">>, 1}]},
|
||||||
|
#?ACL_TABLE2{who = {clientid,<<"client2">>}, rules = [{allow, pub, <<"t2">>, 2}]}
|
||||||
|
],
|
||||||
|
mnesia:transaction(fun() -> lists:foreach(fun mnesia:write/1, Records) end),
|
||||||
|
timer:sleep(100),
|
||||||
|
|
||||||
|
AclData = emqx_json:encode(emqx_mgmt_data_backup:export_acl_mnesia()),
|
||||||
|
|
||||||
|
mnesia:transaction(fun() ->
|
||||||
|
lists:foreach(fun(#?ACL_TABLE2{who = Who}) ->
|
||||||
|
mnesia:delete({?ACL_TABLE2, Who})
|
||||||
|
end,
|
||||||
|
Records)
|
||||||
|
end),
|
||||||
|
|
||||||
|
?assertEqual([], emqx_acl_mnesia_db:all_acls()),
|
||||||
|
|
||||||
|
emqx_mgmt_data_backup:import_acl_mnesia(emqx_json:decode(AclData, [return_maps]), "4.3"),
|
||||||
|
timer:sleep(100),
|
||||||
|
|
||||||
|
?assertMatch([
|
||||||
|
{{clientid, <<"client1">>}, <<"t1">>, sub, allow, _},
|
||||||
|
{{clientid, <<"client2">>}, <<"t2">>, pub, allow, _}
|
||||||
|
], lists:sort(emqx_acl_mnesia_db:all_acls())).
|
||||||
|
|
||||||
do_import(File, Config) ->
|
do_import(File, Config) ->
|
||||||
do_import(File, Config, "{}").
|
do_import(File, Config, "{}").
|
||||||
|
|
||||||
do_import(File, Config, Overrides) ->
|
do_import(File, Config, Overrides) ->
|
||||||
mnesia:clear_table(emqx_acl),
|
mnesia:clear_table(?ACL_TABLE),
|
||||||
|
mnesia:clear_table(?ACL_TABLE2),
|
||||||
mnesia:clear_table(emqx_user),
|
mnesia:clear_table(emqx_user),
|
||||||
|
emqx_acl_mnesia_migrator:migrate_records(),
|
||||||
Filename = filename:join(proplists:get_value(data_dir, Config), File),
|
Filename = filename:join(proplists:get_value(data_dir, Config), File),
|
||||||
emqx_mgmt_data_backup:import(Filename, Overrides).
|
emqx_mgmt_data_backup:import(Filename, Overrides).
|
||||||
|
|
||||||
|
|
|
@ -158,9 +158,9 @@ t_clients_cmd(_) ->
|
||||||
timer:sleep(300),
|
timer:sleep(300),
|
||||||
emqx_mgmt_cli:clients(["list"]),
|
emqx_mgmt_cli:clients(["list"]),
|
||||||
?assertMatch({match, _}, re:run(emqx_mgmt_cli:clients(["show", "client12"]), "client12")),
|
?assertMatch({match, _}, re:run(emqx_mgmt_cli:clients(["show", "client12"]), "client12")),
|
||||||
?assertEqual((emqx_mgmt_cli:clients(["kick", "client12"])), "ok~n"),
|
?assertEqual("ok~n", emqx_mgmt_cli:clients(["kick", "client12"])),
|
||||||
timer:sleep(500),
|
timer:sleep(500),
|
||||||
?assertMatch({match, _}, re:run(emqx_mgmt_cli:clients(["show", "client12"]), "Not Found")),
|
?assertEqual("ok~n", emqx_mgmt_cli:clients(["kick", "client12"])),
|
||||||
receive
|
receive
|
||||||
{'EXIT', T, _} ->
|
{'EXIT', T, _} ->
|
||||||
ok
|
ok
|
||||||
|
|
|
@ -223,8 +223,8 @@ t_clients(_) ->
|
||||||
|
|
||||||
timer:sleep(300),
|
timer:sleep(300),
|
||||||
|
|
||||||
{ok, NotFound0} = request_api(delete, api_path(["clients", binary_to_list(ClientId1)]), auth_header_()),
|
{ok, Ok1} = request_api(delete, api_path(["clients", binary_to_list(ClientId1)]), auth_header_()),
|
||||||
?assertEqual(?ERROR12, get(<<"code">>, NotFound0)),
|
?assertEqual(?SUCCESS, get(<<"code">>, Ok1)),
|
||||||
|
|
||||||
{ok, Clients6} = request_api(get, api_path(["clients"]), "_limit=100&_page=1", auth_header_()),
|
{ok, Clients6} = request_api(get, api_path(["clients"]), "_limit=100&_page=1", auth_header_()),
|
||||||
?assertEqual(1, maps:get(<<"count">>, get(<<"meta">>, Clients6))),
|
?assertEqual(1, maps:get(<<"count">>, get(<<"meta">>, Clients6))),
|
||||||
|
@ -447,6 +447,19 @@ t_pubsub(_) ->
|
||||||
after 100 ->
|
after 100 ->
|
||||||
false
|
false
|
||||||
end),
|
end),
|
||||||
|
|
||||||
|
% no clientid
|
||||||
|
{ok, Code} = request_api(post, api_path(["mqtt/publish"]), [], auth_header_(),
|
||||||
|
#{<<"topic">> => <<"mytopic">>,
|
||||||
|
<<"qos">> => 1,
|
||||||
|
<<"payload">> => <<"hello">>}),
|
||||||
|
?assert(receive
|
||||||
|
{publish, #{payload := <<"hello">>}} ->
|
||||||
|
true
|
||||||
|
after 100 ->
|
||||||
|
false
|
||||||
|
end),
|
||||||
|
|
||||||
%% json payload
|
%% json payload
|
||||||
{ok, Code} = request_api(post, api_path(["mqtt/publish"]), [], auth_header_(),
|
{ok, Code} = request_api(post, api_path(["mqtt/publish"]), [], auth_header_(),
|
||||||
#{<<"clientid">> => ClientId,
|
#{<<"clientid">> => ClientId,
|
||||||
|
@ -491,9 +504,9 @@ t_pubsub(_) ->
|
||||||
|
|
||||||
ok = emqtt:disconnect(C1),
|
ok = emqtt:disconnect(C1),
|
||||||
|
|
||||||
?assertEqual(2, emqx_metrics:val('messages.qos1.received') - Qos1Received),
|
?assertEqual(3, emqx_metrics:val('messages.qos1.received') - Qos1Received),
|
||||||
?assertEqual(2, emqx_metrics:val('messages.qos2.received') - Qos2Received),
|
?assertEqual(2, emqx_metrics:val('messages.qos2.received') - Qos2Received),
|
||||||
?assertEqual(4, emqx_metrics:val('messages.received') - Received).
|
?assertEqual(5, emqx_metrics:val('messages.received') - Received).
|
||||||
|
|
||||||
loop([]) -> [];
|
loop([]) -> [];
|
||||||
|
|
||||||
|
|
|
@ -57,7 +57,7 @@
|
||||||
type => string,
|
type => string,
|
||||||
default => <<"5s">>,
|
default => <<"5s">>,
|
||||||
title => #{en => <<"Request Timeout">>,
|
title => #{en => <<"Request Timeout">>,
|
||||||
zh => <<"请求超时时间时间"/utf8>>},
|
zh => <<"请求超时时间"/utf8>>},
|
||||||
description => #{en => <<"Request Timeout In Seconds">>,
|
description => #{en => <<"Request Timeout In Seconds">>,
|
||||||
zh => <<"请求超时时间"/utf8>>}},
|
zh => <<"请求超时时间"/utf8>>}},
|
||||||
pool_size => #{order => 4,
|
pool_size => #{order => 4,
|
||||||
|
|
69
bin/emqx
69
bin/emqx
|
@ -20,6 +20,41 @@ mkdir -p "$RUNNER_LOG_DIR"
|
||||||
# Make sure data directory exists
|
# Make sure data directory exists
|
||||||
mkdir -p "$RUNNER_DATA_DIR"
|
mkdir -p "$RUNNER_DATA_DIR"
|
||||||
|
|
||||||
|
export ROOTDIR="$RUNNER_ROOT_DIR"
|
||||||
|
export ERTS_DIR="$ROOTDIR/erts-$ERTS_VSN"
|
||||||
|
export BINDIR="$ERTS_DIR/bin"
|
||||||
|
export EMU="beam"
|
||||||
|
export PROGNAME="erl"
|
||||||
|
DYNLIBS_DIR="$RUNNER_ROOT_DIR/dynlibs"
|
||||||
|
ERTS_LIB_DIR="$ERTS_DIR/../lib"
|
||||||
|
MNESIA_DATA_DIR="$RUNNER_DATA_DIR/mnesia/$NAME"
|
||||||
|
|
||||||
|
# Echo to stderr on errors
|
||||||
|
echoerr() { echo "$*" 1>&2; }
|
||||||
|
|
||||||
|
check_eralng_start() {
|
||||||
|
"$BINDIR/$PROGNAME" -noshell -boot "$REL_DIR/start_clean" -s crypto start -s init stop
|
||||||
|
}
|
||||||
|
|
||||||
|
if ! check_eralng_start >/dev/null 2>&1; then
|
||||||
|
BUILT_ON="$(head -1 "${REL_DIR}/BUILT_ON")"
|
||||||
|
## failed to start, might be due to missing libs, try to be portable
|
||||||
|
export LD_LIBRARY_PATH="$DYNLIBS_DIR:$LD_LIBRARY_PATH"
|
||||||
|
if ! check_eralng_start; then
|
||||||
|
## it's hopeless
|
||||||
|
echoerr "FATAL: Unable to start Erlang (with libcrypto)."
|
||||||
|
echoerr "Please make sure it's running on the correct platform with all required dependencies."
|
||||||
|
echoerr "This EMQ X release is built for $BUILT_ON"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echoerr "WARNING: There seem to be missing dynamic libs from the OS. Using libs from ${DYNLIBS_DIR}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
## backward compatible
|
||||||
|
if [ -d "$ERTS_DIR/lib" ]; then
|
||||||
|
export LD_LIBRARY_PATH="$ERTS_DIR/lib:$LD_LIBRARY_PATH"
|
||||||
|
fi
|
||||||
|
|
||||||
# cuttlefish try to read environment variables starting with "EMQX_"
|
# cuttlefish try to read environment variables starting with "EMQX_"
|
||||||
export CUTTLEFISH_ENV_OVERRIDE_PREFIX='EMQX_'
|
export CUTTLEFISH_ENV_OVERRIDE_PREFIX='EMQX_'
|
||||||
|
|
||||||
|
@ -120,9 +155,6 @@ if [ "$ULIMIT_F" -lt 1024 ]; then
|
||||||
echo "!!!!"
|
echo "!!!!"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Echo to stderr on errors
|
|
||||||
echoerr() { echo "$@" 1>&2; }
|
|
||||||
|
|
||||||
# By default, use cuttlefish to generate app.config and vm.args
|
# By default, use cuttlefish to generate app.config and vm.args
|
||||||
CUTTLEFISH="${USE_CUTTLEFISH:-yes}"
|
CUTTLEFISH="${USE_CUTTLEFISH:-yes}"
|
||||||
|
|
||||||
|
@ -238,12 +270,22 @@ generate_config() {
|
||||||
sed '/^#/d' "$CUTTLE_GEN_ARG_FILE" | sed '/^$/d' | while IFS='' read -r ARG_LINE || [ -n "$ARG_LINE" ]; do
|
sed '/^#/d' "$CUTTLE_GEN_ARG_FILE" | sed '/^$/d' | while IFS='' read -r ARG_LINE || [ -n "$ARG_LINE" ]; do
|
||||||
ARG_KEY=$(echo "$ARG_LINE" | awk '{$NF="";print}')
|
ARG_KEY=$(echo "$ARG_LINE" | awk '{$NF="";print}')
|
||||||
ARG_VALUE=$(echo "$ARG_LINE" | awk '{print $NF}')
|
ARG_VALUE=$(echo "$ARG_LINE" | awk '{print $NF}')
|
||||||
TMP_ARG_VALUE=$(grep "^$ARG_KEY" "$TMP_ARG_FILE" | awk '{print $NF}')
|
if [ "$ARG_KEY" = '' ]; then
|
||||||
if [ "$ARG_VALUE" != "$TMP_ARG_VALUE" ] ; then
|
## for the flags, e.g. -heart -emu_args etc
|
||||||
if [ -n "$TMP_ARG_VALUE" ]; then
|
ARG_KEY=$(echo "$ARG_LINE" | awk '{print $1}')
|
||||||
sh -c "$SED_REPLACE 's/^$ARG_KEY.*$/$ARG_LINE/' $TMP_ARG_FILE"
|
ARG_VALUE=''
|
||||||
else
|
TMP_ARG_KEY=$(grep "^$ARG_KEY" "$TMP_ARG_FILE" | awk '{print $1}')
|
||||||
echo "$ARG_LINE" >> "$TMP_ARG_FILE"
|
if [ "$TMP_ARG_KEY" = '' ]; then
|
||||||
|
echo "$ARG_KEY" >> "$TMP_ARG_FILE"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
TMP_ARG_VALUE=$(grep "^$ARG_KEY" "$TMP_ARG_FILE" | awk '{print $NF}')
|
||||||
|
if [ "$ARG_VALUE" != "$TMP_ARG_VALUE" ] ; then
|
||||||
|
if [ -n "$TMP_ARG_VALUE" ]; then
|
||||||
|
sh -c "$SED_REPLACE 's/^$ARG_KEY.*$/$ARG_LINE/' $TMP_ARG_FILE"
|
||||||
|
else
|
||||||
|
echo "$ARG_LINE" >> "$TMP_ARG_FILE"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
@ -354,15 +396,6 @@ else
|
||||||
PROTO_DIST_ARG="-proto_dist $PROTO_DIST"
|
PROTO_DIST_ARG="-proto_dist $PROTO_DIST"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
export ROOTDIR="$RUNNER_ROOT_DIR"
|
|
||||||
export ERTS_DIR="$ROOTDIR/erts-$ERTS_VSN"
|
|
||||||
export BINDIR="$ERTS_DIR/bin"
|
|
||||||
export EMU="beam"
|
|
||||||
export PROGNAME="erl"
|
|
||||||
export LD_LIBRARY_PATH="$ERTS_DIR/lib:$LD_LIBRARY_PATH"
|
|
||||||
ERTS_LIB_DIR="$ERTS_DIR/../lib"
|
|
||||||
MNESIA_DATA_DIR="$RUNNER_DATA_DIR/mnesia/$NAME"
|
|
||||||
|
|
||||||
cd "$ROOTDIR"
|
cd "$ROOTDIR"
|
||||||
|
|
||||||
# User can specify an sname without @hostname
|
# User can specify an sname without @hostname
|
||||||
|
|
32
build
32
build
|
@ -15,18 +15,7 @@ cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")"
|
||||||
PKG_VSN="${PKG_VSN:-$(./pkg-vsn.sh)}"
|
PKG_VSN="${PKG_VSN:-$(./pkg-vsn.sh)}"
|
||||||
export PKG_VSN
|
export PKG_VSN
|
||||||
|
|
||||||
if [ "$(uname -s)" = 'Darwin' ]; then
|
SYSTEM="$(./scripts/get-distro.sh)"
|
||||||
SYSTEM=macos
|
|
||||||
elif [ "$(uname -s)" = 'Linux' ]; then
|
|
||||||
if grep -q -i 'centos' /etc/*-release; then
|
|
||||||
DIST='centos'
|
|
||||||
VERSION_ID="$(rpm --eval '%{centos_ver}')"
|
|
||||||
else
|
|
||||||
DIST="$(sed -n '/^ID=/p' /etc/os-release | sed -r 's/ID=(.*)/\1/g' | sed 's/"//g')"
|
|
||||||
VERSION_ID="$(sed -n '/^VERSION_ID=/p' /etc/os-release | sed -r 's/VERSION_ID=(.*)/\1/g' | sed 's/"//g')"
|
|
||||||
fi
|
|
||||||
SYSTEM="$(echo "${DIST}${VERSION_ID}" | sed -r 's/([a-zA-Z]*)-.*/\1/g')"
|
|
||||||
fi
|
|
||||||
|
|
||||||
ARCH="$(uname -m)"
|
ARCH="$(uname -m)"
|
||||||
case "$ARCH" in
|
case "$ARCH" in
|
||||||
|
@ -46,8 +35,8 @@ export ARCH
|
||||||
## Support RPM and Debian based linux systems
|
## Support RPM and Debian based linux systems
|
||||||
##
|
##
|
||||||
if [ "$(uname -s)" = 'Linux' ]; then
|
if [ "$(uname -s)" = 'Linux' ]; then
|
||||||
case "${DIST:-}" in
|
case "${SYSTEM:-}" in
|
||||||
ubuntu|debian|raspbian)
|
ubuntu*|debian*|raspbian*)
|
||||||
PKGERDIR='deb'
|
PKGERDIR='deb'
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
|
@ -98,6 +87,18 @@ make_relup() {
|
||||||
./rebar3 as "$PROFILE" relup --relname emqx --relvsn "${PKG_VSN}"
|
./rebar3 as "$PROFILE" relup --relname emqx --relvsn "${PKG_VSN}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cp_dyn_libs() {
|
||||||
|
local rel_dir="$1"
|
||||||
|
local target_dir="${rel_dir}/dynlibs"
|
||||||
|
if ! [ "$(uname -s)" = 'Linux' ]; then
|
||||||
|
return 0;
|
||||||
|
fi
|
||||||
|
mkdir -p "$target_dir"
|
||||||
|
while read -r so_file; do
|
||||||
|
cp -L "$so_file" "$target_dir/"
|
||||||
|
done < <(find "$rel_dir" -type f \( -name "*.so*" -o -name "beam.smp" \) -print0 | xargs -0 ldd | grep -E '^\s+.*=>\s(/lib|/usr)' | awk '{print $3}')
|
||||||
|
}
|
||||||
|
|
||||||
## make_zip turns .tar.gz into a .zip with a slightly different name.
|
## make_zip turns .tar.gz into a .zip with a slightly different name.
|
||||||
## It assumes the .tar.gz has been built -- relies on Makefile dependency
|
## It assumes the .tar.gz has been built -- relies on Makefile dependency
|
||||||
make_zip() {
|
make_zip() {
|
||||||
|
@ -117,6 +118,9 @@ make_zip() {
|
||||||
local zipball
|
local zipball
|
||||||
zipball="${pkgpath}/${PROFILE}-${SYSTEM}-${PKG_VSN}-${ARCH}.zip"
|
zipball="${pkgpath}/${PROFILE}-${SYSTEM}-${PKG_VSN}-${ARCH}.zip"
|
||||||
tar zxf "${tarball}" -C "${tard}/emqx"
|
tar zxf "${tarball}" -C "${tard}/emqx"
|
||||||
|
## try to be portable for zip packages.
|
||||||
|
## for DEB and RPM packages the dependencies are resoved by yum and apt
|
||||||
|
cp_dyn_libs "${tard}/emqx"
|
||||||
(cd "${tard}" && zip -qr - emqx) > "${zipball}"
|
(cd "${tard}" && zip -qr - emqx) > "${zipball}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,7 @@ LABEL org.label-schema.docker.dockerfile="Dockerfile" \
|
||||||
org.label-schema.url="https://emqx.io" \
|
org.label-schema.url="https://emqx.io" \
|
||||||
org.label-schema.vcs-type="Git" \
|
org.label-schema.vcs-type="Git" \
|
||||||
org.label-schema.vcs-url="https://github.com/emqx/emqx" \
|
org.label-schema.vcs-url="https://github.com/emqx/emqx" \
|
||||||
maintainer="Raymond M Mouthaan <raymondmmouthaan@gmail.com>, Huang Rui <vowstar@gmail.com>, EMQ X Team <support@emqx.io>"
|
maintainer="EMQ X Team <support@emqx.io>"
|
||||||
|
|
||||||
ARG QEMU_ARCH=x86_64
|
ARG QEMU_ARCH=x86_64
|
||||||
ARG EMQX_NAME=emqx
|
ARG EMQX_NAME=emqx
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
#!/usr/bin/make -f
|
#!/usr/bin/make -f
|
||||||
# -*- makefile -*-
|
# -*- makefile -*-
|
||||||
|
|
||||||
## default globals
|
## default globals.
|
||||||
TARGET ?= emqx/emqx
|
## when built with `make docker` command the default profile is either emqx or emqx-ee (for enterprise)
|
||||||
|
## or the TARGET varialbe can be set beforehand to force a different name
|
||||||
|
TARGET ?= emqx/$(PROFILE)
|
||||||
QEMU_ARCH ?= x86_64
|
QEMU_ARCH ?= x86_64
|
||||||
ARCH ?= amd64
|
ARCH ?= amd64
|
||||||
QEMU_VERSION ?= v5.0.0-2
|
QEMU_VERSION ?= v5.0.0-2
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
{{built_on_arch}}
|
{{built_on_platform}}
|
||||||
|
|
|
@ -199,6 +199,16 @@ node.data_dir = {{ platform_data_dir }}
|
||||||
## Heartbeat monitoring of an Erlang runtime system. Comment the line to disable
|
## Heartbeat monitoring of an Erlang runtime system. Comment the line to disable
|
||||||
## heartbeat, or set the value as 'on'
|
## heartbeat, or set the value as 'on'
|
||||||
##
|
##
|
||||||
|
## Turning this on may cause the node to restart if it becomes unresponsive to
|
||||||
|
## the heartbeat pings.
|
||||||
|
##
|
||||||
|
## NOTE: When managed by systemd (or other supervision tools like systemd),
|
||||||
|
## heart will probably only cause EMQ X to stop, but restart or not will
|
||||||
|
## depend on systemd's restart strategy.
|
||||||
|
## NOTE: When running in docker, the container will die as soon as the the
|
||||||
|
## heart process kills EMQ X, but restart or not will depend on container
|
||||||
|
## supervision strategy, such as k8s restartPolicy.
|
||||||
|
##
|
||||||
## Value: on
|
## Value: on
|
||||||
##
|
##
|
||||||
## vm.args: -heart
|
## vm.args: -heart
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
|
|
||||||
-ifndef(EMQX_ENTERPRISE).
|
-ifndef(EMQX_ENTERPRISE).
|
||||||
|
|
||||||
-define(EMQX_RELEASE, {opensource, "4.3.8"}).
|
-define(EMQX_RELEASE, {opensource, "4.3.9"}).
|
||||||
|
|
||||||
-else.
|
-else.
|
||||||
|
|
||||||
|
|
|
@ -1,20 +1,18 @@
|
||||||
%% -*- mode: erlang -*-
|
%% -*- mode: erlang -*-
|
||||||
{VSN,
|
{VSN,
|
||||||
[ {<<"4.3.[0-9]">>,
|
[ {<<".*">>,
|
||||||
%% load all plugins
|
%% load all plugins
|
||||||
%% NOTE: this depends on the fact that emqx_dashboard is always
|
%% NOTE: this depends on the fact that emqx_dashboard is always
|
||||||
%% the last application gets upgraded
|
%% the last application gets upgraded
|
||||||
[ {apply, {emqx_rule_engine, load_providers, []}}
|
[ {apply, {emqx_rule_engine, load_providers, []}}
|
||||||
, {restart_application, emqx_dashboard}
|
, {restart_application, emqx_dashboard}
|
||||||
, {apply, {emqx_plugins, load, []}}
|
, {apply, {emqx_plugins, load, []}}
|
||||||
]},
|
]}
|
||||||
{<<".*">>, []}
|
|
||||||
],
|
],
|
||||||
[ {<<"4.3.[0-9]">>,
|
[ {<<".*">>,
|
||||||
[ {apply, {emqx_rule_engine, load_providers, []}}
|
[ {apply, {emqx_rule_engine, load_providers, []}}
|
||||||
, {restart_application, emqx_dashboard}
|
, {restart_application, emqx_dashboard}
|
||||||
, {apply, {emqx_plugins, load, []}}
|
, {apply, {emqx_plugins, load, []}}
|
||||||
]},
|
]}
|
||||||
{<<".*">>, []}
|
|
||||||
]
|
]
|
||||||
}.
|
}.
|
||||||
|
|
|
@ -55,7 +55,7 @@
|
||||||
, {recon, {git, "https://github.com/ferd/recon", {tag, "2.5.1"}}}
|
, {recon, {git, "https://github.com/ferd/recon", {tag, "2.5.1"}}}
|
||||||
, {observer_cli, "1.6.1"} % NOTE: depends on recon 2.5.1
|
, {observer_cli, "1.6.1"} % NOTE: depends on recon 2.5.1
|
||||||
, {getopt, "1.0.1"}
|
, {getopt, "1.0.1"}
|
||||||
, {snabbkaffe, {git, "https://github.com/kafka4beam/snabbkaffe.git", {tag, "0.13.0"}}}
|
, {snabbkaffe, {git, "https://github.com/kafka4beam/snabbkaffe.git", {tag, "0.15.0"}}}
|
||||||
]}.
|
]}.
|
||||||
|
|
||||||
{xref_ignores,
|
{xref_ignores,
|
||||||
|
|
|
@ -173,11 +173,24 @@ relx(Vsn, RelType, PkgType) ->
|
||||||
, {vm_args,false}
|
, {vm_args,false}
|
||||||
, {release, {emqx, Vsn}, relx_apps(RelType)}
|
, {release, {emqx, Vsn}, relx_apps(RelType)}
|
||||||
, {overlay, relx_overlay(RelType)}
|
, {overlay, relx_overlay(RelType)}
|
||||||
, {overlay_vars, [ {built_on_arch, rebar_utils:get_arch()}
|
, {overlay_vars, [ {built_on_platform, built_on()}
|
||||||
, {emqx_description, emqx_description(RelType, IsEnterprise)}
|
, {emqx_description, emqx_description(RelType, IsEnterprise)}
|
||||||
| overlay_vars(RelType, PkgType, IsEnterprise)]}
|
| overlay_vars(RelType, PkgType, IsEnterprise)]}
|
||||||
].
|
].
|
||||||
|
|
||||||
|
built_on() ->
|
||||||
|
On = rebar_utils:get_arch(),
|
||||||
|
case distro() of
|
||||||
|
false -> On;
|
||||||
|
Distro -> On ++ "-" ++ Distro
|
||||||
|
end.
|
||||||
|
|
||||||
|
distro() ->
|
||||||
|
case os:type() of
|
||||||
|
{unix, _} -> string:strip(os:cmd("scripts/get-distro.sh"), both, $\n);
|
||||||
|
_ -> false
|
||||||
|
end.
|
||||||
|
|
||||||
emqx_description(cloud, true) -> "EMQ X Enterprise";
|
emqx_description(cloud, true) -> "EMQ X Enterprise";
|
||||||
emqx_description(cloud, false) -> "EMQ X Broker";
|
emqx_description(cloud, false) -> "EMQ X Broker";
|
||||||
emqx_description(edge, _) -> "EMQ X Edge".
|
emqx_description(edge, _) -> "EMQ X Edge".
|
||||||
|
|
|
@ -18,7 +18,7 @@ while read -r app; do
|
||||||
changed="$(git diff --name-only "$latest_release"...HEAD \
|
changed="$(git diff --name-only "$latest_release"...HEAD \
|
||||||
-- "$app_path/src" \
|
-- "$app_path/src" \
|
||||||
-- "$app_path/priv" \
|
-- "$app_path/priv" \
|
||||||
-- "$app_path/c_src" | wc -l)"
|
-- "$app_path/c_src" | { grep -v -E 'appup\.src' || true; } | wc -l)"
|
||||||
if [ "$changed" -gt 0 ]; then
|
if [ "$changed" -gt 0 ]; then
|
||||||
echo "$src_file needs a vsn bump"
|
echo "$src_file needs a vsn bump"
|
||||||
bad_app_count=$(( bad_app_count + 1))
|
bad_app_count=$(( bad_app_count + 1))
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
## This script prints Linux distro name and its version number
|
||||||
|
## e.g. macos, centos8, ubuntu20.04
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if [ "$(uname -s)" = 'Darwin' ]; then
|
||||||
|
echo 'macos'
|
||||||
|
elif [ "$(uname -s)" = 'Linux' ]; then
|
||||||
|
if grep -q -i 'centos' /etc/*-release; then
|
||||||
|
DIST='centos'
|
||||||
|
VERSION_ID="$(rpm --eval '%{centos_ver}')"
|
||||||
|
else
|
||||||
|
DIST="$(sed -n '/^ID=/p' /etc/os-release | sed -r 's/ID=(.*)/\1/g' | sed 's/"//g')"
|
||||||
|
VERSION_ID="$(sed -n '/^VERSION_ID=/p' /etc/os-release | sed -r 's/VERSION_ID=(.*)/\1/g' | sed 's/"//g')"
|
||||||
|
fi
|
||||||
|
echo "${DIST}${VERSION_ID}" | sed -r 's/([a-zA-Z]*)-.*/\1/g'
|
||||||
|
fi
|
|
@ -0,0 +1,106 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# shellcheck disable=2090
|
||||||
|
###############
|
||||||
|
## args and env validation
|
||||||
|
###############
|
||||||
|
|
||||||
|
if ! [ -d "emqx" ]; then
|
||||||
|
echo "[error] this script must be run at the same dir as the emqx"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $# -eq 0 ]
|
||||||
|
then
|
||||||
|
echo "[error] a new emqx name should be provided!"
|
||||||
|
echo "Usage: ./one_more_emqx <new_name>"
|
||||||
|
echo " e.g. ./one_more_emqx emqx2"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
NEW_EMQX=$1
|
||||||
|
if [ -d "$NEW_EMQX" ]; then
|
||||||
|
echo "[error] a dir named ${NEW_EMQX} already exists!"
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
echo creating "$NEW_EMQX" ...
|
||||||
|
|
||||||
|
SED_REPLACE="sed -i "
|
||||||
|
# shellcheck disable=2089
|
||||||
|
case $(sed --help 2>&1) in
|
||||||
|
*GNU*) SED_REPLACE="sed -i ";;
|
||||||
|
*) SED_REPLACE="sed -i ''";;
|
||||||
|
esac
|
||||||
|
|
||||||
|
PORT_INC_=$(cksum <<< "$NEW_EMQX" | cut -f 1 -d ' ')
|
||||||
|
PORT_INC=$((PORT_INC_ % 1000))
|
||||||
|
echo using increment factor: $PORT_INC
|
||||||
|
|
||||||
|
###############
|
||||||
|
## helpers
|
||||||
|
###############
|
||||||
|
process_emqx_conf() {
|
||||||
|
echo "processing config file: $1"
|
||||||
|
$SED_REPLACE '/^#/d' "$1"
|
||||||
|
$SED_REPLACE '/^$/d' "$1"
|
||||||
|
|
||||||
|
for entry_ in "${entries_to_be_inc[@]}"
|
||||||
|
do
|
||||||
|
echo inc port for "$entry_"
|
||||||
|
ip_port_=$(grep -E "$entry_"'[ \t]*=' "$1" 2> /dev/null | tail -1 | cut -d = -f 2- | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')
|
||||||
|
echo -- from: "$ip_port_"
|
||||||
|
ip_=$(echo "$ip_port_" | cut -sd : -f 1)
|
||||||
|
port_=$(echo "$ip_port_" | cut -sd : -f 2)
|
||||||
|
if [ -z "$ip_" ]
|
||||||
|
then
|
||||||
|
new_ip_port=$(( ip_port_ + PORT_INC ))
|
||||||
|
else
|
||||||
|
new_ip_port="${ip_}:$(( port_ + PORT_INC ))"
|
||||||
|
fi
|
||||||
|
echo -- to: "$new_ip_port"
|
||||||
|
$SED_REPLACE 's|'"$entry_"'[ \t]*=.*|'"$entry_"' = '"$new_ip_port"'|g' "$1"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
###############
|
||||||
|
## main
|
||||||
|
###############
|
||||||
|
|
||||||
|
cp -r emqx "$NEW_EMQX"
|
||||||
|
|
||||||
|
## change the rpc ports
|
||||||
|
$SED_REPLACE 's|tcp_server_port[ \t]*=.*|tcp_server_port = 5369|g' emqx/etc/rpc.conf
|
||||||
|
$SED_REPLACE 's|tcp_client_port[ \t]*=.*|tcp_client_port = 5370|g' emqx/etc/rpc.conf
|
||||||
|
$SED_REPLACE 's|tcp_client_port[ \t]*=.*|tcp_client_port = 5369|g' "$NEW_EMQX/etc/rpc.conf"
|
||||||
|
$SED_REPLACE 's|tcp_server_port[ \t]*=.*|tcp_server_port = 5370|g' "$NEW_EMQX/etc/rpc.conf"
|
||||||
|
$SED_REPLACE 's|.*node\.name.*|node.name='"$NEW_EMQX"'@127.0.0.1|g' "$NEW_EMQX/etc/emqx.conf"
|
||||||
|
|
||||||
|
conf_ext="*.conf"
|
||||||
|
|
||||||
|
find "$NEW_EMQX" -name "${conf_ext}" | while read -r conf; do
|
||||||
|
if [ "${conf##*/}" = 'emqx.conf' ]
|
||||||
|
then
|
||||||
|
declare -a entries_to_be_inc=("node.dist_listen_min"
|
||||||
|
"node.dist_listen_max")
|
||||||
|
process_emqx_conf "$conf" "${entries_to_be_inc[@]}"
|
||||||
|
elif [ "${conf##*/}" = 'listeners.conf' ]
|
||||||
|
then
|
||||||
|
declare -a entries_to_be_inc=("listener.tcp.external"
|
||||||
|
"listener.tcp.internal"
|
||||||
|
"listener.ssl.external"
|
||||||
|
"listener.ws.external"
|
||||||
|
"listener.wss.external")
|
||||||
|
process_emqx_conf "$conf" "${entries_to_be_inc[@]}"
|
||||||
|
elif [ "${conf##*/}" = 'emqx_management.conf' ]
|
||||||
|
then
|
||||||
|
declare -a entries_to_be_inc=("management.listener.http"
|
||||||
|
"management.listener.https")
|
||||||
|
process_emqx_conf "$conf" "${entries_to_be_inc[@]}"
|
||||||
|
elif [ "${conf##*/}" = 'emqx_dashboard.conf' ]
|
||||||
|
then
|
||||||
|
declare -a entries_to_be_inc=("dashboard.listener.http"
|
||||||
|
"dashboard.listener.https")
|
||||||
|
process_emqx_conf "$conf" "${entries_to_be_inc[@]}"
|
||||||
|
else
|
||||||
|
echo "."
|
||||||
|
fi
|
||||||
|
done
|
|
@ -0,0 +1,102 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# shellcheck disable=2090
|
||||||
|
###############
|
||||||
|
## args and env validation
|
||||||
|
###############
|
||||||
|
|
||||||
|
if ! [ -d "emqx" ]; then
|
||||||
|
echo "[error] this script must be run at the same dir as the emqx"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $# -eq 0 ]
|
||||||
|
then
|
||||||
|
echo "[error] a new emqx name should be provided!"
|
||||||
|
echo "Usage: ./one_more_emqx <new_name>"
|
||||||
|
echo " e.g. ./one_more_emqx emqx2"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
NEW_EMQX=$1
|
||||||
|
if [ -d "$NEW_EMQX" ]; then
|
||||||
|
echo "[error] a dir named ${NEW_EMQX} already exists!"
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
echo creating "$NEW_EMQX" ...
|
||||||
|
|
||||||
|
SED_REPLACE="sed -i "
|
||||||
|
# shellcheck disable=2089
|
||||||
|
case $(sed --help 2>&1) in
|
||||||
|
*GNU*) SED_REPLACE="sed -i ";;
|
||||||
|
*) SED_REPLACE="sed -i ''";;
|
||||||
|
esac
|
||||||
|
|
||||||
|
PORT_INC_=$(cksum <<< "$NEW_EMQX" | cut -f 1 -d ' ')
|
||||||
|
PORT_INC=$((PORT_INC_ % 1000))
|
||||||
|
echo using increment factor: "$PORT_INC"
|
||||||
|
|
||||||
|
###############
|
||||||
|
## helpers
|
||||||
|
###############
|
||||||
|
process_emqx_conf() {
|
||||||
|
echo "processing config file: $1"
|
||||||
|
$SED_REPLACE '/^#/d' "$1"
|
||||||
|
$SED_REPLACE '/^$/d' "$1"
|
||||||
|
$SED_REPLACE 's|.*node\.name.*|node.name='"$NEW_EMQX"'@127.0.0.1|g' "$1"
|
||||||
|
|
||||||
|
for entry_ in "${entries_to_be_inc[@]}"
|
||||||
|
do
|
||||||
|
echo inc port for "$entry_"
|
||||||
|
ip_port_=$(grep -E "$entry_"'[ \t]*=' "$1" 2> /dev/null | tail -1 | cut -d = -f 2- | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')
|
||||||
|
echo -- from: "$ip_port_"
|
||||||
|
ip_=$(echo "$ip_port_" | cut -sd : -f 1)
|
||||||
|
port_=$(echo "$ip_port_" | cut -sd : -f 2)
|
||||||
|
if [ -z "$ip_" ]
|
||||||
|
then
|
||||||
|
new_ip_port=$(( ip_port_ + PORT_INC ))
|
||||||
|
else
|
||||||
|
new_ip_port="${ip_}:$(( port_ + PORT_INC ))"
|
||||||
|
fi
|
||||||
|
echo -- to: "$new_ip_port"
|
||||||
|
$SED_REPLACE 's|'"$entry_"'[ \t]*=.*|'"$entry_"' = '"$new_ip_port"'|g' "$1"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
###############
|
||||||
|
## main
|
||||||
|
###############
|
||||||
|
|
||||||
|
cp -r emqx "$NEW_EMQX"
|
||||||
|
|
||||||
|
## change the rpc ports
|
||||||
|
$SED_REPLACE 's|tcp_server_port[ \t]*=.*|tcp_server_port = 5369|g' emqx/etc/emqx.conf
|
||||||
|
$SED_REPLACE 's|tcp_client_port[ \t]*=.*|tcp_client_port = 5370|g' emqx/etc/emqx.conf
|
||||||
|
$SED_REPLACE 's|tcp_client_port[ \t]*=.*|tcp_client_port = 5369|g' "$NEW_EMQX/etc/emqx.conf"
|
||||||
|
$SED_REPLACE 's|tcp_server_port[ \t]*=.*|tcp_server_port = 5370|g' "$NEW_EMQX/etc/emqx.conf"
|
||||||
|
|
||||||
|
conf_ext="*.conf"
|
||||||
|
find "$NEW_EMQX" -name "${conf_ext}" | while read -r conf; do
|
||||||
|
if [ "${conf##*/}" = 'emqx.conf' ]
|
||||||
|
then
|
||||||
|
declare -a entries_to_be_inc=("node.dist_listen_min"
|
||||||
|
"dist_listen_max"
|
||||||
|
"listener.tcp.external"
|
||||||
|
"listener.tcp.internal"
|
||||||
|
"listener.ssl.external"
|
||||||
|
"listener.ws.external"
|
||||||
|
"listener.wss.external")
|
||||||
|
process_emqx_conf "$conf" "${entries_to_be_inc[@]}"
|
||||||
|
elif [ "${conf##*/}" = 'emqx_management.conf' ]
|
||||||
|
then
|
||||||
|
declare -a entries_to_be_inc=("management.listener.http"
|
||||||
|
"management.listener.https")
|
||||||
|
process_emqx_conf "$conf" "${entries_to_be_inc[@]}"
|
||||||
|
elif [ "${conf##*/}" = 'emqx_dashboard.conf' ]
|
||||||
|
then
|
||||||
|
declare -a entries_to_be_inc=("dashboard.listener.http"
|
||||||
|
"dashboard.listener.https")
|
||||||
|
process_emqx_conf "$conf" "${entries_to_be_inc[@]}"
|
||||||
|
else
|
||||||
|
echo "."
|
||||||
|
fi
|
||||||
|
done
|
|
@ -1,21 +1,27 @@
|
||||||
%% -*- mode: erlang -*-
|
%% -*- mode: erlang -*-
|
||||||
{VSN,
|
{VSN,
|
||||||
[{"4.3.9",
|
[{"4.3.9",
|
||||||
[{load_module,emqx_ws_connection,brutal_purge,soft_purge,[]},
|
[{load_module,emqx_channel,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module,emqx_cm,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module,emqx_ws_connection,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_pqueue,brutal_purge,soft_purge,[]},
|
{load_module,emqx_pqueue,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_mqueue,brutal_purge,soft_purge,[]},
|
{load_module,emqx_mqueue,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_frame,brutal_purge,soft_purge,[]},
|
{load_module,emqx_frame,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_rpc,brutal_purge,soft_purge,[]},
|
{load_module,emqx_rpc,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_app,brutal_purge,soft_purge,[]}]},
|
{load_module,emqx_app,brutal_purge,soft_purge,[]}]},
|
||||||
{"4.3.8",
|
{"4.3.8",
|
||||||
[{load_module,emqx_ws_connection,brutal_purge,soft_purge,[]},
|
[{load_module,emqx_channel,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module,emqx_cm,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module,emqx_ws_connection,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_pqueue,brutal_purge,soft_purge,[]},
|
{load_module,emqx_pqueue,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_mqueue,brutal_purge,soft_purge,[]},
|
{load_module,emqx_mqueue,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_frame,brutal_purge,soft_purge,[]},
|
{load_module,emqx_frame,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_rpc,brutal_purge,soft_purge,[]},
|
{load_module,emqx_rpc,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_app,brutal_purge,soft_purge,[]}]},
|
{load_module,emqx_app,brutal_purge,soft_purge,[]}]},
|
||||||
{"4.3.7",
|
{"4.3.7",
|
||||||
[{load_module,emqx_ws_connection,brutal_purge,soft_purge,[]},
|
[{load_module,emqx_channel,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module,emqx_cm,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module,emqx_ws_connection,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_alarm_handler,brutal_purge,soft_purge,[]},
|
{load_module,emqx_alarm_handler,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_misc,brutal_purge,soft_purge,[]},
|
{load_module,emqx_misc,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_pqueue,brutal_purge,soft_purge,[]},
|
{load_module,emqx_pqueue,brutal_purge,soft_purge,[]},
|
||||||
|
@ -24,7 +30,9 @@
|
||||||
{load_module,emqx_rpc,brutal_purge,soft_purge,[]},
|
{load_module,emqx_rpc,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_app,brutal_purge,soft_purge,[]}]},
|
{load_module,emqx_app,brutal_purge,soft_purge,[]}]},
|
||||||
{"4.3.6",
|
{"4.3.6",
|
||||||
[{load_module,emqx_ws_connection,brutal_purge,soft_purge,[]},
|
[{load_module,emqx_channel,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module,emqx_cm,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module,emqx_ws_connection,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_alarm_handler,brutal_purge,soft_purge,[]},
|
{load_module,emqx_alarm_handler,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_misc,brutal_purge,soft_purge,[]},
|
{load_module,emqx_misc,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_ctl,brutal_purge,soft_purge,[]},
|
{load_module,emqx_ctl,brutal_purge,soft_purge,[]},
|
||||||
|
@ -34,7 +42,8 @@
|
||||||
{load_module,emqx_rpc,brutal_purge,soft_purge,[]},
|
{load_module,emqx_rpc,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_app,brutal_purge,soft_purge,[]}]},
|
{load_module,emqx_app,brutal_purge,soft_purge,[]}]},
|
||||||
{"4.3.5",
|
{"4.3.5",
|
||||||
[{load_module,emqx_ws_connection,brutal_purge,soft_purge,[]},
|
[{load_module,emqx_channel,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module,emqx_ws_connection,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_alarm_handler,brutal_purge,soft_purge,[]},
|
{load_module,emqx_alarm_handler,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_misc,brutal_purge,soft_purge,[]},
|
{load_module,emqx_misc,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_cm,brutal_purge,soft_purge,[]},
|
{load_module,emqx_cm,brutal_purge,soft_purge,[]},
|
||||||
|
@ -46,7 +55,8 @@
|
||||||
{load_module,emqx_rpc,brutal_purge,soft_purge,[]},
|
{load_module,emqx_rpc,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_app,brutal_purge,soft_purge,[]}]},
|
{load_module,emqx_app,brutal_purge,soft_purge,[]}]},
|
||||||
{"4.3.4",
|
{"4.3.4",
|
||||||
[{load_module,emqx_ws_connection,brutal_purge,soft_purge,[]},
|
[{load_module,emqx_channel,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module,emqx_ws_connection,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_alarm_handler,brutal_purge,soft_purge,[]},
|
{load_module,emqx_alarm_handler,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_misc,brutal_purge,soft_purge,[]},
|
{load_module,emqx_misc,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_cm,brutal_purge,soft_purge,[]},
|
{load_module,emqx_cm,brutal_purge,soft_purge,[]},
|
||||||
|
@ -59,7 +69,8 @@
|
||||||
{load_module,emqx_rpc,brutal_purge,soft_purge,[]},
|
{load_module,emqx_rpc,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_app,brutal_purge,soft_purge,[]}]},
|
{load_module,emqx_app,brutal_purge,soft_purge,[]}]},
|
||||||
{"4.3.3",
|
{"4.3.3",
|
||||||
[{load_module,emqx_alarm_handler,brutal_purge,soft_purge,[]},
|
[{load_module,emqx_channel,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module,emqx_alarm_handler,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_misc,brutal_purge,soft_purge,[]},
|
{load_module,emqx_misc,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_packet,brutal_purge,soft_purge,[]},
|
{load_module,emqx_packet,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_shared_sub,brutal_purge,soft_purge,[]},
|
{load_module,emqx_shared_sub,brutal_purge,soft_purge,[]},
|
||||||
|
@ -137,21 +148,27 @@
|
||||||
{load_module,emqx_app,brutal_purge,soft_purge,[]}]},
|
{load_module,emqx_app,brutal_purge,soft_purge,[]}]},
|
||||||
{<<".*">>,[]}],
|
{<<".*">>,[]}],
|
||||||
[{"4.3.9",
|
[{"4.3.9",
|
||||||
[{load_module,emqx_ws_connection,brutal_purge,soft_purge,[]},
|
[{load_module,emqx_channel,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module,emqx_cm,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module,emqx_ws_connection,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_pqueue,brutal_purge,soft_purge,[]},
|
{load_module,emqx_pqueue,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_mqueue,brutal_purge,soft_purge,[]},
|
{load_module,emqx_mqueue,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_frame,brutal_purge,soft_purge,[]},
|
{load_module,emqx_frame,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_rpc,brutal_purge,soft_purge,[]},
|
{load_module,emqx_rpc,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_app,brutal_purge,soft_purge,[]}]},
|
{load_module,emqx_app,brutal_purge,soft_purge,[]}]},
|
||||||
{"4.3.8",
|
{"4.3.8",
|
||||||
[{load_module,emqx_ws_connection,brutal_purge,soft_purge,[]},
|
[{load_module,emqx_channel,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module,emqx_cm,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module,emqx_ws_connection,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_pqueue,brutal_purge,soft_purge,[]},
|
{load_module,emqx_pqueue,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_mqueue,brutal_purge,soft_purge,[]},
|
{load_module,emqx_mqueue,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_frame,brutal_purge,soft_purge,[]},
|
{load_module,emqx_frame,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_rpc,brutal_purge,soft_purge,[]},
|
{load_module,emqx_rpc,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_app,brutal_purge,soft_purge,[]}]},
|
{load_module,emqx_app,brutal_purge,soft_purge,[]}]},
|
||||||
{"4.3.7",
|
{"4.3.7",
|
||||||
[{load_module,emqx_ws_connection,brutal_purge,soft_purge,[]},
|
[{load_module,emqx_channel,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module,emqx_cm,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module,emqx_ws_connection,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_pqueue,brutal_purge,soft_purge,[]},
|
{load_module,emqx_pqueue,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_mqueue,brutal_purge,soft_purge,[]},
|
{load_module,emqx_mqueue,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_alarm_handler,brutal_purge,soft_purge,[]},
|
{load_module,emqx_alarm_handler,brutal_purge,soft_purge,[]},
|
||||||
|
@ -160,7 +177,9 @@
|
||||||
{load_module,emqx_rpc,brutal_purge,soft_purge,[]},
|
{load_module,emqx_rpc,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_app,brutal_purge,soft_purge,[]}]},
|
{load_module,emqx_app,brutal_purge,soft_purge,[]}]},
|
||||||
{"4.3.6",
|
{"4.3.6",
|
||||||
[{load_module,emqx_ws_connection,brutal_purge,soft_purge,[]},
|
[{load_module,emqx_channel,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module,emqx_cm,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module,emqx_ws_connection,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_pqueue,brutal_purge,soft_purge,[]},
|
{load_module,emqx_pqueue,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_mqueue,brutal_purge,soft_purge,[]},
|
{load_module,emqx_mqueue,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_alarm_handler,brutal_purge,soft_purge,[]},
|
{load_module,emqx_alarm_handler,brutal_purge,soft_purge,[]},
|
||||||
|
@ -170,7 +189,8 @@
|
||||||
{load_module,emqx_rpc,brutal_purge,soft_purge,[]},
|
{load_module,emqx_rpc,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_app,brutal_purge,soft_purge,[]}]},
|
{load_module,emqx_app,brutal_purge,soft_purge,[]}]},
|
||||||
{"4.3.5",
|
{"4.3.5",
|
||||||
[{load_module,emqx_ws_connection,brutal_purge,soft_purge,[]},
|
[{load_module,emqx_channel,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module,emqx_ws_connection,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_pqueue,brutal_purge,soft_purge,[]},
|
{load_module,emqx_pqueue,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_mqueue,brutal_purge,soft_purge,[]},
|
{load_module,emqx_mqueue,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_alarm_handler,brutal_purge,soft_purge,[]},
|
{load_module,emqx_alarm_handler,brutal_purge,soft_purge,[]},
|
||||||
|
@ -182,7 +202,8 @@
|
||||||
{load_module,emqx_rpc,brutal_purge,soft_purge,[]},
|
{load_module,emqx_rpc,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_app,brutal_purge,soft_purge,[]}]},
|
{load_module,emqx_app,brutal_purge,soft_purge,[]}]},
|
||||||
{"4.3.4",
|
{"4.3.4",
|
||||||
[{load_module,emqx_ws_connection,brutal_purge,soft_purge,[]},
|
[{load_module,emqx_channel,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module,emqx_ws_connection,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_pqueue,brutal_purge,soft_purge,[]},
|
{load_module,emqx_pqueue,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_mqueue,brutal_purge,soft_purge,[]},
|
{load_module,emqx_mqueue,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_alarm_handler,brutal_purge,soft_purge,[]},
|
{load_module,emqx_alarm_handler,brutal_purge,soft_purge,[]},
|
||||||
|
@ -195,7 +216,8 @@
|
||||||
{load_module,emqx_rpc,brutal_purge,soft_purge,[]},
|
{load_module,emqx_rpc,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_app,brutal_purge,soft_purge,[]}]},
|
{load_module,emqx_app,brutal_purge,soft_purge,[]}]},
|
||||||
{"4.3.3",
|
{"4.3.3",
|
||||||
[{load_module,emqx_pqueue,brutal_purge,soft_purge,[]},
|
[{load_module,emqx_channel,brutal_purge,soft_purge,[]},
|
||||||
|
{load_module,emqx_pqueue,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_mqueue,brutal_purge,soft_purge,[]},
|
{load_module,emqx_mqueue,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_alarm_handler,brutal_purge,soft_purge,[]},
|
{load_module,emqx_alarm_handler,brutal_purge,soft_purge,[]},
|
||||||
{load_module,emqx_misc,brutal_purge,soft_purge,[]},
|
{load_module,emqx_misc,brutal_purge,soft_purge,[]},
|
||||||
|
|
|
@ -977,8 +977,11 @@ handle_info({sock_closed, Reason}, Channel =
|
||||||
Shutdown -> Shutdown
|
Shutdown -> Shutdown
|
||||||
end;
|
end;
|
||||||
|
|
||||||
handle_info({sock_closed, Reason}, Channel = #channel{conn_state = disconnected}) ->
|
handle_info({sock_closed, _Reason}, Channel = #channel{conn_state = disconnected}) ->
|
||||||
?LOG(error, "Unexpected sock_closed: ~p", [Reason]),
|
%% Since sock_closed messages can be generated multiple times,
|
||||||
|
%% we can simply ignore errors of this type in the disconnected state.
|
||||||
|
%% e.g. when the socket send function returns an error, there is already
|
||||||
|
%% a tcp_closed delivered to the process mailbox
|
||||||
{ok, Channel};
|
{ok, Channel};
|
||||||
|
|
||||||
handle_info(clean_acl_cache, Channel) ->
|
handle_info(clean_acl_cache, Channel) ->
|
||||||
|
|
152
src/emqx_cm.erl
152
src/emqx_cm.erl
|
@ -72,7 +72,7 @@
|
||||||
]).
|
]).
|
||||||
|
|
||||||
%% Internal export
|
%% Internal export
|
||||||
-export([stats_fun/0]).
|
-export([stats_fun/0, clean_down/1]).
|
||||||
|
|
||||||
-type(chan_pid() :: pid()).
|
-type(chan_pid() :: pid()).
|
||||||
|
|
||||||
|
@ -93,7 +93,9 @@
|
||||||
%% Server name
|
%% Server name
|
||||||
-define(CM, ?MODULE).
|
-define(CM, ?MODULE).
|
||||||
|
|
||||||
-define(T_TAKEOVER, 15000).
|
-define(T_KICK, 5_000).
|
||||||
|
-define(T_GET_INFO, 5_000).
|
||||||
|
-define(T_TAKEOVER, 15_000).
|
||||||
|
|
||||||
%% @doc Start the channel manager.
|
%% @doc Start the channel manager.
|
||||||
-spec(start_link() -> startlink_ret()).
|
-spec(start_link() -> startlink_ret()).
|
||||||
|
@ -164,7 +166,7 @@ get_chan_info(ClientId, ChanPid) when node(ChanPid) == node() ->
|
||||||
error:badarg -> undefined
|
error:badarg -> undefined
|
||||||
end;
|
end;
|
||||||
get_chan_info(ClientId, ChanPid) ->
|
get_chan_info(ClientId, ChanPid) ->
|
||||||
rpc_call(node(ChanPid), get_chan_info, [ClientId, ChanPid]).
|
rpc_call(node(ChanPid), get_chan_info, [ClientId, ChanPid], ?T_GET_INFO).
|
||||||
|
|
||||||
%% @doc Update infos of the channel.
|
%% @doc Update infos of the channel.
|
||||||
-spec(set_chan_info(emqx_types:clientid(), emqx_types:attrs()) -> boolean()).
|
-spec(set_chan_info(emqx_types:clientid(), emqx_types:attrs()) -> boolean()).
|
||||||
|
@ -189,7 +191,7 @@ get_chan_stats(ClientId, ChanPid) when node(ChanPid) == node() ->
|
||||||
error:badarg -> undefined
|
error:badarg -> undefined
|
||||||
end;
|
end;
|
||||||
get_chan_stats(ClientId, ChanPid) ->
|
get_chan_stats(ClientId, ChanPid) ->
|
||||||
rpc_call(node(ChanPid), get_chan_stats, [ClientId, ChanPid]).
|
rpc_call(node(ChanPid), get_chan_stats, [ClientId, ChanPid], ?T_GET_INFO).
|
||||||
|
|
||||||
%% @doc Set channel's stats.
|
%% @doc Set channel's stats.
|
||||||
-spec(set_chan_stats(emqx_types:clientid(), emqx_types:stats()) -> boolean()).
|
-spec(set_chan_stats(emqx_types:clientid(), emqx_types:stats()) -> boolean()).
|
||||||
|
@ -257,7 +259,7 @@ takeover_session(ClientId) ->
|
||||||
takeover_session(ClientId, ChanPid);
|
takeover_session(ClientId, ChanPid);
|
||||||
ChanPids ->
|
ChanPids ->
|
||||||
[ChanPid|StalePids] = lists:reverse(ChanPids),
|
[ChanPid|StalePids] = lists:reverse(ChanPids),
|
||||||
?LOG(error, "More than one channel found: ~p", [ChanPids]),
|
?LOG(error, "more_than_one_channel_found: ~p", [ChanPids]),
|
||||||
lists:foreach(fun(StalePid) ->
|
lists:foreach(fun(StalePid) ->
|
||||||
catch discard_session(ClientId, StalePid)
|
catch discard_session(ClientId, StalePid)
|
||||||
end, StalePids),
|
end, StalePids),
|
||||||
|
@ -269,77 +271,113 @@ takeover_session(ClientId, ChanPid) when node(ChanPid) == node() ->
|
||||||
undefined ->
|
undefined ->
|
||||||
{error, not_found};
|
{error, not_found};
|
||||||
ConnMod when is_atom(ConnMod) ->
|
ConnMod when is_atom(ConnMod) ->
|
||||||
|
%% TODO: if takeover times out, maybe kill the old?
|
||||||
Session = ConnMod:call(ChanPid, {takeover, 'begin'}, ?T_TAKEOVER),
|
Session = ConnMod:call(ChanPid, {takeover, 'begin'}, ?T_TAKEOVER),
|
||||||
{ok, ConnMod, ChanPid, Session}
|
{ok, ConnMod, ChanPid, Session}
|
||||||
end;
|
end;
|
||||||
|
|
||||||
takeover_session(ClientId, ChanPid) ->
|
takeover_session(ClientId, ChanPid) ->
|
||||||
rpc_call(node(ChanPid), takeover_session, [ClientId, ChanPid]).
|
rpc_call(node(ChanPid), takeover_session, [ClientId, ChanPid], ?T_TAKEOVER).
|
||||||
|
|
||||||
%% @doc Discard all the sessions identified by the ClientId.
|
%% @doc Discard all the sessions identified by the ClientId.
|
||||||
-spec(discard_session(emqx_types:clientid()) -> ok).
|
-spec(discard_session(emqx_types:clientid()) -> ok).
|
||||||
discard_session(ClientId) when is_binary(ClientId) ->
|
discard_session(ClientId) when is_binary(ClientId) ->
|
||||||
case lookup_channels(ClientId) of
|
case lookup_channels(ClientId) of
|
||||||
[] -> ok;
|
[] -> ok;
|
||||||
ChanPids -> lists:foreach(fun(Pid) -> do_discard_session(ClientId, Pid) end, ChanPids)
|
ChanPids -> lists:foreach(fun(Pid) -> discard_session(ClientId, Pid) end, ChanPids)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
do_discard_session(ClientId, Pid) ->
|
%% @private Kick a local stale session to force it step down.
|
||||||
|
%% If failed to kick (e.g. timeout) force a kill.
|
||||||
|
%% Keeping the stale pid around, or returning error or raise an exception
|
||||||
|
%% benefits nobody.
|
||||||
|
-spec kick_or_kill(kick | discard, module(), pid()) -> ok.
|
||||||
|
kick_or_kill(Action, ConnMod, Pid) ->
|
||||||
try
|
try
|
||||||
discard_session(ClientId, Pid)
|
%% this is essentailly a gen_server:call implemented in emqx_connection
|
||||||
|
%% and emqx_ws_connection.
|
||||||
|
%% the handle_call is implemented in emqx_channel
|
||||||
|
ok = apply(ConnMod, call, [Pid, Action, ?T_KICK])
|
||||||
catch
|
catch
|
||||||
_ : noproc -> % emqx_ws_connection: call
|
_ : noproc -> % emqx_ws_connection: call
|
||||||
?tp(debug, "session_already_gone", #{pid => Pid}),
|
ok = ?tp(debug, "session_already_gone", #{pid => Pid, action => Action});
|
||||||
ok;
|
|
||||||
_ : {noproc, _} -> % emqx_connection: gen_server:call
|
_ : {noproc, _} -> % emqx_connection: gen_server:call
|
||||||
?tp(debug, "session_already_gone", #{pid => Pid}),
|
ok = ?tp(debug, "session_already_gone", #{pid => Pid, action => Action});
|
||||||
ok;
|
_ : {shutdown, _} ->
|
||||||
_ : {'EXIT', {noproc, _}} -> % rpc_call/3
|
ok = ?tp(debug, "session_already_shutdown", #{pid => Pid, action => Action});
|
||||||
?tp(debug, "session_already_gone", #{pid => Pid}),
|
|
||||||
ok;
|
|
||||||
_ : {{shutdown, _}, _} ->
|
_ : {{shutdown, _}, _} ->
|
||||||
?tp(debug, "session_already_shutdown", #{pid => Pid}),
|
ok = ?tp(debug, "session_already_shutdown", #{pid => Pid, action => Action});
|
||||||
ok;
|
_ : {timeout, {gen_server, call, _}} ->
|
||||||
|
?tp(warning, "session_kick_timeout",
|
||||||
|
#{pid => Pid,
|
||||||
|
action => Action,
|
||||||
|
stale_channel => stale_channel_info(Pid)
|
||||||
|
}),
|
||||||
|
ok = force_kill(Pid);
|
||||||
_ : Error : St ->
|
_ : Error : St ->
|
||||||
?tp(error, "failed_to_discard_session",
|
?tp(error, "session_kick_exception",
|
||||||
#{pid => Pid, reason => Error, stacktrace=>St})
|
#{pid => Pid,
|
||||||
|
action => Action,
|
||||||
|
reason => Error,
|
||||||
|
stacktrace => St,
|
||||||
|
stale_channel => stale_channel_info(Pid)
|
||||||
|
}),
|
||||||
|
ok = force_kill(Pid)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
discard_session(ClientId, ChanPid) when node(ChanPid) == node() ->
|
force_kill(Pid) ->
|
||||||
case get_chann_conn_mod(ClientId, ChanPid) of
|
exit(Pid, kill),
|
||||||
undefined -> ok;
|
ok.
|
||||||
ConnMod when is_atom(ConnMod) ->
|
|
||||||
ConnMod:call(ChanPid, discard, ?T_TAKEOVER)
|
stale_channel_info(Pid) ->
|
||||||
end;
|
process_info(Pid, [status, message_queue_len, current_stacktrace]).
|
||||||
|
|
||||||
discard_session(ClientId, ChanPid) ->
|
discard_session(ClientId, ChanPid) ->
|
||||||
rpc_call(node(ChanPid), discard_session, [ClientId, ChanPid]).
|
kick_session(discard, ClientId, ChanPid).
|
||||||
|
|
||||||
|
kick_session(ClientId, ChanPid) ->
|
||||||
|
kick_session(kick, ClientId, ChanPid).
|
||||||
|
|
||||||
|
%% @private This function is shared for session 'kick' and 'discard' (as the first arg Action).
|
||||||
|
kick_session(Action, ClientId, ChanPid) when node(ChanPid) == node() ->
|
||||||
|
case get_chann_conn_mod(ClientId, ChanPid) of
|
||||||
|
undefined ->
|
||||||
|
%% already deregistered
|
||||||
|
ok;
|
||||||
|
ConnMod when is_atom(ConnMod) ->
|
||||||
|
ok = kick_or_kill(Action, ConnMod, ChanPid)
|
||||||
|
end;
|
||||||
|
kick_session(Action, ClientId, ChanPid) ->
|
||||||
|
%% call remote node on the old APIs because we do not know if they have upgraded
|
||||||
|
%% to have kick_session/3
|
||||||
|
Function = case Action of
|
||||||
|
discard -> discard_session;
|
||||||
|
kick -> kick_session
|
||||||
|
end,
|
||||||
|
try
|
||||||
|
rpc_call(node(ChanPid), Function, [ClientId, ChanPid], ?T_KICK)
|
||||||
|
catch
|
||||||
|
Error : Reason ->
|
||||||
|
%% This should mostly be RPC failures.
|
||||||
|
%% However, if the node is still running the old version
|
||||||
|
%% code (prior to emqx app 4.3.10) some of the RPC handler
|
||||||
|
%% exceptions may get propagated to a new version node
|
||||||
|
?LOG(error, "failed_to_kick_session_on_remote_node ~p: ~p ~p ~p",
|
||||||
|
[node(ChanPid), Action, Error, Reason])
|
||||||
|
end.
|
||||||
|
|
||||||
kick_session(ClientId) ->
|
kick_session(ClientId) ->
|
||||||
case lookup_channels(ClientId) of
|
case lookup_channels(ClientId) of
|
||||||
[] -> {error, not_found};
|
[] ->
|
||||||
[ChanPid] ->
|
?LOG(warning, "kiecked_an_unknown_session ~ts", [ClientId]),
|
||||||
kick_session(ClientId, ChanPid);
|
ok;
|
||||||
ChanPids ->
|
ChanPids ->
|
||||||
[ChanPid|StalePids] = lists:reverse(ChanPids),
|
case length(ChanPids) > 1 of
|
||||||
?LOG(error, "More than one channel found: ~p", [ChanPids]),
|
true -> ?LOG(info, "more_than_one_channel_found: ~p", [ChanPids]);
|
||||||
lists:foreach(fun(StalePid) ->
|
false -> ok
|
||||||
catch discard_session(ClientId, StalePid)
|
end,
|
||||||
end, StalePids),
|
lists:foreach(fun(Pid) -> kick_session(ClientId, Pid) end, ChanPids)
|
||||||
kick_session(ClientId, ChanPid)
|
|
||||||
end.
|
end.
|
||||||
|
|
||||||
kick_session(ClientId, ChanPid) when node(ChanPid) == node() ->
|
|
||||||
case get_chan_info(ClientId, ChanPid) of
|
|
||||||
#{conninfo := #{conn_mod := ConnMod}} ->
|
|
||||||
ConnMod:call(ChanPid, kick, ?T_TAKEOVER);
|
|
||||||
undefined ->
|
|
||||||
{error, not_found}
|
|
||||||
end;
|
|
||||||
|
|
||||||
kick_session(ClientId, ChanPid) ->
|
|
||||||
rpc_call(node(ChanPid), kick_session, [ClientId, ChanPid]).
|
|
||||||
|
|
||||||
%% @doc Is clean start?
|
%% @doc Is clean start?
|
||||||
% is_clean_start(#{clean_start := false}) -> false;
|
% is_clean_start(#{clean_start := false}) -> false;
|
||||||
% is_clean_start(_Attrs) -> true.
|
% is_clean_start(_Attrs) -> true.
|
||||||
|
@ -375,10 +413,16 @@ lookup_channels(local, ClientId) ->
|
||||||
[ChanPid || {_, ChanPid} <- ets:lookup(?CHAN_TAB, ClientId)].
|
[ChanPid || {_, ChanPid} <- ets:lookup(?CHAN_TAB, ClientId)].
|
||||||
|
|
||||||
%% @private
|
%% @private
|
||||||
rpc_call(Node, Fun, Args) ->
|
rpc_call(Node, Fun, Args, Timeout) ->
|
||||||
case rpc:call(Node, ?MODULE, Fun, Args, 2 * ?T_TAKEOVER) of
|
case rpc:call(Node, ?MODULE, Fun, Args, 2 * Timeout) of
|
||||||
{badrpc, Reason} -> error(Reason);
|
{badrpc, Reason} ->
|
||||||
Res -> Res
|
%% since eqmx app 4.3.10, the 'kick' and 'discard' calls hanndler
|
||||||
|
%% should catch all exceptions and always return 'ok'.
|
||||||
|
%% This leaves 'badrpc' only possible when there is problem
|
||||||
|
%% calling the remote node.
|
||||||
|
error({badrpc, Reason});
|
||||||
|
Res ->
|
||||||
|
Res
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%% @private
|
%% @private
|
||||||
|
@ -411,7 +455,7 @@ handle_cast(Msg, State) ->
|
||||||
handle_info({'DOWN', _MRef, process, Pid, _Reason}, State = #{chan_pmon := PMon}) ->
|
handle_info({'DOWN', _MRef, process, Pid, _Reason}, State = #{chan_pmon := PMon}) ->
|
||||||
ChanPids = [Pid | emqx_misc:drain_down(?BATCH_SIZE)],
|
ChanPids = [Pid | emqx_misc:drain_down(?BATCH_SIZE)],
|
||||||
{Items, PMon1} = emqx_pmon:erase_all(ChanPids, PMon),
|
{Items, PMon1} = emqx_pmon:erase_all(ChanPids, PMon),
|
||||||
ok = emqx_pool:async_submit(fun lists:foreach/2, [fun clean_down/1, Items]),
|
ok = emqx_pool:async_submit(fun lists:foreach/2, [fun ?MODULE:clean_down/1, Items]),
|
||||||
{noreply, State#{chan_pmon := PMon1}};
|
{noreply, State#{chan_pmon := PMon1}};
|
||||||
|
|
||||||
handle_info(Info, State) ->
|
handle_info(Info, State) ->
|
||||||
|
@ -447,5 +491,5 @@ get_chann_conn_mod(ClientId, ChanPid) when node(ChanPid) == node() ->
|
||||||
error:badarg -> undefined
|
error:badarg -> undefined
|
||||||
end;
|
end;
|
||||||
get_chann_conn_mod(ClientId, ChanPid) ->
|
get_chann_conn_mod(ClientId, ChanPid) ->
|
||||||
rpc_call(node(ChanPid), get_chann_conn_mod, [ClientId, ChanPid]).
|
rpc_call(node(ChanPid), get_chann_conn_mod, [ClientId, ChanPid], ?T_GET_INFO).
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,12 @@
|
||||||
conn_mod => emqx_connection,
|
conn_mod => emqx_connection,
|
||||||
receive_maximum => 100}}).
|
receive_maximum => 100}}).
|
||||||
|
|
||||||
|
-define(WAIT(PATTERN, TIMEOUT, RET),
|
||||||
|
fun() ->
|
||||||
|
receive PATTERN -> RET
|
||||||
|
after TIMEOUT -> error({timeout, ?LINE}) end
|
||||||
|
end()).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% CT callbacks
|
%% CT callbacks
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
@ -180,25 +186,95 @@ t_open_session_race_condition(_) ->
|
||||||
ignored = gen_server:call(emqx_cm, ignore, infinity), %% sync
|
ignored = gen_server:call(emqx_cm, ignore, infinity), %% sync
|
||||||
?assertEqual([], emqx_cm:lookup_channels(ClientId)).
|
?assertEqual([], emqx_cm:lookup_channels(ClientId)).
|
||||||
|
|
||||||
t_discard_session(_) ->
|
t_kick_session_discard_normal(_) ->
|
||||||
|
test_kick_session(discard, normal).
|
||||||
|
|
||||||
|
t_kick_session_discard_shutdown(_) ->
|
||||||
|
test_kick_session(discard, shutdown).
|
||||||
|
|
||||||
|
t_kick_session_discard_shutdown_with_reason(_) ->
|
||||||
|
test_kick_session(discard, {shutdown, discard}).
|
||||||
|
|
||||||
|
t_kick_session_discard_timeout(_) ->
|
||||||
|
test_kick_session(discard, timeout).
|
||||||
|
|
||||||
|
t_kick_session_discard_noproc(_) ->
|
||||||
|
test_kick_session(discard, noproc).
|
||||||
|
|
||||||
|
t_kick_session_kick_normal(_) ->
|
||||||
|
test_kick_session(discard, normal).
|
||||||
|
|
||||||
|
t_kick_session_kick_shutdown(_) ->
|
||||||
|
test_kick_session(discard, shutdown).
|
||||||
|
|
||||||
|
t_kick_session_kick_shutdown_with_reason(_) ->
|
||||||
|
test_kick_session(discard, {shutdown, discard}).
|
||||||
|
|
||||||
|
t_kick_session_kick_timeout(_) ->
|
||||||
|
test_kick_session(discard, timeout).
|
||||||
|
|
||||||
|
t_kick_session_kick_noproc(_) ->
|
||||||
|
test_kick_session(discard, noproc).
|
||||||
|
|
||||||
|
test_kick_session(Action, Reason) ->
|
||||||
ClientId = rand_client_id(),
|
ClientId = rand_client_id(),
|
||||||
#{conninfo := ConnInfo} = ?ChanInfo,
|
#{conninfo := ConnInfo} = ?ChanInfo,
|
||||||
ok = emqx_cm:register_channel(ClientId, self(), ConnInfo),
|
FakeSessionFun =
|
||||||
|
fun Loop() ->
|
||||||
|
receive
|
||||||
|
{'$gen_call', From, A} when A =:= kick orelse
|
||||||
|
A =:= discard ->
|
||||||
|
case Reason of
|
||||||
|
normal ->
|
||||||
|
gen_server:reply(From, ok);
|
||||||
|
timeout ->
|
||||||
|
%% no response to the call
|
||||||
|
Loop();
|
||||||
|
_ ->
|
||||||
|
exit(Reason)
|
||||||
|
end;
|
||||||
|
Msg ->
|
||||||
|
ct:pal("(~p) fake_session_discarded ~p", [Action, Msg]),
|
||||||
|
Loop()
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
{Pid1, _} = spawn_monitor(FakeSessionFun),
|
||||||
|
{Pid2, _} = spawn_monitor(FakeSessionFun),
|
||||||
|
ok = emqx_cm:register_channel(ClientId, Pid1, ConnInfo),
|
||||||
|
ok = emqx_cm:register_channel(ClientId, Pid1, ConnInfo),
|
||||||
|
ok = emqx_cm:register_channel(ClientId, Pid2, ConnInfo),
|
||||||
|
?assertEqual([Pid1, Pid2], lists:sort(emqx_cm:lookup_channels(ClientId))),
|
||||||
|
case Reason of
|
||||||
|
noproc -> exit(Pid1, kill), exit(Pid2, kill);
|
||||||
|
_ -> ok
|
||||||
|
end,
|
||||||
|
ok = case Action of
|
||||||
|
kick -> emqx_cm:kick_session(ClientId);
|
||||||
|
discard -> emqx_cm:discard_session(ClientId)
|
||||||
|
end,
|
||||||
|
case Reason =:= timeout orelse Reason =:= noproc of
|
||||||
|
true ->
|
||||||
|
?assertEqual(killed, ?WAIT({'DOWN', _, process, Pid1, R}, 2_000, R)),
|
||||||
|
?assertEqual(killed, ?WAIT({'DOWN', _, process, Pid2, R}, 2_000, R));
|
||||||
|
false ->
|
||||||
|
?assertEqual(Reason, ?WAIT({'DOWN', _, process, Pid1, R}, 2_000, R)),
|
||||||
|
?assertEqual(Reason, ?WAIT({'DOWN', _, process, Pid2, R}, 2_000, R))
|
||||||
|
end,
|
||||||
|
ok = flush_emqx_pool(),
|
||||||
|
?assertEqual([], emqx_cm:lookup_channels(ClientId)).
|
||||||
|
|
||||||
ok = meck:new(emqx_connection, [passthrough, no_history]),
|
%% Channel deregistration is delegated to emqx_pool as a sync tasks.
|
||||||
ok = meck:expect(emqx_connection, call, fun(_, _) -> ok end),
|
%% The emqx_pool is pool of workers, and there is no way to know
|
||||||
ok = meck:expect(emqx_connection, call, fun(_, _, _) -> ok end),
|
%% which worker was picked for the last deregistration task.
|
||||||
ok = emqx_cm:discard_session(ClientId),
|
%% This help function creates a large enough number of async tasks
|
||||||
ok = emqx_cm:register_channel(ClientId, self(), ConnInfo),
|
%% to sync with the pool workers.
|
||||||
ok = emqx_cm:discard_session(ClientId),
|
%% The number of tasks should be large enough to ensure all workers have
|
||||||
ok = emqx_cm:unregister_channel(ClientId),
|
%% the chance to work on at least one of the tasks.
|
||||||
ok = emqx_cm:register_channel(ClientId, self(), ConnInfo),
|
flush_emqx_pool() ->
|
||||||
ok = emqx_cm:discard_session(ClientId),
|
Self = self(),
|
||||||
ok = meck:expect(emqx_connection, call, fun(_, _) -> error(testing) end),
|
L = lists:seq(1, 1000),
|
||||||
ok = meck:expect(emqx_connection, call, fun(_, _, _) -> error(testing) end),
|
lists:foreach(fun(I) -> emqx_pool:async_submit(fun() -> Self ! {done, I} end, []) end, L),
|
||||||
ok = emqx_cm:discard_session(ClientId),
|
lists:foreach(fun(I) -> receive {done, I} -> ok end end, L).
|
||||||
ok = emqx_cm:unregister_channel(ClientId),
|
|
||||||
ok = meck:unload(emqx_connection).
|
|
||||||
|
|
||||||
t_discard_session_race(_) ->
|
t_discard_session_race(_) ->
|
||||||
ClientId = rand_client_id(),
|
ClientId = rand_client_id(),
|
||||||
|
@ -231,27 +307,6 @@ t_takeover_session(_) ->
|
||||||
{ok, emqx_connection, _, test} = emqx_cm:takeover_session(<<"clientid">>),
|
{ok, emqx_connection, _, test} = emqx_cm:takeover_session(<<"clientid">>),
|
||||||
emqx_cm:unregister_channel(<<"clientid">>).
|
emqx_cm:unregister_channel(<<"clientid">>).
|
||||||
|
|
||||||
t_kick_session(_) ->
|
|
||||||
Info = #{conninfo := ConnInfo} = ?ChanInfo,
|
|
||||||
ok = meck:new(emqx_connection, [passthrough, no_history]),
|
|
||||||
ok = meck:expect(emqx_connection, call, fun(_, _) -> test end),
|
|
||||||
ok = meck:expect(emqx_connection, call, fun(_, _, _) -> test end),
|
|
||||||
{error, not_found} = emqx_cm:kick_session(<<"clientid">>),
|
|
||||||
ok = emqx_cm:register_channel(<<"clientid">>, self(), ConnInfo),
|
|
||||||
ok = emqx_cm:insert_channel_info(<<"clientid">>, Info, []),
|
|
||||||
test = emqx_cm:kick_session(<<"clientid">>),
|
|
||||||
erlang:spawn_link(
|
|
||||||
fun() ->
|
|
||||||
ok = emqx_cm:register_channel(<<"clientid">>, self(), ConnInfo),
|
|
||||||
ok = emqx_cm:insert_channel_info(<<"clientid">>, Info, []),
|
|
||||||
|
|
||||||
timer:sleep(1000)
|
|
||||||
end),
|
|
||||||
ct:sleep(100),
|
|
||||||
test = emqx_cm:kick_session(<<"clientid">>),
|
|
||||||
ok = emqx_cm:unregister_channel(<<"clientid">>),
|
|
||||||
ok = meck:unload(emqx_connection).
|
|
||||||
|
|
||||||
t_all_channels(_) ->
|
t_all_channels(_) ->
|
||||||
?assertEqual(true, is_list(emqx_cm:all_channels())).
|
?assertEqual(true, is_list(emqx_cm:all_channels())).
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue