snapshot of https://github.com/nats-io/nats-server.git (main, 2026-09-15; blobs > 131072B omitted)
Some checks failed
NATS Server Tests / Lint (push) Failing after 3s
NATS Server Tests / Build (Latest Go) (push) Has been skipped
NATS Server Tests / Build (Minimum Go) (push) Has been skipped
NATS Server Tests / Build (Minimum Go, windows-2022) (push) Has been skipped
NATS Server Tests / Build (Minimum Go, windows-2025) (push) Has been skipped
NATS Server Tests / Test Stores (push) Has been skipped
NATS Server Tests / Test JetStream (push) Has been skipped
NATS Server Tests / Test Raft (push) Has been skipped
NATS Server Tests / Test JetStream Consumers (push) Has been skipped
NATS Server Tests / Test JetStream Cluster 1 (push) Has been skipped
NATS Server Tests / Test JetStream Cluster 2 (push) Has been skipped
NATS Server Tests / Test JetStream Cluster 3 (push) Has been skipped
NATS Server Tests / Test JetStream Cluster 4 (push) Has been skipped
NATS Server Tests / Test JetStream Supercluster (push) Has been skipped
NATS Server Tests / Test No-Race 1 (push) Has been skipped
NATS Server Tests / Test No-Race 2 (push) Has been skipped
NATS Server Tests / Test MQTT (push) Has been skipped
NATS Server Tests / Test Message Tracing (push) Has been skipped
NATS Server Tests / Test JWT (push) Has been skipped
NATS Server Tests / Test Remaining Server Tests (push) Has been skipped
NATS Server Tests / Test Other Packages (push) Has been skipped
Vulnerability Scan / govulncheck (push) Failing after 12m2s
Some checks failed
NATS Server Tests / Lint (push) Failing after 3s
NATS Server Tests / Build (Latest Go) (push) Has been skipped
NATS Server Tests / Build (Minimum Go) (push) Has been skipped
NATS Server Tests / Build (Minimum Go, windows-2022) (push) Has been skipped
NATS Server Tests / Build (Minimum Go, windows-2025) (push) Has been skipped
NATS Server Tests / Test Stores (push) Has been skipped
NATS Server Tests / Test JetStream (push) Has been skipped
NATS Server Tests / Test Raft (push) Has been skipped
NATS Server Tests / Test JetStream Consumers (push) Has been skipped
NATS Server Tests / Test JetStream Cluster 1 (push) Has been skipped
NATS Server Tests / Test JetStream Cluster 2 (push) Has been skipped
NATS Server Tests / Test JetStream Cluster 3 (push) Has been skipped
NATS Server Tests / Test JetStream Cluster 4 (push) Has been skipped
NATS Server Tests / Test JetStream Supercluster (push) Has been skipped
NATS Server Tests / Test No-Race 1 (push) Has been skipped
NATS Server Tests / Test No-Race 2 (push) Has been skipped
NATS Server Tests / Test MQTT (push) Has been skipped
NATS Server Tests / Test Message Tracing (push) Has been skipped
NATS Server Tests / Test JWT (push) Has been skipped
NATS Server Tests / Test Remaining Server Tests (push) Has been skipped
NATS Server Tests / Test Other Packages (push) Has been skipped
Vulnerability Scan / govulncheck (push) Failing after 12m2s
This commit is contained in:
382
server/mqtt_ex_test_test.go
Normal file
382
server/mqtt_ex_test_test.go
Normal file
@@ -0,0 +1,382 @@
|
||||
// Copyright 2024-2025 The NATS Authors
|
||||
// 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.
|
||||
|
||||
//go:build !skip_mqtt_tests
|
||||
|
||||
package server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/nats-io/nuid"
|
||||
)
|
||||
|
||||
type mqttDial string
|
||||
|
||||
type mqttTarget struct {
|
||||
singleServers []*Server
|
||||
clusters []*cluster
|
||||
configs []mqttTestConfig
|
||||
all []mqttDial
|
||||
}
|
||||
|
||||
type mqttTestConfig struct {
|
||||
name string
|
||||
pub []mqttDial
|
||||
sub []mqttDial
|
||||
}
|
||||
|
||||
func TestXMQTTCompliance(t *testing.T) {
|
||||
if mqttCLICommandPath == _EMPTY_ {
|
||||
t.Skip(`"mqtt" command is not found in $PATH nor $MQTT_CLI. See https://hivemq.github.io/mqtt-cli/docs/installation/#debian-package for installation instructions`)
|
||||
}
|
||||
|
||||
o := testMQTTDefaultOptions()
|
||||
s := testMQTTRunServer(t, o)
|
||||
o = s.getOpts()
|
||||
defer testMQTTShutdownServer(s)
|
||||
|
||||
cmd := exec.Command(mqttCLICommandPath, "test", "-V", "3", "-p", strconv.Itoa(o.MQTT.Port))
|
||||
|
||||
output, err := cmd.CombinedOutput()
|
||||
t.Log(string(output))
|
||||
if err != nil {
|
||||
if exitError, ok := err.(*exec.ExitError); ok {
|
||||
t.Fatalf("mqtt cli exited with error: %v", exitError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestXMQTTRetainedMessages(t *testing.T) {
|
||||
if mqttTestCommandPath == _EMPTY_ {
|
||||
t.Skip(`"mqtt-test" command is not found in $PATH.`)
|
||||
}
|
||||
|
||||
for _, topo := range []struct {
|
||||
name string
|
||||
makef func(testing.TB) *mqttTarget
|
||||
}{
|
||||
{
|
||||
name: "single server",
|
||||
makef: mqttMakeTestServer,
|
||||
},
|
||||
{
|
||||
name: "cluster",
|
||||
makef: mqttMakeTestCluster(4, ""),
|
||||
},
|
||||
} {
|
||||
t.Run(topo.name, func(t *testing.T) {
|
||||
target := topo.makef(t)
|
||||
t.Cleanup(target.Shutdown)
|
||||
|
||||
// initialize the MQTT assets by "touching" all nodes in the
|
||||
// cluster, but then reload to start with fresh server state.
|
||||
for _, dial := range target.all {
|
||||
mqttInitTestServer(t, dial)
|
||||
}
|
||||
|
||||
numRMS := 100
|
||||
strNumRMS := strconv.Itoa(numRMS)
|
||||
topics := make([]string, len(target.configs))
|
||||
|
||||
for i, tc := range target.configs {
|
||||
// Publish numRMS retained messages one at a time,
|
||||
// round-robin across pub nodes. Remember the topic for each
|
||||
// test config to check the subs after reload.
|
||||
topic := "subret_" + nuid.Next()
|
||||
topics[i] = topic
|
||||
iNode := 0
|
||||
for i := 0; i < numRMS; i++ {
|
||||
pubTopic := fmt.Sprintf("%s/%d", topic, i)
|
||||
dial := tc.pub[iNode%len(tc.pub)]
|
||||
mqttRunExCommandTest(t, "pub", dial,
|
||||
"--retain",
|
||||
"--topic", pubTopic,
|
||||
"--qos", "0",
|
||||
"--size", "128", // message size 128 bytes
|
||||
)
|
||||
iNode++
|
||||
}
|
||||
}
|
||||
|
||||
// Check all sub nodes for retained messages
|
||||
for i, tc := range target.configs {
|
||||
for _, dial := range tc.sub {
|
||||
mqttRunExCommandTest(t, "sub", dial,
|
||||
"--retained", strNumRMS,
|
||||
"--qos", "0",
|
||||
"--topic", topics[i],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Reload the target
|
||||
target.Reload(t)
|
||||
|
||||
// Now check again
|
||||
for i, tc := range target.configs {
|
||||
for _, dial := range tc.sub {
|
||||
mqttRunExCommandTestRetry(t, 1, "sub", dial,
|
||||
"--retained", strNumRMS,
|
||||
"--qos", "0",
|
||||
"--topic", topics[i],
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func mqttInitTestServer(tb testing.TB, dial mqttDial) {
|
||||
tb.Helper()
|
||||
mqttRunExCommandTestRetry(tb, 5, "pub", dial)
|
||||
}
|
||||
|
||||
func mqttNewDialForServer(s *Server, username, password string) mqttDial {
|
||||
o := s.getOpts().MQTT
|
||||
return mqttNewDial(username, password, o.Host, o.Port, s.Name())
|
||||
}
|
||||
|
||||
func mqttNewDial(username, password, host string, port int, comment string) mqttDial {
|
||||
d := ""
|
||||
switch {
|
||||
case username != "" && password != "":
|
||||
d = fmt.Sprintf("%s:%s@%s:%d", username, password, host, port)
|
||||
case username != "":
|
||||
d = fmt.Sprintf("%s@%s:%d", username, host, port)
|
||||
default:
|
||||
d = fmt.Sprintf("%s:%d", host, port)
|
||||
}
|
||||
if comment != "" {
|
||||
d += "#" + comment
|
||||
}
|
||||
return mqttDial(d)
|
||||
}
|
||||
|
||||
func (d mqttDial) Get() (u, p, s, c string) {
|
||||
if d == "" {
|
||||
return "", "", "127.0.0.1:1883", ""
|
||||
}
|
||||
in := string(d)
|
||||
if i := strings.LastIndex(in, "#"); i != -1 {
|
||||
c = in[i+1:]
|
||||
in = in[:i]
|
||||
}
|
||||
if i := strings.LastIndex(in, "@"); i != -1 {
|
||||
up := in[:i]
|
||||
in = in[i+1:]
|
||||
u = up
|
||||
if i := strings.Index(up, ":"); i != -1 {
|
||||
u = up[:i]
|
||||
p = up[i+1:]
|
||||
}
|
||||
}
|
||||
s = in
|
||||
return u, p, s, c
|
||||
}
|
||||
|
||||
func (d mqttDial) Name() string {
|
||||
_, _, _, c := d.Get()
|
||||
return c
|
||||
}
|
||||
|
||||
func (t *mqttTarget) Reload(tb testing.TB) {
|
||||
tb.Helper()
|
||||
|
||||
for _, c := range t.clusters {
|
||||
c.stopAll()
|
||||
c.restartAllSamePorts()
|
||||
}
|
||||
|
||||
for i, s := range t.singleServers {
|
||||
o := s.getOpts()
|
||||
s.Shutdown()
|
||||
t.singleServers[i] = testMQTTRunServer(tb, o)
|
||||
}
|
||||
|
||||
for _, dial := range t.all {
|
||||
mqttInitTestServer(tb, dial)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *mqttTarget) Shutdown() {
|
||||
for _, c := range t.clusters {
|
||||
c.shutdown()
|
||||
}
|
||||
for _, s := range t.singleServers {
|
||||
testMQTTShutdownServer(s)
|
||||
}
|
||||
}
|
||||
|
||||
func mqttMakeTestServer(tb testing.TB) *mqttTarget {
|
||||
tb.Helper()
|
||||
o := testMQTTDefaultOptions()
|
||||
s := testMQTTRunServer(tb, o)
|
||||
all := []mqttDial{mqttNewDialForServer(s, "", "")}
|
||||
return &mqttTarget{
|
||||
singleServers: []*Server{s},
|
||||
all: all,
|
||||
configs: []mqttTestConfig{
|
||||
{
|
||||
name: "single server",
|
||||
pub: all,
|
||||
sub: all,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func mqttMakeTestCluster(size int, domain string) func(tb testing.TB) *mqttTarget {
|
||||
return func(tb testing.TB) *mqttTarget {
|
||||
tb.Helper()
|
||||
if size < 3 {
|
||||
tb.Fatal("cluster size must be at least 3")
|
||||
}
|
||||
|
||||
if domain != "" {
|
||||
domain = "domain: " + domain + ", "
|
||||
}
|
||||
clusterConf := `
|
||||
listen: 127.0.0.1:-1
|
||||
|
||||
server_name: %s
|
||||
jetstream: {max_mem_store: 256MB, max_file_store: 2GB, ` + domain + `store_dir: '%s'}
|
||||
|
||||
leafnodes {
|
||||
listen: 127.0.0.1:-1
|
||||
}
|
||||
|
||||
cluster {
|
||||
name: %s
|
||||
listen: 127.0.0.1:%d
|
||||
routes = [%s]
|
||||
}
|
||||
|
||||
mqtt {
|
||||
listen: 127.0.0.1:-1
|
||||
stream_replicas: 3
|
||||
}
|
||||
|
||||
accounts {
|
||||
ONE { users = [ { user: "one", pass: "p" } ]; jetstream: enabled }
|
||||
$SYS { users = [ { user: "admin", pass: "s3cr3t!" } ] }
|
||||
}
|
||||
`
|
||||
cl := createJetStreamClusterWithTemplate(tb, clusterConf, "MQTT", size)
|
||||
cl.waitOnLeader()
|
||||
|
||||
all := []mqttDial{}
|
||||
for _, s := range cl.servers {
|
||||
all = append(all, mqttNewDialForServer(s, "one", "p"))
|
||||
}
|
||||
|
||||
return &mqttTarget{
|
||||
clusters: []*cluster{cl},
|
||||
all: all,
|
||||
configs: []mqttTestConfig{
|
||||
{
|
||||
name: "publish to one",
|
||||
pub: []mqttDial{
|
||||
mqttNewDialForServer(cl.randomServer(), "one", "p"),
|
||||
},
|
||||
sub: all,
|
||||
},
|
||||
{
|
||||
name: "publish to all",
|
||||
pub: all,
|
||||
sub: all,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var mqttCLICommandPath = func() string {
|
||||
p := os.Getenv("MQTT_CLI")
|
||||
if p == "" {
|
||||
p, _ = exec.LookPath("mqtt")
|
||||
}
|
||||
return p
|
||||
}()
|
||||
|
||||
var mqttTestCommandPath = func() string {
|
||||
p, _ := exec.LookPath("mqtt-test")
|
||||
return p
|
||||
}()
|
||||
|
||||
func mqttRunExCommandTest(tb testing.TB, subCommand string, dial mqttDial, extraArgs ...string) *MQTTBenchmarkResult {
|
||||
tb.Helper()
|
||||
return mqttRunExCommandTestRetry(tb, 1, subCommand, dial, extraArgs...)
|
||||
}
|
||||
|
||||
func mqttRunExCommandTestRetry(tb testing.TB, n int, subCommand string, dial mqttDial, extraArgs ...string) (r *MQTTBenchmarkResult) {
|
||||
tb.Helper()
|
||||
var err error
|
||||
for i := 0; i < n; i++ {
|
||||
if r, err = mqttTryExCommandTest(tb, subCommand, dial, extraArgs...); err == nil {
|
||||
return r
|
||||
}
|
||||
|
||||
if i < (n - 1) {
|
||||
tb.Logf("failed to %q %s to %q on attempt %v, will retry.", subCommand, extraArgs, dial.Name(), i)
|
||||
} else {
|
||||
tb.Fatal(err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func mqttTryExCommandTest(tb testing.TB, subCommand string, dial mqttDial, extraArgs ...string) (r *MQTTBenchmarkResult, err error) {
|
||||
tb.Helper()
|
||||
if mqttTestCommandPath == "" {
|
||||
tb.Skip(`"mqtt-test" command is not found in $PATH.`)
|
||||
}
|
||||
|
||||
args := []string{subCommand, // "-q",
|
||||
"-s", string(dial),
|
||||
}
|
||||
args = append(args, extraArgs...)
|
||||
cmd := exec.Command(mqttTestCommandPath, args...)
|
||||
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error executing %q: %v", cmd.String(), err)
|
||||
}
|
||||
defer stdout.Close()
|
||||
errbuf := bytes.Buffer{}
|
||||
cmd.Stderr = &errbuf
|
||||
if err = cmd.Start(); err != nil {
|
||||
return nil, fmt.Errorf("error executing %q: %v", cmd.String(), err)
|
||||
}
|
||||
out, err := io.ReadAll(stdout)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error executing %q: failed to read output: %v", cmd.String(), err)
|
||||
}
|
||||
if err = cmd.Wait(); err != nil {
|
||||
return nil, fmt.Errorf("error executing %q: %v\n\n%s\n\n%s", cmd.String(), err, string(out), errbuf.String())
|
||||
}
|
||||
|
||||
r = &MQTTBenchmarkResult{}
|
||||
if err := json.Unmarshal(out, r); err != nil {
|
||||
tb.Fatalf("error executing %q: failed to decode output: %v\n\n%s\n\n%s", cmd.String(), err, string(out), errbuf.String())
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
Reference in New Issue
Block a user