├── .gitignore ├── CODE-OF-CONDUCT.md ├── Dockerfile ├── GOVERNANCE.md ├── LICENSE ├── MAINTAINERS.md ├── Makefile ├── README.md ├── dependencies.md ├── deploy ├── helm │ └── mq-bridge │ │ ├── Chart.yaml │ │ ├── templates │ │ ├── config.yaml │ │ └── deployment.yaml │ │ └── values.yaml ├── nats-cluster.yaml ├── nats-mq.yaml ├── operator.yaml └── rbac.yaml ├── docs ├── buildandrun.md ├── config.md ├── messages.md ├── monitoring.md └── tutorial.md ├── go.mod ├── go.sum ├── helpers └── java │ ├── build.gradle │ └── src │ ├── main │ └── java │ │ └── io │ │ └── nats │ │ └── mqbridge │ │ ├── Header.java │ │ ├── Message.java │ │ └── examples │ │ ├── Tick.java │ │ └── Tock.java │ └── test │ └── java │ └── io │ └── nats │ └── mqbridge │ └── MessageTest.java ├── message ├── examples │ ├── tick │ │ └── main.go │ └── tock │ │ └── main.go ├── interchange │ └── main.go ├── message.go └── message_test.go ├── nats-mq ├── conf │ ├── conf.go │ ├── hostport.go │ ├── hostport_test.go │ ├── load.go │ ├── load_test.go │ ├── parse.go │ ├── parse_test.go │ ├── utils.go │ └── utils_test.go ├── core │ ├── bridge.go │ ├── bridge_test.go │ ├── connect.go │ ├── connect_test.go │ ├── connector.go │ ├── handlers.go │ ├── handlers_test.go │ ├── histogram.go │ ├── histogram_test.go │ ├── monitoring.go │ ├── monitoring_test.go │ ├── msgconv.go │ ├── msgconv_test.go │ ├── nats2queue.go │ ├── nats2queue_test.go │ ├── nats2topic.go │ ├── nats2topic_test.go │ ├── queue2nats.go │ ├── queue2nats_test.go │ ├── queue2stan.go │ ├── queue2stan_test.go │ ├── reconnecttimer.go │ ├── request_reply_test.go │ ├── sample_data_test.go │ ├── stan2queue.go │ ├── stan2queue_test.go │ ├── stan2topic.go │ ├── stan2topic_test.go │ ├── stats.go │ ├── stats_test.go │ ├── test_utils.go │ ├── topic2nats.go │ ├── topic2nats_test.go │ ├── topic2stan.go │ └── topic2stan_test.go ├── logging │ ├── logging.go │ ├── nats.go │ └── nats_test.go └── main.go ├── performance ├── encodingperf │ └── main.go ├── full │ └── main.go ├── full_testenv │ └── main.go ├── multiqueue_testenv │ └── main.go ├── queues │ └── main.go ├── readme.md └── singlequeue_testenv │ └── main.go ├── resources ├── certs │ ├── ca.pem │ ├── client-cert.pem │ ├── client-key.pem │ ├── combined.pem │ ├── server-cert.pem │ └── server-key.pem ├── interchange.bin ├── mac.tiktok.conf ├── mqm │ ├── MQClient │ │ └── certs │ │ │ ├── client.crl │ │ │ ├── client.kdb │ │ │ ├── client.rdb │ │ │ ├── client.sth │ │ │ ├── client_key.p12 │ │ │ └── client_key.sth │ └── MQServer │ │ └── certs │ │ ├── QM1.cert │ │ ├── key.p12 │ │ └── key.sth └── tiktok.conf └── scripts ├── run_mq.sh ├── run_mq_detached.sh └── run_mq_tls.sh /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | build/ 11 | cov/ 12 | .idea/ 13 | .vscode/ 14 | 15 | # Java 16 | helpers/java/.gradle/ 17 | helpers/java/bin/ 18 | helpers/java/build/ 19 | helpers/java/.classpath 20 | helpers/java/.project 21 | helpers/java/.settings 22 | 23 | # Output of the go coverage tool, specifically when used with LiteIDE 24 | *.out 25 | -------------------------------------------------------------------------------- /CODE-OF-CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Community Code of Conduct 2 | 3 | NATS follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md). 4 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Stage 1: Build the binary 2 | FROM golang:1.22 AS builder 3 | 4 | LABEL maintainer "Stephen Asbury " 5 | 6 | LABEL "ProductName"="NATS-MQ Bridge" \ 7 | "ProductVersion"="0.5" 8 | 9 | # Install the MQ client from the Redistributable package. This also 10 | # contains the header files we need to compile against. 11 | RUN mkdir -p /opt/mqm && cd /opt/mqm \ 12 | && curl -LO "https://public.dhe.ibm.com/ibmdl/export/pub/software/websphere/messaging/mqdev/redist/9.4.1.0-IBM-MQC-Redist-LinuxX64.tar.gz" \ 13 | && tar -zxf ./*.tar.gz \ 14 | && rm -f ./*.tar.gz 15 | 16 | ENV CGO_CFLAGS="-I/opt/mqm/inc/" 17 | ENV CGO_LDFLAGS_ALLOW="-Wl,-rpath.*" 18 | 19 | # Copy and build the nats-mq code 20 | RUN mkdir -p /nats-mq \ 21 | && chmod -R 777 /nats-mq 22 | COPY . /nats-mq 23 | RUN rm -rf /nats-mq/build /nats-mq/.vscode 24 | RUN chmod -R a+rx /nats-mq 25 | 26 | RUN cd /nats-mq && go mod download 27 | RUN cd /nats-mq/nats-mq && go build -o $GOPATH/bin/nats-mq 28 | 29 | # Stage 2: Create the final image 30 | FROM debian:bookworm-slim 31 | 32 | # Install necessary dependencies 33 | RUN apt-get update && apt-get install -y libstdc++6 && rm -rf /var/lib/apt/lists/* 34 | 35 | # Create a non-root user and set a working directory 36 | RUN useradd -m -s /bin/bash natsmq 37 | 38 | # Add directories that are expected by MQ client 39 | RUN mkdir -p /IBM/MQ/data/errors \ 40 | && mkdir -p /.mqm \ 41 | && chmod -R 777 /IBM \ 42 | && chmod -R 777 /.mqm 43 | 44 | WORKDIR /home/natsmq 45 | 46 | RUN mkdir -p /opt/mqm/lib 47 | RUN mkdir -p /opt/mqm/lib64 48 | RUN mkdir -p /opt/mqm/samp 49 | RUN mkdir -p /opt/mqm/msg 50 | 51 | # Copy the nats-mq binary and MQ libraries from the builder stage 52 | COPY --from=builder /go/bin/nats-mq /usr/local/bin/nats-mq 53 | COPY --from=builder /opt/mqm/lib64/* /opt/mqm/lib64 54 | COPY --from=builder /opt/mqm/msg/* /opt/mqm/msg 55 | COPY --from=builder /opt/mqm/lib/ccsid.tbl /opt/mqm/lib/ccsid.tbl 56 | COPY --from=builder /opt/mqm/samp/ccsid_part2.tbl /opt/mqm/samp/ccsid_part2.tbl 57 | 58 | # Set the library path 59 | ENV LD_LIBRARY_PATH=/opt/mqm/lib64 60 | 61 | # Change ownership of the binary and libraries to the non-root user 62 | RUN chown -R natsmq:natsmq /usr/local/bin/nats-mq /opt/mqm/ 63 | 64 | # Switch to the non-root user 65 | USER natsmq 66 | 67 | # Run the bridge 68 | ENTRYPOINT ["/usr/local/bin/nats-mq", "-c", "/mqbridge.conf"] 69 | -------------------------------------------------------------------------------- /GOVERNANCE.md: -------------------------------------------------------------------------------- 1 | # NATS Server Governance 2 | 3 | This repository is part of the NATS project and is subject to the [NATS Governance](https://github.com/nats-io/nats-general/blob/master/GOVERNANCE.md). 4 | -------------------------------------------------------------------------------- /MAINTAINERS.md: -------------------------------------------------------------------------------- 1 | # Maintainers 2 | 3 | Maintainership is on a per project basis. 4 | 5 | ### Maintainers 6 | - Derek Collison [@derekcollison](https://github.com/derekcollison) 7 | - Stephen Asbury [@sasbury](https://github.com/sasbury) 8 | - Ivan Kozlovic [@kozlovic](https://github.com/kozlovic) 9 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | build: fmt check compile 3 | 4 | fmt: 5 | misspell -locale US . 6 | gofmt -s -w message/*.go 7 | gofmt -s -w nats-mq/*.go 8 | gofmt -s -w nats-mq/conf/*.go 9 | gofmt -s -w nats-mq/core/*.go 10 | gofmt -s -w nats-mq/logging/*.go 11 | gofmt -s -w performance/full/*.go 12 | gofmt -s -w performance/queues/*.go 13 | gofmt -s -w performance/full_testenv/*.go 14 | gofmt -s -w performance/multiqueue_testenv/*.go 15 | gofmt -s -w performance/singlequeue_testenv/*.go 16 | goimports -w message/*.go 17 | goimports -w nats-mq/*.go 18 | goimports -w nats-mq/conf/*.go 19 | goimports -w nats-mq/core/*.go 20 | goimports -w nats-mq/logging/*.go 21 | goimports -w performance/encodingperf/*.go 22 | goimports -w performance/full/*.go 23 | goimports -w performance/queues/*.go 24 | goimports -w performance/full_testenv/*.go 25 | goimports -w performance/multiqueue_testenv/*.go 26 | goimports -w performance/singlequeue_testenv/*.go 27 | 28 | check: 29 | go vet ./... 30 | staticcheck ./... 31 | 32 | update: 33 | go get -u honnef.co/go/tools/cmd/staticcheck 34 | go get -u github.com/client9/misspell/cmd/misspell 35 | 36 | compile: 37 | go build ./... 38 | 39 | cover: test 40 | go tool cover -html=./coverage.out 41 | 42 | test: check 43 | rm -rf ./cover.out 44 | go test -coverpkg=./... -coverprofile=./cover.out ./... 45 | 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NATS-MQ Bridge 2 | 3 | This project implements a simple, but generic, bridge between NATS or NATS streaming and MQ Series queues and topics. 4 | 5 | ## Features 6 | 7 | * Support for bridging from/to MQ-Series queues or topics 8 | * Arbitrary subjects in NATS, wildcards for incoming messages 9 | * Arbitrary channels in NATS streaming 10 | * Optional durable subscriber names for streaming 11 | * Complete mapping with message headers, properties and the message body 12 | * An option to only pass message bodies 13 | * Request/Reply mapping, when connectors are available 14 | * Configurable std-out logging 15 | * A single configuration file, with support for reload 16 | * Optional SSL to/from MQ-Series, NATS and NATS streaming 17 | * HTTP/HTTPS-based monitoring endpoints for health or statistics 18 | 19 | ## Overview 20 | 21 | The bridge runs as a single process with a configured set of connectors mapping an MQ-Series queue or topic to a NATS subject or a NATS streaming channel. Connectors can also map the opposite direction from NATS to MQ-Series. Each connector is a one-way bridge. 22 | 23 | Connectors share a NATS connection and an optional connection to the NATS streaming server. **Connectors each create a connection to the MQ server, subject to TCP connection sharing in the underlying library** 24 | 25 | Messages can be forwarded with or without headers. This mapping is as bi-directional as possible. NATS clients can send messages with MQ headers set, and NATS clients can read the headers contained in MQ messages. However, there are a few limitations where NATS to MQ messages will have headers stripped because they can't be passed in to the queue or topic. When headers are included, the contents of the NATS message is prescribed by a [msgpack-based format.](docs/messages.md) Connectors set to exclude headers will just use the body of the MQ message as the entire NATS message. 26 | 27 | Request-reply is supported for Queues. Topics with a reply-to queue should work, but reply-to topics are not supported. This support is based on the bridge's configuration. If the bridge maps `queue1` to `subject1` and `subject2` to `queue2`, then a message to `queue1` with a reply-to queue of `queue2` will go out on NATS `subject1` with a reply-to of `subject2`, and vice-versa for the other direction. **Request-reply requires message headers**, and will not work if headers are excluded. 28 | 29 | The bridge is [configured with a NATS server-like format](docs/config.md), in a single file and uses the NATS logger. 30 | 31 | An [optional HTTP/HTTPS endpoint](docs/monitoring.md) can be used for monitoring. 32 | 33 | A [Tutorial](docs/tutorial.md) is included in the documentation that will walk through a full example of running with NATS, MQ series, the bridge, a GO client and a Java client. 34 | 35 | ## Documentation 36 | 37 | * [Tutorial](docs/tutorial.md) 38 | * [Build & Run the Bridge](docs/buildandrun.md) 39 | * [Configuration](docs/config.md) 40 | * [Message Format](docs/messages.md) 41 | * [Monitoring](docs/monitoring.md) 42 | 43 | ## External Resources 44 | 45 | * [NATS](https://nats.io/documentation/) 46 | * [NATS server](https://github.com/nats-io/nats-server) 47 | * [NATS Streaming](https://github.com/nats-io/nats-streaming-server) 48 | * [MQ Library](https://github.com/ibm-messaging/mq-golang) 49 | 50 | ## License 51 | 52 | Unless otherwise noted, the NATS-MQ bridge source files are distributed under the Apache Version 2.0 license found in the LICENSE file. 53 | -------------------------------------------------------------------------------- /dependencies.md: -------------------------------------------------------------------------------- 1 | # External Dependencies 2 | 3 | This file lists the dependencies used in this repository. 4 | 5 | | Dependency | License | 6 | |-|-| 7 | | github.com/BurntSushi/toml v0.3.1 | MIT | 8 | | github.com/DataDog/datadog-go v2.2.0+incompatible | MIT | 9 | | github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878 | MIT | 10 | | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 | MIT | 11 | | github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible | BSD 3-Clause "New" or "Revised" License | 12 | | github.com/circonus-labs/circonusllhist v0.1.3 | BSD 3-Clause "New" or "Revised" License | 13 | | github.com/client9/misspell v0.3.4 | MIT | 14 | | github.com/davecgh/go-spew v1.1.1 | ISC License | 15 | | github.com/go-sql-driver/mysql v1.4.1 | Mozilla Public License 2.0 | 16 | | github.com/gogo/protobuf v1.2.1 | BSD 3-Clause "New" or "Revised" License | 17 | | github.com/golang/protobuf v1.3.1 | BSD 3-Clause "New" or "Revised" License | 18 | | github.com/google/renameio v0.1.0 | Apache License 2.0 | 19 | | github.com/hashicorp/go-cleanhttp v0.5.0 | Mozilla Public License 2.0 | 20 | | github.com/hashicorp/go-hclog v0.9.1 | MIT | 21 | | github.com/hashicorp/go-immutable-radix v1.0.0 | Mozilla Public License 2.0 | 22 | | github.com/hashicorp/go-msgpack v0.5.5 | BSD 3-Clause "New" or "Revised" License | 23 | | github.com/hashicorp/go-retryablehttp v0.5.3 | Mozilla Public License 2.0 | 24 | | github.com/hashicorp/go-uuid v1.0.0 | Mozilla Public License 2.0 | 25 | | github.com/hashicorp/golang-lru v0.5.0 | Mozilla Public License 2.0 | 26 | | github.com/hashicorp/raft v1.1.0 | Mozilla Public License 2.0 | 27 | | github.com/ibm-messaging/mq-golang v0.0.0-20190327085124-99a6892c4514 | Apache License 2.0 | 28 | | github.com/kisielk/errcheck v1.1.0 | MIT | 29 | | github.com/kisielk/gotool v1.0.0 | MIT | 30 | | github.com/kr/pretty v0.1.0 | MIT | 31 | | github.com/kr/pty v1.1.1 | MIT | 32 | | github.com/kr/text v0.1.0 | MIT | 33 | | github.com/lib/pq v1.1.1 | MIT | 34 | | github.com/matttproud/golang_protobuf_extensions v1.0.1 | Apache License 2.0 | 35 | | github.com/nats-io/jwt v0.2.6 | Apache License 2.0 | 36 | | github.com/nats-io/nats-server/v2 v2.0.0 | Apache License 2.0 | 37 | | github.com/nats-io/nats-streaming-server v0.15.1 | Apache License 2.0 | 38 | | github.com/nats-io/nats.go v1.8.1 | Apache License 2.0 | 39 | | github.com/nats-io/nkeys v0.0.2 | Apache License 2.0 | 40 | | github.com/nats-io/nuid v1.0.1 | Apache License 2.0 | 41 | | github.com/nats-io/stan.go v0.5.0 | Apache License 2.0 | 42 | | github.com/pascaldekloe/goe v0.1.0 | Creative Commons 1.0 Universal | 43 | | github.com/pkg/errors v0.8.1 | BSD 2-Clause "Simplified" License | 44 | | github.com/pmezard/go-difflib v1.0.0 | BSD 3-Clause "New" or "Revised" License | 45 | | github.com/prometheus/client_golang v0.9.2 | Apache License 2.0 | 46 | | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 | Apache License 2.0 | 47 | | github.com/prometheus/common v0.0.0-20181126121408-4724e9255275 | Apache License 2.0 | 48 | | github.com/prometheus/procfs v0.0.2 | Apache License 2.0 | 49 | | github.com/rogpeppe/go-internal v1.3.0 | BSD 3-Clause "New" or "Revised" License | 50 | | github.com/stretchr/objx v0.1.0 | MIT | 51 | | github.com/stretchr/testify v1.3.0 | MIT | 52 | | github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926 | MIT | 53 | | github.com/ugorji/go v1.1.7 | MIT | 54 | | github.com/ugorji/go/codec v1.1.7 | MIT | 55 | | go.etcd.io/bbolt v1.3.2 | MIT | 56 | | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 | BSD 3-Clause "New" or "Revised" License | 57 | | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee | BSD 3-Clause "New" or "Revised" License | 58 | | golang.org/x/net v0.0.0-20190620200207-3b0461eec859 | BSD 3-Clause "New" or "Revised" License | 59 | | golang.org/x/sync v0.0.0-20190423024810-112230192c58 | BSD 3-Clause "New" or "Revised" License | 60 | | golang.org/x/sys v0.0.0-20190412213103-97732733099d | BSD 3-Clause "New" or "Revised" License | 61 | | golang.org/x/text v0.3.0 | BSD 3-Clause "New" or "Revised" License | 62 | | golang.org/x/tools v0.0.0-20191223181704-8c5978f193d4 | BSD 3-Clause "New" or "Revised" License | 63 | | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 | BSD 3-Clause "New" or "Revised" License | 64 | | google.golang.org/appengine v1.6.0 | Apache License 2.0 | 65 | | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 | BSD 2-Clause "Simplified" License | 66 | | gopkg.in/errgo.v2 v2.1.0 | BSD 3-Clause "New" or "Revised" License | 67 | | honnef.co/go/tools v0.0.1-2019.2.3 | MIT-Like | 68 | -------------------------------------------------------------------------------- /deploy/helm/mq-bridge/Chart.yaml: -------------------------------------------------------------------------------- 1 | name: nats-mq 2 | version: 0.1.0 3 | appVersion: 0.5.0 4 | description: A simple, but generic, bridge between NATS or NATS streaming and MQ Series queues and topics. 5 | keywords: 6 | - addressing 7 | - discovery 8 | - messaging 9 | - nats 10 | - pubsub 11 | home: https://github.com/nats-io/nats-mq 12 | sources: 13 | - https://github.com/nats-io/nats-mq 14 | maintainers: 15 | - name: wallyqs 16 | email: wally@synadia.com 17 | -------------------------------------------------------------------------------- /deploy/helm/mq-bridge/templates/config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | kind: ConfigMap 3 | apiVersion: v1 4 | metadata: 5 | name: {{ .Values.name }}-conf 6 | data: 7 | nats-mq.conf: | 8 | nats { 9 | Servers ["{{ .Values.nats.url }}"] 10 | } 11 | -------------------------------------------------------------------------------- /deploy/helm/mq-bridge/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: {{ .Values.name }} 6 | spec: 7 | replicas: 1 8 | selector: 9 | matchLabels: 10 | name: {{ .Values.name }} 11 | template: 12 | metadata: 13 | labels: 14 | name: {{ .Values.name }} 15 | spec: 16 | containers: 17 | - name: nats-mq 18 | image: {{ .Values.image }} 19 | imagePullPolicy: Always 20 | command: 21 | - /go/bin/nats-mq 22 | - -c 23 | - /etc/nats-mq/nats-mq.conf 24 | volumeMounts: 25 | - name: config 26 | mountPath: /etc/nats-mq/ 27 | volumes: 28 | - name: config 29 | configMap: 30 | name: {{ .Values.name }}-conf 31 | -------------------------------------------------------------------------------- /deploy/helm/mq-bridge/values.yaml: -------------------------------------------------------------------------------- 1 | name: nats-mq-bridge 2 | image: connecteverything/mq-bridge:0.5 3 | nats: 4 | # The NATS endpoint to which it will connect. 5 | url: nats-mq-svc:4222 6 | -------------------------------------------------------------------------------- /deploy/nats-cluster.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: "nats.io/v1alpha2" 3 | kind: NatsCluster 4 | metadata: 5 | name: "nats-mq-svc" 6 | spec: 7 | size: 3 8 | -------------------------------------------------------------------------------- /deploy/nats-mq.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: nats-mq 6 | spec: 7 | replicas: 1 8 | selector: 9 | matchLabels: 10 | name: nats-mq 11 | template: 12 | metadata: 13 | labels: 14 | name: nats-mq 15 | spec: 16 | containers: 17 | - name: nats-mq 18 | image: connecteverything/mq-bridge:0.5 19 | imagePullPolicy: Always 20 | command: 21 | - /go/bin/nats-mq 22 | - -c 23 | - /etc/nats-mq/nats-mq.conf 24 | volumeMounts: 25 | - name: config 26 | mountPath: /etc/nats-mq/ 27 | volumes: 28 | - name: config 29 | configMap: 30 | name: nats-mq-conf 31 | --- 32 | kind: ConfigMap 33 | apiVersion: v1 34 | metadata: 35 | name: nats-mq-conf 36 | data: 37 | nats-mq.conf: | 38 | nats { 39 | Servers ["nats-mq-svc:4222"] 40 | } 41 | -------------------------------------------------------------------------------- /deploy/operator.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: nats-operator 5 | # Change to the name of the namespace where to install NATS Operator. 6 | # Alternatively, change to "nats-io" to perform a cluster-scoped deployment in supported versions. 7 | namespace: default 8 | spec: 9 | replicas: 1 10 | selector: 11 | matchLabels: 12 | name: nats-operator 13 | template: 14 | metadata: 15 | labels: 16 | name: nats-operator 17 | spec: 18 | serviceAccountName: nats-operator 19 | containers: 20 | - name: nats-operator 21 | image: connecteverything/nats-operator:0.4.4-v1alpha2 22 | imagePullPolicy: IfNotPresent 23 | args: 24 | - nats-operator 25 | # Uncomment to perform a cluster-scoped deployment in supported versions. 26 | #- --feature-gates=ClusterScoped=true 27 | ports: 28 | - name: readyz 29 | containerPort: 8080 30 | env: 31 | - name: MY_POD_NAMESPACE 32 | valueFrom: 33 | fieldRef: 34 | fieldPath: metadata.namespace 35 | - name: MY_POD_NAME 36 | valueFrom: 37 | fieldRef: 38 | fieldPath: metadata.name 39 | readinessProbe: 40 | httpGet: 41 | path: /readyz 42 | port: readyz 43 | initialDelaySeconds: 15 44 | timeoutSeconds: 3 45 | -------------------------------------------------------------------------------- /deploy/rbac.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: nats-operator 6 | # Change to the name of the namespace where to install NATS Operator. 7 | # Alternatively, change to "nats-io" to perform a cluster-scoped deployment in supported versions. 8 | namespace: default 9 | 10 | --- 11 | apiVersion: rbac.authorization.k8s.io/v1 12 | kind: ClusterRoleBinding 13 | metadata: 14 | name: nats-operator-binding 15 | roleRef: 16 | apiGroup: rbac.authorization.k8s.io 17 | kind: ClusterRole 18 | name: nats-operator 19 | subjects: 20 | - kind: ServiceAccount 21 | name: nats-operator 22 | # Change to the name of the namespace where to install NATS Operator. 23 | # Alternatively, change to "nats-io" to perform a cluster-scoped deployment in supported versions. 24 | namespace: default 25 | 26 | # NOTE: When performing multiple namespace-scoped installations, all 27 | # "nats-operator" service accounts (across the different namespaces) 28 | # MUST be added to this binding. 29 | #- kind: ServiceAccount 30 | # name: nats-operator 31 | # namespace: nats-io 32 | #- kind: ServiceAccount 33 | # name: nats-operator 34 | # namespace: namespace-2 35 | #(...) 36 | 37 | --- 38 | apiVersion: rbac.authorization.k8s.io/v1 39 | kind: ClusterRole 40 | metadata: 41 | name: nats-operator 42 | rules: 43 | # Allow creating CRDs 44 | - apiGroups: 45 | - apiextensions.k8s.io 46 | resources: 47 | - customresourcedefinitions 48 | verbs: ["get", "list", "create", "update", "watch"] 49 | 50 | # Allow all actions on NATS Operator manager CRDs 51 | - apiGroups: 52 | - nats.io 53 | resources: 54 | - natsclusters 55 | - natsserviceroles 56 | verbs: ["*"] 57 | 58 | # Allowed actions on Pods 59 | - apiGroups: [""] 60 | resources: 61 | - pods 62 | verbs: ["create", "watch", "get", "patch", "update", "delete", "list"] 63 | 64 | # Allowed actions on Services 65 | - apiGroups: [""] 66 | resources: 67 | - services 68 | verbs: ["create", "watch", "get", "patch", "update", "delete", "list"] 69 | 70 | # Allowed actions on Secrets 71 | - apiGroups: [""] 72 | resources: 73 | - secrets 74 | verbs: ["create", "watch", "get", "update", "delete", "list"] 75 | 76 | # Allow all actions on some special subresources 77 | - apiGroups: [""] 78 | resources: 79 | - pods/exec 80 | - pods/log 81 | - serviceaccounts/token 82 | - events 83 | verbs: ["*"] 84 | 85 | # Allow listing Namespaces and ServiceAccounts 86 | - apiGroups: [""] 87 | resources: 88 | - namespaces 89 | - serviceaccounts 90 | verbs: ["list", "get", "watch"] 91 | 92 | # Allow actions on Endpoints 93 | - apiGroups: [""] 94 | resources: 95 | - endpoints 96 | verbs: ["create", "watch", "get", "update", "delete", "list"] 97 | 98 | --- 99 | apiVersion: v1 100 | kind: ServiceAccount 101 | metadata: 102 | name: nats-server 103 | namespace: default 104 | --- 105 | apiVersion: rbac.authorization.k8s.io/v1 106 | kind: ClusterRole 107 | metadata: 108 | name: nats-server 109 | rules: 110 | - apiGroups: [""] 111 | resources: 112 | - nodes 113 | verbs: ["get"] 114 | --- 115 | apiVersion: rbac.authorization.k8s.io/v1 116 | kind: ClusterRoleBinding 117 | metadata: 118 | name: nats-server-binding 119 | roleRef: 120 | apiGroup: rbac.authorization.k8s.io 121 | kind: ClusterRole 122 | name: nats-server 123 | subjects: 124 | - kind: ServiceAccount 125 | name: nats-server 126 | namespace: default -------------------------------------------------------------------------------- /docs/messages.md: -------------------------------------------------------------------------------- 1 | # NATS-MQ Bridge Message Format 2 | 3 | The bridge provides two modes of message handling. In the [ExcludeHeaders](config.md#connectors) mode, a connector will take the raw NATS messages and put them into MQ messages as the body, or vice versa. No translation occurs and all MQ headers and properties are ignored. If ExcludeHeaders is false, the default, MQ messages are translated into a [msgpack](https://msgpack.org/index.html) format. NATS clients are required to use this format as well when in this mode. 4 | 5 | The remainder of this document is focused on the message format when ExcludeHeaders is false, and encoding occurs. 6 | 7 | * [Encoded Messages](#encode) 8 | * [Known Headers/Metadata](#headers) 9 | * [Message Properties](#props) 10 | * [The Message Body](#body) 11 | * [Request-Reply](#reqrep) 12 | * [Helpers](#helpers) 13 | * [Golang](#golang) 14 | 15 | 16 | 17 | ## Encoded Messages 18 | 19 | Encoded messages have three root level elements: 20 | 21 | * Properties - `props` - mapped to/from MQ series message properties. These are typed, and have some type limitations. 22 | * Header - `header` - a structure containing the MQ series message headers/metadata. 23 | * Body - `body` - the byte array body of the MQ message. 24 | 25 | It is worth thinking about the encoding process from two sides. When messages come out of MQ series, the bridge can read all of the properties and create a valid map of them. The bridge can also read all of the known headers and collect them. Of course, the message body can be read as well, although some size limits may be encountered on the NATS side. In other words, messages coming from MQ series should map well to the encoded format. Messages created in NATS and sent to the bridge, as msgpack encoded byte arrays may have some restrictions. For example, the `PutDate` header can't be set by a client so it is ignored when moving through the bridge into MQ series. 26 | 27 | 28 | 29 | ### Known Headers/Metadata 30 | 31 | The encoded header structure contains the following elements (in golang): 32 | 33 | ```golang 34 | type BridgeHeader struct { 35 | Version int32 `codec:"version,omitempty"` 36 | Report int32 `codec:"report,omitempty"` 37 | MsgType int32 `codec:"type,omitempty"` 38 | Expiry int32 `codec:"exp,omitempty"` 39 | Feedback int32 `codec:"feed,omitempty"` 40 | Encoding int32 `codec:"enc,omitempty"` 41 | CodedCharSetID int32 `codec:"charset,omitempty"` 42 | Format string `codec:"format,omitempty"` 43 | Priority int32 `codec:"priority,omitempty"` 44 | Persistence int32 `codec:"persist,omitempty"` 45 | MsgID []byte `codec:"msg_id,omitempty"` 46 | CorrelID []byte `codec:"corr_id,omitempty"` 47 | BackoutCount int32 `codec:"backout,omitempty"` 48 | ReplyToQ string `codec:"rep_q,omitempty"` 49 | ReplyToQMgr string `codec:"rep_qmgr,omitempty"` 50 | UserIdentifier string `codec:"user_id,omitempty"` 51 | AccountingToken []byte `codec:"acct_token,omitempty"` 52 | ApplIdentityData string `codec:"appl_id,omitempty"` 53 | PutApplType int32 `codec:"appl_type,omitempty"` 54 | PutApplName string `codec:"appl_name,omitempty"` 55 | PutDate string `codec:"date,omitempty"` 56 | PutTime string `codec:"time,omitempty"` 57 | ApplOriginData string `codec:"appl_orig_data,omitempty"` 58 | GroupID []byte `codec:"grp_id,omitempty"` 59 | MsgSeqNumber int32 `codec:"seq,omitempty"` 60 | Offset int32 `codec:"offset,omitempty"` 61 | MsgFlags int32 `codec:"flags,omitempty"` 62 | OriginalLength int32 `codec:"orig_length,omitempty"` 63 | ReplyToChannel string `codec:"reply_to_channel,omitempty"` 64 | } 65 | ``` 66 | 67 | These will be encoded into msgpack with their full field names and types. 68 | 69 | 70 | 71 | ### Message Properties 72 | 73 | Because message properties are typed, we encode them into a special struct. The format in Go is: 74 | 75 | ```golang 76 | type Property struct { 77 | Type int `codec:"type,omitempty"` 78 | Value interface{} `codec:"value,omitempty"` 79 | } 80 | ``` 81 | 82 | Where the type is one of the following self-describing values: 83 | 84 | ```golang 85 | PropertyTypeString = 0 86 | PropertyTypeInt8 = 1 87 | PropertyTypeInt16 = 2 88 | PropertyTypeInt32 = 3 89 | PropertyTypeInt64 = 4 90 | PropertyTypeFloat32 = 5 91 | PropertyTypeFloat64 = 6 92 | PropertyTypeBool = 7 93 | PropertyTypeBytes = 8 94 | PropertyTypeNull = 9 95 | ``` 96 | 97 | These map directly to the types provided by the MQ series library used by the bridge. 98 | 99 | 100 | 101 | ### The Message Body 102 | 103 | The message body in MQ series is mapped directly to a body field in the msgpack encoding. 104 | 105 | 106 | 107 | ## Request-Reply 108 | 109 | The bridge tries to respect request-reply semantics. When a message comes in from MQ series with the ReplyToQ header set the bridge will try to find an equivalent subject or channel. In the case of MQ-NATS the subject that maps to the ReplyToQ will be used as the reply to subject. In the case of streaming, a special field in the header ReplyToChannel will be set with the streaming version of the reply to. 110 | 111 | Messages coming from streaming or NATS will have their ReplyToQ and ReplyToQMgr headers set before going into MQ series, if they have a reply to subject or the ReplyToChannel field set in the encoded message. 112 | 113 | Keep in mind that this bi-directional request-reply support requires two connectors, one for MQ-NATS/STAN and one for NATS/STAN-MQ in the same bridge. 114 | 115 | 116 | 117 | ## Helpers 118 | 119 | The bridge comes with a few helpers to read/write encoded messages. 120 | 121 | 122 | 123 | ### Go 124 | 125 | For go, the `/message` package in the bridge code contains everything needed to encode and decode messages. 126 | 127 | Use the `NewBridgeMessage(body []byte)` function to create a message with a set of bytes for the body. Then use `SetProperty()` method on the message to set properties, or set header fields directly. 128 | 129 | Use `DecodeBridgeMessage(encoded []byte)` to create a new message from an encoded set of bytes. Read the headers and body directly, or read properties with the various `Get*Property` methods. 130 | 131 | 132 | 133 | ### Java 134 | 135 | A helper class for java is provided in the `/helpers/java` folder. This code depends on the jackson annotations, jackson object encoder and msg pack library. To create a new message, use `Message.NewMessageWithBody(byte[] body)` and then call `encode()` to get out the encoded bytes. 136 | 137 | To decode a message use `Message.DecodeMessage([]byte encoded)` to create the message object. There is a `getHeader()` method to access the MQ header metadata and assorted `get*Property` method to get the properties. 138 | -------------------------------------------------------------------------------- /docs/monitoring.md: -------------------------------------------------------------------------------- 1 | # Monitoring the NATS-MQ Bridge 2 | 3 | The nats-mq bridge provides optional HTTP/s monitoring. When [configured with a monitoring port](config.md#monitoring) the server will provide two HTTP endpoints: 4 | 5 | * [/varz](#varz) 6 | * [/healthz](#healthz) 7 | 8 | 9 | 10 | ## /varz 11 | 12 | The `/varz` endpoint returns a JSON encoded set of statistics for the server. These statistics are wrapped in a root level object with the following properties: 13 | 14 | * `start_time` - the start time of the bridge, in the bridge's timezone. 15 | * `current_time` - the current time, in the bridge's timezone. 16 | * `uptime` - a string representation of the server's up time. 17 | * `http_requests` - a map of request paths to counts, the keys are `/`, `/varz` and `/healthz`. 18 | * `connectors` - an array of statistics for each connector. 19 | 20 | Each object in the connectors array, one per connector, will contain the following properties: 21 | 22 | * `name` - the name of the connector, a human readable description of the connector. 23 | * `id` - the connectors id, either set in the configuration or generated at runtime. 24 | * `connects` - a count of the number of times the connector has connected. 25 | * `disconnects` - a count of the number of times the connector has disconnected. 26 | * `bytes_in` - the number of bytes the connector has received, may differ from received due to headers and encoding. 27 | * `bytes_out` - the number of bytes the connector has sent, may differ from received due to headers and encoding. 28 | * `msg_in` - the number of messages received. 29 | * `msg_out` - the number of messages sent. 30 | * `count` - the total number of requests for this connector. 31 | * `rma` - a [running moving average](https://en.wikipedia.org/wiki/Moving_average) of the time required to handle each request. The time is in nanoseconds. 32 | * `q50` - the 50% quantile for response times, in nanoseconds. 33 | * `q75` - the 75% quantile for response times, in nanoseconds. 34 | * `q90` - the 90% quantile for response times, in nanoseconds. 35 | * `q95` - the 95% quantile for response times, in nanoseconds. 36 | 37 | 38 | 39 | ## /healthz 40 | 41 | The `/healthz` endpoint is provided for automated up/down style checks. The server returns an HTTP/200 when running and won't respond if it is down. -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/nats-io/nats-mq 2 | 3 | go 1.23.0 4 | 5 | require ( 6 | github.com/ibm-messaging/mq-golang/v5 v5.6.1 7 | github.com/nats-io/nats-server/v2 v2.10.27 8 | github.com/nats-io/nats-streaming-server v0.25.6 9 | github.com/nats-io/nats.go v1.39.1 10 | github.com/nats-io/nuid v1.0.1 11 | github.com/nats-io/stan.go v0.10.4 12 | github.com/stretchr/testify v1.8.4 13 | github.com/ugorji/go/codec v1.2.12 14 | ) 15 | 16 | require ( 17 | github.com/armon/go-metrics v0.4.1 // indirect 18 | github.com/davecgh/go-spew v1.1.1 // indirect 19 | github.com/fatih/color v1.18.0 // indirect 20 | github.com/gogo/protobuf v1.3.2 // indirect 21 | github.com/hashicorp/go-hclog v1.6.3 // indirect 22 | github.com/hashicorp/go-immutable-radix v1.3.1 // indirect 23 | github.com/hashicorp/go-msgpack/v2 v2.1.2 // indirect 24 | github.com/hashicorp/golang-lru v1.0.2 // indirect 25 | github.com/hashicorp/raft v1.7.1 // indirect 26 | github.com/klauspost/compress v1.18.0 // indirect 27 | github.com/mattn/go-colorable v0.1.13 // indirect 28 | github.com/mattn/go-isatty v0.0.20 // indirect 29 | github.com/minio/highwayhash v1.0.3 // indirect 30 | github.com/nats-io/jwt/v2 v2.7.3 // indirect 31 | github.com/nats-io/nkeys v0.4.10 // indirect 32 | github.com/pmezard/go-difflib v1.0.0 // indirect 33 | github.com/prometheus/procfs v0.15.1 // indirect 34 | go.etcd.io/bbolt v1.3.11 // indirect 35 | golang.org/x/crypto v0.34.0 // indirect 36 | golang.org/x/sys v0.30.0 // indirect 37 | golang.org/x/time v0.10.0 // indirect 38 | gopkg.in/yaml.v3 v3.0.1 // indirect 39 | ) 40 | -------------------------------------------------------------------------------- /helpers/java/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | 3 | version = '1.0' 4 | 5 | archivesBaseName = 'mq-nats' 6 | group = 'io.nats' 7 | description = "MQ-NATS bridge helper for Java" 8 | 9 | repositories { 10 | mavenCentral() 11 | } 12 | 13 | dependencies { 14 | compile 'org.msgpack:jackson-dataformat-msgpack:0.7.1' 15 | compile 'com.fasterxml.jackson.core:jackson-annotations' 16 | compile 'io.nats:jnats:2.4+' 17 | testImplementation 'junit:junit:4.12' 18 | } 19 | 20 | jar { 21 | manifest { 22 | attributes('Implementation-Title': 'Java MQ-NATS Bridge Helpers', 23 | 'Implementation-Version': '1.0', 24 | 'Implementation-Vendor': 'synadia.com') 25 | } 26 | } 27 | 28 | task fatJar(type: Jar) { 29 | classifier = 'fat' 30 | manifest { 31 | attributes('Implementation-Title': 'Java MQ-NATS Bridge Helpers', 32 | 'Implementation-Version': '1.0', 33 | 'Implementation-Vendor': 'synadia.com') 34 | } 35 | from { configurations.compileClasspath.collect { it.isDirectory() ? it : zipTree(it) } } 36 | with jar 37 | } -------------------------------------------------------------------------------- /helpers/java/src/main/java/io/nats/mqbridge/examples/Tick.java: -------------------------------------------------------------------------------- 1 | package io.nats.mqbridge.examples; 2 | 3 | import java.nio.charset.StandardCharsets; 4 | import java.text.SimpleDateFormat; 5 | import java.time.Duration; 6 | import java.util.Calendar; 7 | import java.util.Timer; 8 | import java.util.TimerTask; 9 | 10 | import io.nats.client.Connection; 11 | import io.nats.client.NUID; 12 | import io.nats.client.Nats; 13 | import io.nats.client.Options; 14 | import io.nats.mqbridge.Message; 15 | 16 | public class Tick { 17 | public static void main(String args[]) { 18 | 19 | if (args.length < 2) { 20 | System.out.println("usage: Tick serverURL message"); 21 | return; 22 | } 23 | 24 | try { 25 | Options o = new Options.Builder().connectionName("MQ-NATS Bridge tick-tock (ticker) example").server(args[0]).build(); 26 | Connection nc = Nats.connect(o); 27 | byte[] body = args[1].getBytes(StandardCharsets.UTF_8); 28 | Timer timer = new Timer("tick"); 29 | 30 | timer.scheduleAtFixedRate(new TimerTask() { 31 | long counter; 32 | 33 | public void run() { 34 | String subject = "tick"; 35 | Message bridgeMessage = Message.NewMessageWithBody(body); 36 | 37 | this.counter++; 38 | String theTime = new SimpleDateFormat("E M dd HH:mm:ss z yyyy").format(Calendar.getInstance().getTime()); 39 | String corrID = NUID.nextGlobal(); 40 | 41 | bridgeMessage.setLongProperty("counter", this.counter); 42 | bridgeMessage.setStringProperty("time", theTime); 43 | bridgeMessage.getHeader().setCorrelID(corrID.getBytes(StandardCharsets.UTF_8)); 44 | 45 | try { 46 | System.out.println("Sending message:"); 47 | System.out.printf("\tbody: %s\n", new String(bridgeMessage.getBody(), StandardCharsets.UTF_8)); 48 | 49 | long counter = bridgeMessage.getLongProperty("counter"); 50 | System.out.printf("\tcounter: %d\n", counter); 51 | 52 | String time = bridgeMessage.getStringProperty("time"); 53 | System.out.printf("\ttime: %s\n", time); 54 | 55 | System.out.printf("\tid: %s\n", new String(bridgeMessage.getHeader().getCorrelID(), StandardCharsets.UTF_8)); 56 | System.out.println(); 57 | 58 | byte[] encoded = bridgeMessage.encode(); 59 | nc.publish(subject, encoded); 60 | nc.flush(Duration.ofSeconds(5)); 61 | } catch(Exception e) { 62 | e.printStackTrace(); 63 | System.exit(-1); 64 | } 65 | } 66 | }, 1000L, 1000L); 67 | } catch (Exception e) { 68 | e.printStackTrace(); 69 | System.exit(-1); 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /helpers/java/src/main/java/io/nats/mqbridge/examples/Tock.java: -------------------------------------------------------------------------------- 1 | package io.nats.mqbridge.examples; 2 | 3 | import java.nio.charset.StandardCharsets; 4 | import java.time.Duration; 5 | 6 | import io.nats.client.Connection; 7 | import io.nats.client.Nats; 8 | import io.nats.client.Options; 9 | import io.nats.client.Subscription; 10 | import io.nats.mqbridge.Message; 11 | 12 | public class Tock { 13 | public static void main(String args[]) { 14 | 15 | if (args.length < 1) { 16 | System.out.println("usage: Tock serverURL"); 17 | return; 18 | } 19 | 20 | try { 21 | Options o = new Options.Builder().connectionName("MQ-NATS Bridge tick-tock (ticker) example").server(args[0]).build(); 22 | Connection nc = Nats.connect(o); 23 | Subscription sub = nc.subscribe("tock"); 24 | 25 | System.out.println("Listening on tock..."); 26 | 27 | while (true) { 28 | io.nats.client.Message msg = sub.nextMessage(Duration.ofSeconds(10)); 29 | 30 | if (msg == null) { 31 | continue; 32 | } 33 | 34 | Message bridgeMessage = Message.DecodeMessage(msg.getData()); 35 | 36 | System.out.println("Received message:"); 37 | System.out.printf("\tbody: %s\n", new String(bridgeMessage.getBody(), StandardCharsets.UTF_8)); 38 | 39 | long counter = bridgeMessage.getLongProperty("counter"); 40 | System.out.printf("\tcounter: %d\n", counter); 41 | 42 | String time = bridgeMessage.getStringProperty("time"); 43 | System.out.printf("\ttime: %s\n", time); 44 | 45 | System.out.printf("\tid: %s\n", new String(bridgeMessage.getHeader().getCorrelID(), StandardCharsets.UTF_8)); 46 | System.out.println(); 47 | } 48 | } catch (Exception e) { 49 | e.printStackTrace(); 50 | System.exit(-1); 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /helpers/java/src/test/java/io/nats/mqbridge/MessageTest.java: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2019 The NATS Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | package io.nats.mqbridge; 14 | 15 | import static org.junit.Assert.assertEquals; 16 | import static org.junit.Assert.assertTrue; 17 | 18 | import java.io.IOException; 19 | import java.nio.charset.StandardCharsets; 20 | import java.nio.file.Files; 21 | import java.nio.file.Path; 22 | import java.nio.file.Paths; 23 | 24 | import org.junit.Test; 25 | 26 | public class MessageTest { 27 | @Test 28 | public void testInterchange() throws IOException { 29 | Path path = Paths.get("../../resources", "interchange.bin"); 30 | byte[] encoded = Files.readAllBytes(path); 31 | Message msg = Message.DecodeMessage(encoded); 32 | 33 | assertEquals("hello world", msg.getStringProperty("string")); 34 | 35 | assertEquals((byte)9, msg.getByteProperty("int8")); 36 | assertEquals((short)259, msg.getShortProperty("int16")); 37 | assertEquals((int)222222222, msg.getIntProperty("int32")); 38 | assertEquals(222222222222222222L, msg.getLongProperty("int64")); 39 | 40 | assertEquals((float)3.14, msg.getFloatProperty("float32"), 0.0001); 41 | assertEquals((double)6.4999, msg.getDoubleProperty("float64"), 0.0001); 42 | 43 | assertTrue(msg.getBooleanProperty("bool")); 44 | 45 | byte[] bytes = msg.getBytesProperty("bytes"); 46 | String asString = new String(bytes, StandardCharsets.UTF_8); 47 | assertEquals("one two three four", asString); 48 | 49 | assertEquals("hello world", msg.getBodyAsUTF8String()); 50 | assertEquals(1, msg.getHeader().getVersion()); 51 | assertEquals(2, msg.getHeader().getReport()); 52 | 53 | String id = new String(msg.getHeader().getMsgID(), StandardCharsets.UTF_8); 54 | assertEquals("cafebabe", id); 55 | } 56 | } -------------------------------------------------------------------------------- /message/examples/tick/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2019 The NATS Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | package main 14 | 15 | import ( 16 | "flag" 17 | "fmt" 18 | "log" 19 | "runtime" 20 | "time" 21 | 22 | "github.com/nats-io/nats-mq/message" 23 | "github.com/nats-io/nuid" 24 | 25 | "github.com/nats-io/nats.go" 26 | ) 27 | 28 | func usage() { 29 | log.Printf("Usage: tick [-s server] \n") 30 | flag.PrintDefaults() 31 | } 32 | 33 | func main() { 34 | var urls = flag.String("s", nats.DefaultURL, "The nats server URLs (separated by comma)") 35 | 36 | log.SetFlags(0) 37 | flag.Usage = usage 38 | flag.Parse() 39 | 40 | args := flag.Args() 41 | if len(args) != 1 { 42 | usage() 43 | return 44 | } 45 | 46 | opts := []nats.Option{nats.Name("MQ-NATS Bridge tick-tock (ticker) example")} 47 | 48 | nc, err := nats.Connect(*urls, opts...) 49 | if err != nil { 50 | log.Fatal(err) 51 | } 52 | 53 | subj := "tick" 54 | body := []byte(args[0]) 55 | 56 | ticker := time.NewTicker(1 * time.Second) 57 | go func() { 58 | counter := 0 59 | for t := range ticker.C { 60 | counter = counter + 1 61 | bridgeMsg := message.NewBridgeMessage(body) 62 | bridgeMsg.Header.CorrelID = []byte(nuid.Next()) 63 | bridgeMsg.SetProperty("counter", counter) 64 | bridgeMsg.SetProperty("time", t.Format(time.UnixDate)) 65 | 66 | fmt.Printf("Sending message:\n") 67 | fmt.Printf("\tbody: %s\n", string(bridgeMsg.Body)) 68 | 69 | counter, ok := bridgeMsg.GetInt64Property("counter") 70 | if !ok { 71 | log.Fatal("counter property is missing") 72 | } 73 | fmt.Printf("\tcounter: %d\n", counter) 74 | 75 | time, ok := bridgeMsg.GetStringProperty("time") 76 | if !ok { 77 | log.Fatal("time property is missing") 78 | } 79 | fmt.Printf("\ttime: %s\n", time) 80 | 81 | fmt.Printf("\tid: %s\n", string(bridgeMsg.Header.CorrelID)) 82 | fmt.Println() 83 | 84 | encoded, err := bridgeMsg.Encode() 85 | 86 | if err != nil { 87 | log.Fatal(err) 88 | } 89 | nc.Publish(subj, encoded) 90 | nc.Flush() 91 | } 92 | }() 93 | 94 | fmt.Println("Running forever, use ctrl-c to cancel...") 95 | runtime.Goexit() 96 | } 97 | -------------------------------------------------------------------------------- /message/examples/tock/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2019 The NATS Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | package main 14 | 15 | import ( 16 | "flag" 17 | "fmt" 18 | "github.com/nats-io/nats-mq/message" 19 | "log" 20 | "runtime" 21 | "time" 22 | 23 | "github.com/nats-io/nats.go" 24 | ) 25 | 26 | func usage() { 27 | log.Printf("Usage: tock [-s server]\n") 28 | flag.PrintDefaults() 29 | } 30 | 31 | func main() { 32 | var urls = flag.String("s", nats.DefaultURL, "The nats server URLs (separated by comma)") 33 | 34 | log.SetFlags(0) 35 | flag.Usage = usage 36 | flag.Parse() 37 | 38 | args := flag.Args() 39 | if len(args) != 0 { 40 | usage() 41 | return 42 | } 43 | 44 | opts := []nats.Option{nats.Name("MQ-NATS Bridge tick-tock (ticker) example")} 45 | opts = setupConnOptions(opts) 46 | 47 | nc, err := nats.Connect(*urls, opts...) 48 | if err != nil { 49 | log.Fatal(err) 50 | } 51 | 52 | subj := "tock" 53 | 54 | nc.Subscribe(subj, func(msg *nats.Msg) { 55 | bridgeMsg, err := message.DecodeBridgeMessage(msg.Data) 56 | 57 | if err != nil { 58 | log.Fatal(err) 59 | } 60 | 61 | fmt.Printf("Received message:\n") 62 | fmt.Printf("\tbody: %s\n", string(bridgeMsg.Body)) 63 | 64 | counter, ok := bridgeMsg.GetInt64Property("counter") 65 | if !ok { 66 | log.Fatal("counter property is missing") 67 | } 68 | fmt.Printf("\tcounter: %d\n", counter) 69 | 70 | time, ok := bridgeMsg.GetStringProperty("time") 71 | if !ok { 72 | log.Fatal("time property is missing") 73 | } 74 | fmt.Printf("\ttime: %s\n", time) 75 | 76 | fmt.Printf("\tid: %s\n", string(bridgeMsg.Header.CorrelID)) 77 | fmt.Println() 78 | }) 79 | nc.Flush() 80 | 81 | if err := nc.LastError(); err != nil { 82 | log.Fatal(err) 83 | } 84 | 85 | fmt.Println("Listening on tock...") 86 | fmt.Println() 87 | runtime.Goexit() 88 | } 89 | 90 | func setupConnOptions(opts []nats.Option) []nats.Option { 91 | totalWait := 10 * time.Minute 92 | reconnectDelay := time.Second 93 | 94 | opts = append(opts, nats.ReconnectWait(reconnectDelay)) 95 | opts = append(opts, nats.MaxReconnects(int(totalWait/reconnectDelay))) 96 | opts = append(opts, nats.DisconnectHandler(func(nc *nats.Conn) { 97 | log.Printf("Disconnected: will attempt reconnects for %.0fm", totalWait.Minutes()) 98 | })) 99 | opts = append(opts, nats.ReconnectHandler(func(nc *nats.Conn) { 100 | log.Printf("Reconnected [%s]", nc.ConnectedUrl()) 101 | })) 102 | opts = append(opts, nats.ClosedHandler(func(nc *nats.Conn) { 103 | log.Fatal("Exiting, no servers available") 104 | })) 105 | return opts 106 | } 107 | -------------------------------------------------------------------------------- /message/interchange/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2019 The NATS Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | package main 14 | 15 | import ( 16 | "flag" 17 | "github.com/nats-io/nats-mq/message" 18 | "io/ioutil" 19 | "log" 20 | ) 21 | 22 | var outputFile string 23 | 24 | func main() { 25 | flag.StringVar(&outputFile, "o", "", "output filepath") 26 | flag.Parse() 27 | 28 | msg := message.NewBridgeMessage([]byte("hello world")) 29 | msg.Header = message.BridgeHeader{ 30 | Version: 1, 31 | Report: 2, 32 | MsgID: []byte("cafebabe"), 33 | } 34 | 35 | expected := map[string]interface{}{ 36 | "string": "hello world", 37 | "int8": int8(9), 38 | "int16": int16(259), 39 | "int32": int32(222222222), 40 | "int64": int64(222222222222222222), 41 | "float32": float32(3.14), 42 | "float64": float64(6.4999), 43 | "bool": true, 44 | "bytes": []byte("one two three four"), 45 | } 46 | 47 | for k, v := range expected { 48 | err := msg.SetProperty(k, v) 49 | if err != nil { 50 | log.Fatalf("error - %s", err.Error()) 51 | } 52 | } 53 | 54 | bytes, err := msg.Encode() 55 | if err != nil { 56 | log.Fatalf("error - %s", err.Error()) 57 | } 58 | 59 | err = ioutil.WriteFile(outputFile, bytes, 0644) 60 | if err != nil { 61 | log.Fatalf("error - %s", err.Error()) 62 | } 63 | 64 | log.Printf("wrote interchange file to %s", outputFile) 65 | } 66 | -------------------------------------------------------------------------------- /nats-mq/conf/conf.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2019 The NATS Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package conf 15 | 16 | import ( 17 | "github.com/nats-io/nats-mq/nats-mq/logging" 18 | stan "github.com/nats-io/stan.go" 19 | ) 20 | 21 | // Queue2NATS type for an mq queue to nats connector 22 | const Queue2NATS = "Queue2NATS" 23 | 24 | // Queue2Stan type for an mq queue to stan connector 25 | const Queue2Stan = "Queue2Stan" 26 | 27 | // Stan2Queue type for a stan to mq queue connector 28 | const Stan2Queue = "Stan2Queue" 29 | 30 | // NATS2Queue type for a nats to mq queue connector 31 | const NATS2Queue = "NATS2Queue" 32 | 33 | // Topic2NATS type for an mq topic to nats connector 34 | const Topic2NATS = "Topic2NATS" 35 | 36 | // Topic2Stan type for an mq topic to stan connector 37 | const Topic2Stan = "Topic2Stan" 38 | 39 | // Stan2Topic type for a stan to mq topic connector 40 | const Stan2Topic = "Stan2Topic" 41 | 42 | // NATS2Topic type for a nats to mq topic connector 43 | const NATS2Topic = "NATS2Topic" 44 | 45 | // BridgeConfig holds the server configuration 46 | type BridgeConfig struct { 47 | ReconnectInterval int // milliseconds 48 | 49 | NATS NATSConfig 50 | STAN NATSStreamingConfig 51 | 52 | Logging logging.Config 53 | Monitoring MonitoringConfig 54 | 55 | Connect []ConnectorConfig 56 | } 57 | 58 | // DefaultBridgeConfig generates a default configuration with 59 | // logging set to colors, time, debug and trace 60 | // reconnect interval set to 5000 ms (5s) 61 | func DefaultBridgeConfig() BridgeConfig { 62 | return BridgeConfig{ 63 | ReconnectInterval: 5000, 64 | Logging: logging.Config{ 65 | Colors: true, 66 | Time: true, 67 | Debug: false, 68 | Trace: false, 69 | }, 70 | STAN: NATSStreamingConfig{ 71 | PubAckWait: 5000, 72 | DiscoverPrefix: stan.DefaultDiscoverPrefix, 73 | MaxPubAcksInflight: stan.DefaultMaxPubAcksInflight, 74 | ConnectWait: 2000, 75 | }, 76 | } 77 | } 78 | 79 | // TLSConf holds the configuration for a TLS connection/server 80 | type TLSConf struct { 81 | Key string 82 | Cert string 83 | Root string 84 | } 85 | 86 | // MonitoringConfig is used to define the host and port for monitoring 87 | // The HTTPPort vs HTTPSPort setting is used to determine if security is 88 | // enabled. By default the ports are 0 and monitoring is disabled. Set 89 | // a port to -1 to use ephemeral ports. 90 | // Similarly the host defaults to "" which indicates all network interfaces. 91 | type MonitoringConfig struct { 92 | HTTPHost string 93 | HTTPPort int 94 | HTTPSPort int 95 | TLS TLSConf 96 | } 97 | 98 | // MQConfig configuration for an MQ Connection 99 | type MQConfig struct { 100 | ConnectionName string 101 | ChannelName string 102 | QueueManager string 103 | 104 | UserName string 105 | Password string 106 | 107 | KeyRepository string 108 | CertificateLabel string 109 | SSLPeerName string 110 | } 111 | 112 | // NATSConfig configuration for a NATS connection 113 | type NATSConfig struct { 114 | Servers []string 115 | 116 | ConnectTimeout int //milliseconds 117 | ReconnectWait int //milliseconds 118 | MaxReconnects int 119 | 120 | TLS TLSConf 121 | Username string 122 | Password string 123 | CredsFile string 124 | } 125 | 126 | // NATSStreamingConfig configuration for a STAN connection 127 | type NATSStreamingConfig struct { 128 | ClusterID string 129 | ClientID string 130 | 131 | PubAckWait int //milliseconds 132 | DiscoverPrefix string 133 | MaxPubAcksInflight int 134 | ConnectWait int // milliseconds 135 | } 136 | 137 | // ConnectorConfig configuration for a bridge connection (of any type) 138 | type ConnectorConfig struct { 139 | ID string // user specified id for a connector, will be defaulted if none is provided 140 | Type string // Can be Queue2NATS or any of the other constants 141 | 142 | Channel string // Used for stan connections 143 | DurableName string // Optional, used for stan connections 144 | StartAtSequence int64 // Start position for stan connection, -1 means StartWithLastReceived, 0 means DeliverAllAvailable (default) 145 | StartAtTime int64 // Start time, as Unix, time takes precedence over sequence 146 | 147 | Subject string // Used for nats connections 148 | NatsQueue string // Optional, used for nats connections 149 | 150 | MQ MQConfig // Connection information, nats connections are shared 151 | Topic string // Used for the mq side of things 152 | Queue string 153 | 154 | UsePolling bool // use polling vs callbacks when listening to MQ (the default is callbacks) 155 | IncomingBufferSize int // buffer size for polling 156 | IncomingMessageWait int // wait time for polling in ms 157 | 158 | ExcludeHeaders bool //exclude headers, and just send the body to/from nats messages 159 | } 160 | -------------------------------------------------------------------------------- /nats-mq/conf/hostport.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2019 The NATS Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package conf 15 | 16 | import ( 17 | "fmt" 18 | "net" 19 | ) 20 | 21 | //HostPort stores a host port pair 22 | type HostPort struct { 23 | Host string 24 | Port int 25 | } 26 | 27 | // String returns the joined pair 28 | func (hp *HostPort) String() string { 29 | return net.JoinHostPort(hp.Host, fmt.Sprintf("%d", hp.Port)) 30 | } 31 | -------------------------------------------------------------------------------- /nats-mq/conf/hostport_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2019 The NATS Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | package conf 14 | 15 | import ( 16 | "testing" 17 | 18 | "github.com/stretchr/testify/require" 19 | ) 20 | 21 | func TestHostPort(t *testing.T) { 22 | hp := HostPort{ 23 | Host: "localhost", 24 | Port: 4222, 25 | } 26 | require.Equal(t, "localhost:4222", hp.String()) 27 | } 28 | -------------------------------------------------------------------------------- /nats-mq/conf/load.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2019 The NATS Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package conf 15 | 16 | import ( 17 | "fmt" 18 | "io/ioutil" 19 | 20 | "github.com/nats-io/nats-server/v2/conf" 21 | ) 22 | 23 | // LoadConfigFromFile - given a struct, load a config from a file and fill in the struct 24 | // If strict is true, all of the fields in the config struct must be in the file 25 | // otherwise, the fields in the config struct will act as defaults if the file doesn't contain them 26 | // Strict will also force an error if the struct contains any fields which are not settable with reflection 27 | func LoadConfigFromFile(configFile string, configStruct interface{}, strict bool) error { 28 | configString, err := ioutil.ReadFile(configFile) 29 | if err != nil { 30 | return fmt.Errorf("error reading configuration file: %s", err.Error()) 31 | } 32 | 33 | return LoadConfigFromString(string(configString), configStruct, strict) 34 | } 35 | 36 | // LoadConfigFromString - like LoadConfigFromFile but uses a string 37 | func LoadConfigFromString(configString string, configStruct interface{}, strict bool) error { 38 | m, err := conf.Parse(string(configString)) 39 | if err != nil { 40 | return err 41 | } 42 | 43 | return parseStruct(m, configStruct, strict) 44 | } 45 | 46 | // LoadConfigFromMap load a config struct from a map, this is useful if the type of a config isn't known at 47 | // load time. 48 | func LoadConfigFromMap(m map[string]interface{}, configStruct interface{}, strict bool) error { 49 | return parseStruct(m, configStruct, strict) 50 | } 51 | -------------------------------------------------------------------------------- /nats-mq/conf/load_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2019 The NATS Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | package conf 14 | 15 | import ( 16 | "io/ioutil" 17 | "os" 18 | "testing" 19 | 20 | "github.com/stretchr/testify/require" 21 | ) 22 | 23 | type SimpleConf struct { 24 | Name string 25 | Age int64 26 | OptOut bool 27 | Balance float64 28 | } 29 | 30 | func TestLoadFromString(t *testing.T) { 31 | configString := ` 32 | Name: "stephen" 33 | Age: 28 34 | OptOut: true 35 | Balance: 5.5 36 | ` 37 | 38 | config := SimpleConf{} 39 | 40 | err := LoadConfigFromString(configString, &config, false) 41 | require.NoError(t, err) 42 | require.Equal(t, "stephen", config.Name) 43 | require.Equal(t, int64(28), config.Age) 44 | require.Equal(t, true, config.OptOut) 45 | require.Equal(t, 5.5, config.Balance) 46 | } 47 | 48 | func TestLoadFromFile(t *testing.T) { 49 | file, err := ioutil.TempFile(os.TempDir(), "prefix") 50 | require.NoError(t, err) 51 | 52 | configString := ` 53 | Name: "stephen" 54 | Age: 28 55 | OptOut: true 56 | Balance: 5.5 57 | ` 58 | 59 | fullPath, err := ValidateFilePath(file.Name()) 60 | require.NoError(t, err) 61 | 62 | err = ioutil.WriteFile(fullPath, []byte(configString), 0644) 63 | require.NoError(t, err) 64 | 65 | config := SimpleConf{} 66 | 67 | err = LoadConfigFromFile(fullPath, &config, false) 68 | require.NoError(t, err) 69 | require.Equal(t, "stephen", config.Name) 70 | require.Equal(t, int64(28), config.Age) 71 | require.Equal(t, true, config.OptOut) 72 | require.Equal(t, 5.5, config.Balance) 73 | } 74 | 75 | func TestLoadFromMissingFile(t *testing.T) { 76 | config := SimpleConf{} 77 | err := LoadConfigFromFile("/foo/bar/baz", &config, false) 78 | require.Error(t, err) 79 | } 80 | 81 | type MapConf struct { 82 | One map[string]interface{} 83 | Two map[string]interface{} 84 | } 85 | 86 | func TestLoadFromMap(t *testing.T) { 87 | configString := ` 88 | One: { 89 | Name: "stephen" 90 | Age: 28 91 | OptOut: true 92 | Balance: 5.5 93 | }, Two: { 94 | Name: "zero" 95 | Age: 32 96 | OptOut: false 97 | Balance: 7.7 98 | } 99 | ` 100 | 101 | config := MapConf{} 102 | 103 | err := LoadConfigFromString(configString, &config, false) 104 | require.NoError(t, err) 105 | 106 | one := SimpleConf{} 107 | two := SimpleConf{} 108 | 109 | err = LoadConfigFromMap(config.One, &one, false) 110 | require.NoError(t, err) 111 | require.Equal(t, "stephen", one.Name) 112 | require.Equal(t, int64(28), one.Age) 113 | require.Equal(t, true, one.OptOut) 114 | require.Equal(t, 5.5, one.Balance) 115 | 116 | err = LoadConfigFromMap(config.Two, &two, false) 117 | require.NoError(t, err) 118 | require.Equal(t, "zero", two.Name) 119 | require.Equal(t, int64(32), two.Age) 120 | require.Equal(t, false, two.OptOut) 121 | require.Equal(t, 7.7, two.Balance) 122 | } 123 | -------------------------------------------------------------------------------- /nats-mq/conf/utils.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2019 The NATS Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package conf 15 | 16 | import ( 17 | "errors" 18 | "fmt" 19 | "os" 20 | "path/filepath" 21 | ) 22 | 23 | // validatePathExists checks that the provided path exists and is a dir if requested 24 | func validatePathExists(path string, dir bool) (string, error) { 25 | if path == "" { 26 | return "", errors.New("path is not specified") 27 | } 28 | 29 | abs, err := filepath.Abs(path) 30 | if err != nil { 31 | return "", fmt.Errorf("error parsing path [%s]: %v", abs, err) 32 | } 33 | 34 | var finfo os.FileInfo 35 | if finfo, err = os.Stat(abs); os.IsNotExist(err) { 36 | return "", fmt.Errorf("the path [%s] doesn't exist", abs) 37 | } 38 | 39 | mode := finfo.Mode() 40 | if dir && mode.IsRegular() { 41 | return "", fmt.Errorf("the path [%s] is not a directory", abs) 42 | } 43 | 44 | if !dir && mode.IsDir() { 45 | return "", fmt.Errorf("the path [%s] is not a file", abs) 46 | } 47 | 48 | return abs, nil 49 | } 50 | 51 | // ValidateDirPath checks that the provided path exists and is a dir 52 | func ValidateDirPath(path string) (string, error) { 53 | return validatePathExists(path, true) 54 | } 55 | 56 | // ValidateFilePath checks that the provided path exists and is not a dir 57 | func ValidateFilePath(path string) (string, error) { 58 | return validatePathExists(path, false) 59 | } 60 | -------------------------------------------------------------------------------- /nats-mq/conf/utils_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2019 The NATS Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | package conf 14 | 15 | import ( 16 | "io/ioutil" 17 | "os" 18 | "path/filepath" 19 | "testing" 20 | 21 | "github.com/stretchr/testify/require" 22 | ) 23 | 24 | func TestFilePath(t *testing.T) { 25 | file, err := ioutil.TempFile(os.TempDir(), "prefix") 26 | require.NoError(t, err) 27 | 28 | path, err := ValidateFilePath(file.Name()) 29 | require.NoError(t, err) 30 | require.NotEqual(t, "", path) 31 | 32 | _, err = ValidateDirPath(file.Name()) 33 | require.Error(t, err) 34 | } 35 | 36 | func TestDirPath(t *testing.T) { 37 | path, err := ioutil.TempDir(os.TempDir(), "prefix") 38 | require.NoError(t, err) 39 | 40 | abspath, err := ValidateDirPath(path) 41 | require.NoError(t, err) 42 | require.NotEqual(t, "", abspath) 43 | 44 | _, err = ValidateFilePath(path) 45 | require.Error(t, err) 46 | } 47 | 48 | func TestPathDoesntExist(t *testing.T) { 49 | path, err := ioutil.TempDir(os.TempDir(), "prefix") 50 | require.NoError(t, err) 51 | 52 | path = filepath.Join(path, "foo") 53 | 54 | _, err = ValidateDirPath(path) 55 | require.Error(t, err) 56 | } 57 | 58 | func TestEmptyPath(t *testing.T) { 59 | _, err := ValidateFilePath("") 60 | require.Error(t, err) 61 | 62 | _, err = ValidateDirPath("") 63 | require.Error(t, err) 64 | } 65 | 66 | func TestBadPath(t *testing.T) { 67 | _, err := ValidateFilePath("//foo\\br//#!90") 68 | require.Error(t, err) 69 | 70 | _, err = ValidateDirPath("//foo\\br//#!90") 71 | require.Error(t, err) 72 | } 73 | -------------------------------------------------------------------------------- /nats-mq/core/bridge_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2019 The NATS Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | package core 14 | 15 | import ( 16 | "testing" 17 | 18 | "github.com/nats-io/nats-mq/nats-mq/conf" 19 | "github.com/nats-io/nuid" 20 | stan "github.com/nats-io/stan.go" 21 | "github.com/stretchr/testify/require" 22 | ) 23 | 24 | func TestStartBridgeNoNats(t *testing.T) { 25 | tbs, err := StartTestEnvironmentInfrastructure(false) 26 | require.NoError(t, err) 27 | defer tbs.Close() 28 | 29 | config := conf.DefaultBridgeConfig() 30 | config.Monitoring = conf.MonitoringConfig{ 31 | HTTPPort: -1, 32 | } 33 | 34 | // Use a bogus server because we don't want to default to one on the test machine 35 | config.NATS = conf.NATSConfig{ 36 | Servers: []string{"nats://abc:321"}, 37 | ConnectTimeout: 2000, 38 | ReconnectWait: 2000, 39 | MaxReconnects: 5, 40 | } 41 | 42 | bridge := NewBridgeServer() 43 | err = bridge.LoadConfig(config) 44 | require.NoError(t, err) 45 | 46 | err = bridge.Start() 47 | require.Error(t, err) 48 | } 49 | 50 | func TestStartBridgeNATSOnly(t *testing.T) { 51 | tbs, err := StartTestEnvironmentInfrastructure(false) 52 | require.NoError(t, err) 53 | defer tbs.Close() 54 | 55 | config := conf.DefaultBridgeConfig() 56 | config.Monitoring = conf.MonitoringConfig{ 57 | HTTPPort: -1, 58 | } 59 | config.NATS = conf.NATSConfig{ 60 | Servers: []string{tbs.natsURL}, 61 | ConnectTimeout: 2000, 62 | ReconnectWait: 2000, 63 | MaxReconnects: 5, 64 | } 65 | 66 | bridge := NewBridgeServer() 67 | err = bridge.LoadConfig(config) 68 | require.NoError(t, err) 69 | 70 | err = bridge.Start() 71 | require.NoError(t, err) 72 | 73 | bridge.Stop() 74 | } 75 | 76 | func TestStartBridgeNATSAndStan(t *testing.T) { 77 | tbs, err := StartTestEnvironmentInfrastructure(false) 78 | require.NoError(t, err) 79 | defer tbs.Close() 80 | 81 | config := conf.DefaultBridgeConfig() 82 | config.Monitoring = conf.MonitoringConfig{ 83 | HTTPPort: -1, 84 | } 85 | config.NATS = conf.NATSConfig{ 86 | Servers: []string{tbs.natsURL}, 87 | ConnectTimeout: 2000, 88 | ReconnectWait: 2000, 89 | MaxReconnects: 5, 90 | } 91 | config.STAN = conf.NATSStreamingConfig{ 92 | ClusterID: tbs.clusterName, 93 | ClientID: nuid.Next(), 94 | PubAckWait: 5000, 95 | DiscoverPrefix: stan.DefaultDiscoverPrefix, 96 | MaxPubAcksInflight: stan.DefaultMaxPubAcksInflight, 97 | ConnectWait: 2000, 98 | } 99 | 100 | bridge := NewBridgeServer() 101 | err = bridge.LoadConfig(config) 102 | require.NoError(t, err) 103 | 104 | err = bridge.Start() 105 | require.NoError(t, err) 106 | 107 | bridge.Stop() 108 | } 109 | 110 | func TestCantHaveHTTPAndHTTPSMonitoring(t *testing.T) { 111 | tbs, err := StartTestEnvironmentInfrastructure(false) 112 | require.NoError(t, err) 113 | defer tbs.Close() 114 | 115 | config := conf.DefaultBridgeConfig() 116 | config.Monitoring = conf.MonitoringConfig{ 117 | HTTPPort: 9191, 118 | HTTPSPort: 9192, 119 | } 120 | config.NATS = conf.NATSConfig{ 121 | Servers: []string{tbs.natsURL}, 122 | ConnectTimeout: 2000, 123 | ReconnectWait: 2000, 124 | MaxReconnects: 5, 125 | } 126 | 127 | bridge := NewBridgeServer() 128 | err = bridge.LoadConfig(config) 129 | require.NoError(t, err) 130 | 131 | err = bridge.Start() 132 | require.Error(t, err) 133 | bridge.Stop() 134 | } 135 | 136 | func TestMonitoringDisabled(t *testing.T) { 137 | tbs, err := StartTestEnvironmentInfrastructure(false) 138 | require.NoError(t, err) 139 | defer tbs.Close() 140 | 141 | config := conf.DefaultBridgeConfig() 142 | config.Monitoring = conf.MonitoringConfig{ 143 | HTTPPort: 0, 144 | HTTPSPort: 0, 145 | } 146 | config.NATS = conf.NATSConfig{ 147 | Servers: []string{tbs.natsURL}, 148 | ConnectTimeout: 2000, 149 | ReconnectWait: 2000, 150 | MaxReconnects: 5, 151 | } 152 | 153 | bridge := NewBridgeServer() 154 | err = bridge.LoadConfig(config) 155 | require.NoError(t, err) 156 | 157 | err = bridge.Start() 158 | require.NoError(t, err) 159 | 160 | require.Equal(t, "", bridge.GetMonitoringRootURL()) 161 | 162 | bridge.Stop() 163 | } 164 | -------------------------------------------------------------------------------- /nats-mq/core/connect.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 The NATS Authors 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | package core 17 | 18 | import ( 19 | "github.com/ibm-messaging/mq-golang/v5/ibmmq" 20 | "github.com/nats-io/nats-mq/nats-mq/conf" 21 | ) 22 | 23 | // ConnectToQueueManager utility to connect to a queue manager from a configuration 24 | func ConnectToQueueManager(mqconfig conf.MQConfig) (*ibmmq.MQQueueManager, error) { 25 | qMgrName := mqconfig.QueueManager 26 | 27 | connectionOptions := ibmmq.NewMQCNO() 28 | channelDefinition := ibmmq.NewMQCD() 29 | 30 | if mqconfig.UserName != "" { 31 | connectionSecurityParams := ibmmq.NewMQCSP() 32 | connectionSecurityParams.AuthenticationType = ibmmq.MQCSP_AUTH_USER_ID_AND_PWD 33 | connectionSecurityParams.UserId = mqconfig.UserName 34 | connectionSecurityParams.Password = mqconfig.Password 35 | 36 | connectionOptions.SecurityParms = connectionSecurityParams 37 | } 38 | 39 | if mqconfig.KeyRepository != "" { 40 | tlsParams := ibmmq.NewMQSCO() 41 | tlsParams.KeyRepository = mqconfig.KeyRepository 42 | tlsParams.CertificateLabel = mqconfig.CertificateLabel 43 | connectionOptions.SSLConfig = tlsParams 44 | 45 | channelDefinition.SSLCipherSpec = "TLS_RSA_WITH_AES_128_CBC_SHA256" 46 | channelDefinition.SSLPeerName = mqconfig.SSLPeerName 47 | channelDefinition.CertificateLabel = mqconfig.CertificateLabel 48 | channelDefinition.SSLClientAuth = int32(ibmmq.MQSCA_REQUIRED) 49 | } 50 | 51 | channelDefinition.ChannelName = mqconfig.ChannelName 52 | channelDefinition.ConnectionName = mqconfig.ConnectionName 53 | 54 | connectionOptions.Options = ibmmq.MQCNO_CLIENT_BINDING 55 | connectionOptions.ClientConn = channelDefinition 56 | 57 | qMgr, err := ibmmq.Connx(qMgrName, connectionOptions) 58 | 59 | if err != nil { 60 | mqret := err.(*ibmmq.MQReturn) 61 | if mqret.MQCC == ibmmq.MQCC_WARNING && mqret.MQRC == ibmmq.MQRC_SSL_ALREADY_INITIALIZED { 62 | 63 | // double check the connection went through 64 | cmho := ibmmq.NewMQCMHO() 65 | mh, err2 := qMgr.CrtMH(cmho) 66 | if err2 != nil { 67 | return nil, err 68 | } 69 | mh.DltMH(ibmmq.NewMQDMHO()) // ignore the error 70 | 71 | return &qMgr, nil 72 | } 73 | return nil, err 74 | } 75 | 76 | return &qMgr, nil 77 | } 78 | -------------------------------------------------------------------------------- /nats-mq/core/connect_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 The NATS Authors 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | package core 17 | 18 | import ( 19 | "testing" 20 | "time" 21 | 22 | "github.com/stretchr/testify/require" 23 | ) 24 | 25 | func TestMQTestServer(t *testing.T) { 26 | mqServer, qMgr, err := StartMQTestServer(5*time.Second, false, 0) 27 | defer func() { 28 | if qMgr != nil { 29 | qMgr.Disc() 30 | } 31 | if mqServer != nil { 32 | mqServer.Close() 33 | } 34 | }() 35 | require.NoError(t, err) 36 | require.NotNil(t, mqServer) 37 | require.NotNil(t, qMgr) 38 | 39 | time.Sleep(1 * time.Second) 40 | } 41 | 42 | func TestMQTestServerWithTLS(t *testing.T) { 43 | mqServer, qMgr, err := StartMQTestServer(30*time.Second, true, 0) 44 | defer func() { 45 | if qMgr != nil { 46 | qMgr.Disc() 47 | } 48 | if mqServer != nil { 49 | mqServer.Close() 50 | } 51 | }() 52 | require.NoError(t, err) 53 | require.NotNil(t, mqServer) 54 | require.NotNil(t, qMgr) 55 | 56 | time.Sleep(1 * time.Second) 57 | } 58 | -------------------------------------------------------------------------------- /nats-mq/core/handlers.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 The NATS Authors 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | package core 17 | 18 | import ( 19 | nats "github.com/nats-io/nats.go" 20 | stan "github.com/nats-io/stan.go" 21 | ) 22 | 23 | func (bridge *BridgeServer) natsError(nc *nats.Conn, sub *nats.Subscription, err error) { 24 | bridge.logger.Warnf("nats error %s", err.Error()) 25 | } 26 | 27 | func (bridge *BridgeServer) stanConnectionLost(sc stan.Conn, err error) { 28 | if !bridge.checkRunning() { 29 | return 30 | } 31 | bridge.logger.Warnf("nats streaming disconnected") 32 | 33 | bridge.natsLock.Lock() 34 | bridge.stan = nil // we lost stan 35 | bridge.natsLock.Unlock() 36 | 37 | bridge.checkConnections() 38 | } 39 | 40 | func (bridge *BridgeServer) natsDisconnected(nc *nats.Conn) { 41 | if !bridge.checkRunning() { 42 | return 43 | } 44 | bridge.logger.Warnf("nats disconnected") 45 | bridge.checkConnections() 46 | } 47 | 48 | func (bridge *BridgeServer) natsReconnected(nc *nats.Conn) { 49 | bridge.logger.Warnf("nats reconnected") 50 | } 51 | 52 | func (bridge *BridgeServer) natsClosed(nc *nats.Conn) { 53 | if bridge.checkRunning() { 54 | bridge.logger.Errorf("nats connection closed, shutting down bridge") 55 | go bridge.Stop() 56 | } 57 | } 58 | 59 | func (bridge *BridgeServer) natsDiscoveredServers(nc *nats.Conn) { 60 | bridge.logger.Debugf("discovered servers: %v\n", nc.DiscoveredServers()) 61 | bridge.logger.Debugf("known servers: %v\n", nc.Servers()) 62 | } 63 | -------------------------------------------------------------------------------- /nats-mq/core/handlers_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 The NATS Authors 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | package core 17 | 18 | import ( 19 | "testing" 20 | "time" 21 | 22 | "github.com/ibm-messaging/mq-golang/v5/ibmmq" 23 | "github.com/nats-io/nats-mq/nats-mq/conf" 24 | "github.com/stretchr/testify/require" 25 | ) 26 | 27 | func TestMQReconnect(t *testing.T) { 28 | subject := "test" 29 | queue := "DEV.QUEUE.1" 30 | msg := "hello world" 31 | 32 | connect := []conf.ConnectorConfig{ 33 | { 34 | Type: "Queue2NATS", 35 | Subject: subject, 36 | Queue: queue, 37 | ExcludeHeaders: true, 38 | }, 39 | } 40 | 41 | tbs, err := StartTestEnvironment(connect) 42 | require.NoError(t, err) 43 | defer tbs.Close() 44 | 45 | sub, err := tbs.NC.SubscribeSync(subject) 46 | require.NoError(t, err) 47 | defer sub.Unsubscribe() 48 | 49 | err = tbs.PutMessageOnQueue(queue, ibmmq.NewMQMD(), []byte(msg)) 50 | require.NoError(t, err) 51 | 52 | received, err := sub.NextMsg(5 * time.Second) 53 | require.NoError(t, err) 54 | require.NotNil(t, received) 55 | require.Equal(t, msg, string(received.Data)) 56 | 57 | err = tbs.RestartMQ(false) 58 | require.NoError(t, err) 59 | 60 | // Wait up to 15s for the bridge to get reconnected 61 | start := time.Now() 62 | for time.Since(start) < time.Second*15 && tbs.Bridge.checkReconnecting() { 63 | time.Sleep(250 * time.Millisecond) 64 | } 65 | 66 | require.False(t, tbs.Bridge.checkReconnecting()) 67 | 68 | err = tbs.PutMessageOnQueue(queue, ibmmq.NewMQMD(), []byte(msg)) 69 | require.NoError(t, err) 70 | 71 | // sub should have auto reconnected 72 | received, err = sub.NextMsg(5 * time.Second) 73 | require.NoError(t, err) 74 | require.NotNil(t, received) 75 | require.Equal(t, msg, string(received.Data)) 76 | } 77 | 78 | func TestNATSReconnect(t *testing.T) { 79 | subject := "test" 80 | queue := "DEV.QUEUE.1" 81 | msg := "hello world" 82 | 83 | connect := []conf.ConnectorConfig{ 84 | { 85 | Type: "NATS2Queue", 86 | Subject: subject, 87 | Queue: queue, 88 | ExcludeHeaders: true, 89 | }, 90 | } 91 | 92 | tbs, err := StartTestEnvironment(connect) 93 | require.NoError(t, err) 94 | defer tbs.Close() 95 | 96 | err = tbs.NC.Publish("test", []byte(msg)) 97 | require.NoError(t, err) 98 | 99 | _, _, data, err := tbs.GetMessageFromQueue(queue, 5000) 100 | require.NoError(t, err) 101 | require.Equal(t, msg, string(data)) 102 | 103 | err = tbs.RestartNATS(false) 104 | require.NoError(t, err) 105 | 106 | // Wait up to 15s for the bridge to get reconnected 107 | start := time.Now() 108 | for time.Since(start) < time.Second*15 && tbs.Bridge.checkReconnecting() { 109 | time.Sleep(250 * time.Millisecond) 110 | } 111 | 112 | require.False(t, tbs.Bridge.checkReconnecting()) 113 | 114 | err = tbs.NC.Publish("test", []byte(msg)) 115 | require.NoError(t, err) 116 | 117 | _, _, data, err = tbs.GetMessageFromQueue(queue, 5000) 118 | require.NoError(t, err) 119 | require.Equal(t, msg, string(data)) 120 | } 121 | 122 | func TestStanReconnect(t *testing.T) { 123 | channel := "test" 124 | queue := "DEV.QUEUE.1" 125 | msg := "hello world" 126 | 127 | connect := []conf.ConnectorConfig{ 128 | { 129 | Type: "Stan2Queue", 130 | Channel: channel, 131 | Queue: queue, 132 | ExcludeHeaders: true, 133 | }, 134 | } 135 | 136 | tbs, err := StartTestEnvironment(connect) 137 | require.NoError(t, err) 138 | defer tbs.Close() 139 | 140 | err = tbs.SC.Publish("test", []byte(msg)) 141 | require.NoError(t, err) 142 | 143 | _, _, data, err := tbs.GetMessageFromQueue(queue, 5000) 144 | require.NoError(t, err) 145 | require.Equal(t, msg, string(data)) 146 | 147 | err = tbs.RestartNATS(false) 148 | require.NoError(t, err) 149 | 150 | // Wait up to 15s for the bridge to get reconnected 151 | start := time.Now() 152 | for time.Since(start) < time.Second*15 && tbs.Bridge.checkReconnecting() { 153 | time.Sleep(250 * time.Millisecond) 154 | } 155 | 156 | require.False(t, tbs.Bridge.checkReconnecting()) 157 | 158 | err = tbs.SC.Publish("test", []byte(msg)) 159 | require.NoError(t, err) 160 | 161 | _, _, data, err = tbs.GetMessageFromQueue(queue, 5000) 162 | require.NoError(t, err) 163 | require.Equal(t, msg, string(data)) 164 | } 165 | 166 | /* 167 | func TestStanPubFailure(t *testing.T) { 168 | channel := "test" 169 | queue := "DEV.QUEUE.1" 170 | msg := "hello world" 171 | 172 | connect := []conf.ConnectorConfig{ 173 | { 174 | Type: "Queue2Stan", 175 | Channel: channel, 176 | Queue: queue, 177 | ExcludeHeaders: true, 178 | }, 179 | } 180 | 181 | tbs, err := StartTestEnvironment(connect) 182 | require.NoError(t, err) 183 | defer tbs.Close() 184 | 185 | wg := sync.WaitGroup{} 186 | wg.Add(3) 187 | 188 | sub, err := tbs.SC.Subscribe(channel, func(msg *stan.Msg) { 189 | wg.Done() 190 | }) 191 | defer sub.Unsubscribe() 192 | 193 | err = tbs.PutMessageOnQueue(queue, ibmmq.NewMQMD(), []byte(msg)) 194 | require.NoError(t, err) 195 | 196 | err = tbs.StopNATS() 197 | require.NoError(t, err) 198 | 199 | // Queue up 2 more 200 | err = tbs.PutMessageOnQueue(queue, ibmmq.NewMQMD(), []byte(msg)) 201 | require.NoError(t, err) 202 | 203 | err = tbs.PutMessageOnQueue(queue, ibmmq.NewMQMD(), []byte(msg)) 204 | require.NoError(t, err) 205 | 206 | err = tbs.RestartNATS(false) 207 | require.NoError(t, err) 208 | 209 | // Resubscribe, since we lost the connection 210 | sub, err = tbs.SC.Subscribe(channel, func(msg *stan.Msg) { 211 | wg.Done() 212 | }) 213 | defer sub.Unsubscribe() 214 | 215 | // Wait up to 15s for the bridge to get reconnected 216 | start := time.Now() 217 | for time.Since(start) < time.Second*15 && tbs.Bridge.checkReconnecting() { 218 | time.Sleep(250 * time.Millisecond) 219 | } 220 | 221 | require.False(t, tbs.Bridge.checkReconnecting()) 222 | 223 | timedOut := false 224 | 225 | // Don't let wg wait forever 226 | timer := newReconnectTimer() 227 | go func() { 228 | select { 229 | case timedOut = <-timer.After(20 * time.Second): 230 | if !timedOut { 231 | return 232 | } 233 | } 234 | 235 | wg.Done() 236 | wg.Done() 237 | wg.Done() 238 | }() 239 | 240 | wg.Wait() 241 | timer.Cancel() 242 | require.False(t, timedOut) 243 | } 244 | */ 245 | -------------------------------------------------------------------------------- /nats-mq/core/histogram.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 The NATS Authors 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | package core 17 | 18 | // Based on https://github.com/VividCortex/gohistogram MIT license 19 | // Updated to be json friendly 20 | // Histogram based on https://www.vividcortex.com/blog/2013/07/08/streaming-approximate-histograms/ 21 | 22 | // Bin holds a float64 value and count 23 | type Bin struct { 24 | Value float64 `json:"v"` 25 | Count float64 `json:"c"` 26 | } 27 | 28 | // Histogram stores N bins using the streaming approximate histogram approach 29 | // The histogram is not thread safe 30 | type Histogram struct { 31 | Bins []Bin `json:"bins"` 32 | MaxBins int `json:"max"` 33 | Total uint64 `json:"total"` 34 | } 35 | 36 | // NewHistogram returns a new Histogram with a maximum of n bins. 37 | // 38 | // There is no "optimal" bin count, but somewhere between 20 and 80 bins 39 | // should be sufficient. 40 | func NewHistogram(n int) *Histogram { 41 | return &Histogram{ 42 | Bins: make([]Bin, 0), 43 | MaxBins: n, 44 | Total: 0, 45 | } 46 | } 47 | 48 | // Scale the buckets by s, this is useful for requests or other 49 | // values that may be in large numbers ie nanoseconds 50 | func (h *Histogram) Scale(s float64) { 51 | for i := range h.Bins { 52 | h.Bins[i].Value *= s 53 | } 54 | } 55 | 56 | // Add a value to the histogram, creating a bucket if necessary 57 | func (h *Histogram) Add(n float64) { 58 | defer h.trim() 59 | h.Total++ 60 | for i := range h.Bins { 61 | if h.Bins[i].Value == n { 62 | h.Bins[i].Count++ 63 | return 64 | } 65 | 66 | if h.Bins[i].Value > n { 67 | 68 | newbin := Bin{Value: n, Count: 1} 69 | head := append(make([]Bin, 0), h.Bins[0:i]...) 70 | 71 | head = append(head, newbin) 72 | tail := h.Bins[i:] 73 | h.Bins = append(head, tail...) 74 | return 75 | } 76 | } 77 | 78 | h.Bins = append(h.Bins, Bin{Count: 1, Value: n}) 79 | } 80 | 81 | // Quantile returns the value for the bin at the provided quantile 82 | // This is "approximate" in the since that the bin may straddle the quantile value 83 | func (h *Histogram) Quantile(q float64) float64 { 84 | count := q * float64(h.Total) 85 | for i := range h.Bins { 86 | count -= float64(h.Bins[i].Count) 87 | 88 | if count <= 0 { 89 | return h.Bins[i].Value 90 | } 91 | } 92 | 93 | return -1 94 | } 95 | 96 | // Mean returns the sample mean of the distribution 97 | func (h *Histogram) Mean() float64 { 98 | if h.Total == 0 { 99 | return 0 100 | } 101 | 102 | sum := 0.0 103 | 104 | for i := range h.Bins { 105 | sum += h.Bins[i].Value * h.Bins[i].Count 106 | } 107 | 108 | return sum / float64(h.Total) 109 | } 110 | 111 | // Count returns the total number of entries in the histogram 112 | func (h *Histogram) Count() float64 { 113 | return float64(h.Total) 114 | } 115 | 116 | // trim merges adjacent bins to decrease the bin count to the maximum value 117 | func (h *Histogram) trim() { 118 | for len(h.Bins) > h.MaxBins { 119 | // Find closest bins in terms of value 120 | minDelta := 1e99 121 | minDeltaIndex := 0 122 | for i := range h.Bins { 123 | if i == 0 { 124 | continue 125 | } 126 | 127 | if delta := h.Bins[i].Value - h.Bins[i-1].Value; delta < minDelta { 128 | minDelta = delta 129 | minDeltaIndex = i 130 | } 131 | } 132 | 133 | // We need to merge bins minDeltaIndex-1 and minDeltaIndex 134 | totalCount := h.Bins[minDeltaIndex-1].Count + h.Bins[minDeltaIndex].Count 135 | mergedbin := Bin{ 136 | Value: (h.Bins[minDeltaIndex-1].Value* 137 | h.Bins[minDeltaIndex-1].Count + 138 | h.Bins[minDeltaIndex].Value* 139 | h.Bins[minDeltaIndex].Count) / 140 | totalCount, // weighted average 141 | Count: totalCount, // summed heights 142 | } 143 | head := append(make([]Bin, 0), h.Bins[0:minDeltaIndex-1]...) 144 | tail := append([]Bin{mergedbin}, h.Bins[minDeltaIndex+1:]...) 145 | h.Bins = append(head, tail...) 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /nats-mq/core/histogram_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 The NATS Authors 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | package core 17 | 18 | // Based on https://github.com/VividCortex/gohistogram MIT license 19 | 20 | import ( 21 | "math" 22 | "testing" 23 | 24 | "github.com/stretchr/testify/require" 25 | ) 26 | 27 | func approx(x, y float64) bool { 28 | return math.Abs(x-y) < 0.2 29 | } 30 | 31 | func TestHistogram(t *testing.T) { 32 | h := NewHistogram(160) 33 | for _, val := range testData { 34 | h.Add(float64(val)) 35 | } 36 | 37 | firstQ := h.Quantile(0.25) 38 | median := h.Quantile(0.5) 39 | thirdQ := h.Quantile(0.75) 40 | 41 | require.Equal(t, float64(14999), h.Count()) 42 | require.True(t, approx(firstQ, 14)) 43 | require.True(t, approx(median, 18)) 44 | require.True(t, approx(thirdQ, 22)) 45 | 46 | require.True(t, approx(h.Mean(), 20.7)) 47 | 48 | h.Scale(0.5) 49 | median = h.Quantile(0.5) 50 | require.True(t, approx(median, 9)) 51 | } 52 | 53 | func TestHistogramTrim(t *testing.T) { 54 | h := NewHistogram(10) 55 | for _, val := range testData { 56 | h.Add(float64(val)) 57 | } 58 | 59 | require.Equal(t, 10, len(h.Bins)) 60 | } 61 | -------------------------------------------------------------------------------- /nats-mq/core/monitoring_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 The NATS Authors 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | package core 17 | 18 | import ( 19 | "crypto/tls" 20 | "encoding/json" 21 | "io/ioutil" 22 | "net/http" 23 | "strings" 24 | "testing" 25 | "time" 26 | 27 | "github.com/nats-io/nats-mq/nats-mq/conf" 28 | "github.com/stretchr/testify/require" 29 | ) 30 | 31 | func TestMonitoringPages(t *testing.T) { 32 | start := time.Now() 33 | subject := "test" 34 | queue := "DEV.QUEUE.1" 35 | 36 | connect := []conf.ConnectorConfig{ 37 | { 38 | Type: "NATS2Queue", 39 | Subject: subject, 40 | Queue: queue, 41 | ExcludeHeaders: true, 42 | }, 43 | } 44 | 45 | tbs, err := StartTestEnvironment(connect) 46 | require.NoError(t, err) 47 | defer tbs.Close() 48 | 49 | client := http.Client{} 50 | response, err := client.Get(tbs.Bridge.GetMonitoringRootURL()) 51 | require.NoError(t, err) 52 | defer response.Body.Close() 53 | contents, err := ioutil.ReadAll(response.Body) 54 | require.NoError(t, err) 55 | html := string(contents) 56 | require.True(t, strings.Contains(html, "/varz")) 57 | require.True(t, strings.Contains(html, "/healthz")) 58 | 59 | response, err = client.Get(tbs.Bridge.GetMonitoringRootURL() + "healthz") 60 | require.NoError(t, err) 61 | require.Equal(t, http.StatusOK, response.StatusCode) 62 | 63 | response, err = client.Get(tbs.Bridge.GetMonitoringRootURL() + "varz") 64 | require.NoError(t, err) 65 | defer response.Body.Close() 66 | contents, err = ioutil.ReadAll(response.Body) 67 | require.NoError(t, err) 68 | 69 | bridgeStats := BridgeStats{} 70 | err = json.Unmarshal(contents, &bridgeStats) 71 | require.NoError(t, err) 72 | 73 | now := time.Now() 74 | require.True(t, bridgeStats.StartTime >= start.Unix()) 75 | require.True(t, bridgeStats.StartTime <= now.Unix()) 76 | require.True(t, bridgeStats.ServerTime >= start.Unix()) 77 | require.True(t, bridgeStats.ServerTime <= now.Unix()) 78 | 79 | require.Equal(t, bridgeStats.HTTPRequests["/"], int64(1)) 80 | require.Equal(t, bridgeStats.HTTPRequests["/varz"], int64(1)) 81 | require.Equal(t, bridgeStats.HTTPRequests["/healthz"], int64(1)) 82 | 83 | require.Equal(t, 1, len(bridgeStats.Connections)) 84 | require.True(t, bridgeStats.Connections[0].Connected) 85 | require.Equal(t, int64(1), bridgeStats.Connections[0].Connects) 86 | require.Equal(t, int64(0), bridgeStats.Connections[0].MessagesIn) 87 | require.Equal(t, int64(0), bridgeStats.Connections[0].MessagesOut) 88 | require.Equal(t, int64(0), bridgeStats.Connections[0].BytesIn) 89 | require.Equal(t, int64(0), bridgeStats.Connections[0].BytesOut) 90 | } 91 | 92 | func TestHealthzWithTLS(t *testing.T) { 93 | subject := "test" 94 | queue := "DEV.QUEUE.1" 95 | 96 | connect := []conf.ConnectorConfig{ 97 | { 98 | Type: "NATS2Queue", 99 | Subject: subject, 100 | Queue: queue, 101 | ExcludeHeaders: true, 102 | }, 103 | } 104 | 105 | tbs, err := StartTLSTestEnvironment(connect) 106 | require.NoError(t, err) 107 | defer tbs.Close() 108 | 109 | tr := &http.Transport{ 110 | TLSClientConfig: &tls.Config{ 111 | InsecureSkipVerify: true, 112 | }, 113 | } 114 | client := &http.Client{Transport: tr} 115 | response, err := client.Get(tbs.Bridge.GetMonitoringRootURL() + "healthz") 116 | require.NoError(t, err) 117 | require.Equal(t, http.StatusOK, response.StatusCode) 118 | } 119 | -------------------------------------------------------------------------------- /nats-mq/core/nats2queue.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 The NATS Authors 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | package core 17 | 18 | import ( 19 | "fmt" 20 | 21 | "github.com/ibm-messaging/mq-golang/v5/ibmmq" 22 | "github.com/nats-io/nats-mq/nats-mq/conf" 23 | nats "github.com/nats-io/nats.go" 24 | ) 25 | 26 | // NATS2QueueConnector connects a NATS subject to an MQ queue 27 | type NATS2QueueConnector struct { 28 | BridgeConnector 29 | 30 | queue *ibmmq.MQObject 31 | sub *nats.Subscription 32 | } 33 | 34 | // NewNATS2QueueConnector create a nats to MQ connector 35 | func NewNATS2QueueConnector(bridge *BridgeServer, config conf.ConnectorConfig) Connector { 36 | connector := &NATS2QueueConnector{} 37 | connector.init(bridge, config, fmt.Sprintf("NATS:%s to Queue:%s", config.Subject, config.Queue)) 38 | return connector 39 | } 40 | 41 | // Start the connector 42 | func (mq *NATS2QueueConnector) Start() error { 43 | mq.Lock() 44 | defer mq.Unlock() 45 | 46 | if !mq.bridge.CheckNATS() { 47 | return fmt.Errorf("%s connector requires nats to be available", mq.String()) 48 | } 49 | 50 | mq.bridge.Logger().Tracef("starting connection %s", mq.String()) 51 | 52 | err := mq.connectToMQ() 53 | if err != nil { 54 | return err 55 | } 56 | 57 | // Create the Object Descriptor that allows us to give the queue name 58 | qObject, err := mq.connectToQueue(mq.config.Queue, ibmmq.MQOO_OUTPUT) 59 | 60 | if err != nil { 61 | return err 62 | } 63 | 64 | mq.queue = qObject 65 | 66 | sub, err := mq.subscribeToNATS(mq.config.Subject, mq.config.NatsQueue, mq.queue) 67 | if err != nil { 68 | return err 69 | } 70 | mq.sub = sub 71 | 72 | mq.stats.AddConnect() 73 | mq.bridge.Logger().Tracef("opened and reading %s", mq.config.Queue) 74 | mq.bridge.Logger().Noticef("started connection %s", mq.String()) 75 | 76 | return nil 77 | } 78 | 79 | // Shutdown the connector 80 | func (mq *NATS2QueueConnector) Shutdown() error { 81 | mq.Lock() 82 | defer mq.Unlock() 83 | mq.stats.AddDisconnect() 84 | 85 | mq.bridge.Logger().Noticef("shutting down connection %s", mq.String()) 86 | 87 | if mq.sub != nil { 88 | mq.sub.Unsubscribe() 89 | mq.sub = nil 90 | } 91 | 92 | var err error 93 | 94 | queue := mq.queue 95 | mq.queue = nil 96 | 97 | if queue != nil { 98 | err = queue.Close(0) 99 | } 100 | 101 | if mq.qMgr != nil { 102 | _ = mq.qMgr.Disc() 103 | mq.qMgr = nil 104 | mq.bridge.Logger().Tracef("disconnected from queue manager for %s", mq.String()) 105 | } 106 | 107 | return err // ignore the disconnect error 108 | } 109 | 110 | // CheckConnections ensures the nats/stan connection and report an error if it is down 111 | func (mq *NATS2QueueConnector) CheckConnections() error { 112 | if !mq.bridge.CheckNATS() { 113 | return fmt.Errorf("%s connector requires nats to be available", mq.String()) 114 | } 115 | return nil 116 | } 117 | -------------------------------------------------------------------------------- /nats-mq/core/nats2queue_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 The NATS Authors 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | package core 17 | 18 | import ( 19 | "bytes" 20 | "testing" 21 | "time" 22 | 23 | "github.com/ibm-messaging/mq-golang/v5/ibmmq" 24 | "github.com/nats-io/nats-mq/message" 25 | "github.com/nats-io/nats-mq/nats-mq/conf" 26 | "github.com/stretchr/testify/require" 27 | ) 28 | 29 | func TestSimpleSendOnNatsReceiveOnQueue(t *testing.T) { 30 | subject := "test" 31 | queue := "DEV.QUEUE.1" 32 | msg := "hello world" 33 | 34 | connect := []conf.ConnectorConfig{ 35 | { 36 | Type: "NATS2Queue", 37 | Subject: subject, 38 | Queue: queue, 39 | ExcludeHeaders: true, 40 | }, 41 | } 42 | 43 | tbs, err := StartTestEnvironment(connect) 44 | require.NoError(t, err) 45 | defer tbs.Close() 46 | 47 | err = tbs.NC.Publish("test", []byte(msg)) 48 | require.NoError(t, err) 49 | 50 | _, _, data, err := tbs.GetMessageFromQueue(queue, 5000) 51 | require.NoError(t, err) 52 | require.Equal(t, msg, string(data)) 53 | 54 | stats := tbs.Bridge.SafeStats() 55 | connStats := stats.Connections[0] 56 | require.Equal(t, int64(1), connStats.MessagesIn) 57 | require.Equal(t, int64(1), connStats.MessagesOut) 58 | require.Equal(t, int64(len([]byte(msg))), connStats.BytesIn) 59 | require.Equal(t, int64(len([]byte(msg))), connStats.BytesOut) 60 | require.Equal(t, int64(1), connStats.Connects) 61 | require.Equal(t, int64(0), connStats.Disconnects) 62 | require.True(t, connStats.Connected) 63 | } 64 | 65 | func TestSendOnNATSReceiveOnQueueMQMD(t *testing.T) { 66 | start := time.Now().UTC() 67 | subject := "test" 68 | queue := "DEV.QUEUE.1" 69 | msg := "hello world" 70 | id := bytes.Repeat([]byte{1}, int(ibmmq.MQ_MSG_ID_LENGTH)) 71 | corr := bytes.Repeat([]byte{1}, int(ibmmq.MQ_CORREL_ID_LENGTH)) 72 | 73 | connect := []conf.ConnectorConfig{ 74 | { 75 | Type: "NATS2Queue", 76 | Subject: subject, 77 | Queue: queue, 78 | ExcludeHeaders: false, 79 | }, 80 | } 81 | 82 | tbs, err := StartTestEnvironment(connect) 83 | require.NoError(t, err) 84 | defer tbs.Close() 85 | 86 | bridgeMessage := message.NewBridgeMessage([]byte(msg)) 87 | bridgeMessage.Header.CorrelID = corr 88 | bridgeMessage.Header.MsgID = id 89 | bridgeMessage.SetProperty("count", 11) 90 | encoded, err := bridgeMessage.Encode() 91 | require.NoError(t, err) 92 | 93 | err = tbs.NC.Publish("test", encoded) 94 | require.NoError(t, err) 95 | 96 | mqmd, gmo, data, err := tbs.GetMessageFromQueue(queue, 5000) 97 | require.NoError(t, err) 98 | require.Equal(t, msg, string(data)) 99 | 100 | require.Equal(t, start.Format("20060102"), mqmd.PutDate) 101 | require.True(t, start.Format("15040500") < mqmd.PutTime) 102 | require.ElementsMatch(t, id, mqmd.MsgId) 103 | require.ElementsMatch(t, corr, mqmd.CorrelId) 104 | 105 | require.NotNil(t, gmo) 106 | require.NotNil(t, gmo.MsgHandle) 107 | 108 | impo := ibmmq.NewMQIMPO() 109 | pd := ibmmq.NewMQPD() 110 | impo.Options = ibmmq.MQIMPO_CONVERT_VALUE 111 | name, value, err := gmo.MsgHandle.InqMP(impo, pd, "count") 112 | require.NoError(t, err) 113 | require.Equal(t, "count", name) 114 | require.Equal(t, int64(11), value.(int64)) 115 | 116 | stats := tbs.Bridge.SafeStats() 117 | connStats := stats.Connections[0] 118 | require.Equal(t, int64(1), connStats.MessagesIn) 119 | require.Equal(t, int64(1), connStats.MessagesOut) 120 | require.Equal(t, int64(len(encoded)), connStats.BytesIn) 121 | require.Equal(t, int64(len(data)), connStats.BytesOut) 122 | require.Equal(t, int64(1), connStats.Connects) 123 | require.Equal(t, int64(0), connStats.Disconnects) 124 | require.True(t, connStats.Connected) 125 | } 126 | 127 | func TestSimpleSendOnNatsReceiveOnQueueWithTLS(t *testing.T) { 128 | subject := "test" 129 | queue := "DEV.QUEUE.1" 130 | msg := "hello world" 131 | 132 | connect := []conf.ConnectorConfig{ 133 | { 134 | Type: "NATS2Queue", 135 | Subject: subject, 136 | Queue: queue, 137 | ExcludeHeaders: true, 138 | }, 139 | } 140 | 141 | tbs, err := StartTLSTestEnvironment(connect) 142 | require.NoError(t, err) 143 | defer tbs.Close() 144 | 145 | err = tbs.NC.Publish("test", []byte(msg)) 146 | require.NoError(t, err) 147 | 148 | _, _, data, err := tbs.GetMessageFromQueue(queue, 5000) 149 | require.NoError(t, err) 150 | require.Equal(t, msg, string(data)) 151 | } 152 | 153 | func TestWildcardSendRecieveOnQueue(t *testing.T) { 154 | queue := "DEV.QUEUE.1" 155 | msg := "hello world" 156 | 157 | connect := []conf.ConnectorConfig{ 158 | { 159 | Type: "NATS2Queue", 160 | Subject: "test.*", 161 | Queue: queue, 162 | ExcludeHeaders: true, 163 | }, 164 | } 165 | 166 | tbs, err := StartTestEnvironment(connect) 167 | require.NoError(t, err) 168 | defer tbs.Close() 169 | 170 | err = tbs.NC.Publish("test.a", []byte(msg)) 171 | require.NoError(t, err) 172 | 173 | _, _, data, err := tbs.GetMessageFromQueue(queue, 5000) 174 | require.NoError(t, err) 175 | require.Equal(t, msg, string(data)) 176 | } 177 | 178 | func TestSendOnNatsQueueReceiveOnQueue(t *testing.T) { 179 | subject := "test" 180 | queue := "DEV.QUEUE.1" 181 | msg := "hello world" 182 | 183 | connect := []conf.ConnectorConfig{ 184 | { 185 | Type: "NATS2Queue", 186 | Subject: subject, 187 | NatsQueue: "workers", 188 | Queue: queue, 189 | ExcludeHeaders: true, 190 | }, 191 | } 192 | 193 | tbs, err := StartTestEnvironment(connect) 194 | require.NoError(t, err) 195 | defer tbs.Close() 196 | 197 | err = tbs.NC.Publish("test", []byte(msg)) 198 | require.NoError(t, err) 199 | 200 | _, _, data, err := tbs.GetMessageFromQueue(queue, 5000) 201 | require.NoError(t, err) 202 | require.Equal(t, msg, string(data)) 203 | 204 | stats := tbs.Bridge.SafeStats() 205 | connStats := stats.Connections[0] 206 | require.Equal(t, int64(1), connStats.MessagesIn) 207 | require.Equal(t, int64(1), connStats.MessagesOut) 208 | require.Equal(t, int64(len([]byte(msg))), connStats.BytesIn) 209 | require.Equal(t, int64(len([]byte(msg))), connStats.BytesOut) 210 | require.Equal(t, int64(1), connStats.Connects) 211 | require.Equal(t, int64(0), connStats.Disconnects) 212 | require.True(t, connStats.Connected) 213 | } 214 | -------------------------------------------------------------------------------- /nats-mq/core/nats2topic.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 The NATS Authors 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | package core 17 | 18 | import ( 19 | "fmt" 20 | 21 | "github.com/ibm-messaging/mq-golang/v5/ibmmq" 22 | "github.com/nats-io/nats-mq/nats-mq/conf" 23 | nats "github.com/nats-io/nats.go" 24 | ) 25 | 26 | // NATS2TopicConnector connects a NATS subject to an MQ topic 27 | type NATS2TopicConnector struct { 28 | BridgeConnector 29 | 30 | topic *ibmmq.MQObject 31 | sub *nats.Subscription 32 | } 33 | 34 | // NewNATS2TopicConnector create a nats to MQ connector 35 | func NewNATS2TopicConnector(bridge *BridgeServer, config conf.ConnectorConfig) Connector { 36 | connector := &NATS2TopicConnector{} 37 | connector.init(bridge, config, fmt.Sprintf("NATS:%s to Topic:%s", config.Subject, config.Topic)) 38 | return connector 39 | } 40 | 41 | // Start the connector 42 | func (mq *NATS2TopicConnector) Start() error { 43 | mq.Lock() 44 | defer mq.Unlock() 45 | 46 | if !mq.bridge.CheckNATS() { 47 | return fmt.Errorf("%s connector requires nats to be available", mq.String()) 48 | } 49 | 50 | mq.bridge.Logger().Tracef("starting connection %s", mq.String()) 51 | 52 | err := mq.connectToMQ() 53 | if err != nil { 54 | return err 55 | } 56 | 57 | // Create the Object Descriptor that allows us to give the queue name 58 | topicObject, err := mq.connectToTopic(mq.config.Topic) 59 | 60 | if err != nil { 61 | return err 62 | } 63 | 64 | mq.topic = topicObject 65 | 66 | sub, err := mq.subscribeToNATS(mq.config.Subject, mq.config.NatsQueue, mq.topic) 67 | if err != nil { 68 | return err 69 | } 70 | mq.sub = sub 71 | 72 | mq.stats.AddConnect() 73 | mq.bridge.Logger().Tracef("opened and reading %s", mq.config.Topic) 74 | mq.bridge.Logger().Noticef("started connection %s", mq.String()) 75 | 76 | return nil 77 | } 78 | 79 | // Shutdown the connector 80 | func (mq *NATS2TopicConnector) Shutdown() error { 81 | mq.Lock() 82 | defer mq.Unlock() 83 | mq.stats.AddDisconnect() 84 | 85 | mq.bridge.Logger().Noticef("shutting down connection %s", mq.String()) 86 | 87 | if mq.sub != nil { 88 | mq.sub.Unsubscribe() 89 | mq.sub = nil 90 | } 91 | 92 | var err error 93 | 94 | topic := mq.topic 95 | mq.topic = nil 96 | 97 | if topic != nil { 98 | err = topic.Close(0) 99 | } 100 | 101 | if mq.qMgr != nil { 102 | _ = mq.qMgr.Disc() 103 | mq.qMgr = nil 104 | mq.bridge.Logger().Tracef("disconnected from queue manager for %s", mq.String()) 105 | } 106 | 107 | return err // ignore the disconnect error 108 | } 109 | 110 | // CheckConnections ensures the nats/stan connection and report an error if it is down 111 | func (mq *NATS2TopicConnector) CheckConnections() error { 112 | if !mq.bridge.CheckNATS() { 113 | return fmt.Errorf("%s connector requires nats to be available", mq.String()) 114 | } 115 | return nil 116 | } 117 | -------------------------------------------------------------------------------- /nats-mq/core/nats2topic_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 The NATS Authors 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | package core 17 | 18 | import ( 19 | "testing" 20 | "time" 21 | 22 | "github.com/ibm-messaging/mq-golang/v5/ibmmq" 23 | "github.com/nats-io/nats-mq/message" 24 | "github.com/nats-io/nats-mq/nats-mq/conf" 25 | "github.com/stretchr/testify/require" 26 | ) 27 | 28 | func TestSimpleSendOnNatsReceiveOnTopic(t *testing.T) { 29 | var topicObject ibmmq.MQObject 30 | 31 | subject := "test" 32 | topic := "dev/" 33 | msg := "hello world" 34 | 35 | connect := []conf.ConnectorConfig{ 36 | { 37 | Type: "NATS2Topic", 38 | Subject: subject, 39 | Topic: topic, 40 | ExcludeHeaders: true, 41 | }, 42 | } 43 | 44 | tbs, err := StartTestEnvironment(connect) 45 | require.NoError(t, err) 46 | defer tbs.Close() 47 | 48 | mqsd := ibmmq.NewMQSD() 49 | mqsd.Options = ibmmq.MQSO_CREATE | ibmmq.MQSO_NON_DURABLE | ibmmq.MQSO_MANAGED 50 | mqsd.ObjectString = topic 51 | sub, err := tbs.QMgr.Sub(mqsd, &topicObject) 52 | require.NoError(t, err) 53 | defer sub.Close(0) 54 | 55 | err = tbs.NC.Publish("test", []byte(msg)) 56 | require.NoError(t, err) 57 | 58 | mqmd := ibmmq.NewMQMD() 59 | gmo := ibmmq.NewMQGMO() 60 | gmo.Options = ibmmq.MQGMO_NO_SYNCPOINT 61 | gmo.Options |= ibmmq.MQGMO_WAIT 62 | gmo.WaitInterval = 3 * 1000 // The WaitInterval is in milliseconds 63 | buffer := make([]byte, 1024) 64 | 65 | datalen, err := topicObject.Get(mqmd, gmo, buffer) 66 | require.NoError(t, err) 67 | require.Equal(t, msg, string(buffer[:datalen])) 68 | 69 | stats := tbs.Bridge.SafeStats() 70 | connStats := stats.Connections[0] 71 | require.Equal(t, int64(1), connStats.MessagesIn) 72 | require.Equal(t, int64(1), connStats.MessagesOut) 73 | require.Equal(t, int64(len([]byte(msg))), connStats.BytesIn) 74 | require.Equal(t, int64(len([]byte(msg))), connStats.BytesOut) 75 | require.Equal(t, int64(1), connStats.Connects) 76 | require.Equal(t, int64(0), connStats.Disconnects) 77 | require.True(t, connStats.Connected) 78 | } 79 | 80 | func TestSendOnNATSReceiveOnTopicMQMD(t *testing.T) { 81 | var topicObject ibmmq.MQObject 82 | 83 | start := time.Now().UTC() 84 | subject := "test" 85 | topic := "dev/" 86 | msg := "hello world" 87 | 88 | connect := []conf.ConnectorConfig{ 89 | { 90 | Type: "NATS2Topic", 91 | Subject: subject, 92 | Topic: topic, 93 | ExcludeHeaders: false, 94 | }, 95 | } 96 | 97 | tbs, err := StartTestEnvironment(connect) 98 | require.NoError(t, err) 99 | defer tbs.Close() 100 | 101 | mqsd := ibmmq.NewMQSD() 102 | mqsd.Options = ibmmq.MQSO_CREATE | ibmmq.MQSO_NON_DURABLE | ibmmq.MQSO_MANAGED 103 | mqsd.ObjectString = topic 104 | sub, err := tbs.QMgr.Sub(mqsd, &topicObject) 105 | require.NoError(t, err) 106 | defer sub.Close(0) 107 | 108 | bridgeMessage := message.NewBridgeMessage([]byte(msg)) 109 | data, err := bridgeMessage.Encode() 110 | require.NoError(t, err) 111 | 112 | err = tbs.NC.Publish("test", data) 113 | require.NoError(t, err) 114 | 115 | mqmd := ibmmq.NewMQMD() 116 | gmo := ibmmq.NewMQGMO() 117 | gmo.Options = ibmmq.MQGMO_NO_SYNCPOINT 118 | gmo.Options |= ibmmq.MQGMO_WAIT 119 | gmo.WaitInterval = 3 * 1000 // The WaitInterval is in milliseconds 120 | buffer := make([]byte, 1024) 121 | 122 | datalen, err := topicObject.Get(mqmd, gmo, buffer) 123 | require.NoError(t, err) 124 | require.Equal(t, msg, string(buffer[:datalen])) 125 | 126 | require.Equal(t, start.Format("20060102"), mqmd.PutDate) 127 | require.True(t, start.Format("15040500") < mqmd.PutTime) 128 | 129 | stats := tbs.Bridge.SafeStats() 130 | connStats := stats.Connections[0] 131 | require.Equal(t, int64(1), connStats.MessagesIn) 132 | require.Equal(t, int64(1), connStats.MessagesOut) 133 | require.Equal(t, int64(len(data)), connStats.BytesIn) 134 | require.Equal(t, int64(datalen), connStats.BytesOut) 135 | require.Equal(t, int64(1), connStats.Connects) 136 | require.Equal(t, int64(0), connStats.Disconnects) 137 | require.True(t, connStats.Connected) 138 | } 139 | 140 | func TestSimpleSendOnNatsReceiveOnTopicWithTLS(t *testing.T) { 141 | var topicObject ibmmq.MQObject 142 | 143 | subject := "test" 144 | topic := "dev/" 145 | msg := "hello world" 146 | 147 | connect := []conf.ConnectorConfig{ 148 | { 149 | Type: "NATS2Topic", 150 | Subject: subject, 151 | Topic: topic, 152 | ExcludeHeaders: true, 153 | }, 154 | } 155 | 156 | tbs, err := StartTLSTestEnvironment(connect) 157 | require.NoError(t, err) 158 | defer tbs.Close() 159 | 160 | mqsd := ibmmq.NewMQSD() 161 | mqsd.Options = ibmmq.MQSO_CREATE | ibmmq.MQSO_NON_DURABLE | ibmmq.MQSO_MANAGED 162 | mqsd.ObjectString = topic 163 | sub, err := tbs.QMgr.Sub(mqsd, &topicObject) 164 | require.NoError(t, err) 165 | defer sub.Close(0) 166 | 167 | err = tbs.NC.Publish("test", []byte(msg)) 168 | require.NoError(t, err) 169 | 170 | mqmd := ibmmq.NewMQMD() 171 | gmo := ibmmq.NewMQGMO() 172 | gmo.Options = ibmmq.MQGMO_NO_SYNCPOINT 173 | gmo.Options |= ibmmq.MQGMO_WAIT 174 | gmo.WaitInterval = 3 * 1000 // The WaitInterval is in milliseconds 175 | buffer := make([]byte, 1024) 176 | 177 | datalen, err := topicObject.Get(mqmd, gmo, buffer) 178 | require.NoError(t, err) 179 | require.Equal(t, msg, string(buffer[:datalen])) 180 | } 181 | 182 | func TestWildcardSendRecieveOnTopic(t *testing.T) { 183 | var topicObject ibmmq.MQObject 184 | 185 | topic := "dev/" 186 | msg := "hello world" 187 | 188 | connect := []conf.ConnectorConfig{ 189 | { 190 | Type: "NATS2Topic", 191 | Subject: "test.*", 192 | Topic: topic, 193 | ExcludeHeaders: true, 194 | }, 195 | } 196 | 197 | tbs, err := StartTestEnvironment(connect) 198 | require.NoError(t, err) 199 | defer tbs.Close() 200 | 201 | mqsd := ibmmq.NewMQSD() 202 | mqsd.Options = ibmmq.MQSO_CREATE | ibmmq.MQSO_NON_DURABLE | ibmmq.MQSO_MANAGED 203 | mqsd.ObjectString = topic 204 | sub, err := tbs.QMgr.Sub(mqsd, &topicObject) 205 | require.NoError(t, err) 206 | defer sub.Close(0) 207 | 208 | err = tbs.NC.Publish("test.a", []byte(msg)) 209 | require.NoError(t, err) 210 | 211 | mqmd := ibmmq.NewMQMD() 212 | gmo := ibmmq.NewMQGMO() 213 | gmo.Options = ibmmq.MQGMO_NO_SYNCPOINT 214 | gmo.Options |= ibmmq.MQGMO_WAIT 215 | gmo.WaitInterval = 3 * 1000 // The WaitInterval is in milliseconds 216 | buffer := make([]byte, 1024) 217 | 218 | datalen, err := topicObject.Get(mqmd, gmo, buffer) 219 | require.NoError(t, err) 220 | require.Equal(t, msg, string(buffer[:datalen])) 221 | } 222 | -------------------------------------------------------------------------------- /nats-mq/core/queue2nats.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 The NATS Authors 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | package core 17 | 18 | import ( 19 | "fmt" 20 | 21 | "github.com/ibm-messaging/mq-golang/v5/ibmmq" 22 | "github.com/nats-io/nats-mq/nats-mq/conf" 23 | ) 24 | 25 | // Queue2NATSConnector connects an MQ queue to a NATS subject 26 | type Queue2NATSConnector struct { 27 | BridgeConnector 28 | 29 | queue *ibmmq.MQObject 30 | shutdownCB ShutdownCallback 31 | } 32 | 33 | // NewQueue2NATSConnector create a new MQ to Stan connector 34 | func NewQueue2NATSConnector(bridge *BridgeServer, config conf.ConnectorConfig) Connector { 35 | connector := &Queue2NATSConnector{} 36 | connector.init(bridge, config, fmt.Sprintf("Queue:%s to NATS:%s", config.Queue, config.Subject)) 37 | return connector 38 | } 39 | 40 | // Start the connector 41 | func (mq *Queue2NATSConnector) Start() error { 42 | mq.Lock() 43 | defer mq.Unlock() 44 | 45 | if !mq.bridge.CheckNATS() { 46 | return fmt.Errorf("%s connector requires nats to be available", mq.String()) 47 | } 48 | 49 | mq.bridge.Logger().Tracef("starting connection %s", mq.String()) 50 | 51 | err := mq.connectToMQ() 52 | if err != nil { 53 | return err 54 | } 55 | 56 | // Create the Object Descriptor that allows us to give the queue name 57 | qObject, err := mq.connectToQueue(mq.config.Queue, ibmmq.MQOO_INPUT_SHARED) 58 | if err != nil { 59 | return err 60 | } 61 | 62 | mq.queue = qObject 63 | 64 | cb, err := mq.setUpListener(mq.queue, mq.natsMessageHandler, mq) 65 | if err != nil { 66 | return err 67 | } 68 | mq.shutdownCB = cb 69 | 70 | mq.stats.AddConnect() 71 | mq.bridge.Logger().Tracef("opened and reading %s", mq.config.Queue) 72 | mq.bridge.Logger().Noticef("started connection %s", mq.String()) 73 | 74 | return nil 75 | } 76 | 77 | // Shutdown the connector 78 | func (mq *Queue2NATSConnector) Shutdown() error { 79 | mq.Lock() 80 | defer mq.Unlock() 81 | mq.stats.AddDisconnect() 82 | 83 | mq.bridge.Logger().Noticef("shutting down connection %s", mq.String()) 84 | 85 | if mq.shutdownCB != nil { 86 | if err := mq.shutdownCB(); err != nil { 87 | mq.bridge.Logger().Noticef("error stopping listener for %s, %s", mq.String(), err.Error()) 88 | } 89 | mq.shutdownCB = nil 90 | } 91 | 92 | queue := mq.queue 93 | mq.queue = nil 94 | 95 | if queue != nil { 96 | if err := queue.Close(0); err != nil { 97 | mq.bridge.Logger().Noticef("error closing queue for %s, %s", mq.String(), err.Error()) 98 | } 99 | } 100 | 101 | if mq.qMgr != nil { 102 | if err := mq.qMgr.Disc(); err != nil { 103 | mq.bridge.Logger().Noticef("error disconnecting from queue manager for %s, %s", mq.String(), err.Error()) 104 | } 105 | mq.qMgr = nil 106 | mq.bridge.Logger().Tracef("disconnected from queue manager for %s", mq.String()) 107 | } 108 | 109 | return nil // ignore the disconnect error 110 | } 111 | 112 | // CheckConnections ensures the nats/stan connection and report an error if it is down 113 | func (mq *Queue2NATSConnector) CheckConnections() error { 114 | if !mq.bridge.CheckNATS() { 115 | return fmt.Errorf("%s connector requires nats to be available", mq.String()) 116 | } 117 | return nil 118 | } 119 | -------------------------------------------------------------------------------- /nats-mq/core/queue2nats_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 The NATS Authors 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | package core 17 | 18 | import ( 19 | "bytes" 20 | "testing" 21 | "time" 22 | 23 | "github.com/ibm-messaging/mq-golang/v5/ibmmq" 24 | "github.com/nats-io/nats-mq/message" 25 | "github.com/nats-io/nats-mq/nats-mq/conf" 26 | nats "github.com/nats-io/nats.go" 27 | "github.com/stretchr/testify/require" 28 | ) 29 | 30 | func TestSimpleSendOnQueueReceiveOnNatsCallback(t *testing.T) { 31 | subject := "test" 32 | queue := "DEV.QUEUE.1" 33 | msg := "hello world" 34 | 35 | connect := []conf.ConnectorConfig{ 36 | { 37 | Type: "Queue2NATS", 38 | Subject: subject, 39 | Queue: queue, 40 | ExcludeHeaders: true, 41 | UsePolling: false, 42 | }, 43 | } 44 | 45 | tbs, err := StartTestEnvironment(connect) 46 | require.NoError(t, err) 47 | defer tbs.Close() 48 | 49 | done := make(chan string) 50 | 51 | sub, err := tbs.NC.Subscribe(subject, func(msg *nats.Msg) { 52 | done <- string(msg.Data) 53 | }) 54 | require.NoError(t, err) 55 | defer sub.Unsubscribe() 56 | 57 | err = tbs.PutMessageOnQueue(queue, ibmmq.NewMQMD(), []byte(msg)) 58 | require.NoError(t, err) 59 | 60 | timer := time.NewTimer(3 * time.Second) 61 | go func() { 62 | <-timer.C 63 | done <- "" 64 | }() 65 | 66 | received := <-done 67 | require.Equal(t, msg, received) 68 | 69 | stats := tbs.Bridge.SafeStats() 70 | connStats := stats.Connections[0] 71 | require.Equal(t, int64(1), connStats.MessagesIn) 72 | require.Equal(t, int64(1), connStats.MessagesOut) 73 | require.Equal(t, int64(len([]byte(msg))), connStats.BytesIn) 74 | require.Equal(t, int64(len([]byte(msg))), connStats.BytesOut) 75 | require.Equal(t, int64(1), connStats.Connects) 76 | require.Equal(t, int64(0), connStats.Disconnects) 77 | require.True(t, connStats.Connected) 78 | } 79 | 80 | func TestSendOnQueueReceiveOnNatsMQMD(t *testing.T) { 81 | start := time.Now().UTC() 82 | subject := "test" 83 | queue := "DEV.QUEUE.1" 84 | msg := "hello world" 85 | id := bytes.Repeat([]byte{1}, int(ibmmq.MQ_MSG_ID_LENGTH)) 86 | corr := bytes.Repeat([]byte{1}, int(ibmmq.MQ_CORREL_ID_LENGTH)) 87 | 88 | connect := []conf.ConnectorConfig{ 89 | { 90 | Type: "Queue2NATS", 91 | Subject: subject, 92 | Queue: queue, 93 | ExcludeHeaders: false, 94 | }, 95 | } 96 | 97 | tbs, err := StartTestEnvironment(connect) 98 | require.NoError(t, err) 99 | defer tbs.Close() 100 | 101 | done := make(chan []byte) 102 | 103 | sub, err := tbs.NC.Subscribe(subject, func(msg *nats.Msg) { 104 | done <- msg.Data 105 | }) 106 | require.NoError(t, err) 107 | defer sub.Unsubscribe() 108 | 109 | mqmd := ibmmq.NewMQMD() 110 | mqmd.CorrelId = corr 111 | mqmd.MsgId = id 112 | err = tbs.PutMessageOnQueue(queue, mqmd, []byte(msg)) 113 | require.NoError(t, err) 114 | 115 | // don't wait forever 116 | timer := time.NewTimer(3 * time.Second) 117 | go func() { 118 | <-timer.C 119 | done <- []byte{} 120 | }() 121 | 122 | received := <-done 123 | 124 | require.True(t, len(received) > 0) 125 | 126 | bridgeMessage, err := message.DecodeBridgeMessage(received) 127 | require.NoError(t, err) 128 | 129 | require.Equal(t, msg, string(bridgeMessage.Body)) 130 | require.Equal(t, start.Format("20060102"), bridgeMessage.Header.PutDate) 131 | require.True(t, start.Format("15040500") < bridgeMessage.Header.PutTime) 132 | require.ElementsMatch(t, id, bridgeMessage.Header.MsgID) 133 | require.ElementsMatch(t, corr, bridgeMessage.Header.CorrelID) 134 | 135 | stats := tbs.Bridge.SafeStats() 136 | connStats := stats.Connections[0] 137 | require.Equal(t, int64(1), connStats.MessagesIn) 138 | require.Equal(t, int64(1), connStats.MessagesOut) 139 | require.Equal(t, int64(len([]byte(msg))), connStats.BytesIn) 140 | require.Equal(t, int64(len(received)), connStats.BytesOut) 141 | require.Equal(t, int64(1), connStats.Connects) 142 | require.Equal(t, int64(0), connStats.Disconnects) 143 | require.True(t, connStats.Connected) 144 | } 145 | 146 | func TestSimpleSendOnQueueReceiveOnNatsWithTLS(t *testing.T) { 147 | subject := "test" 148 | queue := "DEV.QUEUE.1" 149 | msg := "hello world" 150 | 151 | connect := []conf.ConnectorConfig{ 152 | { 153 | Type: "Queue2NATS", 154 | Subject: subject, 155 | Queue: queue, 156 | ExcludeHeaders: true, 157 | }, 158 | } 159 | 160 | tbs, err := StartTLSTestEnvironment(connect) 161 | require.NoError(t, err) 162 | defer tbs.Close() 163 | 164 | done := make(chan string) 165 | 166 | sub, err := tbs.NC.Subscribe(subject, func(msg *nats.Msg) { 167 | done <- string(msg.Data) 168 | }) 169 | require.NoError(t, err) 170 | defer sub.Unsubscribe() 171 | 172 | err = tbs.PutMessageOnQueue(queue, ibmmq.NewMQMD(), []byte(msg)) 173 | require.NoError(t, err) 174 | 175 | timer := time.NewTimer(3 * time.Second) 176 | go func() { 177 | <-timer.C 178 | done <- "" 179 | }() 180 | 181 | received := <-done 182 | require.Equal(t, msg, received) 183 | } 184 | 185 | func TestSimpleSendOnQueueReceiveOnNatsPolling(t *testing.T) { 186 | subject := "test" 187 | queue := "DEV.QUEUE.1" 188 | msg := "hello world" 189 | 190 | connect := []conf.ConnectorConfig{ 191 | { 192 | Type: "Queue2NATS", 193 | Subject: subject, 194 | Queue: queue, 195 | ExcludeHeaders: true, 196 | UsePolling: true, 197 | }, 198 | } 199 | 200 | tbs, err := StartTestEnvironment(connect) 201 | require.NoError(t, err) 202 | defer tbs.Close() 203 | 204 | done := make(chan string) 205 | 206 | sub, err := tbs.NC.Subscribe(subject, func(msg *nats.Msg) { 207 | done <- string(msg.Data) 208 | }) 209 | require.NoError(t, err) 210 | defer sub.Unsubscribe() 211 | 212 | err = tbs.PutMessageOnQueue(queue, ibmmq.NewMQMD(), []byte(msg)) 213 | require.NoError(t, err) 214 | 215 | timer := time.NewTimer(3 * time.Second) 216 | go func() { 217 | <-timer.C 218 | done <- "" 219 | }() 220 | 221 | received := <-done 222 | require.Equal(t, msg, received) 223 | 224 | stats := tbs.Bridge.SafeStats() 225 | connStats := stats.Connections[0] 226 | require.Equal(t, int64(1), connStats.MessagesIn) 227 | require.Equal(t, int64(1), connStats.MessagesOut) 228 | require.Equal(t, int64(len([]byte(msg))), connStats.BytesIn) 229 | require.Equal(t, int64(len([]byte(msg))), connStats.BytesOut) 230 | require.Equal(t, int64(1), connStats.Connects) 231 | require.Equal(t, int64(0), connStats.Disconnects) 232 | require.True(t, connStats.Connected) 233 | } 234 | -------------------------------------------------------------------------------- /nats-mq/core/queue2stan.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 The NATS Authors 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | package core 17 | 18 | import ( 19 | "fmt" 20 | 21 | "github.com/ibm-messaging/mq-golang/v5/ibmmq" 22 | "github.com/nats-io/nats-mq/nats-mq/conf" 23 | ) 24 | 25 | // Queue2STANConnector connects an MQ queue to a NATS subject 26 | type Queue2STANConnector struct { 27 | BridgeConnector 28 | 29 | queue *ibmmq.MQObject 30 | shutdownCB ShutdownCallback 31 | } 32 | 33 | // NewQueue2STANConnector create a new MQ to Stan connector 34 | func NewQueue2STANConnector(bridge *BridgeServer, config conf.ConnectorConfig) Connector { 35 | connector := &Queue2STANConnector{} 36 | connector.init(bridge, config, fmt.Sprintf("Queue:%s to STAN:%s", config.Queue, config.Channel)) 37 | return connector 38 | } 39 | 40 | // Start the connector 41 | func (mq *Queue2STANConnector) Start() error { 42 | mq.Lock() 43 | defer mq.Unlock() 44 | 45 | if !mq.bridge.CheckStan() { 46 | return fmt.Errorf("%s connector requires nats streaming to be available", mq.String()) 47 | } 48 | 49 | mq.bridge.Logger().Tracef("starting connection %s", mq.String()) 50 | 51 | err := mq.connectToMQ() 52 | if err != nil { 53 | return err 54 | } 55 | 56 | // Create the Object Descriptor that allows us to give the queue name 57 | qObject, err := mq.connectToQueue(mq.config.Queue, ibmmq.MQOO_INPUT_SHARED) 58 | if err != nil { 59 | return err 60 | } 61 | 62 | mq.queue = qObject 63 | 64 | cb, err := mq.setUpListener(mq.queue, mq.stanMessageHandler, mq) 65 | if err != nil { 66 | return err 67 | } 68 | mq.shutdownCB = cb 69 | 70 | mq.stats.AddConnect() 71 | mq.bridge.Logger().Tracef("opened and reading %s", mq.config.Queue) 72 | mq.bridge.Logger().Noticef("started connection %s", mq.String()) 73 | return nil 74 | } 75 | 76 | // Shutdown the connector 77 | func (mq *Queue2STANConnector) Shutdown() error { 78 | mq.Lock() 79 | defer mq.Unlock() 80 | mq.stats.AddDisconnect() 81 | 82 | mq.bridge.Logger().Noticef("shutting down connection %s", mq.String()) 83 | 84 | if mq.shutdownCB != nil { 85 | if err := mq.shutdownCB(); err != nil { 86 | mq.bridge.Logger().Noticef("error stopping listener for %s, %s", mq.String(), err.Error()) 87 | } 88 | mq.shutdownCB = nil 89 | } 90 | 91 | queue := mq.queue 92 | mq.queue = nil 93 | 94 | if queue != nil { 95 | mq.bridge.Logger().Noticef("shutting down queue") 96 | if err := queue.Close(0); err != nil { 97 | mq.bridge.Logger().Noticef("error closing queue for %s, %s", mq.String(), err.Error()) 98 | } 99 | } 100 | 101 | if mq.qMgr != nil { 102 | mq.bridge.Logger().Noticef("shutting down qmgr") 103 | if err := mq.qMgr.Disc(); err != nil { 104 | mq.bridge.Logger().Noticef("error disconnecting from queue manager for %s, %s", mq.String(), err.Error()) 105 | } 106 | mq.qMgr = nil 107 | mq.bridge.Logger().Tracef("disconnected from queue manager for %s", mq.String()) 108 | } 109 | 110 | return nil 111 | } 112 | 113 | // CheckConnections ensures the nats/stan connection and report an error if it is down 114 | func (mq *Queue2STANConnector) CheckConnections() error { 115 | if !mq.bridge.CheckStan() { 116 | return fmt.Errorf("%s connector requires nats streaming to be available", mq.String()) 117 | } 118 | return nil 119 | } 120 | -------------------------------------------------------------------------------- /nats-mq/core/queue2stan_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 The NATS Authors 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | package core 17 | 18 | import ( 19 | "bytes" 20 | "testing" 21 | "time" 22 | 23 | "github.com/ibm-messaging/mq-golang/v5/ibmmq" 24 | "github.com/nats-io/nats-mq/message" 25 | "github.com/nats-io/nats-mq/nats-mq/conf" 26 | stan "github.com/nats-io/stan.go" 27 | "github.com/stretchr/testify/require" 28 | ) 29 | 30 | func TestSimpleSendOnQueueReceiveOnStan(t *testing.T) { 31 | channel := "test" 32 | queue := "DEV.QUEUE.1" 33 | msg := "hello world" 34 | 35 | connect := []conf.ConnectorConfig{ 36 | { 37 | Type: "Queue2Stan", 38 | Channel: channel, 39 | Queue: queue, 40 | ExcludeHeaders: true, 41 | }, 42 | } 43 | 44 | tbs, err := StartTestEnvironment(connect) 45 | require.NoError(t, err) 46 | defer tbs.Close() 47 | 48 | done := make(chan string) 49 | 50 | sub, err := tbs.SC.Subscribe(channel, func(msg *stan.Msg) { 51 | done <- string(msg.Data) 52 | }) 53 | require.NoError(t, err) 54 | defer sub.Unsubscribe() 55 | 56 | err = tbs.PutMessageOnQueue(queue, ibmmq.NewMQMD(), []byte(msg)) 57 | require.NoError(t, err) 58 | 59 | timer := time.NewTimer(3 * time.Second) 60 | go func() { 61 | <-timer.C 62 | done <- "" 63 | }() 64 | 65 | received := <-done 66 | require.Equal(t, msg, received) 67 | 68 | stats := tbs.Bridge.SafeStats() 69 | connStats := stats.Connections[0] 70 | require.Equal(t, int64(1), connStats.MessagesIn) 71 | require.Equal(t, int64(1), connStats.MessagesOut) 72 | require.Equal(t, int64(len([]byte(msg))), connStats.BytesIn) 73 | require.Equal(t, int64(len([]byte(msg))), connStats.BytesOut) 74 | require.Equal(t, int64(1), connStats.Connects) 75 | require.Equal(t, int64(0), connStats.Disconnects) 76 | require.True(t, connStats.Connected) 77 | } 78 | 79 | func TestSendOnQueueReceiveOnStanMQMD(t *testing.T) { 80 | start := time.Now().UTC() 81 | channel := "test" 82 | queue := "DEV.QUEUE.1" 83 | msg := "hello world" 84 | id := bytes.Repeat([]byte{1}, int(ibmmq.MQ_MSG_ID_LENGTH)) 85 | corr := bytes.Repeat([]byte{1}, int(ibmmq.MQ_CORREL_ID_LENGTH)) 86 | 87 | connect := []conf.ConnectorConfig{ 88 | { 89 | Type: "Queue2Stan", 90 | Channel: channel, 91 | Queue: queue, 92 | ExcludeHeaders: false, 93 | }, 94 | } 95 | 96 | tbs, err := StartTestEnvironment(connect) 97 | require.NoError(t, err) 98 | defer tbs.Close() 99 | 100 | done := make(chan []byte) 101 | 102 | sub, err := tbs.SC.Subscribe(channel, func(msg *stan.Msg) { 103 | done <- msg.Data 104 | }) 105 | require.NoError(t, err) 106 | defer sub.Unsubscribe() 107 | 108 | mqmd := ibmmq.NewMQMD() 109 | mqmd.CorrelId = corr 110 | mqmd.MsgId = id 111 | err = tbs.PutMessageOnQueue(queue, mqmd, []byte(msg)) 112 | require.NoError(t, err) 113 | 114 | // don't wait forever 115 | timer := time.NewTimer(3 * time.Second) 116 | go func() { 117 | <-timer.C 118 | done <- []byte{} 119 | }() 120 | 121 | received := <-done 122 | 123 | require.True(t, len(received) > 0) 124 | 125 | bridgeMessage, err := message.DecodeBridgeMessage(received) 126 | require.NoError(t, err) 127 | 128 | require.Equal(t, msg, string(bridgeMessage.Body)) 129 | require.Equal(t, start.Format("20060102"), bridgeMessage.Header.PutDate) 130 | require.True(t, start.Format("15040500") < bridgeMessage.Header.PutTime) 131 | require.ElementsMatch(t, id, bridgeMessage.Header.MsgID) 132 | require.ElementsMatch(t, corr, bridgeMessage.Header.CorrelID) 133 | 134 | stats := tbs.Bridge.SafeStats() 135 | connStats := stats.Connections[0] 136 | require.Equal(t, int64(1), connStats.MessagesIn) 137 | require.Equal(t, int64(1), connStats.MessagesOut) 138 | require.Equal(t, int64(len([]byte(msg))), connStats.BytesIn) 139 | require.Equal(t, int64(len(received)), connStats.BytesOut) 140 | require.Equal(t, int64(1), connStats.Connects) 141 | require.Equal(t, int64(0), connStats.Disconnects) 142 | require.True(t, connStats.Connected) 143 | } 144 | 145 | func TestSimpleSendOnQueueReceiveOnStanWithTLS(t *testing.T) { 146 | channel := "test" 147 | queue := "DEV.QUEUE.1" 148 | msg := "hello world" 149 | 150 | connect := []conf.ConnectorConfig{ 151 | { 152 | Type: "Queue2Stan", 153 | Channel: channel, 154 | Queue: queue, 155 | ExcludeHeaders: true, 156 | }, 157 | } 158 | 159 | tbs, err := StartTLSTestEnvironment(connect) 160 | require.NoError(t, err) 161 | defer tbs.Close() 162 | 163 | done := make(chan string) 164 | 165 | sub, err := tbs.SC.Subscribe(channel, func(msg *stan.Msg) { 166 | done <- string(msg.Data) 167 | }) 168 | require.NoError(t, err) 169 | defer sub.Unsubscribe() 170 | 171 | err = tbs.PutMessageOnQueue(queue, ibmmq.NewMQMD(), []byte(msg)) 172 | require.NoError(t, err) 173 | 174 | timer := time.NewTimer(3 * time.Second) 175 | go func() { 176 | <-timer.C 177 | done <- "" 178 | }() 179 | 180 | received := <-done 181 | require.Equal(t, msg, received) 182 | } 183 | -------------------------------------------------------------------------------- /nats-mq/core/reconnecttimer.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 The NATS Authors 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | package core 18 | 19 | import ( 20 | "time" 21 | ) 22 | 23 | type reconnectTimer struct { 24 | cancel chan bool 25 | } 26 | 27 | // NewReconnectTimer builds a new reconnect timer 28 | func newReconnectTimer() *reconnectTimer { 29 | return &reconnectTimer{ 30 | cancel: make(chan bool), 31 | } 32 | } 33 | 34 | // After returns a channel that will return true/false based on whether the timer was canceled 35 | func (c *reconnectTimer) After(d time.Duration) chan bool { 36 | ch := make(chan bool) 37 | go func() { 38 | select { 39 | case <-time.After(d): 40 | ch <- true 41 | case <-c.cancel: 42 | ch <- false 43 | } 44 | }() 45 | return ch 46 | } 47 | 48 | // Cancel stops the timer 49 | func (c *reconnectTimer) Cancel() { 50 | close(c.cancel) 51 | } 52 | -------------------------------------------------------------------------------- /nats-mq/core/stan2queue.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 The NATS Authors 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | package core 18 | 19 | import ( 20 | "fmt" 21 | 22 | "github.com/ibm-messaging/mq-golang/v5/ibmmq" 23 | "github.com/nats-io/nats-mq/nats-mq/conf" 24 | stan "github.com/nats-io/stan.go" 25 | ) 26 | 27 | // Stan2QueueConnector connects a STAN channel to an MQ Queue 28 | type Stan2QueueConnector struct { 29 | BridgeConnector 30 | 31 | queue *ibmmq.MQObject 32 | sub stan.Subscription 33 | } 34 | 35 | // NewStan2QueueConnector create a new Stan to MQ connector 36 | func NewStan2QueueConnector(bridge *BridgeServer, config conf.ConnectorConfig) Connector { 37 | connector := &Stan2QueueConnector{} 38 | connector.init(bridge, config, fmt.Sprintf("STAN:%s to Queue:%s", config.Channel, config.Queue)) 39 | return connector 40 | } 41 | 42 | // Start the connector 43 | func (mq *Stan2QueueConnector) Start() error { 44 | mq.Lock() 45 | defer mq.Unlock() 46 | 47 | if !mq.bridge.CheckStan() { 48 | return fmt.Errorf("%s connector requires nats streaming to be available", mq.String()) 49 | } 50 | 51 | mq.bridge.Logger().Tracef("starting connection %s", mq.String()) 52 | 53 | err := mq.connectToMQ() 54 | if err != nil { 55 | return err 56 | } 57 | 58 | // Create the Object Descriptor that allows us to give the queue name 59 | qObject, err := mq.connectToQueue(mq.config.Queue, ibmmq.MQOO_OUTPUT) 60 | if err != nil { 61 | return err 62 | } 63 | 64 | mq.queue = qObject 65 | 66 | sub, err := mq.subscribeToChannel(mq.queue) 67 | if err != nil { 68 | return err 69 | } 70 | mq.sub = sub 71 | 72 | mq.stats.AddConnect() 73 | mq.bridge.Logger().Tracef("opened and reading %s", mq.config.Queue) 74 | mq.bridge.Logger().Noticef("started connection %s", mq.String()) 75 | 76 | return nil 77 | } 78 | 79 | // Shutdown the connector 80 | func (mq *Stan2QueueConnector) Shutdown() error { 81 | mq.Lock() 82 | defer mq.Unlock() 83 | mq.stats.AddDisconnect() 84 | 85 | mq.bridge.Logger().Noticef("shutting down connection %s", mq.String()) 86 | 87 | if mq.sub != nil && mq.config.DurableName == "" { // Don't unsubscribe from durables 88 | mq.sub.Unsubscribe() 89 | mq.sub = nil 90 | } 91 | 92 | var err error 93 | 94 | queue := mq.queue 95 | mq.queue = nil 96 | 97 | if queue != nil { 98 | err = queue.Close(0) 99 | } 100 | 101 | if mq.qMgr != nil { 102 | _ = mq.qMgr.Disc() 103 | mq.qMgr = nil 104 | mq.bridge.Logger().Tracef("disconnected from queue manager for %s", mq.String()) 105 | } 106 | return err // ignore the disconnect error 107 | } 108 | 109 | // CheckConnections ensures the nats/stan connection and report an error if it is down 110 | func (mq *Stan2QueueConnector) CheckConnections() error { 111 | if !mq.bridge.CheckStan() { 112 | return fmt.Errorf("%s connector requires nats streaming to be available", mq.String()) 113 | } 114 | return nil 115 | } 116 | -------------------------------------------------------------------------------- /nats-mq/core/stan2topic.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 The NATS Authors 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | package core 18 | 19 | import ( 20 | "fmt" 21 | 22 | "github.com/ibm-messaging/mq-golang/v5/ibmmq" 23 | "github.com/nats-io/nats-mq/nats-mq/conf" 24 | stan "github.com/nats-io/stan.go" 25 | ) 26 | 27 | // Stan2TopicConnector connects a STAN channel to an MQ Topic 28 | type Stan2TopicConnector struct { 29 | BridgeConnector 30 | 31 | sub stan.Subscription 32 | topic *ibmmq.MQObject 33 | } 34 | 35 | // NewStan2TopicConnector create a new Stan to MQ connector 36 | func NewStan2TopicConnector(bridge *BridgeServer, config conf.ConnectorConfig) Connector { 37 | connector := &Stan2TopicConnector{} 38 | connector.init(bridge, config, fmt.Sprintf("STAN:%s to Topic:%s", config.Channel, config.Topic)) 39 | return connector 40 | } 41 | 42 | // Start the connector 43 | func (mq *Stan2TopicConnector) Start() error { 44 | mq.Lock() 45 | defer mq.Unlock() 46 | 47 | if !mq.bridge.CheckStan() { 48 | return fmt.Errorf("%s connector requires nats streaming to be available", mq.String()) 49 | } 50 | 51 | mq.bridge.Logger().Tracef("starting connection %s", mq.String()) 52 | 53 | err := mq.connectToMQ() 54 | if err != nil { 55 | return err 56 | } 57 | 58 | // Create the Object Descriptor that allows us to give the queue name 59 | topicObject, err := mq.connectToTopic(mq.config.Topic) 60 | if err != nil { 61 | return err 62 | } 63 | 64 | mq.topic = topicObject 65 | 66 | sub, err := mq.subscribeToChannel(mq.topic) 67 | if err != nil { 68 | return err 69 | } 70 | mq.sub = sub 71 | 72 | mq.stats.AddConnect() 73 | mq.bridge.Logger().Tracef("opened and reading %s", mq.config.Topic) 74 | mq.bridge.Logger().Noticef("started connection %s", mq.String()) 75 | 76 | return nil 77 | } 78 | 79 | // Shutdown the connector 80 | func (mq *Stan2TopicConnector) Shutdown() error { 81 | mq.Lock() 82 | defer mq.Unlock() 83 | mq.stats.AddDisconnect() 84 | 85 | mq.bridge.Logger().Noticef("shutting down connection %s", mq.String()) 86 | 87 | if mq.sub != nil && mq.config.DurableName == "" { // Don't unsubscribe from durables 88 | mq.sub.Unsubscribe() 89 | mq.sub = nil 90 | } 91 | 92 | var err error 93 | 94 | topic := mq.topic 95 | mq.topic = nil 96 | 97 | if topic != nil { 98 | err = topic.Close(0) 99 | } 100 | 101 | if mq.qMgr != nil { 102 | _ = mq.qMgr.Disc() 103 | mq.qMgr = nil 104 | mq.bridge.Logger().Tracef("disconnected from queue manager for %s", mq.String()) 105 | } 106 | 107 | return err // ignore the disconnect error 108 | } 109 | 110 | // CheckConnections ensures the nats/stan connection and report an error if it is down 111 | func (mq *Stan2TopicConnector) CheckConnections() error { 112 | if !mq.bridge.CheckStan() { 113 | return fmt.Errorf("%s connector requires nats streaming to be available", mq.String()) 114 | } 115 | return nil 116 | } 117 | -------------------------------------------------------------------------------- /nats-mq/core/stats.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 The NATS Authors 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | package core 18 | 19 | import ( 20 | "time" 21 | ) 22 | 23 | // BridgeStats wraps the current status of the bridge and all of its connectors 24 | type BridgeStats struct { 25 | StartTime int64 `json:"start_time"` 26 | ServerTime int64 `json:"current_time"` 27 | UpTime string `json:"uptime"` 28 | Connections []ConnectorStats `json:"connectors"` 29 | HTTPRequests map[string]int64 `json:"http_requests"` 30 | } 31 | 32 | // ConnectorStats captures the statistics for a single connector 33 | type ConnectorStats struct { 34 | Name string `json:"name"` 35 | ID string `json:"id"` 36 | Connected bool `json:"connected"` 37 | Connects int64 `json:"connects"` 38 | Disconnects int64 `json:"disconnects"` 39 | BytesIn int64 `json:"bytes_in"` 40 | BytesOut int64 `json:"bytes_out"` 41 | MessagesIn int64 `json:"msg_in"` 42 | MessagesOut int64 `json:"msg_out"` 43 | RequestCount int64 `json:"count"` 44 | MovingAverage float64 `json:"rma"` 45 | Quintile50 float64 `json:"q50"` 46 | Quintile75 float64 `json:"q75"` 47 | Quintile90 float64 `json:"q90"` 48 | Quintile95 float64 `json:"q95"` 49 | histogram *Histogram 50 | } 51 | 52 | // NewConnectorStats creates an empty stats, and initializes the request time histogram 53 | func NewConnectorStats() ConnectorStats { 54 | return ConnectorStats{ 55 | histogram: NewHistogram(60), 56 | } 57 | } 58 | 59 | // AddMessageIn updates the messages in and bytes in fields 60 | func (stats *ConnectorStats) AddMessageIn(bytes int64) { 61 | stats.MessagesIn++ 62 | stats.BytesIn += bytes 63 | } 64 | 65 | // AddMessageOut updates the messages out and bytes out fields 66 | func (stats *ConnectorStats) AddMessageOut(bytes int64) { 67 | stats.MessagesOut++ 68 | stats.BytesOut += bytes 69 | } 70 | 71 | // AddDisconnect updates the disconnects field 72 | func (stats *ConnectorStats) AddDisconnect() { 73 | stats.Disconnects++ 74 | stats.Connected = false 75 | } 76 | 77 | // AddConnect updates the reconnects field 78 | func (stats *ConnectorStats) AddConnect() { 79 | stats.Connects++ 80 | stats.Connected = true 81 | } 82 | 83 | // AddRequestTime register a time, updating the request count, RMA and histogram 84 | // For information on the running moving average, see https://en.wikipedia.org/wiki/Moving_average 85 | func (stats *ConnectorStats) AddRequestTime(reqTime time.Duration) { 86 | reqns := float64(reqTime.Nanoseconds()) 87 | stats.RequestCount++ 88 | stats.MovingAverage = ((float64(stats.RequestCount-1) * stats.MovingAverage) + reqns) / float64(stats.RequestCount) 89 | stats.histogram.Add(reqns) 90 | } 91 | 92 | // UpdateQuintiles updates the quantile fields, these are not updated on each request 93 | // to reduce the cost of tracking statistics 94 | func (stats *ConnectorStats) UpdateQuintiles() { 95 | stats.Quintile50 = stats.histogram.Quantile(0.5) 96 | stats.Quintile75 = stats.histogram.Quantile(0.75) 97 | stats.Quintile90 = stats.histogram.Quantile(0.9) 98 | stats.Quintile95 = stats.histogram.Quantile(0.95) 99 | } 100 | -------------------------------------------------------------------------------- /nats-mq/core/stats_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 The NATS Authors 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | package core 18 | 19 | import ( 20 | "testing" 21 | "time" 22 | 23 | "github.com/stretchr/testify/require" 24 | ) 25 | 26 | func TestMessageCounts(t *testing.T) { 27 | stats := NewConnectorStats() 28 | loops := int64(101) 29 | sizeIn := int64(11) 30 | sizeOut := int64(13) 31 | 32 | for i := int64(0); i < loops; i++ { 33 | stats.AddMessageIn(sizeIn) 34 | stats.AddMessageOut(sizeOut) 35 | } 36 | 37 | require.Equal(t, loops, stats.MessagesIn) 38 | require.Equal(t, loops, stats.MessagesOut) 39 | require.Equal(t, loops*sizeIn, stats.BytesIn) 40 | require.Equal(t, loops*sizeOut, stats.BytesOut) 41 | } 42 | 43 | func TestConnectDisconnectCounts(t *testing.T) { 44 | stats := NewConnectorStats() 45 | loops := int64(101) 46 | 47 | for i := int64(0); i < loops; i++ { 48 | stats.AddDisconnect() 49 | stats.AddConnect() 50 | stats.AddConnect() 51 | } 52 | 53 | require.Equal(t, loops, stats.Disconnects) 54 | require.Equal(t, 2*loops, stats.Connects) 55 | require.Equal(t, int64(0), stats.BytesIn) 56 | require.Equal(t, int64(0), stats.BytesOut) 57 | require.Equal(t, int64(0), stats.MessagesIn) 58 | require.Equal(t, int64(0), stats.MessagesOut) 59 | } 60 | 61 | func TestRequestTimes(t *testing.T) { 62 | stats := NewConnectorStats() 63 | 64 | dur := 4 * time.Second 65 | 66 | stats.AddRequestTime(dur) 67 | 68 | require.Equal(t, float64(dur.Nanoseconds()), stats.MovingAverage) 69 | require.Equal(t, int64(1), stats.RequestCount) 70 | } 71 | -------------------------------------------------------------------------------- /nats-mq/core/topic2nats.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 The NATS Authors 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | package core 18 | 19 | import ( 20 | "fmt" 21 | 22 | "github.com/ibm-messaging/mq-golang/v5/ibmmq" 23 | "github.com/nats-io/nats-mq/nats-mq/conf" 24 | ) 25 | 26 | // Topic2NATSConnector connects an MQ queue to a NATS subject 27 | type Topic2NATSConnector struct { 28 | BridgeConnector 29 | 30 | topic *ibmmq.MQObject 31 | sub *ibmmq.MQObject 32 | shutdownCB ShutdownCallback 33 | } 34 | 35 | // NewTopic2NATSConnector create a new MQ to Stan connector 36 | func NewTopic2NATSConnector(bridge *BridgeServer, config conf.ConnectorConfig) Connector { 37 | connector := &Topic2NATSConnector{} 38 | connector.init(bridge, config, fmt.Sprintf("MQ:%s to NATS:%s", config.Topic, config.Subject)) 39 | return connector 40 | } 41 | 42 | // Start the connector 43 | func (mq *Topic2NATSConnector) Start() error { 44 | mq.Lock() 45 | defer mq.Unlock() 46 | 47 | if !mq.bridge.CheckNATS() { 48 | return fmt.Errorf("%s connector requires nats to be available", mq.String()) 49 | } 50 | 51 | mq.bridge.Logger().Tracef("starting connection %s", mq.String()) 52 | 53 | err := mq.connectToMQ() 54 | if err != nil { 55 | return err 56 | } 57 | 58 | topic, sub, err := mq.subscribeToTopic(mq.config.Topic) 59 | 60 | if err != nil { 61 | return err 62 | } 63 | 64 | mq.topic = topic 65 | mq.sub = sub 66 | 67 | mq.bridge.Logger().Tracef("subscribed to %s", mq.config.Topic) 68 | 69 | cb, err := mq.setUpListener(mq.topic, mq.natsMessageHandler, mq) 70 | if err != nil { 71 | return err 72 | } 73 | mq.shutdownCB = cb 74 | 75 | mq.stats.AddConnect() 76 | mq.bridge.Logger().Tracef("opened and subscribed to %s", mq.config.Topic) 77 | mq.bridge.Logger().Noticef("started connection %s", mq.String()) 78 | 79 | return nil 80 | } 81 | 82 | // Shutdown the connector 83 | func (mq *Topic2NATSConnector) Shutdown() error { 84 | mq.Lock() 85 | defer mq.Unlock() 86 | mq.stats.AddDisconnect() 87 | 88 | if mq.topic == nil { 89 | return nil 90 | } 91 | 92 | mq.bridge.Logger().Noticef("shutting down connection %s", mq.String()) 93 | 94 | sub := mq.sub 95 | topic := mq.topic 96 | mq.topic = nil 97 | mq.sub = nil 98 | 99 | if mq.shutdownCB != nil { 100 | if err := mq.shutdownCB(); err != nil { 101 | mq.bridge.Logger().Noticef("error stopping listener for %s, %s", mq.String(), err.Error()) 102 | } 103 | mq.shutdownCB = nil 104 | } 105 | 106 | if sub != nil { 107 | if err := sub.Close(0); err != nil { 108 | mq.bridge.Logger().Noticef("error closing subscription for %s", mq.String()) 109 | } 110 | } 111 | 112 | if topic != nil { 113 | if err := topic.Close(0); err != nil { 114 | mq.bridge.Logger().Noticef("error closing topic for %s", mq.String()) 115 | } 116 | } 117 | 118 | if mq.qMgr != nil { 119 | mq.bridge.Logger().Noticef("shutting down qmgr") 120 | if err := mq.qMgr.Disc(); err != nil { 121 | mq.bridge.Logger().Noticef("error disconnecting from queue manager for %s, %s", mq.String(), err.Error()) 122 | } 123 | mq.qMgr = nil 124 | mq.bridge.Logger().Tracef("disconnected from queue manager for %s", mq.String()) 125 | } 126 | 127 | return nil 128 | } 129 | 130 | // CheckConnections ensures the nats/stan connection and report an error if it is down 131 | func (mq *Topic2NATSConnector) CheckConnections() error { 132 | if !mq.bridge.CheckNATS() { 133 | return fmt.Errorf("%s connector requires nats to be available", mq.String()) 134 | } 135 | return nil 136 | } 137 | -------------------------------------------------------------------------------- /nats-mq/core/topic2nats_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 The NATS Authors 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | package core 18 | 19 | import ( 20 | "bytes" 21 | "testing" 22 | "time" 23 | 24 | "github.com/ibm-messaging/mq-golang/v5/ibmmq" 25 | "github.com/nats-io/nats-mq/message" 26 | "github.com/nats-io/nats-mq/nats-mq/conf" 27 | nats "github.com/nats-io/nats.go" 28 | "github.com/stretchr/testify/require" 29 | ) 30 | 31 | func TestSimpleSendOnTopicReceiveOnNats(t *testing.T) { 32 | subject := "test" 33 | topic := "dev/" 34 | msg := "hello world" 35 | 36 | connect := []conf.ConnectorConfig{ 37 | { 38 | Type: "Topic2NATS", 39 | Subject: subject, 40 | Topic: topic, 41 | ExcludeHeaders: true, 42 | }, 43 | } 44 | 45 | tbs, err := StartTestEnvironment(connect) 46 | require.NoError(t, err) 47 | defer tbs.Close() 48 | 49 | done := make(chan string) 50 | 51 | sub, err := tbs.NC.Subscribe(subject, func(msg *nats.Msg) { 52 | done <- string(msg.Data) 53 | }) 54 | require.NoError(t, err) 55 | defer sub.Unsubscribe() 56 | 57 | err = tbs.PutMessageOnTopic(topic, ibmmq.NewMQMD(), []byte(msg)) 58 | require.NoError(t, err) 59 | 60 | timer := time.NewTimer(3 * time.Second) 61 | go func() { 62 | <-timer.C 63 | done <- "" 64 | }() 65 | 66 | received := <-done 67 | require.Equal(t, msg, received) 68 | 69 | stats := tbs.Bridge.SafeStats() 70 | connStats := stats.Connections[0] 71 | require.Equal(t, int64(1), connStats.MessagesIn) 72 | require.Equal(t, int64(1), connStats.MessagesOut) 73 | require.Equal(t, int64(len([]byte(msg))), connStats.BytesIn) 74 | require.Equal(t, int64(len([]byte(msg))), connStats.BytesOut) 75 | require.Equal(t, int64(1), connStats.Connects) 76 | require.Equal(t, int64(0), connStats.Disconnects) 77 | require.True(t, connStats.Connected) 78 | } 79 | 80 | func TestSendOnTopicReceiveOnNatsMQMD(t *testing.T) { 81 | start := time.Now().UTC() 82 | subject := "test" 83 | topic := "dev/" 84 | msg := "hello world" 85 | id := bytes.Repeat([]byte{1}, int(ibmmq.MQ_MSG_ID_LENGTH)) 86 | corr := bytes.Repeat([]byte{1}, int(ibmmq.MQ_CORREL_ID_LENGTH)) 87 | 88 | connect := []conf.ConnectorConfig{ 89 | { 90 | Type: "Topic2NATS", 91 | Subject: subject, 92 | Topic: topic, 93 | ExcludeHeaders: false, 94 | }, 95 | } 96 | 97 | tbs, err := StartTestEnvironment(connect) 98 | require.NoError(t, err) 99 | defer tbs.Close() 100 | 101 | done := make(chan []byte) 102 | 103 | sub, err := tbs.NC.Subscribe(subject, func(msg *nats.Msg) { 104 | done <- msg.Data 105 | }) 106 | require.NoError(t, err) 107 | defer sub.Unsubscribe() 108 | 109 | mqmd := ibmmq.NewMQMD() 110 | mqmd.CorrelId = corr 111 | mqmd.MsgId = id 112 | err = tbs.PutMessageOnTopic(topic, ibmmq.NewMQMD(), []byte(msg)) 113 | require.NoError(t, err) 114 | 115 | // don't wait forever 116 | timer := time.NewTimer(3 * time.Second) 117 | go func() { 118 | <-timer.C 119 | done <- []byte{} 120 | }() 121 | 122 | received := <-done 123 | 124 | require.True(t, len(received) > 0) 125 | 126 | bridgeMessage, err := message.DecodeBridgeMessage(received) 127 | require.NoError(t, err) 128 | 129 | require.Equal(t, msg, string(bridgeMessage.Body)) 130 | require.Equal(t, start.Format("20060102"), bridgeMessage.Header.PutDate) 131 | require.True(t, start.Format("15040500") < bridgeMessage.Header.PutTime) 132 | 133 | // TODO looks like topics generate these, perhaps it is a setting 134 | //require.ElementsMatch(t, id, bridgeMessage.Header.MsgID) 135 | //require.ElementsMatch(t, corr, bridgeMessage.Header.CorrelID) 136 | 137 | stats := tbs.Bridge.SafeStats() 138 | connStats := stats.Connections[0] 139 | require.Equal(t, int64(1), connStats.MessagesIn) 140 | require.Equal(t, int64(1), connStats.MessagesOut) 141 | require.Equal(t, int64(len([]byte(msg))), connStats.BytesIn) 142 | require.Equal(t, int64(len(received)), connStats.BytesOut) 143 | require.Equal(t, int64(1), connStats.Connects) 144 | require.Equal(t, int64(0), connStats.Disconnects) 145 | require.True(t, connStats.Connected) 146 | } 147 | 148 | func TestSimpleSendOnTopicReceiveOnNatsWithTLS(t *testing.T) { 149 | subject := "test" 150 | topic := "dev/" 151 | msg := "hello world" 152 | 153 | connect := []conf.ConnectorConfig{ 154 | { 155 | Type: "Topic2NATS", 156 | Subject: subject, 157 | Topic: topic, 158 | ExcludeHeaders: true, 159 | }, 160 | } 161 | 162 | tbs, err := StartTLSTestEnvironment(connect) 163 | require.NoError(t, err) 164 | defer tbs.Close() 165 | 166 | done := make(chan string) 167 | 168 | sub, err := tbs.NC.Subscribe(subject, func(msg *nats.Msg) { 169 | done <- string(msg.Data) 170 | }) 171 | require.NoError(t, err) 172 | defer sub.Unsubscribe() 173 | 174 | err = tbs.PutMessageOnTopic(topic, ibmmq.NewMQMD(), []byte(msg)) 175 | require.NoError(t, err) 176 | 177 | timer := time.NewTimer(3 * time.Second) 178 | go func() { 179 | <-timer.C 180 | done <- "" 181 | }() 182 | 183 | received := <-done 184 | require.Equal(t, msg, received) 185 | } 186 | -------------------------------------------------------------------------------- /nats-mq/core/topic2stan.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 The NATS Authors 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | package core 18 | 19 | import ( 20 | "fmt" 21 | 22 | "github.com/ibm-messaging/mq-golang/v5/ibmmq" 23 | "github.com/nats-io/nats-mq/nats-mq/conf" 24 | ) 25 | 26 | // Topic2StanConnector connects an MQ queue to a NATS channel 27 | type Topic2StanConnector struct { 28 | BridgeConnector 29 | 30 | topic *ibmmq.MQObject 31 | sub *ibmmq.MQObject 32 | shutdownCB ShutdownCallback 33 | } 34 | 35 | // NewTopic2StanConnector create a new MQ to Stan connector 36 | func NewTopic2StanConnector(bridge *BridgeServer, config conf.ConnectorConfig) Connector { 37 | connector := &Topic2StanConnector{} 38 | connector.init(bridge, config, fmt.Sprintf("MQ:%s to Stan:%s", config.Topic, config.Channel)) 39 | return connector 40 | } 41 | 42 | // Start the connector 43 | func (mq *Topic2StanConnector) Start() error { 44 | mq.Lock() 45 | defer mq.Unlock() 46 | 47 | if !mq.bridge.CheckStan() { 48 | return fmt.Errorf("%s connector requires nats streaming to be available", mq.String()) 49 | } 50 | 51 | mq.bridge.Logger().Tracef("starting connection %s", mq.String()) 52 | 53 | err := mq.connectToMQ() 54 | if err != nil { 55 | return err 56 | } 57 | 58 | topic, sub, err := mq.subscribeToTopic(mq.config.Topic) 59 | if err != nil { 60 | return err 61 | } 62 | 63 | mq.topic = topic 64 | mq.sub = sub 65 | 66 | mq.bridge.Logger().Tracef("subscribed to %s", mq.config.Topic) 67 | 68 | cb, err := mq.setUpListener(mq.topic, mq.stanMessageHandler, mq) 69 | if err != nil { 70 | return err 71 | } 72 | mq.shutdownCB = cb 73 | 74 | mq.stats.AddConnect() 75 | mq.bridge.Logger().Tracef("opened and subscribed to %s", mq.config.Topic) 76 | mq.bridge.Logger().Noticef("started connection %s", mq.String()) 77 | 78 | return nil 79 | } 80 | 81 | // Shutdown the connector 82 | func (mq *Topic2StanConnector) Shutdown() error { 83 | mq.Lock() 84 | defer mq.Unlock() 85 | 86 | mq.stats.AddDisconnect() 87 | 88 | if mq.topic == nil { 89 | return nil 90 | } 91 | 92 | mq.bridge.Logger().Noticef("shutting down connection %s", mq.String()) 93 | 94 | sub := mq.sub 95 | topic := mq.topic 96 | mq.topic = nil 97 | mq.sub = nil 98 | 99 | if mq.shutdownCB != nil { 100 | if err := mq.shutdownCB(); err != nil { 101 | mq.bridge.Logger().Noticef("error stopping listener for %s, %s", mq.String(), err.Error()) 102 | } 103 | mq.shutdownCB = nil 104 | } 105 | 106 | if sub != nil { 107 | if err := sub.Close(0); err != nil { 108 | mq.bridge.Logger().Noticef("error closing subscription for %s", mq.String()) 109 | } 110 | } 111 | 112 | if topic != nil { 113 | if err := topic.Close(0); err != nil { 114 | mq.bridge.Logger().Noticef("error closing topic for %s", mq.String()) 115 | } 116 | } 117 | 118 | if mq.qMgr != nil { 119 | mq.bridge.Logger().Noticef("shutting down qmgr") 120 | if err := mq.qMgr.Disc(); err != nil { 121 | mq.bridge.Logger().Noticef("error disconnecting from queue manager for %s, %s", mq.String(), err.Error()) 122 | } 123 | mq.qMgr = nil 124 | mq.bridge.Logger().Tracef("disconnected from queue manager for %s", mq.String()) 125 | } 126 | 127 | return nil 128 | } 129 | 130 | // CheckConnections ensures the nats/stan connection and report an error if it is down 131 | func (mq *Topic2StanConnector) CheckConnections() error { 132 | if !mq.bridge.CheckStan() { 133 | return fmt.Errorf("%s connector requires nats streaming to be available", mq.String()) 134 | } 135 | return nil 136 | } 137 | -------------------------------------------------------------------------------- /nats-mq/core/topic2stan_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 The NATS Authors 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | package core 18 | 19 | import ( 20 | "testing" 21 | "time" 22 | 23 | "github.com/ibm-messaging/mq-golang/v5/ibmmq" 24 | "github.com/nats-io/nats-mq/message" 25 | "github.com/nats-io/nats-mq/nats-mq/conf" 26 | stan "github.com/nats-io/stan.go" 27 | "github.com/stretchr/testify/require" 28 | ) 29 | 30 | func TestSimpleSendOnTopicReceiveOnStan(t *testing.T) { 31 | channel := "test" 32 | topic := "dev/" 33 | msg := "hello world" 34 | 35 | connect := []conf.ConnectorConfig{ 36 | { 37 | Type: "Topic2Stan", 38 | Channel: channel, 39 | Topic: topic, 40 | ExcludeHeaders: true, 41 | }, 42 | } 43 | 44 | tbs, err := StartTestEnvironment(connect) 45 | require.NoError(t, err) 46 | defer tbs.Close() 47 | 48 | done := make(chan string) 49 | 50 | sub, err := tbs.SC.Subscribe(channel, func(msg *stan.Msg) { 51 | done <- string(msg.Data) 52 | }) 53 | require.NoError(t, err) 54 | defer sub.Unsubscribe() 55 | 56 | err = tbs.PutMessageOnTopic(topic, ibmmq.NewMQMD(), []byte(msg)) 57 | require.NoError(t, err) 58 | 59 | timer := time.NewTimer(3 * time.Second) 60 | go func() { 61 | <-timer.C 62 | done <- "" 63 | }() 64 | 65 | received := <-done 66 | require.Equal(t, msg, received) 67 | 68 | stats := tbs.Bridge.SafeStats() 69 | connStats := stats.Connections[0] 70 | require.Equal(t, int64(1), connStats.MessagesIn) 71 | require.Equal(t, int64(1), connStats.MessagesOut) 72 | require.Equal(t, int64(len([]byte(msg))), connStats.BytesIn) 73 | require.Equal(t, int64(len([]byte(msg))), connStats.BytesOut) 74 | require.Equal(t, int64(1), connStats.Connects) 75 | require.Equal(t, int64(0), connStats.Disconnects) 76 | require.True(t, connStats.Connected) 77 | } 78 | 79 | func TestSendOnTopicReceiveOnStanMQMD(t *testing.T) { 80 | start := time.Now().UTC() 81 | channel := "test" 82 | topic := "dev/" 83 | msg := "hello world" 84 | 85 | connect := []conf.ConnectorConfig{ 86 | { 87 | Type: "Topic2Stan", 88 | Channel: channel, 89 | Topic: topic, 90 | ExcludeHeaders: false, 91 | }, 92 | } 93 | 94 | tbs, err := StartTestEnvironment(connect) 95 | require.NoError(t, err) 96 | defer tbs.Close() 97 | 98 | done := make(chan []byte) 99 | 100 | sub, err := tbs.SC.Subscribe(channel, func(msg *stan.Msg) { 101 | done <- msg.Data 102 | }) 103 | require.NoError(t, err) 104 | defer sub.Unsubscribe() 105 | 106 | mqmd := ibmmq.NewMQMD() 107 | err = tbs.PutMessageOnTopic(topic, mqmd, []byte(msg)) 108 | require.NoError(t, err) 109 | 110 | // don't wait forever 111 | timer := time.NewTimer(3 * time.Second) 112 | go func() { 113 | <-timer.C 114 | done <- []byte{} 115 | }() 116 | 117 | received := <-done 118 | 119 | require.True(t, len(received) > 0) 120 | 121 | bridgeMessage, err := message.DecodeBridgeMessage(received) 122 | require.NoError(t, err) 123 | 124 | require.Equal(t, msg, string(bridgeMessage.Body)) 125 | require.Equal(t, start.Format("20060102"), bridgeMessage.Header.PutDate) 126 | require.True(t, start.Format("15040500") < bridgeMessage.Header.PutTime) 127 | 128 | stats := tbs.Bridge.SafeStats() 129 | connStats := stats.Connections[0] 130 | require.Equal(t, int64(1), connStats.MessagesIn) 131 | require.Equal(t, int64(1), connStats.MessagesOut) 132 | require.Equal(t, int64(len([]byte(msg))), connStats.BytesIn) 133 | require.Equal(t, int64(len(received)), connStats.BytesOut) 134 | require.Equal(t, int64(1), connStats.Connects) 135 | require.Equal(t, int64(0), connStats.Disconnects) 136 | require.True(t, connStats.Connected) 137 | } 138 | 139 | func TestSimpleSendOnTopicReceiveOnStanWithTLS(t *testing.T) { 140 | channel := "test" 141 | topic := "dev/" 142 | msg := "hello world" 143 | 144 | connect := []conf.ConnectorConfig{ 145 | { 146 | Type: "Topic2Stan", 147 | Channel: channel, 148 | Topic: topic, 149 | ExcludeHeaders: true, 150 | }, 151 | } 152 | 153 | tbs, err := StartTLSTestEnvironment(connect) 154 | require.NoError(t, err) 155 | defer tbs.Close() 156 | 157 | done := make(chan string) 158 | 159 | sub, err := tbs.SC.Subscribe(channel, func(msg *stan.Msg) { 160 | done <- string(msg.Data) 161 | }) 162 | require.NoError(t, err) 163 | defer sub.Unsubscribe() 164 | 165 | err = tbs.PutMessageOnTopic(topic, ibmmq.NewMQMD(), []byte(msg)) 166 | require.NoError(t, err) 167 | 168 | timer := time.NewTimer(3 * time.Second) 169 | go func() { 170 | <-timer.C 171 | done <- "" 172 | }() 173 | 174 | received := <-done 175 | require.Equal(t, msg, received) 176 | } 177 | -------------------------------------------------------------------------------- /nats-mq/logging/logging.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 The NATS Authors 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | package logging 18 | 19 | // Config defines logging flags for the NATS logger 20 | type Config struct { 21 | Time bool 22 | Debug bool 23 | Trace bool 24 | Colors bool 25 | PID bool 26 | } 27 | 28 | // Logger interface 29 | type Logger interface { 30 | Debugf(format string, v ...interface{}) 31 | Errorf(format string, v ...interface{}) 32 | Fatalf(format string, v ...interface{}) 33 | Noticef(format string, v ...interface{}) 34 | Tracef(format string, v ...interface{}) 35 | Warnf(format string, v ...interface{}) 36 | 37 | Close() error 38 | } 39 | -------------------------------------------------------------------------------- /nats-mq/logging/nats.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 The NATS Authors 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | package logging 18 | 19 | import ( 20 | "github.com/nats-io/nats-server/v2/logger" 21 | ) 22 | 23 | // NewNATSLogger creates a new logger that uses the nats-server/v2 library 24 | func NewNATSLogger(conf Config) Logger { 25 | l := logger.NewStdLogger(conf.Time, conf.Debug, conf.Trace, conf.Colors, conf.PID) 26 | return &NATSLogger{ 27 | logger: l, 28 | } 29 | } 30 | 31 | // NATSLogger - uses the nats-server/v2 logging code 32 | type NATSLogger struct { 33 | logger *logger.Logger 34 | } 35 | 36 | // Close forwards to the nats logger 37 | func (logger *NATSLogger) Close() error { 38 | return logger.logger.Close() 39 | } 40 | 41 | // Debugf forwards to the nats logger 42 | func (logger *NATSLogger) Debugf(format string, v ...interface{}) { 43 | logger.logger.Debugf(format, v...) 44 | } 45 | 46 | // Errorf forwards to the nats logger 47 | func (logger *NATSLogger) Errorf(format string, v ...interface{}) { 48 | logger.logger.Errorf(format, v...) 49 | } 50 | 51 | // Fatalf forwards to the nats logger 52 | func (logger *NATSLogger) Fatalf(format string, v ...interface{}) { 53 | logger.logger.Fatalf(format, v...) 54 | } 55 | 56 | // Noticef forwards to the nats logger 57 | func (logger *NATSLogger) Noticef(format string, v ...interface{}) { 58 | logger.logger.Noticef(format, v...) 59 | } 60 | 61 | // Tracef forwards to the nats logger 62 | func (logger *NATSLogger) Tracef(format string, v ...interface{}) { 63 | logger.logger.Tracef(format, v...) 64 | } 65 | 66 | // Warnf forwards to the nats logger 67 | func (logger *NATSLogger) Warnf(format string, v ...interface{}) { 68 | logger.logger.Warnf(format, v...) 69 | } 70 | -------------------------------------------------------------------------------- /nats-mq/logging/nats_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 The NATS Authors 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | package logging 18 | 19 | import ( 20 | "testing" 21 | ) 22 | 23 | func TestNATSForCoverage(t *testing.T) { 24 | logger := NewNATSLogger(Config{}) 25 | logger.Debugf("test") 26 | logger.Tracef("test") 27 | logger.Noticef("test") 28 | logger.Errorf("test") 29 | logger.Warnf("test") 30 | // skip fatal 31 | logger.Close() 32 | } 33 | -------------------------------------------------------------------------------- /nats-mq/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 The NATS Authors 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "flag" 21 | "fmt" 22 | "log" 23 | "os" 24 | "os/signal" 25 | "runtime" 26 | "syscall" 27 | 28 | "github.com/nats-io/nats-mq/nats-mq/core" 29 | ) 30 | 31 | var configFile string 32 | 33 | func main() { 34 | var server *core.BridgeServer 35 | var err error 36 | 37 | flag.StringVar(&configFile, "c", "", "configuration filepath") 38 | flag.Parse() 39 | 40 | go func() { 41 | sigChan := make(chan os.Signal, 1) 42 | signal.Notify(sigChan, os.Interrupt, syscall.SIGHUP) 43 | 44 | for { 45 | signal := <-sigChan 46 | 47 | if signal == os.Interrupt { 48 | if server.Logger() != nil { 49 | fmt.Println() // clear the line for the control-C 50 | server.Logger().Noticef("received sig-interrupt, shutting down") 51 | } 52 | server.Stop() 53 | os.Exit(0) 54 | } 55 | 56 | if signal == syscall.SIGHUP { 57 | if server.Logger() != nil { 58 | server.Logger().Errorf("received sig-hup, restarting") 59 | } 60 | server.Stop() 61 | server := core.NewBridgeServer() 62 | server.LoadConfigFile(configFile) 63 | err = server.Start() 64 | 65 | if err != nil { 66 | if server.Logger() != nil { 67 | server.Logger().Errorf("error starting bridge, %s", err.Error()) 68 | } else { 69 | log.Printf("error starting bridge, %s", err.Error()) 70 | } 71 | server.Stop() 72 | os.Exit(0) 73 | } 74 | } 75 | } 76 | }() 77 | 78 | server = core.NewBridgeServer() 79 | server.LoadConfigFile(configFile) 80 | err = server.Start() 81 | 82 | if err != nil { 83 | if server.Logger() != nil { 84 | server.Logger().Errorf("error starting bridge, %s", err.Error()) 85 | } else { 86 | log.Printf("error starting bridge, %s", err.Error()) 87 | } 88 | server.Stop() 89 | os.Exit(0) 90 | } 91 | 92 | // exit main but keep running goroutines 93 | runtime.Goexit() 94 | } 95 | -------------------------------------------------------------------------------- /performance/encodingperf/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 The NATS Authors 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "flag" 21 | "log" 22 | "time" 23 | 24 | "github.com/nats-io/nats-mq/message" 25 | ) 26 | 27 | var iterations int 28 | 29 | func main() { 30 | flag.IntVar(&iterations, "i", 100, "iterations") 31 | flag.Parse() 32 | 33 | log.Printf("encoding and decoding %d messages", iterations) 34 | 35 | msg := message.NewBridgeMessage([]byte("hello")) 36 | msg.SetProperty("one", int32(249)) 37 | toDecode, err := msg.Encode() 38 | if err != nil { 39 | log.Fatalf("error encoding message, %s", err.Error()) 40 | } 41 | 42 | start := time.Now() 43 | for i := 0; i < iterations; i++ { 44 | _, err := msg.Encode() 45 | if err != nil { 46 | log.Fatalf("error encoding message, %s", err.Error()) 47 | } 48 | } 49 | end := time.Now() 50 | 51 | diff := end.Sub(start) 52 | rate := float64(iterations) / float64(diff.Seconds()) 53 | log.Printf("Encoded %d messages in %s, or %.2f msgs/sec", iterations, diff, rate) 54 | 55 | start = time.Now() 56 | for i := 0; i < iterations; i++ { 57 | decoded, err := message.DecodeBridgeMessage(toDecode) 58 | if err != nil || string(decoded.Body) != "hello" { 59 | log.Fatalf("error decoding message, %s", err.Error()) 60 | } 61 | } 62 | end = time.Now() 63 | 64 | diff = end.Sub(start) 65 | rate = float64(iterations) / float64(diff.Seconds()) 66 | log.Printf("Decoded %d messages in %s, or %.2f msgs/sec", iterations, diff, rate) 67 | } 68 | -------------------------------------------------------------------------------- /performance/full/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 The NATS Authors 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "flag" 21 | "log" 22 | "strings" 23 | "sync" 24 | "time" 25 | 26 | "github.com/ibm-messaging/mq-golang/v5/ibmmq" 27 | "github.com/nats-io/nats-mq/nats-mq/conf" 28 | "github.com/nats-io/nats-mq/nats-mq/core" 29 | nats "github.com/nats-io/nats.go" 30 | ) 31 | 32 | var iterations int 33 | var queues string 34 | var subjects string 35 | var connName string 36 | var channelName string 37 | var qmgrName string 38 | var natsURL string 39 | 40 | func main() { 41 | flag.IntVar(&iterations, "i", 1000, "iterations, docker image defaults to 5000 in queue") 42 | flag.StringVar(&queues, "q", "DEV.QUEUE.1,DEV.QUEUE.2,DEV.QUEUE.3", "comma separated list of queues, should match the subjects based on your bridge configuration") 43 | flag.StringVar(&subjects, "s", "one,two,three", "comma separated list of subjects, should match the queues based on your bridge configuration") 44 | flag.StringVar(&connName, "conn", "localhost(1414)", "the MQ server connection name - localhost(1414)") 45 | flag.StringVar(&channelName, "chan", "DEV.APP.SVRCONN", "the MQ server channel name - DEV.APP.SVRCONN") 46 | flag.StringVar(&qmgrName, "qmgr", "QM1", "the qMgr name - QM1") 47 | flag.StringVar(&natsURL, "nats", "localhost:4222", "nats url - localhost:4222") 48 | flag.Parse() 49 | 50 | msg := strings.Repeat("stannats", 128) // 1024 bytes 51 | 52 | config := conf.MQConfig{ 53 | ConnectionName: connName, 54 | ChannelName: channelName, 55 | QueueManager: qmgrName, 56 | } 57 | 58 | nc, err := nats.Connect(natsURL) 59 | if err != nil { 60 | log.Fatalf("unable to connect to nats") 61 | } 62 | 63 | queueList := strings.Split(queues, ",") 64 | subjectList := strings.Split(subjects, ",") 65 | 66 | ready := sync.WaitGroup{} 67 | done := sync.WaitGroup{} 68 | starter := sync.WaitGroup{} 69 | 70 | starter.Add(1) 71 | 72 | for i, queue := range queueList { 73 | ready.Add(1) 74 | done.Add(1) 75 | subject := subjectList[i] 76 | 77 | go func(queue, subject string) { 78 | qMgr, err := core.ConnectToQueueManager(config) 79 | 80 | if err != nil || qMgr == nil { 81 | log.Fatalf("unable to connect to queue manager") 82 | } 83 | 84 | mqod := ibmmq.NewMQOD() 85 | openOptions := ibmmq.MQOO_OUTPUT 86 | mqod.ObjectType = ibmmq.MQOT_Q 87 | mqod.ObjectName = queue 88 | qObject, err := qMgr.Open(mqod, openOptions) 89 | if err != nil { 90 | log.Fatalf("error opening queue object %s, %s", queue, err.Error()) 91 | } 92 | defer qObject.Close(0) 93 | 94 | count := 0 95 | 96 | nc.Subscribe(subject, func(msg *nats.Msg) { 97 | count++ 98 | if count%1000 == 0 { 99 | log.Printf("%s: count = %d", subject, count) 100 | } 101 | if count == iterations { 102 | done.Done() 103 | } 104 | }) 105 | 106 | putmqmd := ibmmq.NewMQMD() 107 | pmo := ibmmq.NewMQPMO() 108 | pmo.Options = ibmmq.MQPMO_NO_SYNCPOINT 109 | buffer := []byte(msg) 110 | 111 | log.Printf("sender ready for queue %s", queue) 112 | ready.Done() 113 | starter.Wait() 114 | log.Printf("sending %d messages through %s to bridge to NATS...", iterations, queue) 115 | for i := 0; i < iterations; i++ { 116 | err = qObject.Put(putmqmd, pmo, buffer) 117 | if err != nil { 118 | log.Fatalf("error putting messages on queue") 119 | } 120 | } 121 | }(queue, subject) 122 | } 123 | 124 | ready.Wait() 125 | start := time.Now() 126 | starter.Done() 127 | done.Wait() 128 | end := time.Now() 129 | 130 | count := iterations * len(queueList) 131 | diff := end.Sub(start) 132 | rate := float64(count) / float64(diff.Seconds()) 133 | log.Printf("Sent %d messages through %d queues to a NATS subscribers in %s, or %.2f msgs/sec", count, len(queueList), diff, rate) 134 | } 135 | -------------------------------------------------------------------------------- /performance/full_testenv/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 The NATS Authors 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "encoding/json" 21 | "flag" 22 | "log" 23 | "strings" 24 | "time" 25 | 26 | "github.com/ibm-messaging/mq-golang/v5/ibmmq" 27 | "github.com/nats-io/nats-mq/nats-mq/conf" 28 | "github.com/nats-io/nats-mq/nats-mq/core" 29 | nats "github.com/nats-io/nats.go" 30 | ) 31 | 32 | var iterations int 33 | 34 | func main() { 35 | flag.IntVar(&iterations, "i", 1000, "iterations, docker image defaults to 5000 in queue") 36 | flag.Parse() 37 | 38 | subject := "test" 39 | queue := "DEV.QUEUE.1" 40 | msg := strings.Repeat("stannats", 128) // 1024 bytes 41 | 42 | connect := []conf.ConnectorConfig{ 43 | { 44 | Type: "Queue2NATS", 45 | Subject: subject, 46 | Queue: queue, 47 | ExcludeHeaders: true, 48 | }, 49 | } 50 | tbs, err := core.StartTestEnvironment(connect) 51 | if err != nil { 52 | log.Fatalf("error starting test environment, %s", err.Error()) 53 | } 54 | 55 | mqod := ibmmq.NewMQOD() 56 | openOptions := ibmmq.MQOO_OUTPUT 57 | mqod.ObjectType = ibmmq.MQOT_Q 58 | mqod.ObjectName = queue 59 | qObject, err := tbs.QMgr.Open(mqod, openOptions) 60 | if err != nil { 61 | log.Fatalf("error opening queue object %s, %s", queue, err.Error()) 62 | } 63 | defer qObject.Close(0) 64 | 65 | done := make(chan bool) 66 | count := 0 67 | 68 | tbs.NC.Subscribe(subject, func(msg *nats.Msg) { 69 | count++ 70 | if count%1000 == 0 { 71 | log.Printf("received count = %d", count) 72 | } 73 | if count == iterations { 74 | done <- true 75 | } 76 | }) 77 | 78 | putmqmd := ibmmq.NewMQMD() 79 | pmo := ibmmq.NewMQPMO() 80 | pmo.Options = ibmmq.MQPMO_NO_SYNCPOINT 81 | buffer := []byte(msg) 82 | 83 | log.Printf("sending %d messages through the MQ to bridge to NATS...", iterations) 84 | start := time.Now() 85 | for i := 0; i < iterations; i++ { 86 | err = qObject.Put(putmqmd, pmo, buffer) 87 | if err != nil { 88 | log.Fatalf("error putting messages on queue") 89 | } 90 | } 91 | <-done 92 | end := time.Now() 93 | 94 | stats := tbs.Bridge.SafeStats() 95 | statsJSON, _ := json.MarshalIndent(stats, "", " ") 96 | 97 | // Close the test environ so we clean up the log 98 | tbs.Close() 99 | 100 | diff := end.Sub(start) 101 | rate := float64(iterations) / float64(diff.Seconds()) 102 | log.Printf("Bridge Stats:\n\n%s\n", statsJSON) 103 | log.Printf("Sent %d messages through an MQ queue to a NATS subscriber in %s, or %.2f msgs/sec", iterations, diff, rate) 104 | } 105 | -------------------------------------------------------------------------------- /performance/multiqueue_testenv/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 The NATS Authors 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "encoding/json" 21 | "flag" 22 | "fmt" 23 | "log" 24 | "strings" 25 | "sync/atomic" 26 | "time" 27 | 28 | "github.com/ibm-messaging/mq-golang/v5/ibmmq" 29 | "github.com/nats-io/nats-mq/nats-mq/conf" 30 | "github.com/nats-io/nats-mq/nats-mq/core" 31 | nats "github.com/nats-io/nats.go" 32 | ) 33 | 34 | var iterations int 35 | var useTLS bool 36 | var queueCount int 37 | var showStats bool 38 | 39 | func main() { 40 | flag.IntVar(&iterations, "i", 1000, "iterations, docker image defaults to 5000 in queue") 41 | flag.IntVar(&queueCount, "q", 5, "number of queues to run against, at most 25 (see test_utils.go), default is 5") 42 | flag.BoolVar(&useTLS, "tls", false, "use tls") 43 | flag.BoolVar(&showStats, "stats", false, "show stats json at the end") 44 | flag.Parse() 45 | 46 | msg := strings.Repeat("stannats", 128) // 1024 bytes 47 | 48 | connect := []conf.ConnectorConfig{} 49 | 50 | log.Printf("preparing bridge with %d connectors", queueCount) 51 | 52 | for i := 1; i <= queueCount; i++ { 53 | connect = append(connect, conf.ConnectorConfig{ 54 | Type: "Queue2NATS", 55 | Subject: fmt.Sprintf("test.%d", i), 56 | Queue: fmt.Sprintf("TEST.QUEUE.%d", i), 57 | ExcludeHeaders: true, 58 | }) 59 | } 60 | 61 | // Start the infrastructure 62 | tbs, err := core.StartTestEnvironmentInfrastructure(useTLS) 63 | if err != nil { 64 | log.Fatalf("error starting test environment, %s", err.Error()) 65 | return 66 | } 67 | 68 | start := time.Now() 69 | done := make(chan bool) 70 | var count uint64 71 | maxCount := uint64(iterations * len(connect)) 72 | 73 | for _, c := range connect { 74 | tbs.NC.Subscribe(c.Subject, func(msg *nats.Msg) { 75 | if atomic.LoadUint64(&count) == 0 { 76 | log.Printf("received first message") 77 | start = time.Now() // start on the first message 78 | } 79 | 80 | newCount := atomic.AddUint64(&count, 1) 81 | if newCount%1000 == 0 { 82 | log.Printf("received count = %d", count) 83 | } 84 | 85 | if newCount == maxCount { 86 | done <- true 87 | } 88 | }) 89 | 90 | mqod := ibmmq.NewMQOD() 91 | openOptions := ibmmq.MQOO_OUTPUT 92 | mqod.ObjectType = ibmmq.MQOT_Q 93 | mqod.ObjectName = c.Queue 94 | qObject, err := tbs.QMgr.Open(mqod, openOptions) 95 | if err != nil { 96 | log.Fatalf("error opening queue object %s, %s", c.Queue, err.Error()) 97 | } 98 | 99 | putmqmd := ibmmq.NewMQMD() 100 | pmo := ibmmq.NewMQPMO() 101 | pmo.Options = ibmmq.MQPMO_NO_SYNCPOINT 102 | buffer := []byte(msg) 103 | 104 | log.Printf("prepping queue %s with %d messages...", c.Queue, iterations) 105 | for i := 0; i < iterations; i++ { 106 | err = qObject.Put(putmqmd, pmo, buffer) 107 | if err != nil { 108 | log.Fatalf("error putting messages on queue") 109 | } 110 | } 111 | qObject.Close(0) 112 | } 113 | 114 | // Queues are ready, now start the bridge 115 | tbs.StartBridge(connect, useTLS) 116 | 117 | <-done 118 | end := time.Now() 119 | 120 | stats := tbs.Bridge.SafeStats() 121 | statsJSON, _ := json.MarshalIndent(stats, "", " ") 122 | 123 | // Close the test environ so we clean up the log 124 | tbs.Close() 125 | 126 | diff := end.Sub(start) 127 | rate := float64(maxCount) / float64(diff.Seconds()) 128 | 129 | if showStats { 130 | log.Printf("Bridge Stats:\n\n%s\n", statsJSON) 131 | } 132 | log.Printf("Read %d messages from %d MQ queues via a bridge to NATS in %s, or %.2f msgs/sec", maxCount, len(connect), diff, rate) 133 | } 134 | -------------------------------------------------------------------------------- /performance/queues/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 The NATS Authors 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "flag" 21 | "log" 22 | "strings" 23 | "sync" 24 | "time" 25 | 26 | "github.com/ibm-messaging/mq-golang/v5/ibmmq" 27 | "github.com/nats-io/nats-mq/nats-mq/conf" 28 | "github.com/nats-io/nats-mq/nats-mq/core" 29 | ) 30 | 31 | var iterations int 32 | var queues string 33 | var connName string 34 | var channelName string 35 | var qmgrName string 36 | 37 | func main() { 38 | flag.IntVar(&iterations, "i", 1000, "iterations, docker image defaults to 5000 in queue") 39 | flag.StringVar(&queues, "q", "DEV.QUEUE.1,DEV.QUEUE.2,DEV.QUEUE.3", "comma separated list of queues, should match the subjects based on your bridge configuration") 40 | flag.StringVar(&connName, "conn", "localhost(1414)", "the MQ server connection name - localhost(1414)") 41 | flag.StringVar(&channelName, "chan", "DEV.APP.SVRCONN", "the MQ server channel name - DEV.APP.SVRCONN") 42 | flag.StringVar(&qmgrName, "qmgr", "QM1", "the qMgr name - QM1") 43 | flag.Parse() 44 | 45 | msg := strings.Repeat("stannats", 128) // 1024 bytes 46 | 47 | config := conf.MQConfig{ 48 | ConnectionName: connName, 49 | ChannelName: channelName, 50 | QueueManager: qmgrName, 51 | } 52 | 53 | queueList := strings.Split(queues, ",") 54 | 55 | ready := sync.WaitGroup{} 56 | done := sync.WaitGroup{} 57 | starter := sync.WaitGroup{} 58 | 59 | starter.Add(1) 60 | 61 | for _, queue := range queueList { 62 | ready.Add(1) 63 | done.Add(1) 64 | 65 | go func(queue string) { 66 | qMgrForPub, err := core.ConnectToQueueManager(config) 67 | 68 | if err != nil || qMgrForPub == nil { 69 | log.Fatalf("unable to connect to queue manager") 70 | } 71 | 72 | qMgrForSub, err := core.ConnectToQueueManager(config) 73 | 74 | if err != nil || qMgrForSub == nil { 75 | log.Fatalf("unable to connect to queue manager") 76 | } 77 | 78 | mqod := ibmmq.NewMQOD() 79 | openOptions := ibmmq.MQOO_OUTPUT 80 | mqod.ObjectType = ibmmq.MQOT_Q 81 | mqod.ObjectName = queue 82 | qObjectForPub, err := qMgrForPub.Open(mqod, openOptions) 83 | if err != nil { 84 | log.Fatalf("error opening queue object %s, %s", queue, err.Error()) 85 | } 86 | 87 | mqod = ibmmq.NewMQOD() 88 | openOptions = ibmmq.MQOO_INPUT_SHARED 89 | mqod.ObjectType = ibmmq.MQOT_Q 90 | mqod.ObjectName = queue 91 | qObjectForSub, err := qMgrForSub.Open(mqod, openOptions) 92 | if err != nil { 93 | log.Fatalf("error opening queue object %s, %s", queue, err.Error()) 94 | } 95 | 96 | count := 0 97 | mqmd := ibmmq.NewMQMD() 98 | gmo := ibmmq.NewMQGMO() 99 | cmho := ibmmq.NewMQCMHO() 100 | propsMsgHandle, err := qMgrForSub.CrtMH(cmho) 101 | 102 | if err != nil { 103 | log.Fatalf("error creating message handle %s, %s", queue, err.Error()) 104 | } 105 | 106 | gmo.MsgHandle = propsMsgHandle 107 | gmo.Options = ibmmq.MQGMO_SYNCPOINT 108 | gmo.Options |= ibmmq.MQGMO_WAIT 109 | gmo.Options |= ibmmq.MQGMO_FAIL_IF_QUIESCING 110 | gmo.Options |= ibmmq.MQGMO_PROPERTIES_IN_HANDLE 111 | 112 | cbd := ibmmq.NewMQCBD() 113 | cbd.CallbackFunction = func(qMgr *ibmmq.MQQueueManager, hObj *ibmmq.MQObject, md *ibmmq.MQMD, gmo *ibmmq.MQGMO, buffer []byte, cbc *ibmmq.MQCBC, mqErr *ibmmq.MQReturn) { 114 | if mqErr != nil && mqErr.MQCC != ibmmq.MQCC_OK { 115 | if mqErr.MQRC == ibmmq.MQRC_NO_MSG_AVAILABLE { 116 | return 117 | } 118 | log.Fatalf("mq error %s", queue) 119 | return 120 | } 121 | 122 | // ignore event calls 123 | if cbc != nil && cbc.CallType == ibmmq.MQCBCT_EVENT_CALL { 124 | return 125 | } 126 | 127 | qMgr.Cmit() 128 | 129 | count++ 130 | if count%1000 == 0 { 131 | log.Printf("%s: count = %d", queue, count) 132 | } 133 | if count == iterations { 134 | done.Done() 135 | } 136 | } 137 | 138 | err = qObjectForSub.CB(ibmmq.MQOP_REGISTER, cbd, mqmd, gmo) 139 | 140 | if err != nil { 141 | log.Fatalf("error creating callback %s, %s", queue, err.Error()) 142 | } 143 | 144 | ctlo := ibmmq.NewMQCTLO() 145 | ctlo.Options = ibmmq.MQCTLO_FAIL_IF_QUIESCING 146 | err = qMgrForSub.Ctl(ibmmq.MQOP_START, ctlo) 147 | 148 | if err != nil { 149 | log.Fatalf("error starting callback %s, %s", queue, err.Error()) 150 | } 151 | 152 | putmqmd := ibmmq.NewMQMD() 153 | pmo := ibmmq.NewMQPMO() 154 | pmo.Options = ibmmq.MQPMO_NO_SYNCPOINT 155 | buffer := []byte(msg) 156 | 157 | log.Printf("sender ready for queue %s", queue) 158 | ready.Done() 159 | starter.Wait() 160 | log.Printf("sending %d messages through %s mq series...", iterations, queue) 161 | for i := 0; i < iterations; i++ { 162 | err = qObjectForPub.Put(putmqmd, pmo, buffer) 163 | if err != nil { 164 | log.Fatalf("error putting messages on queue, %s", err.Error()) 165 | } 166 | } 167 | }(queue) 168 | } 169 | 170 | ready.Wait() 171 | start := time.Now() 172 | starter.Done() 173 | done.Wait() 174 | end := time.Now() 175 | 176 | count := iterations * len(queueList) 177 | diff := end.Sub(start) 178 | rate := float64(count) / float64(diff.Seconds()) 179 | log.Printf("Sent %d messages through %d queues MQ callbacks in %s, or %.2f msgs/sec", count, len(queueList), diff, rate) 180 | } 181 | -------------------------------------------------------------------------------- /performance/readme.md: -------------------------------------------------------------------------------- 1 | # Performance Tests/Examples/Metrics 2 | 3 | This folder contains several apps to look at performance of the bridge: 4 | 5 | * `encodingperf` - measures performance encoding and decoding messages. 6 | * `full` - runs a set of messages through the bridge from queue -> NATS, with an external bridge and mq server. Messages are 1024 bytes long. 7 | * `full_testenv` - runs a set of messages through MQ -> NATs and measures the total time. The test environment from test_utils.go is used for the bridge, nats and MQ server. Messages are 1024 bytes long. 8 | * `multiqueue_testenv` - prepares multiple queues with messages and then runs the bridge reading those messages to nats. The test environment from test_utils.go is used for the bridge, nats and MQ server. Has an option to run with TLS. Messages are 1024 bytes long. 9 | * `queues` - runs a set of messages through MQ from queue -> queue, with an external mq server. Messages are 1024 bytes long. This test is useful for comparing performance to `full`. 10 | * `singlequeue_testenv` - prepares a single queue with messages and then runs the bridge reading those messages to nats. The test environment from test_utils.go is used for the bridge, nats and MQ server. Messages are 1024 bytes long. -------------------------------------------------------------------------------- /performance/singlequeue_testenv/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 The NATS Authors 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "encoding/json" 21 | "flag" 22 | "log" 23 | "strings" 24 | "time" 25 | 26 | "github.com/ibm-messaging/mq-golang/v5/ibmmq" 27 | "github.com/nats-io/nats-mq/nats-mq/conf" 28 | "github.com/nats-io/nats-mq/nats-mq/core" 29 | nats "github.com/nats-io/nats.go" 30 | ) 31 | 32 | var iterations int 33 | 34 | func main() { 35 | flag.IntVar(&iterations, "i", 1000, "iterations, docker image defaults to 5000 in queue") 36 | flag.Parse() 37 | 38 | subject := "test" 39 | queue := "DEV.QUEUE.1" 40 | msg := strings.Repeat("stannats", 128) // 1024 bytes 41 | 42 | connect := []conf.ConnectorConfig{ 43 | { 44 | Type: "Queue2NATS", 45 | Subject: subject, 46 | Queue: queue, 47 | ExcludeHeaders: true, 48 | }, 49 | } 50 | 51 | // Start the infrastructure 52 | tbs, err := core.StartTestEnvironmentInfrastructure(false) 53 | if err != nil { 54 | log.Fatalf("error starting test environment, %s", err.Error()) 55 | } 56 | 57 | mqod := ibmmq.NewMQOD() 58 | openOptions := ibmmq.MQOO_OUTPUT 59 | mqod.ObjectType = ibmmq.MQOT_Q 60 | mqod.ObjectName = queue 61 | qObject, err := tbs.QMgr.Open(mqod, openOptions) 62 | if err != nil { 63 | log.Fatalf("error opening queue object %s, %s", queue, err.Error()) 64 | } 65 | defer qObject.Close(0) 66 | 67 | done := make(chan bool) 68 | count := 0 69 | 70 | start := time.Now() 71 | tbs.NC.Subscribe(subject, func(msg *nats.Msg) { 72 | if count == 0 { 73 | log.Printf("received first message") 74 | start = time.Now() // start on the first message 75 | } 76 | count++ 77 | if count%1000 == 0 { 78 | log.Printf("received count = %d", count) 79 | } 80 | if count == iterations+1 { 81 | done <- true 82 | } 83 | }) 84 | 85 | putmqmd := ibmmq.NewMQMD() 86 | pmo := ibmmq.NewMQPMO() 87 | pmo.Options = ibmmq.MQPMO_NO_SYNCPOINT 88 | buffer := []byte(msg) 89 | 90 | log.Printf("prepping queue with %d messages...", iterations) 91 | for i := 0; i < iterations+1; i++ { 92 | err = qObject.Put(putmqmd, pmo, buffer) 93 | if err != nil { 94 | log.Fatalf("error putting messages on queue") 95 | } 96 | } 97 | 98 | // Queue is ready, now start the bridge 99 | tbs.StartBridge(connect, false) 100 | 101 | <-done 102 | end := time.Now() 103 | 104 | stats := tbs.Bridge.SafeStats() 105 | statsJSON, _ := json.MarshalIndent(stats, "", " ") 106 | 107 | // Close the test environ so we clean up the log 108 | tbs.Close() 109 | 110 | diff := end.Sub(start) 111 | rate := float64(iterations) / float64(diff.Seconds()) 112 | log.Printf("Bridge Stats:\n\n%s\n", statsJSON) 113 | log.Printf("Read %d messages from an MQ queues via a bridge to NATS in %s, or %.2f msgs/sec", iterations, diff, rate) 114 | } 115 | -------------------------------------------------------------------------------- /resources/certs/ca.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | 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 37 | +Svp 38 | -----END CERTIFICATE----- 39 | -------------------------------------------------------------------------------- /resources/certs/client-cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFPDCCAySgAwIBAgIJAO+k4G7bNTypMA0GCSqGSIb3DQEBCwUAMIGLMQswCQYD 3 | VQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xEzAR 4 | BgNVBAoTCkFwY2VyYSBJbmMxEDAOBgNVBAsTB25hdHMuaW8xEjAQBgNVBAMTCWxv 5 | Y2FsaG9zdDEcMBoGCSqGSIb3DQEJARYNZGVyZWtAbmF0cy5pbzAeFw0xNTExMDUy 6 | MzEwNDdaFw0xOTExMDQyMzEwNDdaMBYxFDASBgNVBAMTC25hdHMtY2xpZW50MIIC 7 | IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEArgLxszD5/vDrDUwwIEgQx9I0 8 | J/H6MXPO0Tj9D2BnR+nwjCe9M03fsq4Il96BVzoaAiAQD1r4NyAX2adKydlnE3/m 9 | bUFiSVHErJceEi9aSs+WlLdmKEgU2qrsIal9KzthlI786qtjb7OFSCxP14R4xYA5 10 | dlZXhJ9oUuFhVTdaVmRMzWuWj8RbBx8VptSZ0f7Q+Uv8GuB0kyiVkv6GYcH/IWuI 11 | 7jnM0QcVWBmxJfWmqd0yx/FLlX/LRXqdiyoFSIlMaP0VOwto3uEhAoBk83Z+/zrZ 12 | Brymx1Nnz3qzTCf8/mdMjPuWibXDTLbo0/Kf6neHs6wxx8irb1ZfIwhn8grXTcgd 13 | rg9bfcyyUOBey7QXiedpU0xFqoH26E+Aq+CV4R56i1sJKsSYEGu8O69H8zu5dgan 14 | LZRhcCHcZhMe7Nbiu5BcuOW4r3rGDMTLXSugEX91iy5jJaYmRjtPN5imQIJtf+GK 15 | Vq7YLv4MQV6R3xRiZXaocCae1qzIMc4kxCKvZTmxuJsvIUPjNnGumwbjV/a2fLFX 16 | 9tMqUKyEmiPtFtqNH/kmkHCQ5FGYIIj3wGuD5yWfK5Tr3iHOdNJoNNPgPBg9tMRw 17 | j3+W8+uyBxc+FUEb8a9m3R4VmAYyiqgzCA0DWZBF1fOYLWfRnwS5OBKiP4OUlUEb 18 | YZUEzfvDbLOwQrb123cCAwEAAaMXMBUwEwYDVR0lBAwwCgYIKwYBBQUHAwIwDQYJ 19 | KoZIhvcNAQELBQADggIBACNKPbvaXwl5rRTqFw37Am1r6e+LkUg9dFogSwXDnuT/ 20 | RRZJi5MHsC5MUOkHB28lTmPwkAogs+LBmKrM0Npzk6OPkT/LCgKqpVoz2Tc1nGMI 21 | Jy8jxPYogMmDCOhoEoC7zsWABMLiX5KDAuKommk61w7AwKu4kK198ngwbfF2fzdH 22 | 1DUGID7iV4fyPGI+pCU3Ullv51c5xkhqjVy1JYdYc0+s6rFyVTibSABa7PfHE2ML 23 | A+cNFWoKQhugVHQU7qYvuWvnEqZro2T6nmSmpK3oOaUgVnDuY2q4JwiMbZAtuyD7 24 | 8LFwCim49WzgYcfs/BwKlUrTV/QBYurruHWjElZzwA39/ZlbnOjJJ85j/YqxR+4S 25 | fK/KktegyrPJU3fxdl2+77zVlfgzxaQ//58vx5LgXWhl2KeHyakeD0jQFVn1R7GD 26 | bynAlHlSOr+nGkwP2WVqXKf+l/gb/gUEY7bC8fCVRCctkcK+smEl+sIKH3O9JY8l 27 | rBWjOXkMY91ZDh77hfTNni/s2/DGAoNrEft8rgu3/NPxhCTfQH3ranCryth9mF6I 28 | qsOFr5/81WGKqU+Kec8st/RSU2vBjBp41HILAEEhUiB6prhc9B3+exwkvQSPz22W 29 | PIvhkzqeOYRoEDE2bWGC1ukd818qvQp618eLBmJSvwGh4YfUcmgqHaEk2NjoPIMV 30 | -----END CERTIFICATE----- 31 | -------------------------------------------------------------------------------- /resources/certs/client-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | 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 51 | -----END RSA PRIVATE KEY----- 52 | -------------------------------------------------------------------------------- /resources/certs/combined.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIJKAIBAAKCAgEArgLxszD5/vDrDUwwIEgQx9I0J/H6MXPO0Tj9D2BnR+nwjCe9 3 | M03fsq4Il96BVzoaAiAQD1r4NyAX2adKydlnE3/mbUFiSVHErJceEi9aSs+WlLdm 4 | KEgU2qrsIal9KzthlI786qtjb7OFSCxP14R4xYA5dlZXhJ9oUuFhVTdaVmRMzWuW 5 | j8RbBx8VptSZ0f7Q+Uv8GuB0kyiVkv6GYcH/IWuI7jnM0QcVWBmxJfWmqd0yx/FL 6 | lX/LRXqdiyoFSIlMaP0VOwto3uEhAoBk83Z+/zrZBrymx1Nnz3qzTCf8/mdMjPuW 7 | ibXDTLbo0/Kf6neHs6wxx8irb1ZfIwhn8grXTcgdrg9bfcyyUOBey7QXiedpU0xF 8 | qoH26E+Aq+CV4R56i1sJKsSYEGu8O69H8zu5dganLZRhcCHcZhMe7Nbiu5BcuOW4 9 | r3rGDMTLXSugEX91iy5jJaYmRjtPN5imQIJtf+GKVq7YLv4MQV6R3xRiZXaocCae 10 | 1qzIMc4kxCKvZTmxuJsvIUPjNnGumwbjV/a2fLFX9tMqUKyEmiPtFtqNH/kmkHCQ 11 | 5FGYIIj3wGuD5yWfK5Tr3iHOdNJoNNPgPBg9tMRwj3+W8+uyBxc+FUEb8a9m3R4V 12 | mAYyiqgzCA0DWZBF1fOYLWfRnwS5OBKiP4OUlUEbYZUEzfvDbLOwQrb123cCAwEA 13 | AQKCAgAQUkBfYVGhgvFZDvNYo8nHJEU2FfE0oDsezqyVu6IUUbH5Q2TwofZAaShv 14 | LjSNfOqhlmZLOmobqYvzI0jVg+myH4X6a26Pl/bNhWMRq5VZfP0Pt+ACGTizheKe 15 | Caqu2mP9rie0zxyFhp4Ste1LNqapR6ycF98flmAPngomFwoHHmNBxTybAXzUPysl 16 | ub0vwCnTqDfeQX1NrDnTTsJF+w82EEMIrS0z0elDmS1PdSoLtq6jqFNBk3n6a1TJ 17 | j8htFEuxcUODhT9x4EXbWTWezFd/EwL2Kc2u1njfMhANLZcCOagpdROamQzXbjSK 18 | ZLBxKoL07ErDBWRnDf/gZlJxlmi5QFgy3LFvmZ93sbedzRaTDsjXEpbTse/l36QY 19 | 6YCjSnb2zUX2AElKmyC/QwR8BZ9afRQM7x3eqLkE1q4jkLsk3+W3VroyaoOfQxiB 20 | k+xtL5cxoa9SiTgETNHpFQhiTNyX7FlH1ykoJzTryLsbccTd1iP7DF5ZPt8DfgIZ 21 | PLzwh7PDiK5cpitm8g6TdvuLA9FT+bEtd/78odN++VDhkcCmSQMWKk3Xt8wznNcY 22 | 8Ye5JC/4aHRueWCziWaJYJHi6ZNCt4CR5wzEGBmPlf0562UpQpfEuDOQDRX3FaMs 23 | qYbCrRVeQL3wXcu3sVToj9zSES2R+kQfTwaqdypgS79y0Dp6eQKCAQEA2BAu0Cqn 24 | xmjuqn/qpPXtW3kryHPP7eyzt53o8Xg7RqQ0oT+FNiO3o4aGoVlxkMjBW+NOpWo1 25 | VtsTrsB+RxIiuugb9/D2dy1z5BK2x4bvurxkyOovU3J2WHSNIUsbQ5FSN8w5sAcl 26 | +1QFNcM5ooBa7VahRV2vJcGe9P+QFR75c4xSCvG6AOu8WzZNUNOw97s/N24NevU5 27 | 26Ql20zwn+E0avd3yuFU7bKrvXh9v6lNqWhjkJePk8eTh/5O4cTuF/cB3wPcgjiC 28 | 24uyNI29lAVHS/+h0nVTdm0F1Fel8nwPkOLyRJUyEzWm8SX2rnwI3EegWaRyDohp 29 | a1hmjHsCcpoxhQKCAQEAzizucnHqwxEQiMaJPUKBi3v3j+a/me3PfsY1760LdLVY 30 | AcMuGr+wg2/e9d7jMvEIxlACng4aU2kKG0fOxS0G0e7AefB9DiwzexJ+pHu0R49p 31 | PmkAoPl2+mAlfeqvwEJ4gQEH8hKoIEkU0XAPZfWMTlshCJgAyYYpsLlJl0f8ooa3 32 | 4VRg3hjfWj+Z5pQryojN/Pfl4XRoM11xdaa79odvtptpN3KWxs9IhesM1o4mi4kC 33 | Dd996iQpNau1bF6LHmEXJhbkEJ+SDXUDvEx6d3HYAFNPyWLe4DtJn38qb1gtuesZ 34 | vGntToaAN12z4vJIj75vuduSJei8ceXcixYo1WZrywKCAQEAiz9avERRXpjwAChy 35 | lB/++i4MnqKtBjy/0n3NzBndsfhQBwAGHU9FofkoOUKI43PO0iab4BWkDLciZ0Sd 36 | 3bX9dhHzPIcqgMJlZz78V3lKdUHHfokXOSOSzA1Ji4R5LMGyiE1xfFYPD3wl43FP 37 | asBoWX+0bh0jrSStCl7OgB43TFXJ5k3Fv6Qt/2buy0GzUuV1p4ag33a99CVFVKGw 38 | jom4m5ujs7gnYQ3+ixzlhilZ6O1jBaP4H5jHJyUpt22QuRczOISnj7FV/KJ6lk4n 39 | OQdx3LQCmb2NrcwzrpdSVwXHjmwFEVhKLoEsd0wtQGSl3Tm4SS2naGBX+Ju/c5gv 40 | iqZ/dQKCAQAzDJcByUkKgZgpdZcXjvcKdWhnvgek8mgVCLjkHmGexSQEU7J/twTa 41 | loGLOWPiAiJdEASF5BIKoxB4jsAYvDxbEJWh27TrJHCewYaP7X1G1rCFXnRkZ0BZ 42 | YCMIWWqo3Qx/TKUOACaWz+GStf9qDHFwGUpFmXVgcJK0Cjy5c36PM3ImHcFaXKg4 43 | 7VSK7hclr9fpEexedXczeKiWK/GQahp0CWj07K9+jGZ1mix0l3/dvs++ZZ8EsW1u 44 | t5RVP9eMbxfPO42+u/Pq1xVUs08DcjG8auRvhcaPmL5y+oakSR4RUa/uof+7GLx4 45 | eQAIalsjFFEPoNk//69hODvySEtWA2UfAoIBACGXYc0SuE9m2KxnxLiy4yEvDbw1 46 | 3KO9Gwv+0iRaeCizdCTwaSu/weQrw9ddpfmeqdGhwsvH1S5WyFqtwsjS7abdj4cg 47 | KJ3nuR1EDInFQcu9ii+T8MSTc64cPkJVIYHwYiwE2Whj+6F7KFc1mf33/zrivruT 48 | 6Mm1YJv11KkBDAaM4Bj37DQfCrYh6quxczCT827YX7Wuw9YGQZYZh/xzss0Tkfzm 49 | LgHriX+8U7+rL24Fi+merhDhjO95NVkRSIDmg+pULaWkeDOyVxfLCIMmy7JByHW4 50 | fyDr/w1dfkx/yiV0xvkrfT+sOFmnMjfgMwmit3tfm7zkmkzNfmASugDPWjA= 51 | -----END RSA PRIVATE KEY----- 52 | -----BEGIN CERTIFICATE----- 53 | MIIFPDCCAySgAwIBAgIJAO+k4G7bNTypMA0GCSqGSIb3DQEBCwUAMIGLMQswCQYD 54 | VQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xEzAR 55 | BgNVBAoTCkFwY2VyYSBJbmMxEDAOBgNVBAsTB25hdHMuaW8xEjAQBgNVBAMTCWxv 56 | Y2FsaG9zdDEcMBoGCSqGSIb3DQEJARYNZGVyZWtAbmF0cy5pbzAeFw0xNTExMDUy 57 | MzEwNDdaFw0xOTExMDQyMzEwNDdaMBYxFDASBgNVBAMTC25hdHMtY2xpZW50MIIC 58 | IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEArgLxszD5/vDrDUwwIEgQx9I0 59 | J/H6MXPO0Tj9D2BnR+nwjCe9M03fsq4Il96BVzoaAiAQD1r4NyAX2adKydlnE3/m 60 | bUFiSVHErJceEi9aSs+WlLdmKEgU2qrsIal9KzthlI786qtjb7OFSCxP14R4xYA5 61 | dlZXhJ9oUuFhVTdaVmRMzWuWj8RbBx8VptSZ0f7Q+Uv8GuB0kyiVkv6GYcH/IWuI 62 | 7jnM0QcVWBmxJfWmqd0yx/FLlX/LRXqdiyoFSIlMaP0VOwto3uEhAoBk83Z+/zrZ 63 | Brymx1Nnz3qzTCf8/mdMjPuWibXDTLbo0/Kf6neHs6wxx8irb1ZfIwhn8grXTcgd 64 | rg9bfcyyUOBey7QXiedpU0xFqoH26E+Aq+CV4R56i1sJKsSYEGu8O69H8zu5dgan 65 | LZRhcCHcZhMe7Nbiu5BcuOW4r3rGDMTLXSugEX91iy5jJaYmRjtPN5imQIJtf+GK 66 | Vq7YLv4MQV6R3xRiZXaocCae1qzIMc4kxCKvZTmxuJsvIUPjNnGumwbjV/a2fLFX 67 | 9tMqUKyEmiPtFtqNH/kmkHCQ5FGYIIj3wGuD5yWfK5Tr3iHOdNJoNNPgPBg9tMRw 68 | j3+W8+uyBxc+FUEb8a9m3R4VmAYyiqgzCA0DWZBF1fOYLWfRnwS5OBKiP4OUlUEb 69 | YZUEzfvDbLOwQrb123cCAwEAAaMXMBUwEwYDVR0lBAwwCgYIKwYBBQUHAwIwDQYJ 70 | KoZIhvcNAQELBQADggIBACNKPbvaXwl5rRTqFw37Am1r6e+LkUg9dFogSwXDnuT/ 71 | RRZJi5MHsC5MUOkHB28lTmPwkAogs+LBmKrM0Npzk6OPkT/LCgKqpVoz2Tc1nGMI 72 | Jy8jxPYogMmDCOhoEoC7zsWABMLiX5KDAuKommk61w7AwKu4kK198ngwbfF2fzdH 73 | 1DUGID7iV4fyPGI+pCU3Ullv51c5xkhqjVy1JYdYc0+s6rFyVTibSABa7PfHE2ML 74 | A+cNFWoKQhugVHQU7qYvuWvnEqZro2T6nmSmpK3oOaUgVnDuY2q4JwiMbZAtuyD7 75 | 8LFwCim49WzgYcfs/BwKlUrTV/QBYurruHWjElZzwA39/ZlbnOjJJ85j/YqxR+4S 76 | fK/KktegyrPJU3fxdl2+77zVlfgzxaQ//58vx5LgXWhl2KeHyakeD0jQFVn1R7GD 77 | bynAlHlSOr+nGkwP2WVqXKf+l/gb/gUEY7bC8fCVRCctkcK+smEl+sIKH3O9JY8l 78 | rBWjOXkMY91ZDh77hfTNni/s2/DGAoNrEft8rgu3/NPxhCTfQH3ranCryth9mF6I 79 | qsOFr5/81WGKqU+Kec8st/RSU2vBjBp41HILAEEhUiB6prhc9B3+exwkvQSPz22W 80 | PIvhkzqeOYRoEDE2bWGC1ukd818qvQp618eLBmJSvwGh4YfUcmgqHaEk2NjoPIMV 81 | -----END CERTIFICATE----- 82 | -------------------------------------------------------------------------------- /resources/certs/server-cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFQTCCAymgAwIBAgIJAO+k4G7bNTyoMA0GCSqGSIb3DQEBCwUAMIGLMQswCQYD 3 | VQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xEzAR 4 | BgNVBAoTCkFwY2VyYSBJbmMxEDAOBgNVBAsTB25hdHMuaW8xEjAQBgNVBAMTCWxv 5 | Y2FsaG9zdDEcMBoGCSqGSIb3DQEJARYNZGVyZWtAbmF0cy5pbzAeFw0xNTExMDUy 6 | MzA2MzRaFw0xOTExMDQyMzA2MzRaMBQxEjAQBgNVBAMTCWxvY2FsaG9zdDCCAiIw 7 | DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALYBy3IEY3kqlf5h2vEtk9CB4Vnt 8 | AD+eaVc3Y9xuFHJ4k0ScsjwrYH3YcwQW0fDpOQCI0102YoQT7tPCBT+rC0w1mM82 9 | 0ZSKS/y2SIK9cM6LHWkUbQcWfeaL+uz2HB3jTEm8tmEEFTLBJFMbMpUsvjA5GLqG 10 | URswsNjYEl8M9wS1BETw2e+eCFa4wxq9oGHp/Dgh0vZglHzQL5osEpRct1aaQo6O 11 | jWzZc1Cgx4SxmMOoMWF8BQzlO7aikbZEJxk03TIFNph/azJt7mviMseW72mP+bX9 12 | sm/8bsINiYgJMM2HAyjIgFVMXX8AYfEFC4wozYloLqn0yy9TdjhyGbsUjg0yTd4l 13 | A9LkGKroBdY1drPSek5Nj9br27UGGGfU2ddAD5xYBIeeFY+3nqST868oIXB/m1P7 14 | 1p8tpkgujx/RqKr3nvOqBHizmoQaWZsPC3X/Jc4NvVHihpuNzN/u1D5mxGhxsx+R 15 | qnrIkhS0IqNrokggPZazugmHntd95HgTb3JpjY3RGEYXAQNr+mZGUCc+CVu0mhFX 16 | xAMZcfVp5nDg4hKHiaRv0KcaqBmnn8AB5w5FiTppzUbRP0zz7GkwrdulwR6c2Eb5 17 | 75+/022TbgCx8B9SH4zJRTj5mtrK56eFgTcnuXB+YnWaP7/7qmKIZzxrd3UDvnza 18 | bhnMiiIK7vL8qiOTAgMBAAGjHjAcMBoGA1UdEQQTMBGCCWxvY2FsaG9zdIcEfwAA 19 | ATANBgkqhkiG9w0BAQsFAAOCAgEAOrh8XfW6quwBAcCxHf6/uvu/iNq4yHCg2qH6 20 | VtWs/x38I2t3BRSNsLsJGieh6yLlZDzOus+XYui4uDE50XmcwaIsY0VcXnvdyZVZ 21 | w9+lMyfp00kRF1o3B6eVxq0pRE5VB0cai7XI7tyfpRwGzA+oNLF4vBvxAHm9Ony5 22 | Q57DC/HFzyUogdkMYciO/kd9oa4HosDEXwaE8UvZUL8OVl/dptMXLL/GGwzZsUAE 23 | 1sLAbgm044YChLUDzgBAtDTkB/HNkcPzSKwULuskhe7ndoaEQNXVZuP7quGiZ/W1 24 | 1lE59gnmnyG8ySFCL05jHrKLtFAJe88gQjgDK65ZJv4W/k7ocmT+HhCxWyQWcX6v 25 | abJ0EssqeSQuzRMuZebMJJ8s46d6RcYuMdIX3RDXq+1moJDFopE7lgNrlRhWgaky 26 | Og8f/u8s1j75tk1YaYcY9uBKjKk7f681R9wMumkd6IEmEvkUwHNFsctxi4fGI7h1 27 | PRdKL0DlhVmnpHlKs6Kvm2sJ3twSAGSrC4u0LuxACeR3XbiBfyhFV/291LSuw/y1 28 | JtWOW5koh0g1k9xtkiu3/ePVdG/CLp796IyRhdB1jP/vD7W5RLLG/VAlomfjsPsB 29 | AnwFYbVZ8KrmMKYUpTJOH31CRzFdOB6nWqXu5tk3nOtLKo1nIOuVtmp9XLz3VtHe 30 | NiZPnqA= 31 | -----END CERTIFICATE----- 32 | -------------------------------------------------------------------------------- /resources/certs/server-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | 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 51 | -----END RSA PRIVATE KEY----- 52 | -------------------------------------------------------------------------------- /resources/interchange.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nats-io/nats-mq/bc8d0e64d64b3b8a21f182130354e9c029f8a374/resources/interchange.bin -------------------------------------------------------------------------------- /resources/mac.tiktok.conf: -------------------------------------------------------------------------------- 1 | # Version of tiktok.conf with MacOS docker host names 2 | reconnectinterval: 5000, 3 | 4 | logging: { 5 | Time: true, 6 | Debug: true, 7 | Trace: true, 8 | Colors: true, 9 | PID: false, 10 | }, 11 | 12 | monitoring: { 13 | httpport: 9090, 14 | } 15 | 16 | nats: { 17 | Servers: ["docker.for.mac.host.internal:4222"], 18 | ConnectTimeout: 5000, 19 | MaxReconnects: 5, 20 | ReconnectWait: 5000, 21 | } 22 | 23 | connect: [ 24 | { 25 | type: NATS2Queue, 26 | id: "tick", 27 | mq: { 28 | ConnectionName: "docker.for.mac.host.internal(1414)", 29 | ChannelName: "DEV.APP.SVRCONN", 30 | QueueManager: "QM1", 31 | }, 32 | queue: "DEV.QUEUE.1", 33 | subject: "tick", 34 | },{ 35 | type: Queue2NATS, 36 | id: "tock", 37 | mq: { 38 | ConnectionName: "docker.for.mac.host.internal(1414)", 39 | ChannelName: "DEV.APP.SVRCONN", 40 | QueueManager: "QM1", 41 | }, 42 | queue: "DEV.QUEUE.1", 43 | subject: "tock", 44 | }, 45 | ], -------------------------------------------------------------------------------- /resources/mqm/MQClient/certs/client.crl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nats-io/nats-mq/bc8d0e64d64b3b8a21f182130354e9c029f8a374/resources/mqm/MQClient/certs/client.crl -------------------------------------------------------------------------------- /resources/mqm/MQClient/certs/client.kdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nats-io/nats-mq/bc8d0e64d64b3b8a21f182130354e9c029f8a374/resources/mqm/MQClient/certs/client.kdb -------------------------------------------------------------------------------- /resources/mqm/MQClient/certs/client.rdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nats-io/nats-mq/bc8d0e64d64b3b8a21f182130354e9c029f8a374/resources/mqm/MQClient/certs/client.rdb -------------------------------------------------------------------------------- /resources/mqm/MQClient/certs/client.sth: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nats-io/nats-mq/bc8d0e64d64b3b8a21f182130354e9c029f8a374/resources/mqm/MQClient/certs/client.sth -------------------------------------------------------------------------------- /resources/mqm/MQClient/certs/client_key.p12: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nats-io/nats-mq/bc8d0e64d64b3b8a21f182130354e9c029f8a374/resources/mqm/MQClient/certs/client_key.p12 -------------------------------------------------------------------------------- /resources/mqm/MQClient/certs/client_key.sth: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nats-io/nats-mq/bc8d0e64d64b3b8a21f182130354e9c029f8a374/resources/mqm/MQClient/certs/client_key.sth -------------------------------------------------------------------------------- /resources/mqm/MQServer/certs/QM1.cert: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDFDCCAfygAwIBAgIIVeJusHeSI8swDQYJKoZIhvcNAQEFBQAwKDELMAkGA1UE 3 | BhMCdWsxDDAKBgNVBAoTA2libTELMAkGA1UEAxMCcW0wHhcNMTkwMzEzMjAwNzQ1 4 | WhcNMjAwMzEzMjAwNzQ1WjAoMQswCQYDVQQGEwJ1azEMMAoGA1UEChMDaWJtMQsw 5 | CQYDVQQDEwJxbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALjokgcF 6 | SfPg9oqxVNOwd8wumy5PVHSNZn+P+IDaS55F824BSgC6zqsmlaus/xUVDdZ7R4AN 7 | BDOCKO00GE5e1hwApqtIrSYY5qQhKnmdDa/j7228efQh/kunDNvdW564zh6ntDLK 8 | aq7XxqBtlXRcfdzvpAkDdP+gzO+O46uZkGScZDuck0bCnpXCh7USaV9aI0+StYL9 9 | p0MVBUmdxY5InveR3eMRiOwHtbEUHuhiVhJb+0JOkCb4K9IWO/W1mIPjiQNEn5Iv 10 | T/w3k4xHyfWht5TSZ5NGHaCdDoytnZpSQ0sWyd5hKtYcM4+g952yir6Cd2NI/qIv 11 | KaUG6ZXGq04mVfMCAwEAAaNCMEAwHQYDVR0OBBYEFNV+1qB0kzCrajQVh1NW1fuT 12 | zfVyMB8GA1UdIwQYMBaAFNV+1qB0kzCrajQVh1NW1fuTzfVyMA0GCSqGSIb3DQEB 13 | BQUAA4IBAQAIvsZxhUKwi2OM4axdFhg3NnNPJNwxwY1H4VtcF+0SWUmS0s/AHuWJ 14 | Ax7CTEQImBXT+Enc4DKY9e/Cqn2InhWQZ9U5quyoFf4tzvc/Qfx1DVlMTNeBmjnA 15 | CLCoFHL2+1J7nB3Ra9FX5kPoLouO1UMxh3HvjHIl65qWPW7+lvMIJsVB8ZV2uki4 16 | myGpQh/BSBwlQOp9g2DDZo525mRAgsyjX+5luleKb/8mafuxxeh3kPcvqtnde38o 17 | eqWJoPO3aL96umGDFUbwAsSoJ9t/LkrJ3/zF6TqA96F3SOxx4Z9lkVkMuDK+d9Qj 18 | 9+83zMy9HMjnH+mOMfm6wOOf2PqtfKF2 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /resources/mqm/MQServer/certs/key.p12: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nats-io/nats-mq/bc8d0e64d64b3b8a21f182130354e9c029f8a374/resources/mqm/MQServer/certs/key.p12 -------------------------------------------------------------------------------- /resources/mqm/MQServer/certs/key.sth: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nats-io/nats-mq/bc8d0e64d64b3b8a21f182130354e9c029f8a374/resources/mqm/MQServer/certs/key.sth -------------------------------------------------------------------------------- /resources/tiktok.conf: -------------------------------------------------------------------------------- 1 | reconnectinterval: 5000, 2 | 3 | logging: { 4 | Time: true, 5 | Debug: true, 6 | Trace: true, 7 | Colors: true, 8 | PID: false, 9 | }, 10 | 11 | monitoring: { 12 | httpport: 9090, 13 | } 14 | 15 | nats: { 16 | Servers: ["localhost:4222"], 17 | ConnectTimeout: 5000, 18 | MaxReconnects: 5, 19 | ReconnectWait: 5000, 20 | } 21 | 22 | connect: [ 23 | { 24 | type: NATS2Queue, 25 | id: "tick", 26 | mq: { 27 | ConnectionName: "localhost(1414)", 28 | ChannelName: "DEV.APP.SVRCONN", 29 | QueueManager: "QM1", 30 | }, 31 | queue: "DEV.QUEUE.1", 32 | subject: "tick", 33 | },{ 34 | type: Queue2NATS, 35 | id: "tock", 36 | mq: { 37 | ConnectionName: "localhost(1414)", 38 | ChannelName: "DEV.APP.SVRCONN", 39 | QueueManager: "QM1", 40 | }, 41 | queue: "DEV.QUEUE.1", 42 | subject: "tock", 43 | }, 44 | ], -------------------------------------------------------------------------------- /scripts/run_mq.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | docker run \ 4 | --env LICENSE=accept \ 5 | --env MQ_QMGR_NAME=QM1 \ 6 | --publish 1414:1414 \ 7 | --publish 9443:9443 \ 8 | ibmcom/mq -------------------------------------------------------------------------------- /scripts/run_mq_detached.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | docker run \ 4 | --env LICENSE=accept \ 5 | --env MQ_QMGR_NAME=QM1 \ 6 | --publish 1414:1414 \ 7 | --publish 9443:9443 \ 8 | --detach \ 9 | ibmcom/mq -------------------------------------------------------------------------------- /scripts/run_mq_tls.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | docker run \ 4 | --volume `pwd`/resources/mqm:/mnt/tls \ 5 | --env LICENSE=accept \ 6 | --env MQ_QMGR_NAME=QM1 \ 7 | --env MQ_APP_PASSWORD=passw0rd \ 8 | --env MQ_TLS_KEYSTORE=/mnt/tls/MQServer/certs/key.p12 \ 9 | --env MQ_TLS_PASSPHRASE=k3ypassw0rd \ 10 | --publish 1414:1414 \ 11 | --publish 9443:9443 \ 12 | ibmcom/mq 13 | --------------------------------------------------------------------------------