Merge branch 'nats-endpoint' of https://github.com/lennycampino/tile38 into lennycampino-nats-endpoint

This commit is contained in:
tidwall 2018-08-08 18:29:22 -07:00
commit f17e95c6b6
121 changed files with 40905 additions and 0 deletions

View File

@ -33,6 +33,8 @@ const (
AMQP = Protocol("amqp")
// SQS protocol
SQS = Protocol("sqs")
// NATS protocol
NATS = Protocol("nats")
)
// Endpoint represents an endpoint.
@ -93,6 +95,14 @@ type Endpoint struct {
CredProfile string
QueueName string
}
NATS struct {
Host string
Port int
User string
Pass string
Topic string
}
}
// Conn is an endpoint connection
@ -168,6 +178,8 @@ func (epc *Manager) Send(endpoint, msg string) error {
conn = newAMQPConn(ep)
case SQS:
conn = newSQSConn(ep)
case NATS:
conn = newNATSConn(ep)
}
epc.conns[endpoint] = conn
}
@ -212,6 +224,8 @@ func parseEndpoint(s string) (Endpoint, error) {
endpoint.Protocol = MQTT
case strings.HasPrefix(s, "sqs:"):
endpoint.Protocol = SQS
case strings.HasPrefix(s, "nats:"):
endpoint.Protocol = NATS
}
s = s[strings.Index(s, ":")+1:]
@ -558,6 +572,59 @@ func parseEndpoint(s string) (Endpoint, error) {
}
}
// Basic NATS connection strings in HOOKS interface
// nats://<host>:<port>/<topic_name>/?params=value
//
// params are:
//
// user - username
// pass - password
// when user or pass is not set then login without password is used
if endpoint.Protocol == NATS {
// Parsing connection from URL string
hp := strings.Split(s, ":")
switch len(hp) {
default:
return endpoint, errors.New("invalid SQS url")
case 2:
endpoint.NATS.Host = hp[0]
port, err := strconv.Atoi(hp[1])
if err != nil {
endpoint.NATS.Port = 4222 // default nats port
} else {
endpoint.NATS.Port = port
}
}
// Parsing NATS topic name
if len(sp) > 1 {
var err error
endpoint.NATS.Topic, err = url.QueryUnescape(sp[1])
if err != nil {
return endpoint, errors.New("invalid NATS topic name")
}
}
// Parsing additional params
if len(sqp) > 1 {
m, err := url.ParseQuery(sqp[1])
if err != nil {
return endpoint, errors.New("invalid NATS url")
}
for key, val := range m {
if len(val) == 0 {
continue
}
switch key {
case "user":
endpoint.NATS.User = val[0]
case "pass":
endpoint.NATS.Pass = val[0]
}
}
}
}
return endpoint, nil
}

80
pkg/endpoint/nats.go Normal file
View File

@ -0,0 +1,80 @@
package endpoint
import (
"fmt"
"sync"
"time"
"github.com/nats-io/go-nats"
)
const (
natsExpiresAfter = time.Second * 30
)
// NATSConn is an endpoint connection
type NATSConn struct {
mu sync.Mutex
ep Endpoint
ex bool
t time.Time
conn *nats.Conn
}
func newNATSConn(ep Endpoint) *NATSConn {
return &NATSConn{
ep: ep,
t: time.Now(),
}
}
// Expired returns true if the connection has expired
func (conn *NATSConn) Expired() bool {
conn.mu.Lock()
defer conn.mu.Unlock()
if !conn.ex {
if time.Now().Sub(conn.t) > natsExpiresAfter {
if conn.conn != nil {
conn.close()
}
conn.ex = true
}
}
return conn.ex
}
func (conn *NATSConn) close() {
if conn.conn != nil {
conn.conn.Close()
conn.conn = nil
}
}
// Send sends a message
func (conn *NATSConn) Send(msg string) error {
conn.mu.Lock()
defer conn.mu.Unlock()
if conn.ex {
return errExpired
}
conn.t = time.Now()
if conn.conn == nil {
addr := fmt.Sprintf("nats://%s:%d", conn.ep.NATS.Host, conn.ep.NATS.Port)
var err error
if conn.ep.NATS.User != "" && conn.ep.NATS.Pass != "" {
conn.conn, err = nats.Connect(addr, nats.UserInfo(conn.ep.NATS.User, conn.ep.NATS.Pass))
}
conn.conn, err = nats.Connect(addr)
if err != nil {
conn.close()
return err
}
}
err := conn.conn.Publish(conn.ep.NATS.Topic, []byte(msg))
if err != nil {
conn.close()
return err
}
return nil
}

201
vendor/github.com/nats-io/gnatsd/LICENSE generated vendored Normal file
View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

1141
vendor/github.com/nats-io/gnatsd/conf/lex.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

1224
vendor/github.com/nats-io/gnatsd/conf/lex_test.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

295
vendor/github.com/nats-io/gnatsd/conf/parse.go generated vendored Normal file
View File

@ -0,0 +1,295 @@
// Copyright 2013-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package conf supports a configuration file format used by gnatsd. It is
// a flexible format that combines the best of traditional
// configuration formats and newer styles such as JSON and YAML.
package conf
// The format supported is less restrictive than today's formats.
// Supports mixed Arrays [], nested Maps {}, multiple comment types (# and //)
// Also supports key value assigments using '=' or ':' or whiteSpace()
// e.g. foo = 2, foo : 2, foo 2
// maps can be assigned with no key separator as well
// semicolons as value terminators in key/value assignments are optional
//
// see parse_test.go for more examples.
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"unicode"
)
type parser struct {
mapping map[string]interface{}
lx *lexer
// The current scoped context, can be array or map
ctx interface{}
// stack of contexts, either map or array/slice stack
ctxs []interface{}
// Keys stack
keys []string
// The config file path, empty by default.
fp string
}
// Parse will return a map of keys to interface{}, although concrete types
// underly them. The values supported are string, bool, int64, float64, DateTime.
// Arrays and nested Maps are also supported.
func Parse(data string) (map[string]interface{}, error) {
p, err := parse(data, "")
if err != nil {
return nil, err
}
return p.mapping, nil
}
// ParseFile is a helper to open file, etc. and parse the contents.
func ParseFile(fp string) (map[string]interface{}, error) {
data, err := ioutil.ReadFile(fp)
if err != nil {
return nil, fmt.Errorf("error opening config file: %v", err)
}
p, err := parse(string(data), filepath.Dir(fp))
if err != nil {
return nil, err
}
return p.mapping, nil
}
func parse(data, fp string) (p *parser, err error) {
p = &parser{
mapping: make(map[string]interface{}),
lx: lex(data),
ctxs: make([]interface{}, 0, 4),
keys: make([]string, 0, 4),
fp: fp,
}
p.pushContext(p.mapping)
for {
it := p.next()
if it.typ == itemEOF {
break
}
if err := p.processItem(it); err != nil {
return nil, err
}
}
return p, nil
}
func (p *parser) next() item {
return p.lx.nextItem()
}
func (p *parser) pushContext(ctx interface{}) {
p.ctxs = append(p.ctxs, ctx)
p.ctx = ctx
}
func (p *parser) popContext() interface{} {
if len(p.ctxs) == 0 {
panic("BUG in parser, context stack empty")
}
li := len(p.ctxs) - 1
last := p.ctxs[li]
p.ctxs = p.ctxs[0:li]
p.ctx = p.ctxs[len(p.ctxs)-1]
return last
}
func (p *parser) pushKey(key string) {
p.keys = append(p.keys, key)
}
func (p *parser) popKey() string {
if len(p.keys) == 0 {
panic("BUG in parser, keys stack empty")
}
li := len(p.keys) - 1
last := p.keys[li]
p.keys = p.keys[0:li]
return last
}
func (p *parser) processItem(it item) error {
switch it.typ {
case itemError:
return fmt.Errorf("Parse error on line %d: '%s'", it.line, it.val)
case itemKey:
p.pushKey(it.val)
case itemMapStart:
newCtx := make(map[string]interface{})
p.pushContext(newCtx)
case itemMapEnd:
p.setValue(p.popContext())
case itemString:
p.setValue(it.val) // FIXME(dlc) sanitize string?
case itemInteger:
lastDigit := 0
for _, r := range it.val {
if !unicode.IsDigit(r) && r != '-' {
break
}
lastDigit++
}
numStr := it.val[:lastDigit]
num, err := strconv.ParseInt(numStr, 10, 64)
if err != nil {
if e, ok := err.(*strconv.NumError); ok &&
e.Err == strconv.ErrRange {
return fmt.Errorf("Integer '%s' is out of the range.", it.val)
}
return fmt.Errorf("Expected integer, but got '%s'.", it.val)
}
// Process a suffix
suffix := strings.ToLower(strings.TrimSpace(it.val[lastDigit:]))
switch suffix {
case "":
p.setValue(num)
case "k":
p.setValue(num * 1000)
case "kb":
p.setValue(num * 1024)
case "m":
p.setValue(num * 1000 * 1000)
case "mb":
p.setValue(num * 1024 * 1024)
case "g":
p.setValue(num * 1000 * 1000 * 1000)
case "gb":
p.setValue(num * 1024 * 1024 * 1024)
}
case itemFloat:
num, err := strconv.ParseFloat(it.val, 64)
if err != nil {
if e, ok := err.(*strconv.NumError); ok &&
e.Err == strconv.ErrRange {
return fmt.Errorf("Float '%s' is out of the range.", it.val)
}
return fmt.Errorf("Expected float, but got '%s'.", it.val)
}
p.setValue(num)
case itemBool:
switch strings.ToLower(it.val) {
case "true", "yes", "on":
p.setValue(true)
case "false", "no", "off":
p.setValue(false)
default:
return fmt.Errorf("Expected boolean value, but got '%s'.", it.val)
}
case itemDatetime:
dt, err := time.Parse("2006-01-02T15:04:05Z", it.val)
if err != nil {
return fmt.Errorf(
"Expected Zulu formatted DateTime, but got '%s'.", it.val)
}
p.setValue(dt)
case itemArrayStart:
var array = make([]interface{}, 0)
p.pushContext(array)
case itemArrayEnd:
array := p.ctx
p.popContext()
p.setValue(array)
case itemVariable:
if value, ok := p.lookupVariable(it.val); ok {
p.setValue(value)
} else {
return fmt.Errorf("Variable reference for '%s' on line %d can not be found.",
it.val, it.line)
}
case itemInclude:
m, err := ParseFile(filepath.Join(p.fp, it.val))
if err != nil {
return fmt.Errorf("Error parsing include file '%s', %v.", it.val, err)
}
for k, v := range m {
p.pushKey(k)
p.setValue(v)
}
}
return nil
}
// Used to map an environment value into a temporary map to pass to secondary Parse call.
const pkey = "pk"
// We special case raw strings here that are bcrypt'd. This allows us not to force quoting the strings
const bcryptPrefix = "2a$"
// lookupVariable will lookup a variable reference. It will use block scoping on keys
// it has seen before, with the top level scoping being the environment variables. We
// ignore array contexts and only process the map contexts..
//
// Returns true for ok if it finds something, similar to map.
func (p *parser) lookupVariable(varReference string) (interface{}, bool) {
// Do special check to see if it is a raw bcrypt string.
if strings.HasPrefix(varReference, bcryptPrefix) {
return "$" + varReference, true
}
// Loop through contexts currently on the stack.
for i := len(p.ctxs) - 1; i >= 0; i -= 1 {
ctx := p.ctxs[i]
// Process if it is a map context
if m, ok := ctx.(map[string]interface{}); ok {
if v, ok := m[varReference]; ok {
return v, ok
}
}
}
// If we are here, we have exhausted our context maps and still not found anything.
// Parse from the environment.
if vStr, ok := os.LookupEnv(varReference); ok {
// Everything we get here will be a string value, so we need to process as a parser would.
if vmap, err := Parse(fmt.Sprintf("%s=%s", pkey, vStr)); err == nil {
v, ok := vmap[pkey]
return v, ok
}
}
return nil, false
}
func (p *parser) setValue(val interface{}) {
// Test to see if we are on an array or a map
// Array processing
if ctx, ok := p.ctx.([]interface{}); ok {
p.ctx = append(ctx, val)
p.ctxs[len(p.ctxs)-1] = p.ctx
}
// Map processing
if ctx, ok := p.ctx.(map[string]interface{}); ok {
key := p.popKey()
// FIXME(dlc), make sure to error if redefining same key?
ctx[key] = val
}
}

275
vendor/github.com/nats-io/gnatsd/conf/parse_test.go generated vendored Normal file
View File

@ -0,0 +1,275 @@
package conf
import (
"fmt"
"os"
"reflect"
"strings"
"testing"
"time"
)
// Test to make sure we get what we expect.
func test(t *testing.T, data string, ex map[string]interface{}) {
m, err := Parse(data)
if err != nil {
t.Fatalf("Received err: %v\n", err)
}
if m == nil {
t.Fatal("Received nil map")
}
if !reflect.DeepEqual(m, ex) {
t.Fatalf("Not Equal:\nReceived: '%+v'\nExpected: '%+v'\n", m, ex)
}
}
func TestSimpleTopLevel(t *testing.T) {
ex := map[string]interface{}{
"foo": "1",
"bar": float64(2.2),
"baz": true,
"boo": int64(22),
}
test(t, "foo='1'; bar=2.2; baz=true; boo=22", ex)
}
func TestBools(t *testing.T) {
ex := map[string]interface{}{
"foo": true,
}
test(t, "foo=true", ex)
test(t, "foo=TRUE", ex)
test(t, "foo=true", ex)
test(t, "foo=yes", ex)
test(t, "foo=on", ex)
}
var varSample = `
index = 22
foo = $index
`
func TestSimpleVariable(t *testing.T) {
ex := map[string]interface{}{
"index": int64(22),
"foo": int64(22),
}
test(t, varSample, ex)
}
var varNestedSample = `
index = 22
nest {
index = 11
foo = $index
}
bar = $index
`
func TestNestedVariable(t *testing.T) {
ex := map[string]interface{}{
"index": int64(22),
"nest": map[string]interface{}{
"index": int64(11),
"foo": int64(11),
},
"bar": int64(22),
}
test(t, varNestedSample, ex)
}
func TestMissingVariable(t *testing.T) {
_, err := Parse("foo=$index")
if err == nil {
t.Fatalf("Expected an error for a missing variable, got none")
}
if !strings.HasPrefix(err.Error(), "Variable reference") {
t.Fatalf("Wanted a variable reference err, got %q\n", err)
}
}
func TestEnvVariable(t *testing.T) {
ex := map[string]interface{}{
"foo": int64(22),
}
evar := "__UNIQ22__"
os.Setenv(evar, "22")
defer os.Unsetenv(evar)
test(t, fmt.Sprintf("foo = $%s", evar), ex)
}
func TestBcryptVariable(t *testing.T) {
ex := map[string]interface{}{
"password": "$2a$11$ooo",
}
test(t, "password: $2a$11$ooo", ex)
}
var easynum = `
k = 8k
kb = 4kb
m = 1m
mb = 2MB
g = 2g
gb = 22GB
`
func TestConvenientNumbers(t *testing.T) {
ex := map[string]interface{}{
"k": int64(8 * 1000),
"kb": int64(4 * 1024),
"m": int64(1000 * 1000),
"mb": int64(2 * 1024 * 1024),
"g": int64(2 * 1000 * 1000 * 1000),
"gb": int64(22 * 1024 * 1024 * 1024),
}
test(t, easynum, ex)
}
var sample1 = `
foo {
host {
ip = '127.0.0.1'
port = 4242
}
servers = [ "a.com", "b.com", "c.com"]
}
`
func TestSample1(t *testing.T) {
ex := map[string]interface{}{
"foo": map[string]interface{}{
"host": map[string]interface{}{
"ip": "127.0.0.1",
"port": int64(4242),
},
"servers": []interface{}{"a.com", "b.com", "c.com"},
},
}
test(t, sample1, ex)
}
var cluster = `
cluster {
port: 4244
authorization {
user: route_user
password: top_secret
timeout: 1
}
# Routes are actively solicited and connected to from this server.
# Other servers can connect to us if they supply the correct credentials
# in their routes definitions from above.
// Test both styles of comments
routes = [
nats-route://foo:bar@apcera.me:4245
nats-route://foo:bar@apcera.me:4246
]
}
`
func TestSample2(t *testing.T) {
ex := map[string]interface{}{
"cluster": map[string]interface{}{
"port": int64(4244),
"authorization": map[string]interface{}{
"user": "route_user",
"password": "top_secret",
"timeout": int64(1),
},
"routes": []interface{}{
"nats-route://foo:bar@apcera.me:4245",
"nats-route://foo:bar@apcera.me:4246",
},
},
}
test(t, cluster, ex)
}
var sample3 = `
foo {
expr = '(true == "false")'
text = 'This is a multi-line
text block.'
}
`
func TestSample3(t *testing.T) {
ex := map[string]interface{}{
"foo": map[string]interface{}{
"expr": "(true == \"false\")",
"text": "This is a multi-line\ntext block.",
},
}
test(t, sample3, ex)
}
var sample4 = `
array [
{ abc: 123 }
{ xyz: "word" }
]
`
func TestSample4(t *testing.T) {
ex := map[string]interface{}{
"array": []interface{}{
map[string]interface{}{"abc": int64(123)},
map[string]interface{}{"xyz": "word"},
},
}
test(t, sample4, ex)
}
var sample5 = `
now = 2016-05-04T18:53:41Z
gmt = false
`
func TestSample5(t *testing.T) {
dt, _ := time.Parse("2006-01-02T15:04:05Z", "2016-05-04T18:53:41Z")
ex := map[string]interface{}{
"now": dt,
"gmt": false,
}
test(t, sample5, ex)
}
func TestIncludes(t *testing.T) {
ex := map[string]interface{}{
"listen": "127.0.0.1:4222",
"authorization": map[string]interface{}{
"ALICE_PASS": "$2a$10$UHR6GhotWhpLsKtVP0/i6.Nh9.fuY73cWjLoJjb2sKT8KISBcUW5q",
"BOB_PASS": "$2a$11$dZM98SpGeI7dCFFGSpt.JObQcix8YHml4TBUZoge9R1uxnMIln5ly",
"users": []interface{}{
map[string]interface{}{
"user": "alice",
"password": "$2a$10$UHR6GhotWhpLsKtVP0/i6.Nh9.fuY73cWjLoJjb2sKT8KISBcUW5q"},
map[string]interface{}{
"user": "bob",
"password": "$2a$11$dZM98SpGeI7dCFFGSpt.JObQcix8YHml4TBUZoge9R1uxnMIln5ly"},
},
"timeout": float64(0.5),
},
}
m, err := ParseFile("simple.conf")
if err != nil {
t.Fatalf("Received err: %v\n", err)
}
if m == nil {
t.Fatal("Received nil map")
}
if !reflect.DeepEqual(m, ex) {
t.Fatalf("Not Equal:\nReceived: '%+v'\nExpected: '%+v'\n", m, ex)
}
}

6
vendor/github.com/nats-io/gnatsd/conf/simple.conf generated vendored Normal file
View File

@ -0,0 +1,6 @@
listen: 127.0.0.1:4222
authorization {
include 'includes/users.conf' # Pull in from file
timeout: 0.5
}

152
vendor/github.com/nats-io/gnatsd/logger/log.go generated vendored Normal file
View File

@ -0,0 +1,152 @@
// Copyright 2012-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//Package logger provides logging facilities for the NATS server
package logger
import (
"fmt"
"log"
"os"
)
// Logger is the server logger
type Logger struct {
logger *log.Logger
debug bool
trace bool
infoLabel string
errorLabel string
fatalLabel string
debugLabel string
traceLabel string
logFile *os.File // file pointer for the file logger.
}
// NewStdLogger creates a logger with output directed to Stderr
func NewStdLogger(time, debug, trace, colors, pid bool) *Logger {
flags := 0
if time {
flags = log.LstdFlags | log.Lmicroseconds
}
pre := ""
if pid {
pre = pidPrefix()
}
l := &Logger{
logger: log.New(os.Stderr, pre, flags),
debug: debug,
trace: trace,
}
if colors {
setColoredLabelFormats(l)
} else {
setPlainLabelFormats(l)
}
return l
}
// NewFileLogger creates a logger with output directed to a file
func NewFileLogger(filename string, time, debug, trace, pid bool) *Logger {
fileflags := os.O_WRONLY | os.O_APPEND | os.O_CREATE
f, err := os.OpenFile(filename, fileflags, 0660)
if err != nil {
log.Fatalf("error opening file: %v", err)
}
flags := 0
if time {
flags = log.LstdFlags | log.Lmicroseconds
}
pre := ""
if pid {
pre = pidPrefix()
}
l := &Logger{
logger: log.New(f, pre, flags),
debug: debug,
trace: trace,
logFile: f,
}
setPlainLabelFormats(l)
return l
}
// Close implements the io.Closer interface to clean up
// resources in the server's logger implementation.
// Caller must ensure threadsafety.
func (l *Logger) Close() error {
if f := l.logFile; f != nil {
l.logFile = nil
return f.Close()
}
return nil
}
// Generate the pid prefix string
func pidPrefix() string {
return fmt.Sprintf("[%d] ", os.Getpid())
}
func setPlainLabelFormats(l *Logger) {
l.infoLabel = "[INF] "
l.debugLabel = "[DBG] "
l.errorLabel = "[ERR] "
l.fatalLabel = "[FTL] "
l.traceLabel = "[TRC] "
}
func setColoredLabelFormats(l *Logger) {
colorFormat := "[\x1b[%dm%s\x1b[0m] "
l.infoLabel = fmt.Sprintf(colorFormat, 32, "INF")
l.debugLabel = fmt.Sprintf(colorFormat, 36, "DBG")
l.errorLabel = fmt.Sprintf(colorFormat, 31, "ERR")
l.fatalLabel = fmt.Sprintf(colorFormat, 31, "FTL")
l.traceLabel = fmt.Sprintf(colorFormat, 33, "TRC")
}
// Noticef logs a notice statement
func (l *Logger) Noticef(format string, v ...interface{}) {
l.logger.Printf(l.infoLabel+format, v...)
}
// Errorf logs an error statement
func (l *Logger) Errorf(format string, v ...interface{}) {
l.logger.Printf(l.errorLabel+format, v...)
}
// Fatalf logs a fatal error
func (l *Logger) Fatalf(format string, v ...interface{}) {
l.logger.Fatalf(l.fatalLabel+format, v...)
}
// Debugf logs a debug statement
func (l *Logger) Debugf(format string, v ...interface{}) {
if l.debug {
l.logger.Printf(l.debugLabel+format, v...)
}
}
// Tracef logs a trace statement
func (l *Logger) Tracef(format string, v ...interface{}) {
if l.trace {
l.logger.Printf(l.traceLabel+format, v...)
}
}

185
vendor/github.com/nats-io/gnatsd/logger/log_test.go generated vendored Normal file
View File

@ -0,0 +1,185 @@
// Copyright 2012-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package logger
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"strings"
"testing"
)
func TestStdLogger(t *testing.T) {
logger := NewStdLogger(false, false, false, false, false)
flags := logger.logger.Flags()
if flags != 0 {
t.Fatalf("Expected %q, received %q\n", 0, flags)
}
if logger.debug {
t.Fatalf("Expected %t, received %t\n", false, logger.debug)
}
if logger.trace {
t.Fatalf("Expected %t, received %t\n", false, logger.trace)
}
}
func TestStdLoggerWithDebugTraceAndTime(t *testing.T) {
logger := NewStdLogger(true, true, true, false, false)
flags := logger.logger.Flags()
if flags != log.LstdFlags|log.Lmicroseconds {
t.Fatalf("Expected %d, received %d\n", log.LstdFlags, flags)
}
if !logger.debug {
t.Fatalf("Expected %t, received %t\n", true, logger.debug)
}
if !logger.trace {
t.Fatalf("Expected %t, received %t\n", true, logger.trace)
}
}
func TestStdLoggerNotice(t *testing.T) {
expectOutput(t, func() {
logger := NewStdLogger(false, false, false, false, false)
logger.Noticef("foo")
}, "[INF] foo\n")
}
func TestStdLoggerNoticeWithColor(t *testing.T) {
expectOutput(t, func() {
logger := NewStdLogger(false, false, false, true, false)
logger.Noticef("foo")
}, "[\x1b[32mINF\x1b[0m] foo\n")
}
func TestStdLoggerDebug(t *testing.T) {
expectOutput(t, func() {
logger := NewStdLogger(false, true, false, false, false)
logger.Debugf("foo %s", "bar")
}, "[DBG] foo bar\n")
}
func TestStdLoggerDebugWithOutDebug(t *testing.T) {
expectOutput(t, func() {
logger := NewStdLogger(false, false, false, false, false)
logger.Debugf("foo")
}, "")
}
func TestStdLoggerTrace(t *testing.T) {
expectOutput(t, func() {
logger := NewStdLogger(false, false, true, false, false)
logger.Tracef("foo")
}, "[TRC] foo\n")
}
func TestStdLoggerTraceWithOutDebug(t *testing.T) {
expectOutput(t, func() {
logger := NewStdLogger(false, false, false, false, false)
logger.Tracef("foo")
}, "")
}
func TestFileLogger(t *testing.T) {
tmpDir, err := ioutil.TempDir("", "_gnatsd")
if err != nil {
t.Fatal("Could not create tmp dir")
}
defer os.RemoveAll(tmpDir)
file, err := ioutil.TempFile(tmpDir, "gnatsd:log_")
if err != nil {
t.Fatalf("Could not create the temp file: %v", err)
}
file.Close()
logger := NewFileLogger(file.Name(), false, false, false, false)
logger.Noticef("foo")
buf, err := ioutil.ReadFile(file.Name())
if err != nil {
t.Fatalf("Could not read logfile: %v", err)
}
if len(buf) <= 0 {
t.Fatal("Expected a non-zero length logfile")
}
if string(buf) != "[INF] foo\n" {
t.Fatalf("Expected '%s', received '%s'\n", "[INFO] foo", string(buf))
}
file, err = ioutil.TempFile(tmpDir, "gnatsd:log_")
if err != nil {
t.Fatalf("Could not create the temp file: %v", err)
}
file.Close()
logger = NewFileLogger(file.Name(), true, true, true, true)
logger.Errorf("foo")
buf, err = ioutil.ReadFile(file.Name())
if err != nil {
t.Fatalf("Could not read logfile: %v", err)
}
if len(buf) <= 0 {
t.Fatal("Expected a non-zero length logfile")
}
str := string(buf)
errMsg := fmt.Sprintf("Expected '%s', received '%s'\n", "[pid] <date> [ERR] foo", str)
pidEnd := strings.Index(str, " ")
infoStart := strings.LastIndex(str, "[ERR]")
if pidEnd == -1 || infoStart == -1 {
t.Fatalf("%v", errMsg)
}
pid := str[0:pidEnd]
if pid[0] != '[' || pid[len(pid)-1] != ']' {
t.Fatalf("%v", errMsg)
}
//TODO: Parse date.
if !strings.HasSuffix(str, "[ERR] foo\n") {
t.Fatalf("%v", errMsg)
}
}
func expectOutput(t *testing.T, f func(), expected string) {
old := os.Stderr // keep backup of the real stdout
r, w, _ := os.Pipe()
os.Stderr = w
f()
outC := make(chan string)
// copy the output in a separate goroutine so printing can't block indefinitely
go func() {
var buf bytes.Buffer
io.Copy(&buf, r)
outC <- buf.String()
}()
os.Stderr.Close()
os.Stderr = old // restoring the real stdout
out := <-outC
if out != expected {
t.Fatalf("Expected '%s', received '%s'\n", expected, out)
}
}

123
vendor/github.com/nats-io/gnatsd/logger/syslog.go generated vendored Normal file
View File

@ -0,0 +1,123 @@
// Copyright 2012-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build !windows
package logger
import (
"fmt"
"log"
"log/syslog"
"net/url"
"os"
"strings"
)
// SysLogger provides a system logger facility
type SysLogger struct {
writer *syslog.Writer
debug bool
trace bool
}
// GetSysLoggerTag generates the tag name for use in syslog statements. If
// the executable is linked, the name of the link will be used as the tag,
// otherwise, the name of the executable is used. "gnatsd" is the default
// for the NATS server.
func GetSysLoggerTag() string {
procName := os.Args[0]
if strings.ContainsRune(procName, os.PathSeparator) {
parts := strings.FieldsFunc(procName, func(c rune) bool {
return c == os.PathSeparator
})
procName = parts[len(parts)-1]
}
return procName
}
// NewSysLogger creates a new system logger
func NewSysLogger(debug, trace bool) *SysLogger {
w, err := syslog.New(syslog.LOG_DAEMON|syslog.LOG_NOTICE, GetSysLoggerTag())
if err != nil {
log.Fatalf("error connecting to syslog: %q", err.Error())
}
return &SysLogger{
writer: w,
debug: debug,
trace: trace,
}
}
// NewRemoteSysLogger creates a new remote system logger
func NewRemoteSysLogger(fqn string, debug, trace bool) *SysLogger {
network, addr := getNetworkAndAddr(fqn)
w, err := syslog.Dial(network, addr, syslog.LOG_DEBUG, GetSysLoggerTag())
if err != nil {
log.Fatalf("error connecting to syslog: %q", err.Error())
}
return &SysLogger{
writer: w,
debug: debug,
trace: trace,
}
}
func getNetworkAndAddr(fqn string) (network, addr string) {
u, err := url.Parse(fqn)
if err != nil {
log.Fatal(err)
}
network = u.Scheme
if network == "udp" || network == "tcp" {
addr = u.Host
} else if network == "unix" {
addr = u.Path
} else {
log.Fatalf("error invalid network type: %q", u.Scheme)
}
return
}
// Noticef logs a notice statement
func (l *SysLogger) Noticef(format string, v ...interface{}) {
l.writer.Notice(fmt.Sprintf(format, v...))
}
// Fatalf logs a fatal error
func (l *SysLogger) Fatalf(format string, v ...interface{}) {
l.writer.Crit(fmt.Sprintf(format, v...))
}
// Errorf logs an error statement
func (l *SysLogger) Errorf(format string, v ...interface{}) {
l.writer.Err(fmt.Sprintf(format, v...))
}
// Debugf logs a debug statement
func (l *SysLogger) Debugf(format string, v ...interface{}) {
if l.debug {
l.writer.Debug(fmt.Sprintf(format, v...))
}
}
// Tracef logs a trace statement
func (l *SysLogger) Tracef(format string, v ...interface{}) {
if l.trace {
l.writer.Notice(fmt.Sprintf(format, v...))
}
}

235
vendor/github.com/nats-io/gnatsd/logger/syslog_test.go generated vendored Normal file
View File

@ -0,0 +1,235 @@
// Copyright 2012-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build !windows
package logger
import (
"fmt"
"log"
"net"
"os"
"path/filepath"
"strings"
"testing"
"time"
)
var serverFQN string
func TestSysLogger(t *testing.T) {
logger := NewSysLogger(false, false)
if logger.debug {
t.Fatalf("Expected %t, received %t\n", false, logger.debug)
}
if logger.trace {
t.Fatalf("Expected %t, received %t\n", false, logger.trace)
}
}
func TestSysLoggerWithDebugAndTrace(t *testing.T) {
logger := NewSysLogger(true, true)
if !logger.debug {
t.Fatalf("Expected %t, received %t\n", true, logger.debug)
}
if !logger.trace {
t.Fatalf("Expected %t, received %t\n", true, logger.trace)
}
}
func testTag(t *testing.T, exePath, expected string) {
os.Args[0] = exePath
if result := GetSysLoggerTag(); result != expected {
t.Fatalf("Expected %s, received %s", expected, result)
}
}
func restoreArg(orig string) {
os.Args[0] = orig
}
func TestSysLoggerTagGen(t *testing.T) {
origArg := os.Args[0]
defer restoreArg(origArg)
testTag(t, "gnatsd", "gnatsd")
testTag(t, filepath.Join(".", "gnatsd"), "gnatsd")
testTag(t, filepath.Join("home", "bin", "gnatsd"), "gnatsd")
testTag(t, filepath.Join("..", "..", "gnatsd"), "gnatsd")
testTag(t, "gnatsd.service1", "gnatsd.service1")
testTag(t, "gnatsd_service1", "gnatsd_service1")
testTag(t, "gnatsd-service1", "gnatsd-service1")
testTag(t, "gnatsd service1", "gnatsd service1")
}
func TestSysLoggerTag(t *testing.T) {
origArg := os.Args[0]
defer restoreArg(origArg)
os.Args[0] = "ServerLoggerTag"
done := make(chan string)
startServer(done)
logger := NewRemoteSysLogger(serverFQN, true, true)
logger.Noticef("foo")
line := <-done
data := strings.Split(line, "[")
if len(data) != 2 {
t.Fatalf("Unexpected syslog line %s\n", line)
}
if !strings.Contains(data[0], os.Args[0]) {
t.Fatalf("Expected '%s', received '%s'\n", os.Args[0], data[0])
}
}
func TestRemoteSysLogger(t *testing.T) {
done := make(chan string)
startServer(done)
logger := NewRemoteSysLogger(serverFQN, true, true)
if !logger.debug {
t.Fatalf("Expected %t, received %t\n", true, logger.debug)
}
if !logger.trace {
t.Fatalf("Expected %t, received %t\n", true, logger.trace)
}
}
func TestRemoteSysLoggerNotice(t *testing.T) {
done := make(chan string)
startServer(done)
logger := NewRemoteSysLogger(serverFQN, true, true)
logger.Noticef("foo %s", "bar")
expectSyslogOutput(t, <-done, "foo bar\n")
}
func TestRemoteSysLoggerDebug(t *testing.T) {
done := make(chan string)
startServer(done)
logger := NewRemoteSysLogger(serverFQN, true, true)
logger.Debugf("foo %s", "qux")
expectSyslogOutput(t, <-done, "foo qux\n")
}
func TestRemoteSysLoggerDebugDisabled(t *testing.T) {
done := make(chan string)
startServer(done)
logger := NewRemoteSysLogger(serverFQN, false, false)
logger.Debugf("foo %s", "qux")
rcvd := <-done
if rcvd != "" {
t.Fatalf("Unexpected syslog response %s\n", rcvd)
}
}
func TestRemoteSysLoggerTrace(t *testing.T) {
done := make(chan string)
startServer(done)
logger := NewRemoteSysLogger(serverFQN, true, true)
logger.Tracef("foo %s", "qux")
expectSyslogOutput(t, <-done, "foo qux\n")
}
func TestRemoteSysLoggerTraceDisabled(t *testing.T) {
done := make(chan string)
startServer(done)
logger := NewRemoteSysLogger(serverFQN, true, false)
logger.Tracef("foo %s", "qux")
rcvd := <-done
if rcvd != "" {
t.Fatalf("Unexpected syslog response %s\n", rcvd)
}
}
func TestGetNetworkAndAddrUDP(t *testing.T) {
n, a := getNetworkAndAddr("udp://foo.com:1000")
if n != "udp" {
t.Fatalf("Unexpected network %s\n", n)
}
if a != "foo.com:1000" {
t.Fatalf("Unexpected addr %s\n", a)
}
}
func TestGetNetworkAndAddrTCP(t *testing.T) {
n, a := getNetworkAndAddr("tcp://foo.com:1000")
if n != "tcp" {
t.Fatalf("Unexpected network %s\n", n)
}
if a != "foo.com:1000" {
t.Fatalf("Unexpected addr %s\n", a)
}
}
func TestGetNetworkAndAddrUnix(t *testing.T) {
n, a := getNetworkAndAddr("unix:///foo.sock")
if n != "unix" {
t.Fatalf("Unexpected network %s\n", n)
}
if a != "/foo.sock" {
t.Fatalf("Unexpected addr %s\n", a)
}
}
func expectSyslogOutput(t *testing.T, line string, expected string) {
data := strings.Split(line, "]: ")
if len(data) != 2 {
t.Fatalf("Unexpected syslog line %s\n", line)
}
if data[1] != expected {
t.Fatalf("Expected '%s', received '%s'\n", expected, data[1])
}
}
func runSyslog(c net.PacketConn, done chan<- string) {
var buf [4096]byte
var rcvd string
for {
n, _, err := c.ReadFrom(buf[:])
if err != nil || n == 0 {
break
}
rcvd += string(buf[:n])
}
done <- rcvd
}
func startServer(done chan<- string) {
c, e := net.ListenPacket("udp", "127.0.0.1:0")
if e != nil {
log.Fatalf("net.ListenPacket failed udp :0 %v", e)
}
serverFQN = fmt.Sprintf("udp://%s", c.LocalAddr().String())
c.SetReadDeadline(time.Now().Add(100 * time.Millisecond))
go runSyslog(c, done)
}

View File

@ -0,0 +1,104 @@
// Copyright 2012-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package logger logs to the windows event log
package logger
import (
"fmt"
"os"
"strings"
"golang.org/x/sys/windows/svc/eventlog"
)
const (
natsEventSource = "NATS-Server"
)
// SysLogger logs to the windows event logger
type SysLogger struct {
writer *eventlog.Log
debug bool
trace bool
}
// NewSysLogger creates a log using the windows event logger
func NewSysLogger(debug, trace bool) *SysLogger {
if err := eventlog.InstallAsEventCreate(natsEventSource, eventlog.Info|eventlog.Error|eventlog.Warning); err != nil {
if !strings.Contains(err.Error(), "registry key already exists") {
panic(fmt.Sprintf("could not access event log: %v", err))
}
}
w, err := eventlog.Open(natsEventSource)
if err != nil {
panic(fmt.Sprintf("could not open event log: %v", err))
}
return &SysLogger{
writer: w,
debug: debug,
trace: trace,
}
}
// NewRemoteSysLogger creates a remote event logger
func NewRemoteSysLogger(fqn string, debug, trace bool) *SysLogger {
w, err := eventlog.OpenRemote(fqn, natsEventSource)
if err != nil {
panic(fmt.Sprintf("could not open event log: %v", err))
}
return &SysLogger{
writer: w,
debug: debug,
trace: trace,
}
}
func formatMsg(tag, format string, v ...interface{}) string {
orig := fmt.Sprintf(format, v...)
return fmt.Sprintf("pid[%d][%s]: %s", os.Getpid(), tag, orig)
}
// Noticef logs a notice statement
func (l *SysLogger) Noticef(format string, v ...interface{}) {
l.writer.Info(1, formatMsg("NOTICE", format, v...))
}
// Fatalf logs a fatal error
func (l *SysLogger) Fatalf(format string, v ...interface{}) {
msg := formatMsg("FATAL", format, v...)
l.writer.Error(5, msg)
panic(msg)
}
// Errorf logs an error statement
func (l *SysLogger) Errorf(format string, v ...interface{}) {
l.writer.Error(2, formatMsg("ERROR", format, v...))
}
// Debugf logs a debug statement
func (l *SysLogger) Debugf(format string, v ...interface{}) {
if l.debug {
l.writer.Info(3, formatMsg("DEBUG", format, v...))
}
}
// Tracef logs a trace statement
func (l *SysLogger) Tracef(format string, v ...interface{}) {
if l.trace {
l.writer.Info(4, formatMsg("TRACE", format, v...))
}
}

View File

@ -0,0 +1,139 @@
// Copyright 2012-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build windows
package logger
import (
"os/exec"
"strings"
"testing"
"golang.org/x/sys/windows/svc/eventlog"
)
// Skips testing if we do not have privledges to run this test.
// This lets us skip the tests for general (non admin/system) users.
func checkPrivledges(t *testing.T) {
src := "NATS-eventlog-testsource"
defer eventlog.Remove(src)
if err := eventlog.InstallAsEventCreate(src, eventlog.Info|eventlog.Error|eventlog.Warning); err != nil {
if strings.Contains(err.Error(), "Access is denied") {
t.Skip("skipping: elevated privledges are required.")
}
// let the tests report other types of errors
}
}
// lastLogEntryContains reads the last entry (/c:1 /rd:true) written
// to the event log by the NATS-Server source, returning true if the
// passed text was found, false otherwise.
func lastLogEntryContains(t *testing.T, text string) bool {
var output []byte
var err error
cmd := exec.Command("wevtutil.exe", "qe", "Application", "/q:*[System[Provider[@Name='NATS-Server']]]",
"/rd:true", "/c:1")
if output, err = cmd.Output(); err != nil {
t.Fatalf("Unable to execute command: %v", err)
}
return strings.Contains(string(output), text)
}
// TestSysLogger tests event logging on windows
func TestSysLogger(t *testing.T) {
checkPrivledges(t)
logger := NewSysLogger(false, false)
if logger.debug {
t.Fatalf("Expected %t, received %t\n", false, logger.debug)
}
if logger.trace {
t.Fatalf("Expected %t, received %t\n", false, logger.trace)
}
logger.Noticef("%s", "Noticef")
if !lastLogEntryContains(t, "[NOTICE]: Noticef") {
t.Fatalf("missing log entry")
}
logger.Errorf("%s", "Errorf")
if !lastLogEntryContains(t, "[ERROR]: Errorf") {
t.Fatalf("missing log entry")
}
logger.Tracef("%s", "Tracef")
if lastLogEntryContains(t, "Tracef") {
t.Fatalf("should not contain log entry")
}
logger.Debugf("%s", "Debugf")
if lastLogEntryContains(t, "Debugf") {
t.Fatalf("should not contain log entry")
}
}
// TestSysLoggerWithDebugAndTrace tests event logging
func TestSysLoggerWithDebugAndTrace(t *testing.T) {
checkPrivledges(t)
logger := NewSysLogger(true, true)
if !logger.debug {
t.Fatalf("Expected %t, received %t\n", true, logger.debug)
}
if !logger.trace {
t.Fatalf("Expected %t, received %t\n", true, logger.trace)
}
logger.Tracef("%s", "Tracef")
if !lastLogEntryContains(t, "[TRACE]: Tracef") {
t.Fatalf("missing log entry")
}
logger.Debugf("%s", "Debugf")
if !lastLogEntryContains(t, "[DEBUG]: Debugf") {
t.Fatalf("missing log entry")
}
}
// TestSysLoggerWithDebugAndTrace tests remote event logging
func TestRemoteSysLoggerWithDebugAndTrace(t *testing.T) {
checkPrivledges(t)
logger := NewRemoteSysLogger("", true, true)
if !logger.debug {
t.Fatalf("Expected %t, received %t\n", true, logger.debug)
}
if !logger.trace {
t.Fatalf("Expected %t, received %t\n", true, logger.trace)
}
logger.Tracef("NATS %s", "[TRACE]: Remote Noticef")
if !lastLogEntryContains(t, "Remote Noticef") {
t.Fatalf("missing log entry")
}
}
func TestSysLoggerFatalf(t *testing.T) {
defer func() {
if r := recover(); r != nil {
if !lastLogEntryContains(t, "[FATAL]: Fatalf") {
t.Fatalf("missing log entry")
}
}
}()
checkPrivledges(t)
logger := NewSysLogger(true, true)
logger.Fatalf("%s", "Fatalf")
t.Fatalf("did not panic when expected to")
}

249
vendor/github.com/nats-io/gnatsd/server/auth.go generated vendored Normal file
View File

@ -0,0 +1,249 @@
// Copyright 2012-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package server
import (
"crypto/tls"
"fmt"
"strings"
"golang.org/x/crypto/bcrypt"
)
// Authentication is an interface for implementing authentication
type Authentication interface {
// Check if a client is authorized to connect
Check(c ClientAuthentication) bool
}
// ClientAuthentication is an interface for client authentication
type ClientAuthentication interface {
// Get options associated with a client
GetOpts() *clientOpts
// If TLS is enabled, TLS ConnectionState, nil otherwise
GetTLSConnectionState() *tls.ConnectionState
// Optionally map a user after auth.
RegisterUser(*User)
}
// User is for multiple accounts/users.
type User struct {
Username string `json:"user"`
Password string `json:"password"`
Permissions *Permissions `json:"permissions"`
}
// clone performs a deep copy of the User struct, returning a new clone with
// all values copied.
func (u *User) clone() *User {
if u == nil {
return nil
}
clone := &User{}
*clone = *u
clone.Permissions = u.Permissions.clone()
return clone
}
// Permissions are the allowed subjects on a per
// publish or subscribe basis.
type Permissions struct {
Publish []string `json:"publish"`
Subscribe []string `json:"subscribe"`
}
// RoutePermissions are similar to user permissions
// but describe what a server can import/export from and to
// another server.
type RoutePermissions struct {
Import []string `json:"import"`
Export []string `json:"export"`
}
// clone performs a deep copy of the Permissions struct, returning a new clone
// with all values copied.
func (p *Permissions) clone() *Permissions {
if p == nil {
return nil
}
clone := &Permissions{}
if p.Publish != nil {
clone.Publish = make([]string, len(p.Publish))
copy(clone.Publish, p.Publish)
}
if p.Subscribe != nil {
clone.Subscribe = make([]string, len(p.Subscribe))
copy(clone.Subscribe, p.Subscribe)
}
return clone
}
// configureAuthorization will do any setup needed for authorization.
// Lock is assumed held.
func (s *Server) configureAuthorization() {
if s.opts == nil {
return
}
// Snapshot server options.
opts := s.getOpts()
// Check for multiple users first
// This just checks and sets up the user map if we have multiple users.
if opts.CustomClientAuthentication != nil {
s.info.AuthRequired = true
} else if opts.Users != nil {
s.users = make(map[string]*User)
for _, u := range opts.Users {
s.users[u.Username] = u
}
s.info.AuthRequired = true
} else if opts.Username != "" || opts.Authorization != "" {
s.info.AuthRequired = true
} else {
s.users = nil
s.info.AuthRequired = false
}
}
// checkAuthorization will check authorization based on client type and
// return boolean indicating if client is authorized.
func (s *Server) checkAuthorization(c *client) bool {
switch c.typ {
case CLIENT:
return s.isClientAuthorized(c)
case ROUTER:
return s.isRouterAuthorized(c)
default:
return false
}
}
// hasUsers leyt's us know if we have a users array.
func (s *Server) hasUsers() bool {
s.mu.Lock()
hu := s.users != nil
s.mu.Unlock()
return hu
}
// isClientAuthorized will check the client against the proper authorization method and data.
// This could be token or username/password based.
func (s *Server) isClientAuthorized(c *client) bool {
// Snapshot server options.
opts := s.getOpts()
// Check custom auth first, then multiple users, then token, then single user/pass.
if opts.CustomClientAuthentication != nil {
return opts.CustomClientAuthentication.Check(c)
} else if s.hasUsers() {
s.mu.Lock()
user, ok := s.users[c.opts.Username]
s.mu.Unlock()
if !ok {
return false
}
ok = comparePasswords(user.Password, c.opts.Password)
// If we are authorized, register the user which will properly setup any permissions
// for pub/sub authorizations.
if ok {
c.RegisterUser(user)
}
return ok
} else if opts.Authorization != "" {
return comparePasswords(opts.Authorization, c.opts.Authorization)
} else if opts.Username != "" {
if opts.Username != c.opts.Username {
return false
}
return comparePasswords(opts.Password, c.opts.Password)
}
return true
}
// checkRouterAuth checks optional router authorization which can be nil or username/password.
func (s *Server) isRouterAuthorized(c *client) bool {
// Snapshot server options.
opts := s.getOpts()
if s.opts.CustomRouterAuthentication != nil {
return s.opts.CustomRouterAuthentication.Check(c)
}
if opts.Cluster.Username == "" {
return true
}
if opts.Cluster.Username != c.opts.Username {
return false
}
if !comparePasswords(opts.Cluster.Password, c.opts.Password) {
return false
}
c.setRoutePermissions(opts.Cluster.Permissions)
return true
}
// removeUnauthorizedSubs removes any subscriptions the client has that are no
// longer authorized, e.g. due to a config reload.
func (s *Server) removeUnauthorizedSubs(c *client) {
c.mu.Lock()
if c.perms == nil {
c.mu.Unlock()
return
}
subs := make(map[string]*subscription, len(c.subs))
for sid, sub := range c.subs {
subs[sid] = sub
}
c.mu.Unlock()
for sid, sub := range subs {
if !c.canSubscribe(sub.subject) {
_ = s.sl.Remove(sub)
c.mu.Lock()
delete(c.subs, sid)
c.mu.Unlock()
c.sendErr(fmt.Sprintf("Permissions Violation for Subscription to %q (sid %s)",
sub.subject, sub.sid))
s.Noticef("Removed sub %q for user %q - not authorized",
string(sub.subject), c.opts.Username)
}
}
}
// Support for bcrypt stored passwords and tokens.
const bcryptPrefix = "$2a$"
// isBcrypt checks whether the given password or token is bcrypted.
func isBcrypt(password string) bool {
return strings.HasPrefix(password, bcryptPrefix)
}
func comparePasswords(serverPassword, clientPassword string) bool {
// Check to see if the server password is a bcrypt hash
if isBcrypt(serverPassword) {
if err := bcrypt.CompareHashAndPassword([]byte(serverPassword), []byte(clientPassword)); err != nil {
return false
}
} else if serverPassword != clientPassword {
return false
}
return true
}

99
vendor/github.com/nats-io/gnatsd/server/auth_test.go generated vendored Normal file
View File

@ -0,0 +1,99 @@
// Copyright 2012-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package server
import (
"reflect"
"testing"
)
func TestUserCloneNilPermissions(t *testing.T) {
user := &User{
Username: "foo",
Password: "bar",
}
clone := user.clone()
if !reflect.DeepEqual(user, clone) {
t.Fatalf("Cloned Users are incorrect.\nexpected: %+v\ngot: %+v",
user, clone)
}
clone.Password = "baz"
if reflect.DeepEqual(user, clone) {
t.Fatal("Expected Users to be different")
}
}
func TestUserClone(t *testing.T) {
user := &User{
Username: "foo",
Password: "bar",
Permissions: &Permissions{
Publish: []string{"foo"},
Subscribe: []string{"bar"},
},
}
clone := user.clone()
if !reflect.DeepEqual(user, clone) {
t.Fatalf("Cloned Users are incorrect.\nexpected: %+v\ngot: %+v",
user, clone)
}
clone.Permissions.Subscribe = []string{"baz"}
if reflect.DeepEqual(user, clone) {
t.Fatal("Expected Users to be different")
}
}
func TestUserClonePermissionsNoLists(t *testing.T) {
user := &User{
Username: "foo",
Password: "bar",
Permissions: &Permissions{},
}
clone := user.clone()
if clone.Permissions.Publish != nil {
t.Fatalf("Expected Publish to be nil, got: %v", clone.Permissions.Publish)
}
if clone.Permissions.Subscribe != nil {
t.Fatalf("Expected Subscribe to be nil, got: %v", clone.Permissions.Subscribe)
}
}
func TestUserCloneNoPermissions(t *testing.T) {
user := &User{
Username: "foo",
Password: "bar",
}
clone := user.clone()
if clone.Permissions != nil {
t.Fatalf("Expected Permissions to be nil, got: %v", clone.Permissions)
}
}
func TestUserCloneNil(t *testing.T) {
user := (*User)(nil)
clone := user.clone()
if clone != nil {
t.Fatalf("Expected nil, got: %+v", clone)
}
}

View File

@ -0,0 +1,97 @@
// Copyright 2016-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package server
import (
"crypto/tls"
)
// Where we maintain all of the available ciphers
var cipherMap = map[string]uint16{
"TLS_RSA_WITH_RC4_128_SHA": tls.TLS_RSA_WITH_RC4_128_SHA,
"TLS_RSA_WITH_3DES_EDE_CBC_SHA": tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
"TLS_RSA_WITH_AES_128_CBC_SHA": tls.TLS_RSA_WITH_AES_128_CBC_SHA,
"TLS_RSA_WITH_AES_128_CBC_SHA256": tls.TLS_RSA_WITH_AES_128_CBC_SHA256,
"TLS_RSA_WITH_AES_256_CBC_SHA": tls.TLS_RSA_WITH_AES_256_CBC_SHA,
"TLS_RSA_WITH_AES_256_GCM_SHA384": tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
"TLS_ECDHE_ECDSA_WITH_RC4_128_SHA": tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
"TLS_ECDHE_RSA_WITH_RC4_128_SHA": tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA,
"TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384": tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384": tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305": tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305": tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
}
var cipherMapByID = map[uint16]string{
tls.TLS_RSA_WITH_RC4_128_SHA: "TLS_RSA_WITH_RC4_128_SHA",
tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA: "TLS_RSA_WITH_3DES_EDE_CBC_SHA",
tls.TLS_RSA_WITH_AES_128_CBC_SHA: "TLS_RSA_WITH_AES_128_CBC_SHA",
tls.TLS_RSA_WITH_AES_128_CBC_SHA256: "TLS_RSA_WITH_AES_128_CBC_SHA256",
tls.TLS_RSA_WITH_AES_256_CBC_SHA: "TLS_RSA_WITH_AES_256_CBC_SHA",
tls.TLS_RSA_WITH_AES_256_GCM_SHA384: "TLS_RSA_WITH_AES_256_GCM_SHA384",
tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA: "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA",
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA: "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA: "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA: "TLS_ECDHE_RSA_WITH_RC4_128_SHA",
tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA: "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA",
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA: "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256: "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256: "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA: "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305: "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305",
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305: "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
}
func defaultCipherSuites() []uint16 {
return []uint16{
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
}
}
// Where we maintain available curve preferences
var curvePreferenceMap = map[string]tls.CurveID{
"CurveP256": tls.CurveP256,
"CurveP384": tls.CurveP384,
"CurveP521": tls.CurveP521,
"X25519": tls.X25519,
}
// reorder to default to the highest level of security. See:
// https://blog.bracebin.com/achieving-perfect-ssl-labs-score-with-go
func defaultCurvePreferences() []tls.CurveID {
return []tls.CurveID{
tls.CurveP521,
tls.CurveP384,
tls.X25519, // faster than P256, arguably more secure
tls.CurveP256,
}
}

1810
vendor/github.com/nats-io/gnatsd/server/client.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

1096
vendor/github.com/nats-io/gnatsd/server/client_test.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,361 @@
// Copyright 2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package server
import (
"fmt"
"net"
"strings"
"testing"
"time"
nats "github.com/nats-io/go-nats"
)
func checkClosedConns(t *testing.T, s *Server, num int, wait time.Duration) {
t.Helper()
checkFor(t, wait, 5*time.Millisecond, func() error {
if nc := s.numClosedConns(); nc != num {
return fmt.Errorf("Closed conns expected to be %v, got %v", num, nc)
}
return nil
})
}
func checkTotalClosedConns(t *testing.T, s *Server, num uint64, wait time.Duration) {
t.Helper()
checkFor(t, wait, 5*time.Millisecond, func() error {
if nc := s.totalClosedConns(); nc != num {
return fmt.Errorf("Total closed conns expected to be %v, got %v", num, nc)
}
return nil
})
}
func TestClosedConnsAccounting(t *testing.T) {
opts := DefaultOptions()
opts.MaxClosedClients = 10
s := RunServer(opts)
defer s.Shutdown()
wait := 20 * time.Millisecond
nc, err := nats.Connect(fmt.Sprintf("nats://%s:%d", opts.Host, opts.Port))
if err != nil {
t.Fatalf("Error on connect: %v", err)
}
nc.Close()
checkClosedConns(t, s, 1, wait)
conns := s.closedClients()
if lc := len(conns); lc != 1 {
t.Fatalf("len(conns) expected to be %d, got %d\n", 1, lc)
}
if conns[0].Cid != 1 {
t.Fatalf("Expected CID to be 1, got %d\n", conns[0].Cid)
}
// Now create 21 more
for i := 0; i < 21; i++ {
nc, err = nats.Connect(fmt.Sprintf("nats://%s:%d", opts.Host, opts.Port))
if err != nil {
t.Fatalf("Error on connect: %v", err)
}
nc.Close()
checkTotalClosedConns(t, s, uint64(i+2), wait)
}
checkClosedConns(t, s, opts.MaxClosedClients, wait)
checkTotalClosedConns(t, s, 22, wait)
conns = s.closedClients()
if lc := len(conns); lc != opts.MaxClosedClients {
t.Fatalf("len(conns) expected to be %d, got %d\n",
opts.MaxClosedClients, lc)
}
// Set it to the start after overflow.
cid := uint64(22 - opts.MaxClosedClients)
for _, ci := range conns {
cid++
if ci.Cid != cid {
t.Fatalf("Expected cid of %d, got %d\n", cid, ci.Cid)
}
}
}
func TestClosedConnsSubsAccounting(t *testing.T) {
opts := DefaultOptions()
s := RunServer(opts)
defer s.Shutdown()
url := fmt.Sprintf("nats://%s:%d", opts.Host, opts.Port)
nc, err := nats.Connect(url)
if err != nil {
t.Fatalf("Error on subscribe: %v", err)
}
// Now create some subscriptions
numSubs := 10
for i := 0; i < numSubs; i++ {
subj := fmt.Sprintf("foo.%d", i)
nc.Subscribe(subj, func(m *nats.Msg) {})
}
nc.Flush()
nc.Close()
checkClosedConns(t, s, 1, 20*time.Millisecond)
conns := s.closedClients()
if lc := len(conns); lc != 1 {
t.Fatalf("len(conns) expected to be 1, got %d\n", lc)
}
ci := conns[0]
if len(ci.subs) != numSubs {
t.Fatalf("Expected number of Subs to be %d, got %d\n", numSubs, len(ci.subs))
}
}
func checkReason(t *testing.T, reason string, expected ClosedState) {
if !strings.Contains(reason, expected.String()) {
t.Fatalf("Expected closed connection with `%s` state, got `%s`\n",
expected, reason)
}
}
func TestClosedAuthorizationTimeout(t *testing.T) {
serverOptions := DefaultOptions()
serverOptions.Authorization = "my_token"
serverOptions.AuthTimeout = 0.4
s := RunServer(serverOptions)
defer s.Shutdown()
conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", serverOptions.Host, serverOptions.Port))
if err != nil {
t.Fatalf("Error dialing server: %v\n", err)
}
defer conn.Close()
checkClosedConns(t, s, 1, 2*time.Second)
conns := s.closedClients()
if lc := len(conns); lc != 1 {
t.Fatalf("len(conns) expected to be %d, got %d\n", 1, lc)
}
checkReason(t, conns[0].Reason, AuthenticationTimeout)
}
func TestClosedAuthorizationViolation(t *testing.T) {
serverOptions := DefaultOptions()
serverOptions.Authorization = "my_token"
s := RunServer(serverOptions)
defer s.Shutdown()
opts := s.getOpts()
url := fmt.Sprintf("nats://%s:%d", opts.Host, opts.Port)
nc, err := nats.Connect(url)
if err == nil {
nc.Close()
t.Fatal("Expected failure for connection")
}
checkClosedConns(t, s, 1, 2*time.Second)
conns := s.closedClients()
if lc := len(conns); lc != 1 {
t.Fatalf("len(conns) expected to be %d, got %d\n", 1, lc)
}
checkReason(t, conns[0].Reason, AuthenticationViolation)
}
func TestClosedUPAuthorizationViolation(t *testing.T) {
serverOptions := DefaultOptions()
serverOptions.Username = "my_user"
serverOptions.Password = "my_secret"
s := RunServer(serverOptions)
defer s.Shutdown()
opts := s.getOpts()
url := fmt.Sprintf("nats://%s:%d", opts.Host, opts.Port)
nc, err := nats.Connect(url)
if err == nil {
nc.Close()
t.Fatal("Expected failure for connection")
}
url2 := fmt.Sprintf("nats://my_user:wrong_pass@%s:%d", opts.Host, opts.Port)
nc, err = nats.Connect(url2)
if err == nil {
nc.Close()
t.Fatal("Expected failure for connection")
}
checkClosedConns(t, s, 2, 2*time.Second)
conns := s.closedClients()
if lc := len(conns); lc != 2 {
t.Fatalf("len(conns) expected to be %d, got %d\n", 2, lc)
}
checkReason(t, conns[0].Reason, AuthenticationViolation)
checkReason(t, conns[1].Reason, AuthenticationViolation)
}
func TestClosedMaxPayload(t *testing.T) {
serverOptions := DefaultOptions()
serverOptions.MaxPayload = 100
s := RunServer(serverOptions)
defer s.Shutdown()
opts := s.getOpts()
endpoint := fmt.Sprintf("%s:%d", opts.Host, opts.Port)
conn, err := net.DialTimeout("tcp", endpoint, time.Second)
if err != nil {
t.Fatalf("Could not make a raw connection to the server: %v", err)
}
defer conn.Close()
// This should trigger it.
pub := fmt.Sprintf("PUB foo.bar 1024\r\n")
conn.Write([]byte(pub))
checkClosedConns(t, s, 1, 2*time.Second)
conns := s.closedClients()
if lc := len(conns); lc != 1 {
t.Fatalf("len(conns) expected to be %d, got %d\n", 1, lc)
}
checkReason(t, conns[0].Reason, MaxPayloadExceeded)
}
func TestClosedSlowConsumerWriteDeadline(t *testing.T) {
opts := DefaultOptions()
opts.WriteDeadline = 10 * time.Millisecond // Make very small to trip.
opts.MaxPending = 500 * 1024 * 1024 // Set high so it will not trip here.
s := RunServer(opts)
defer s.Shutdown()
c, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", opts.Host, opts.Port), 3*time.Second)
if err != nil {
t.Fatalf("Error on connect: %v", err)
}
defer c.Close()
if _, err := c.Write([]byte("CONNECT {}\r\nPING\r\nSUB foo 1\r\n")); err != nil {
t.Fatalf("Error sending protocols to server: %v", err)
}
// Reduce socket buffer to increase reliability of data backing up in the server destined
// for our subscribed client.
c.(*net.TCPConn).SetReadBuffer(128)
url := fmt.Sprintf("nats://%s:%d", opts.Host, opts.Port)
sender, err := nats.Connect(url)
if err != nil {
t.Fatalf("Error on connect: %v", err)
}
defer sender.Close()
payload := make([]byte, 1024*1024)
for i := 0; i < 100; i++ {
if err := sender.Publish("foo", payload); err != nil {
t.Fatalf("Error on publish: %v", err)
}
}
// Flush sender connection to ensure that all data has been sent.
if err := sender.Flush(); err != nil {
t.Fatalf("Error on flush: %v", err)
}
// At this point server should have closed connection c.
checkClosedConns(t, s, 1, 2*time.Second)
conns := s.closedClients()
if lc := len(conns); lc != 1 {
t.Fatalf("len(conns) expected to be %d, got %d\n", 1, lc)
}
checkReason(t, conns[0].Reason, SlowConsumerWriteDeadline)
}
func TestClosedSlowConsumerPendingBytes(t *testing.T) {
opts := DefaultOptions()
opts.WriteDeadline = 30 * time.Second // Wait for long time so write deadline does not trigger slow consumer.
opts.MaxPending = 1 * 1024 * 1024 // Set to low value (1MB) to allow SC to trip.
s := RunServer(opts)
defer s.Shutdown()
c, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", opts.Host, opts.Port), 3*time.Second)
if err != nil {
t.Fatalf("Error on connect: %v", err)
}
defer c.Close()
if _, err := c.Write([]byte("CONNECT {}\r\nPING\r\nSUB foo 1\r\n")); err != nil {
t.Fatalf("Error sending protocols to server: %v", err)
}
// Reduce socket buffer to increase reliability of data backing up in the server destined
// for our subscribed client.
c.(*net.TCPConn).SetReadBuffer(128)
url := fmt.Sprintf("nats://%s:%d", opts.Host, opts.Port)
sender, err := nats.Connect(url)
if err != nil {
t.Fatalf("Error on connect: %v", err)
}
defer sender.Close()
payload := make([]byte, 1024*1024)
for i := 0; i < 100; i++ {
if err := sender.Publish("foo", payload); err != nil {
t.Fatalf("Error on publish: %v", err)
}
}
// Flush sender connection to ensure that all data has been sent.
if err := sender.Flush(); err != nil {
t.Fatalf("Error on flush: %v", err)
}
// At this point server should have closed connection c.
checkClosedConns(t, s, 1, 2*time.Second)
conns := s.closedClients()
if lc := len(conns); lc != 1 {
t.Fatalf("len(conns) expected to be %d, got %d\n", 1, lc)
}
checkReason(t, conns[0].Reason, SlowConsumerPendingBytes)
}
func TestClosedTLSHandshake(t *testing.T) {
opts, err := ProcessConfigFile("./configs/tls.conf")
if err != nil {
t.Fatalf("Error processing config file: %v", err)
}
opts.TLSVerify = true
opts.NoLog = true
opts.NoSigs = true
s := RunServer(opts)
defer s.Shutdown()
nc, err := nats.Connect(fmt.Sprintf("tls://%s:%d", opts.Host, opts.Port))
if err == nil {
nc.Close()
t.Fatal("Expected failure for connection")
}
checkClosedConns(t, s, 1, 2*time.Second)
conns := s.closedClients()
if lc := len(conns); lc != 1 {
t.Fatalf("len(conns) expected to be %d, got %d\n", 1, lc)
}
checkReason(t, conns[0].Reason, TLSHandshakeError)
}

124
vendor/github.com/nats-io/gnatsd/server/const.go generated vendored Normal file
View File

@ -0,0 +1,124 @@
// Copyright 2012-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package server
import (
"time"
)
// Command is a signal used to control a running gnatsd process.
type Command string
// Valid Command values.
const (
CommandStop = Command("stop")
CommandQuit = Command("quit")
CommandReopen = Command("reopen")
CommandReload = Command("reload")
)
var (
// gitCommit injected at build
gitCommit string
)
const (
// VERSION is the current version for the server.
VERSION = "1.2.0"
// PROTO is the currently supported protocol.
// 0 was the original
// 1 maintains proto 0, adds echo abilities for CONNECT from the client. Clients
// should not send echo unless proto in INFO is >= 1.
PROTO = 1
// DEFAULT_PORT is the default port for client connections.
DEFAULT_PORT = 4222
// RANDOM_PORT is the value for port that, when supplied, will cause the
// server to listen on a randomly-chosen available port. The resolved port
// is available via the Addr() method.
RANDOM_PORT = -1
// DEFAULT_HOST defaults to all interfaces.
DEFAULT_HOST = "0.0.0.0"
// MAX_CONTROL_LINE_SIZE is the maximum allowed protocol control line size.
// 1k should be plenty since payloads sans connect string are separate
MAX_CONTROL_LINE_SIZE = 1024
// MAX_PAYLOAD_SIZE is the maximum allowed payload size. Should be using
// something different if > 1MB payloads are needed.
MAX_PAYLOAD_SIZE = (1024 * 1024)
// MAX_PENDING_SIZE is the maximum outbound pending bytes per client.
MAX_PENDING_SIZE = (256 * 1024 * 1024)
// DEFAULT_MAX_CONNECTIONS is the default maximum connections allowed.
DEFAULT_MAX_CONNECTIONS = (64 * 1024)
// TLS_TIMEOUT is the TLS wait time.
TLS_TIMEOUT = 500 * time.Millisecond
// AUTH_TIMEOUT is the authorization wait time.
AUTH_TIMEOUT = 2 * TLS_TIMEOUT
// DEFAULT_PING_INTERVAL is how often pings are sent to clients and routes.
DEFAULT_PING_INTERVAL = 2 * time.Minute
// DEFAULT_PING_MAX_OUT is maximum allowed pings outstanding before disconnect.
DEFAULT_PING_MAX_OUT = 2
// CR_LF string
CR_LF = "\r\n"
// LEN_CR_LF hold onto the computed size.
LEN_CR_LF = len(CR_LF)
// DEFAULT_FLUSH_DEADLINE is the write/flush deadlines.
DEFAULT_FLUSH_DEADLINE = 2 * time.Second
// DEFAULT_HTTP_PORT is the default monitoring port.
DEFAULT_HTTP_PORT = 8222
// ACCEPT_MIN_SLEEP is the minimum acceptable sleep times on temporary errors.
ACCEPT_MIN_SLEEP = 10 * time.Millisecond
// ACCEPT_MAX_SLEEP is the maximum acceptable sleep times on temporary errors
ACCEPT_MAX_SLEEP = 1 * time.Second
// DEFAULT_ROUTE_CONNECT Route solicitation intervals.
DEFAULT_ROUTE_CONNECT = 1 * time.Second
// DEFAULT_ROUTE_RECONNECT Route reconnect intervals.
DEFAULT_ROUTE_RECONNECT = 1 * time.Second
// DEFAULT_ROUTE_DIAL Route dial timeout.
DEFAULT_ROUTE_DIAL = 1 * time.Second
// PROTO_SNIPPET_SIZE is the default size of proto to print on parse errors.
PROTO_SNIPPET_SIZE = 32
// MAX_MSG_ARGS Maximum possible number of arguments from MSG proto.
MAX_MSG_ARGS = 4
// MAX_PUB_ARGS Maximum possible number of arguments from PUB proto.
MAX_PUB_ARGS = 3
// DEFAULT_REMOTE_QSUBS_SWEEPER
DEFAULT_REMOTE_QSUBS_SWEEPER = 30 * time.Second
// DEFAULT_MAX_CLOSED_CLIENTS
DEFAULT_MAX_CLOSED_CLIENTS = 10000
)

51
vendor/github.com/nats-io/gnatsd/server/errors.go generated vendored Normal file
View File

@ -0,0 +1,51 @@
// Copyright 2012-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package server
import "errors"
var (
// ErrConnectionClosed represents an error condition on a closed connection.
ErrConnectionClosed = errors.New("Connection Closed")
// ErrAuthorization represents an error condition on failed authorization.
ErrAuthorization = errors.New("Authorization Error")
// ErrAuthTimeout represents an error condition on failed authorization due to timeout.
ErrAuthTimeout = errors.New("Authorization Timeout")
// ErrMaxPayload represents an error condition when the payload is too big.
ErrMaxPayload = errors.New("Maximum Payload Exceeded")
// ErrMaxControlLine represents an error condition when the control line is too big.
ErrMaxControlLine = errors.New("Maximum Control Line Exceeded")
// ErrReservedPublishSubject represents an error condition when sending to a reserved subject, e.g. _SYS.>
ErrReservedPublishSubject = errors.New("Reserved Internal Subject")
// ErrBadClientProtocol signals a client requested an invalud client protocol.
ErrBadClientProtocol = errors.New("Invalid Client Protocol")
// ErrTooManyConnections signals a client that the maximum number of connections supported by the
// server has been reached.
ErrTooManyConnections = errors.New("Maximum Connections Exceeded")
// ErrTooManySubs signals a client that the maximum number of subscriptions per connection
// has been reached.
ErrTooManySubs = errors.New("Maximum Subscriptions Exceeded")
// ErrClientConnectedToRoutePort represents an error condition when a client
// attempted to connect to the route listen port.
ErrClientConnectedToRoutePort = errors.New("Attempted To Connect To Route Port")
)

184
vendor/github.com/nats-io/gnatsd/server/log.go generated vendored Normal file
View File

@ -0,0 +1,184 @@
// Copyright 2012-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package server
import (
"io"
"os"
"sync/atomic"
srvlog "github.com/nats-io/gnatsd/logger"
)
// Logger interface of the NATS Server
type Logger interface {
// Log a notice statement
Noticef(format string, v ...interface{})
// Log a fatal error
Fatalf(format string, v ...interface{})
// Log an error
Errorf(format string, v ...interface{})
// Log a debug statement
Debugf(format string, v ...interface{})
// Log a trace statement
Tracef(format string, v ...interface{})
}
// ConfigureLogger configures and sets the logger for the server.
func (s *Server) ConfigureLogger() {
var (
log Logger
// Snapshot server options.
opts = s.getOpts()
)
syslog := opts.Syslog
if isWindowsService() && opts.LogFile == "" {
// Enable syslog if no log file is specified and we're running as a
// Windows service so that logs are written to the Windows event log.
syslog = true
}
if opts.LogFile != "" {
log = srvlog.NewFileLogger(opts.LogFile, opts.Logtime, opts.Debug, opts.Trace, true)
} else if opts.RemoteSyslog != "" {
log = srvlog.NewRemoteSysLogger(opts.RemoteSyslog, opts.Debug, opts.Trace)
} else if syslog {
log = srvlog.NewSysLogger(opts.Debug, opts.Trace)
} else {
colors := true
// Check to see if stderr is being redirected and if so turn off color
// Also turn off colors if we're running on Windows where os.Stderr.Stat() returns an invalid handle-error
stat, err := os.Stderr.Stat()
if err != nil || (stat.Mode()&os.ModeCharDevice) == 0 {
colors = false
}
log = srvlog.NewStdLogger(opts.Logtime, opts.Debug, opts.Trace, colors, true)
}
s.SetLogger(log, opts.Debug, opts.Trace)
}
// SetLogger sets the logger of the server
func (s *Server) SetLogger(logger Logger, debugFlag, traceFlag bool) {
if debugFlag {
atomic.StoreInt32(&s.logging.debug, 1)
} else {
atomic.StoreInt32(&s.logging.debug, 0)
}
if traceFlag {
atomic.StoreInt32(&s.logging.trace, 1)
} else {
atomic.StoreInt32(&s.logging.trace, 0)
}
s.logging.Lock()
if s.logging.logger != nil {
// Check to see if the logger implements io.Closer. This could be a
// logger from another process embedding the NATS server or a dummy
// test logger that may not implement that interface.
if l, ok := s.logging.logger.(io.Closer); ok {
if err := l.Close(); err != nil {
s.Errorf("Error closing logger: %v", err)
}
}
}
s.logging.logger = logger
s.logging.Unlock()
}
// If the logger is a file based logger, close and re-open the file.
// This allows for file rotation by 'mv'ing the file then signaling
// the process to trigger this function.
func (s *Server) ReOpenLogFile() {
// Check to make sure this is a file logger.
s.logging.RLock()
ll := s.logging.logger
s.logging.RUnlock()
if ll == nil {
s.Noticef("File log re-open ignored, no logger")
return
}
// Snapshot server options.
opts := s.getOpts()
if opts.LogFile == "" {
s.Noticef("File log re-open ignored, not a file logger")
} else {
fileLog := srvlog.NewFileLogger(opts.LogFile,
opts.Logtime, opts.Debug, opts.Trace, true)
s.SetLogger(fileLog, opts.Debug, opts.Trace)
s.Noticef("File log re-opened")
}
}
// Noticef logs a notice statement
func (s *Server) Noticef(format string, v ...interface{}) {
s.executeLogCall(func(logger Logger, format string, v ...interface{}) {
logger.Noticef(format, v...)
}, format, v...)
}
// Errorf logs an error
func (s *Server) Errorf(format string, v ...interface{}) {
s.executeLogCall(func(logger Logger, format string, v ...interface{}) {
logger.Errorf(format, v...)
}, format, v...)
}
// Fatalf logs a fatal error
func (s *Server) Fatalf(format string, v ...interface{}) {
s.executeLogCall(func(logger Logger, format string, v ...interface{}) {
logger.Fatalf(format, v...)
}, format, v...)
}
// Debugf logs a debug statement
func (s *Server) Debugf(format string, v ...interface{}) {
if atomic.LoadInt32(&s.logging.debug) == 0 {
return
}
s.executeLogCall(func(logger Logger, format string, v ...interface{}) {
logger.Debugf(format, v...)
}, format, v...)
}
// Tracef logs a trace statement
func (s *Server) Tracef(format string, v ...interface{}) {
if atomic.LoadInt32(&s.logging.trace) == 0 {
return
}
s.executeLogCall(func(logger Logger, format string, v ...interface{}) {
logger.Tracef(format, v...)
}, format, v...)
}
func (s *Server) executeLogCall(f func(logger Logger, format string, v ...interface{}), format string, args ...interface{}) {
s.logging.RLock()
defer s.logging.RUnlock()
if s.logging.logger == nil {
return
}
f(s.logging.logger, format, args...)
}

175
vendor/github.com/nats-io/gnatsd/server/log_test.go generated vendored Normal file
View File

@ -0,0 +1,175 @@
// Copyright 2012-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package server
import (
"fmt"
"io/ioutil"
"os"
"runtime"
"strings"
"sync"
"testing"
"github.com/nats-io/gnatsd/logger"
)
func TestSetLogger(t *testing.T) {
server := &Server{}
defer server.SetLogger(nil, false, false)
dl := &DummyLogger{}
server.SetLogger(dl, true, true)
// We assert that the logger has change to the DummyLogger
_ = server.logging.logger.(*DummyLogger)
if server.logging.debug != 1 {
t.Fatalf("Expected debug 1, received value %d\n", server.logging.debug)
}
if server.logging.trace != 1 {
t.Fatalf("Expected trace 1, received value %d\n", server.logging.trace)
}
// Check traces
expectedStr := "This is a Notice"
server.Noticef(expectedStr)
dl.checkContent(t, expectedStr)
expectedStr = "This is an Error"
server.Errorf(expectedStr)
dl.checkContent(t, expectedStr)
expectedStr = "This is a Fatal"
server.Fatalf(expectedStr)
dl.checkContent(t, expectedStr)
expectedStr = "This is a Debug"
server.Debugf(expectedStr)
dl.checkContent(t, expectedStr)
expectedStr = "This is a Trace"
server.Tracef(expectedStr)
dl.checkContent(t, expectedStr)
// Make sure that we can reset to fal
server.SetLogger(dl, false, false)
if server.logging.debug != 0 {
t.Fatalf("Expected debug 0, got %v", server.logging.debug)
}
if server.logging.trace != 0 {
t.Fatalf("Expected trace 0, got %v", server.logging.trace)
}
// Now, Debug and Trace should not produce anything
dl.msg = ""
server.Debugf("This Debug should not be traced")
dl.checkContent(t, "")
server.Tracef("This Trace should not be traced")
dl.checkContent(t, "")
}
type DummyLogger struct {
sync.Mutex
msg string
}
func (l *DummyLogger) checkContent(t *testing.T, expectedStr string) {
l.Lock()
defer l.Unlock()
if l.msg != expectedStr {
stackFatalf(t, "Expected log to be: %v, got %v", expectedStr, l.msg)
}
}
func (l *DummyLogger) Noticef(format string, v ...interface{}) {
l.Lock()
defer l.Unlock()
l.msg = fmt.Sprintf(format, v...)
}
func (l *DummyLogger) Errorf(format string, v ...interface{}) {
l.Lock()
defer l.Unlock()
l.msg = fmt.Sprintf(format, v...)
}
func (l *DummyLogger) Fatalf(format string, v ...interface{}) {
l.Lock()
defer l.Unlock()
l.msg = fmt.Sprintf(format, v...)
}
func (l *DummyLogger) Debugf(format string, v ...interface{}) {
l.Lock()
defer l.Unlock()
l.msg = fmt.Sprintf(format, v...)
}
func (l *DummyLogger) Tracef(format string, v ...interface{}) {
l.Lock()
defer l.Unlock()
l.msg = fmt.Sprintf(format, v...)
}
func TestReOpenLogFile(t *testing.T) {
// We can't rename the file log when still opened on Windows, so skip
if runtime.GOOS == "windows" {
t.SkipNow()
}
s := &Server{opts: &Options{}}
defer s.SetLogger(nil, false, false)
// First check with no logger
s.SetLogger(nil, false, false)
s.ReOpenLogFile()
// Then when LogFile is not provided.
dl := &DummyLogger{}
s.SetLogger(dl, false, false)
s.ReOpenLogFile()
dl.checkContent(t, "File log re-open ignored, not a file logger")
// Set a File log
s.opts.LogFile = "test.log"
defer os.Remove(s.opts.LogFile)
defer os.Remove(s.opts.LogFile + ".bak")
fileLog := logger.NewFileLogger(s.opts.LogFile, s.opts.Logtime, s.opts.Debug, s.opts.Trace, true)
s.SetLogger(fileLog, false, false)
// Add some log
expectedStr := "This is a Notice"
s.Noticef(expectedStr)
// Check content of log
buf, err := ioutil.ReadFile(s.opts.LogFile)
if err != nil {
t.Fatalf("Error reading file: %v", err)
}
if !strings.Contains(string(buf), expectedStr) {
t.Fatalf("Expected log to contain: %q, got %q", expectedStr, string(buf))
}
// Close the file and rename it
if err := os.Rename(s.opts.LogFile, s.opts.LogFile+".bak"); err != nil {
t.Fatalf("Unable to rename log file: %v", err)
}
// Now re-open LogFile
s.ReOpenLogFile()
// Content should indicate that we have re-opened the log
buf, err = ioutil.ReadFile(s.opts.LogFile)
if err != nil {
t.Fatalf("Error reading file: %v", err)
}
if strings.HasSuffix(string(buf), "File log-reopened") {
t.Fatalf("File should indicate that file log was re-opened, got: %v", string(buf))
}
// Make sure we can append to the log
s.Noticef("New message")
buf, err = ioutil.ReadFile(s.opts.LogFile)
if err != nil {
t.Fatalf("Error reading file: %v", err)
}
if strings.HasSuffix(string(buf), "New message") {
t.Fatalf("New message was not appended after file was re-opened, got: %v", string(buf))
}
}

1029
vendor/github.com/nats-io/gnatsd/server/monitor.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,147 @@
// Copyright 2013-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package server
import (
"time"
)
// Represents a connection info list. We use pointers since it will be sorted.
type ConnInfos []*ConnInfo
// For sorting
func (cl ConnInfos) Len() int { return len(cl) }
func (cl ConnInfos) Swap(i, j int) { cl[i], cl[j] = cl[j], cl[i] }
// SortOpt is a helper type to sort clients
type SortOpt string
// Possible sort options
const (
ByCid SortOpt = "cid" // By connection ID
ByStart SortOpt = "start" // By connection start time, same as CID
BySubs SortOpt = "subs" // By number of subscriptions
ByPending SortOpt = "pending" // By amount of data in bytes waiting to be sent to client
ByOutMsgs SortOpt = "msgs_to" // By number of messages sent
ByInMsgs SortOpt = "msgs_from" // By number of messages received
ByOutBytes SortOpt = "bytes_to" // By amount of bytes sent
ByInBytes SortOpt = "bytes_from" // By amount of bytes received
ByLast SortOpt = "last" // By the last activity
ByIdle SortOpt = "idle" // By the amount of inactivity
ByUptime SortOpt = "uptime" // By the amount of time connections exist
ByStop SortOpt = "stop" // By the stop time for a closed connection
ByReason SortOpt = "reason" // By the reason for a closed connection
)
// Individual sort options provide the Less for sort.Interface. Len and Swap are on cList.
// CID
type byCid struct{ ConnInfos }
func (l byCid) Less(i, j int) bool { return l.ConnInfos[i].Cid < l.ConnInfos[j].Cid }
// Number of Subscriptions
type bySubs struct{ ConnInfos }
func (l bySubs) Less(i, j int) bool { return l.ConnInfos[i].NumSubs < l.ConnInfos[j].NumSubs }
// Pending Bytes
type byPending struct{ ConnInfos }
func (l byPending) Less(i, j int) bool { return l.ConnInfos[i].Pending < l.ConnInfos[j].Pending }
// Outbound Msgs
type byOutMsgs struct{ ConnInfos }
func (l byOutMsgs) Less(i, j int) bool { return l.ConnInfos[i].OutMsgs < l.ConnInfos[j].OutMsgs }
// Inbound Msgs
type byInMsgs struct{ ConnInfos }
func (l byInMsgs) Less(i, j int) bool { return l.ConnInfos[i].InMsgs < l.ConnInfos[j].InMsgs }
// Outbound Bytes
type byOutBytes struct{ ConnInfos }
func (l byOutBytes) Less(i, j int) bool { return l.ConnInfos[i].OutBytes < l.ConnInfos[j].OutBytes }
// Inbound Bytes
type byInBytes struct{ ConnInfos }
func (l byInBytes) Less(i, j int) bool { return l.ConnInfos[i].InBytes < l.ConnInfos[j].InBytes }
// Last Activity
type byLast struct{ ConnInfos }
func (l byLast) Less(i, j int) bool {
return l.ConnInfos[i].LastActivity.UnixNano() < l.ConnInfos[j].LastActivity.UnixNano()
}
// Idle time
type byIdle struct{ ConnInfos }
func (l byIdle) Less(i, j int) bool {
ii := l.ConnInfos[i].LastActivity.Sub(l.ConnInfos[i].Start)
ij := l.ConnInfos[j].LastActivity.Sub(l.ConnInfos[j].Start)
return ii < ij
}
// Uptime
type byUptime struct {
ConnInfos
now time.Time
}
func (l byUptime) Less(i, j int) bool {
ci := l.ConnInfos[i]
cj := l.ConnInfos[j]
var upi, upj time.Duration
if ci.Stop == nil || ci.Stop.IsZero() {
upi = l.now.Sub(ci.Start)
} else {
upi = ci.Stop.Sub(ci.Start)
}
if cj.Stop == nil || cj.Stop.IsZero() {
upj = l.now.Sub(cj.Start)
} else {
upj = cj.Stop.Sub(cj.Start)
}
return upi < upj
}
// Stop
type byStop struct{ ConnInfos }
func (l byStop) Less(i, j int) bool {
ciStop := l.ConnInfos[i].Stop
cjStop := l.ConnInfos[j].Stop
return ciStop.Before(*cjStop)
}
// Reason
type byReason struct{ ConnInfos }
func (l byReason) Less(i, j int) bool {
return l.ConnInfos[i].Reason < l.ConnInfos[j].Reason
}
// IsValid determines if a sort option is valid
func (s SortOpt) IsValid() bool {
switch s {
case "", ByCid, ByStart, BySubs, ByPending, ByOutMsgs, ByInMsgs, ByOutBytes, ByInBytes, ByLast, ByIdle, ByUptime, ByStop, ByReason:
return true
default:
return false
}
}

1933
vendor/github.com/nats-io/gnatsd/server/monitor_test.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

87
vendor/github.com/nats-io/gnatsd/server/norace_test.go generated vendored Normal file
View File

@ -0,0 +1,87 @@
// Copyright 2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build !race
package server
import (
"fmt"
"math/rand"
"sync/atomic"
"testing"
"time"
"github.com/nats-io/go-nats"
)
// IMPORTANT: Tests in this file are not executed when running with the -race flag.
func TestAvoidSlowConsumerBigMessages(t *testing.T) {
opts := DefaultOptions() // Use defaults to make sure they avoid pending slow consumer.
s := RunServer(opts)
defer s.Shutdown()
nc1, err := nats.Connect(fmt.Sprintf("nats://%s:%d", opts.Host, opts.Port))
if err != nil {
t.Fatalf("Error on connect: %v", err)
}
defer nc1.Close()
nc2, err := nats.Connect(fmt.Sprintf("nats://%s:%d", opts.Host, opts.Port))
if err != nil {
t.Fatalf("Error on connect: %v", err)
}
defer nc2.Close()
data := make([]byte, 1024*1024) // 1MB payload
rand.Read(data)
expected := int32(500)
received := int32(0)
done := make(chan bool)
// Create Subscription.
nc1.Subscribe("slow.consumer", func(m *nats.Msg) {
// Just eat it so that we are not measuring
// code time, just delivery.
atomic.AddInt32(&received, 1)
if received >= expected {
done <- true
}
})
// Create Error handler
nc1.SetErrorHandler(func(c *nats.Conn, s *nats.Subscription, err error) {
t.Fatalf("Received an error on the subscription's connection: %v\n", err)
})
nc1.Flush()
for i := 0; i < int(expected); i++ {
nc2.Publish("slow.consumer", data)
}
nc2.Flush()
select {
case <-done:
return
case <-time.After(10 * time.Second):
r := atomic.LoadInt32(&received)
if s.NumSlowConsumers() > 0 {
t.Fatalf("Did not receive all large messages due to slow consumer status: %d of %d", r, expected)
}
t.Fatalf("Failed to receive all large messages: %d of %d\n", r, expected)
}
}

1259
vendor/github.com/nats-io/gnatsd/server/opts.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

1037
vendor/github.com/nats-io/gnatsd/server/opts_test.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

749
vendor/github.com/nats-io/gnatsd/server/parser.go generated vendored Normal file
View File

@ -0,0 +1,749 @@
// Copyright 2012-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package server
import (
"fmt"
)
type pubArg struct {
subject []byte
reply []byte
sid []byte
szb []byte
size int
}
type parseState struct {
state int
as int
drop int
pa pubArg
argBuf []byte
msgBuf []byte
scratch [MAX_CONTROL_LINE_SIZE]byte
}
// Parser constants
const (
OP_START = iota
OP_PLUS
OP_PLUS_O
OP_PLUS_OK
OP_MINUS
OP_MINUS_E
OP_MINUS_ER
OP_MINUS_ERR
OP_MINUS_ERR_SPC
MINUS_ERR_ARG
OP_C
OP_CO
OP_CON
OP_CONN
OP_CONNE
OP_CONNEC
OP_CONNECT
CONNECT_ARG
OP_P
OP_PU
OP_PUB
OP_PUB_SPC
PUB_ARG
OP_PI
OP_PIN
OP_PING
OP_PO
OP_PON
OP_PONG
MSG_PAYLOAD
MSG_END
OP_S
OP_SU
OP_SUB
OP_SUB_SPC
SUB_ARG
OP_U
OP_UN
OP_UNS
OP_UNSU
OP_UNSUB
OP_UNSUB_SPC
UNSUB_ARG
OP_M
OP_MS
OP_MSG
OP_MSG_SPC
MSG_ARG
OP_I
OP_IN
OP_INF
OP_INFO
INFO_ARG
)
func (c *client) parse(buf []byte) error {
var i int
var b byte
mcl := MAX_CONTROL_LINE_SIZE
if c.srv != nil && c.srv.getOpts() != nil {
mcl = c.srv.getOpts().MaxControlLine
}
// snapshot this, and reset when we receive a
// proper CONNECT if needed.
authSet := c.isAuthTimerSet()
// Move to loop instead of range syntax to allow jumping of i
for i = 0; i < len(buf); i++ {
b = buf[i]
switch c.state {
case OP_START:
if b != 'C' && b != 'c' && authSet {
goto authErr
}
switch b {
case 'P', 'p':
c.state = OP_P
case 'S', 's':
c.state = OP_S
case 'U', 'u':
c.state = OP_U
case 'M', 'm':
if c.typ == CLIENT {
goto parseErr
} else {
c.state = OP_M
}
case 'C', 'c':
c.state = OP_C
case 'I', 'i':
c.state = OP_I
case '+':
c.state = OP_PLUS
case '-':
c.state = OP_MINUS
default:
goto parseErr
}
case OP_P:
switch b {
case 'U', 'u':
c.state = OP_PU
case 'I', 'i':
c.state = OP_PI
case 'O', 'o':
c.state = OP_PO
default:
goto parseErr
}
case OP_PU:
switch b {
case 'B', 'b':
c.state = OP_PUB
default:
goto parseErr
}
case OP_PUB:
switch b {
case ' ', '\t':
c.state = OP_PUB_SPC
default:
goto parseErr
}
case OP_PUB_SPC:
switch b {
case ' ', '\t':
continue
default:
c.state = PUB_ARG
c.as = i
}
case PUB_ARG:
switch b {
case '\r':
c.drop = 1
case '\n':
var arg []byte
if c.argBuf != nil {
arg = c.argBuf
} else {
arg = buf[c.as : i-c.drop]
}
if err := c.processPub(arg); err != nil {
return err
}
c.drop, c.as, c.state = OP_START, i+1, MSG_PAYLOAD
// If we don't have a saved buffer then jump ahead with
// the index. If this overruns what is left we fall out
// and process split buffer.
if c.msgBuf == nil {
i = c.as + c.pa.size - LEN_CR_LF
}
default:
if c.argBuf != nil {
c.argBuf = append(c.argBuf, b)
}
}
case MSG_PAYLOAD:
if c.msgBuf != nil {
// copy as much as we can to the buffer and skip ahead.
toCopy := c.pa.size - len(c.msgBuf)
avail := len(buf) - i
if avail < toCopy {
toCopy = avail
}
if toCopy > 0 {
start := len(c.msgBuf)
// This is needed for copy to work.
c.msgBuf = c.msgBuf[:start+toCopy]
copy(c.msgBuf[start:], buf[i:i+toCopy])
// Update our index
i = (i + toCopy) - 1
} else {
// Fall back to append if needed.
c.msgBuf = append(c.msgBuf, b)
}
if len(c.msgBuf) >= c.pa.size {
c.state = MSG_END
}
} else if i-c.as >= c.pa.size {
c.state = MSG_END
}
case MSG_END:
switch b {
case '\n':
if c.msgBuf != nil {
c.msgBuf = append(c.msgBuf, b)
} else {
c.msgBuf = buf[c.as : i+1]
}
// strict check for proto
if len(c.msgBuf) != c.pa.size+LEN_CR_LF {
goto parseErr
}
c.processMsg(c.msgBuf)
c.argBuf, c.msgBuf = nil, nil
c.drop, c.as, c.state = 0, i+1, OP_START
default:
if c.msgBuf != nil {
c.msgBuf = append(c.msgBuf, b)
}
continue
}
case OP_S:
switch b {
case 'U', 'u':
c.state = OP_SU
default:
goto parseErr
}
case OP_SU:
switch b {
case 'B', 'b':
c.state = OP_SUB
default:
goto parseErr
}
case OP_SUB:
switch b {
case ' ', '\t':
c.state = OP_SUB_SPC
default:
goto parseErr
}
case OP_SUB_SPC:
switch b {
case ' ', '\t':
continue
default:
c.state = SUB_ARG
c.as = i
}
case SUB_ARG:
switch b {
case '\r':
c.drop = 1
case '\n':
var arg []byte
if c.argBuf != nil {
arg = c.argBuf
c.argBuf = nil
} else {
arg = buf[c.as : i-c.drop]
}
if err := c.processSub(arg); err != nil {
return err
}
c.drop, c.as, c.state = 0, i+1, OP_START
default:
if c.argBuf != nil {
c.argBuf = append(c.argBuf, b)
}
}
case OP_U:
switch b {
case 'N', 'n':
c.state = OP_UN
default:
goto parseErr
}
case OP_UN:
switch b {
case 'S', 's':
c.state = OP_UNS
default:
goto parseErr
}
case OP_UNS:
switch b {
case 'U', 'u':
c.state = OP_UNSU
default:
goto parseErr
}
case OP_UNSU:
switch b {
case 'B', 'b':
c.state = OP_UNSUB
default:
goto parseErr
}
case OP_UNSUB:
switch b {
case ' ', '\t':
c.state = OP_UNSUB_SPC
default:
goto parseErr
}
case OP_UNSUB_SPC:
switch b {
case ' ', '\t':
continue
default:
c.state = UNSUB_ARG
c.as = i
}
case UNSUB_ARG:
switch b {
case '\r':
c.drop = 1
case '\n':
var arg []byte
if c.argBuf != nil {
arg = c.argBuf
c.argBuf = nil
} else {
arg = buf[c.as : i-c.drop]
}
if err := c.processUnsub(arg); err != nil {
return err
}
c.drop, c.as, c.state = 0, i+1, OP_START
default:
if c.argBuf != nil {
c.argBuf = append(c.argBuf, b)
}
}
case OP_PI:
switch b {
case 'N', 'n':
c.state = OP_PIN
default:
goto parseErr
}
case OP_PIN:
switch b {
case 'G', 'g':
c.state = OP_PING
default:
goto parseErr
}
case OP_PING:
switch b {
case '\n':
c.processPing()
c.drop, c.state = 0, OP_START
}
case OP_PO:
switch b {
case 'N', 'n':
c.state = OP_PON
default:
goto parseErr
}
case OP_PON:
switch b {
case 'G', 'g':
c.state = OP_PONG
default:
goto parseErr
}
case OP_PONG:
switch b {
case '\n':
c.processPong()
c.drop, c.state = 0, OP_START
}
case OP_C:
switch b {
case 'O', 'o':
c.state = OP_CO
default:
goto parseErr
}
case OP_CO:
switch b {
case 'N', 'n':
c.state = OP_CON
default:
goto parseErr
}
case OP_CON:
switch b {
case 'N', 'n':
c.state = OP_CONN
default:
goto parseErr
}
case OP_CONN:
switch b {
case 'E', 'e':
c.state = OP_CONNE
default:
goto parseErr
}
case OP_CONNE:
switch b {
case 'C', 'c':
c.state = OP_CONNEC
default:
goto parseErr
}
case OP_CONNEC:
switch b {
case 'T', 't':
c.state = OP_CONNECT
default:
goto parseErr
}
case OP_CONNECT:
switch b {
case ' ', '\t':
continue
default:
c.state = CONNECT_ARG
c.as = i
}
case CONNECT_ARG:
switch b {
case '\r':
c.drop = 1
case '\n':
var arg []byte
if c.argBuf != nil {
arg = c.argBuf
c.argBuf = nil
} else {
arg = buf[c.as : i-c.drop]
}
if err := c.processConnect(arg); err != nil {
return err
}
c.drop, c.state = 0, OP_START
// Reset notion on authSet
authSet = c.isAuthTimerSet()
default:
if c.argBuf != nil {
c.argBuf = append(c.argBuf, b)
}
}
case OP_M:
switch b {
case 'S', 's':
c.state = OP_MS
default:
goto parseErr
}
case OP_MS:
switch b {
case 'G', 'g':
c.state = OP_MSG
default:
goto parseErr
}
case OP_MSG:
switch b {
case ' ', '\t':
c.state = OP_MSG_SPC
default:
goto parseErr
}
case OP_MSG_SPC:
switch b {
case ' ', '\t':
continue
default:
c.state = MSG_ARG
c.as = i
}
case MSG_ARG:
switch b {
case '\r':
c.drop = 1
case '\n':
var arg []byte
if c.argBuf != nil {
arg = c.argBuf
} else {
arg = buf[c.as : i-c.drop]
}
if err := c.processMsgArgs(arg); err != nil {
return err
}
c.drop, c.as, c.state = 0, i+1, MSG_PAYLOAD
// jump ahead with the index. If this overruns
// what is left we fall out and process split
// buffer.
i = c.as + c.pa.size - 1
default:
if c.argBuf != nil {
c.argBuf = append(c.argBuf, b)
}
}
case OP_I:
switch b {
case 'N', 'n':
c.state = OP_IN
default:
goto parseErr
}
case OP_IN:
switch b {
case 'F', 'f':
c.state = OP_INF
default:
goto parseErr
}
case OP_INF:
switch b {
case 'O', 'o':
c.state = OP_INFO
default:
goto parseErr
}
case OP_INFO:
switch b {
case ' ', '\t':
continue
default:
c.state = INFO_ARG
c.as = i
}
case INFO_ARG:
switch b {
case '\r':
c.drop = 1
case '\n':
var arg []byte
if c.argBuf != nil {
arg = c.argBuf
c.argBuf = nil
} else {
arg = buf[c.as : i-c.drop]
}
if err := c.processInfo(arg); err != nil {
return err
}
c.drop, c.as, c.state = 0, i+1, OP_START
default:
if c.argBuf != nil {
c.argBuf = append(c.argBuf, b)
}
}
case OP_PLUS:
switch b {
case 'O', 'o':
c.state = OP_PLUS_O
default:
goto parseErr
}
case OP_PLUS_O:
switch b {
case 'K', 'k':
c.state = OP_PLUS_OK
default:
goto parseErr
}
case OP_PLUS_OK:
switch b {
case '\n':
c.drop, c.state = 0, OP_START
}
case OP_MINUS:
switch b {
case 'E', 'e':
c.state = OP_MINUS_E
default:
goto parseErr
}
case OP_MINUS_E:
switch b {
case 'R', 'r':
c.state = OP_MINUS_ER
default:
goto parseErr
}
case OP_MINUS_ER:
switch b {
case 'R', 'r':
c.state = OP_MINUS_ERR
default:
goto parseErr
}
case OP_MINUS_ERR:
switch b {
case ' ', '\t':
c.state = OP_MINUS_ERR_SPC
default:
goto parseErr
}
case OP_MINUS_ERR_SPC:
switch b {
case ' ', '\t':
continue
default:
c.state = MINUS_ERR_ARG
c.as = i
}
case MINUS_ERR_ARG:
switch b {
case '\r':
c.drop = 1
case '\n':
var arg []byte
if c.argBuf != nil {
arg = c.argBuf
c.argBuf = nil
} else {
arg = buf[c.as : i-c.drop]
}
c.processErr(string(arg))
c.drop, c.as, c.state = 0, i+1, OP_START
default:
if c.argBuf != nil {
c.argBuf = append(c.argBuf, b)
}
}
default:
goto parseErr
}
}
// Check for split buffer scenarios for any ARG state.
if c.state == SUB_ARG || c.state == UNSUB_ARG || c.state == PUB_ARG ||
c.state == MSG_ARG || c.state == MINUS_ERR_ARG ||
c.state == CONNECT_ARG || c.state == INFO_ARG {
// Setup a holder buffer to deal with split buffer scenario.
if c.argBuf == nil {
c.argBuf = c.scratch[:0]
c.argBuf = append(c.argBuf, buf[c.as:i-c.drop]...)
}
// Check for violations of control line length here. Note that this is not
// exact at all but the performance hit is too great to be precise, and
// catching here should prevent memory exhaustion attacks.
if len(c.argBuf) > mcl {
c.sendErr("Maximum Control Line Exceeded")
c.closeConnection(MaxControlLineExceeded)
return ErrMaxControlLine
}
}
// Check for split msg
if (c.state == MSG_PAYLOAD || c.state == MSG_END) && c.msgBuf == nil {
// We need to clone the pubArg if it is still referencing the
// read buffer and we are not able to process the msg.
if c.argBuf == nil {
// Works also for MSG_ARG, when message comes from ROUTE.
c.clonePubArg()
}
// If we will overflow the scratch buffer, just create a
// new buffer to hold the split message.
if c.pa.size > cap(c.scratch)-len(c.argBuf) {
lrem := len(buf[c.as:])
// Consider it a protocol error when the remaining payload
// is larger than the reported size for PUB. It can happen
// when processing incomplete messages from rogue clients.
if lrem > c.pa.size+LEN_CR_LF {
goto parseErr
}
c.msgBuf = make([]byte, lrem, c.pa.size+LEN_CR_LF)
copy(c.msgBuf, buf[c.as:])
} else {
c.msgBuf = c.scratch[len(c.argBuf):len(c.argBuf)]
c.msgBuf = append(c.msgBuf, (buf[c.as:])...)
}
}
return nil
authErr:
c.authViolation()
return ErrAuthorization
parseErr:
c.sendErr("Unknown Protocol Operation")
snip := protoSnippet(i, buf)
err := fmt.Errorf("%s parser ERROR, state=%d, i=%d: proto='%s...'",
c.typeString(), c.state, i, snip)
return err
}
func protoSnippet(start int, buf []byte) string {
stop := start + PROTO_SNIPPET_SIZE
bufSize := len(buf)
if start >= bufSize {
return `""`
}
if stop > bufSize {
stop = bufSize - 1
}
return fmt.Sprintf("%q", buf[start:stop])
}
// clonePubArg is used when the split buffer scenario has the pubArg in the existing read buffer, but
// we need to hold onto it into the next read.
func (c *client) clonePubArg() {
c.argBuf = c.scratch[:0]
c.argBuf = append(c.argBuf, c.pa.subject...)
c.argBuf = append(c.argBuf, c.pa.reply...)
c.argBuf = append(c.argBuf, c.pa.sid...)
c.argBuf = append(c.argBuf, c.pa.szb...)
c.pa.subject = c.argBuf[:len(c.pa.subject)]
if c.pa.reply != nil {
c.pa.reply = c.argBuf[len(c.pa.subject) : len(c.pa.subject)+len(c.pa.reply)]
}
if c.pa.sid != nil {
c.pa.sid = c.argBuf[len(c.pa.subject)+len(c.pa.reply) : len(c.pa.subject)+len(c.pa.reply)+len(c.pa.sid)]
}
c.pa.szb = c.argBuf[len(c.pa.subject)+len(c.pa.reply)+len(c.pa.sid):]
}

546
vendor/github.com/nats-io/gnatsd/server/parser_test.go generated vendored Normal file
View File

@ -0,0 +1,546 @@
// Copyright 2012-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package server
import (
"bytes"
"testing"
)
func dummyClient() *client {
return &client{srv: New(&defaultServerOptions)}
}
func dummyRouteClient() *client {
return &client{srv: New(&defaultServerOptions), typ: ROUTER}
}
func TestParsePing(t *testing.T) {
c := dummyClient()
if c.state != OP_START {
t.Fatalf("Expected OP_START vs %d\n", c.state)
}
ping := []byte("PING\r\n")
err := c.parse(ping[:1])
if err != nil || c.state != OP_P {
t.Fatalf("Unexpected: %d : %v\n", c.state, err)
}
err = c.parse(ping[1:2])
if err != nil || c.state != OP_PI {
t.Fatalf("Unexpected: %d : %v\n", c.state, err)
}
err = c.parse(ping[2:3])
if err != nil || c.state != OP_PIN {
t.Fatalf("Unexpected: %d : %v\n", c.state, err)
}
err = c.parse(ping[3:4])
if err != nil || c.state != OP_PING {
t.Fatalf("Unexpected: %d : %v\n", c.state, err)
}
err = c.parse(ping[4:5])
if err != nil || c.state != OP_PING {
t.Fatalf("Unexpected: %d : %v\n", c.state, err)
}
err = c.parse(ping[5:6])
if err != nil || c.state != OP_START {
t.Fatalf("Unexpected: %d : %v\n", c.state, err)
}
err = c.parse(ping)
if err != nil || c.state != OP_START {
t.Fatalf("Unexpected: %d : %v\n", c.state, err)
}
// Should tolerate spaces
ping = []byte("PING \r")
err = c.parse(ping)
if err != nil || c.state != OP_PING {
t.Fatalf("Unexpected: %d : %v\n", c.state, err)
}
c.state = OP_START
ping = []byte("PING \r \n")
err = c.parse(ping)
if err != nil || c.state != OP_START {
t.Fatalf("Unexpected: %d : %v\n", c.state, err)
}
}
func TestParsePong(t *testing.T) {
c := dummyClient()
if c.state != OP_START {
t.Fatalf("Expected OP_START vs %d\n", c.state)
}
pong := []byte("PONG\r\n")
err := c.parse(pong[:1])
if err != nil || c.state != OP_P {
t.Fatalf("Unexpected: %d : %v\n", c.state, err)
}
err = c.parse(pong[1:2])
if err != nil || c.state != OP_PO {
t.Fatalf("Unexpected: %d : %v\n", c.state, err)
}
err = c.parse(pong[2:3])
if err != nil || c.state != OP_PON {
t.Fatalf("Unexpected: %d : %v\n", c.state, err)
}
err = c.parse(pong[3:4])
if err != nil || c.state != OP_PONG {
t.Fatalf("Unexpected: %d : %v\n", c.state, err)
}
err = c.parse(pong[4:5])
if err != nil || c.state != OP_PONG {
t.Fatalf("Unexpected: %d : %v\n", c.state, err)
}
err = c.parse(pong[5:6])
if err != nil || c.state != OP_START {
t.Fatalf("Unexpected: %d : %v\n", c.state, err)
}
if c.ping.out != 0 {
t.Fatalf("Unexpected ping.out value: %d vs 0\n", c.ping.out)
}
err = c.parse(pong)
if err != nil || c.state != OP_START {
t.Fatalf("Unexpected: %d : %v\n", c.state, err)
}
if c.ping.out != 0 {
t.Fatalf("Unexpected ping.out value: %d vs 0\n", c.ping.out)
}
// Should tolerate spaces
pong = []byte("PONG \r")
err = c.parse(pong)
if err != nil || c.state != OP_PONG {
t.Fatalf("Unexpected: %d : %v\n", c.state, err)
}
c.state = OP_START
pong = []byte("PONG \r \n")
err = c.parse(pong)
if err != nil || c.state != OP_START {
t.Fatalf("Unexpected: %d : %v\n", c.state, err)
}
if c.ping.out != 0 {
t.Fatalf("Unexpected ping.out value: %d vs 0\n", c.ping.out)
}
// Should be adjusting c.pout (Pings Outstanding): reset to 0
c.state = OP_START
c.ping.out = 10
pong = []byte("PONG\r\n")
err = c.parse(pong)
if err != nil || c.state != OP_START {
t.Fatalf("Unexpected: %d : %v\n", c.state, err)
}
if c.ping.out != 0 {
t.Fatalf("Unexpected ping.out: %d vs 0\n", c.ping.out)
}
}
func TestParseConnect(t *testing.T) {
c := dummyClient()
connect := []byte("CONNECT {\"verbose\":false,\"pedantic\":true,\"tls_required\":false}\r\n")
err := c.parse(connect)
if err != nil || c.state != OP_START {
t.Fatalf("Unexpected: %d : %v\n", c.state, err)
}
// Check saved state
if c.as != 8 {
t.Fatalf("ArgStart state incorrect: 8 vs %d\n", c.as)
}
}
func TestParseSub(t *testing.T) {
c := dummyClient()
sub := []byte("SUB foo 1\r")
err := c.parse(sub)
if err != nil || c.state != SUB_ARG {
t.Fatalf("Unexpected: %d : %v\n", c.state, err)
}
// Check saved state
if c.as != 4 {
t.Fatalf("ArgStart state incorrect: 4 vs %d\n", c.as)
}
if c.drop != 1 {
t.Fatalf("Drop state incorrect: 1 vs %d\n", c.as)
}
if !bytes.Equal(sub[c.as:], []byte("foo 1\r")) {
t.Fatalf("Arg state incorrect: %s\n", sub[c.as:])
}
}
func TestParsePub(t *testing.T) {
c := dummyClient()
pub := []byte("PUB foo 5\r\nhello\r")
err := c.parse(pub)
if err != nil || c.state != MSG_END {
t.Fatalf("Unexpected: %d : %v\n", c.state, err)
}
if !bytes.Equal(c.pa.subject, []byte("foo")) {
t.Fatalf("Did not parse subject correctly: 'foo' vs '%s'\n", string(c.pa.subject))
}
if c.pa.reply != nil {
t.Fatalf("Did not parse reply correctly: 'nil' vs '%s'\n", string(c.pa.reply))
}
if c.pa.size != 5 {
t.Fatalf("Did not parse msg size correctly: 5 vs %d\n", c.pa.size)
}
// Clear snapshots
c.argBuf, c.msgBuf, c.state = nil, nil, OP_START
pub = []byte("PUB foo.bar INBOX.22 11\r\nhello world\r")
err = c.parse(pub)
if err != nil || c.state != MSG_END {
t.Fatalf("Unexpected: %d : %v\n", c.state, err)
}
if !bytes.Equal(c.pa.subject, []byte("foo.bar")) {
t.Fatalf("Did not parse subject correctly: 'foo' vs '%s'\n", string(c.pa.subject))
}
if !bytes.Equal(c.pa.reply, []byte("INBOX.22")) {
t.Fatalf("Did not parse reply correctly: 'INBOX.22' vs '%s'\n", string(c.pa.reply))
}
if c.pa.size != 11 {
t.Fatalf("Did not parse msg size correctly: 11 vs %d\n", c.pa.size)
}
}
func TestParsePubArg(t *testing.T) {
c := dummyClient()
for _, test := range []struct {
arg string
subject string
reply string
size int
szb string
}{
{arg: "a 2",
subject: "a", reply: "", size: 2, szb: "2"},
{arg: "a 222",
subject: "a", reply: "", size: 222, szb: "222"},
{arg: "foo 22",
subject: "foo", reply: "", size: 22, szb: "22"},
{arg: " foo 22",
subject: "foo", reply: "", size: 22, szb: "22"},
{arg: "foo 22 ",
subject: "foo", reply: "", size: 22, szb: "22"},
{arg: "foo 22",
subject: "foo", reply: "", size: 22, szb: "22"},
{arg: " foo 22 ",
subject: "foo", reply: "", size: 22, szb: "22"},
{arg: " foo 22 ",
subject: "foo", reply: "", size: 22, szb: "22"},
{arg: "foo bar 22",
subject: "foo", reply: "bar", size: 22, szb: "22"},
{arg: " foo bar 22",
subject: "foo", reply: "bar", size: 22, szb: "22"},
{arg: "foo bar 22 ",
subject: "foo", reply: "bar", size: 22, szb: "22"},
{arg: "foo bar 22",
subject: "foo", reply: "bar", size: 22, szb: "22"},
{arg: " foo bar 22 ",
subject: "foo", reply: "bar", size: 22, szb: "22"},
{arg: " foo bar 22 ",
subject: "foo", reply: "bar", size: 22, szb: "22"},
{arg: " foo bar 2222 ",
subject: "foo", reply: "bar", size: 2222, szb: "2222"},
{arg: " foo 2222 ",
subject: "foo", reply: "", size: 2222, szb: "2222"},
{arg: "a\t2",
subject: "a", reply: "", size: 2, szb: "2"},
{arg: "a\t222",
subject: "a", reply: "", size: 222, szb: "222"},
{arg: "foo\t22",
subject: "foo", reply: "", size: 22, szb: "22"},
{arg: "\tfoo\t22",
subject: "foo", reply: "", size: 22, szb: "22"},
{arg: "foo\t22\t",
subject: "foo", reply: "", size: 22, szb: "22"},
{arg: "foo\t\t\t22",
subject: "foo", reply: "", size: 22, szb: "22"},
{arg: "\tfoo\t22\t",
subject: "foo", reply: "", size: 22, szb: "22"},
{arg: "\tfoo\t\t\t22\t",
subject: "foo", reply: "", size: 22, szb: "22"},
{arg: "foo\tbar\t22",
subject: "foo", reply: "bar", size: 22, szb: "22"},
{arg: "\tfoo\tbar\t22",
subject: "foo", reply: "bar", size: 22, szb: "22"},
{arg: "foo\tbar\t22\t",
subject: "foo", reply: "bar", size: 22, szb: "22"},
{arg: "foo\t\tbar\t\t22",
subject: "foo", reply: "bar", size: 22, szb: "22"},
{arg: "\tfoo\tbar\t22\t",
subject: "foo", reply: "bar", size: 22, szb: "22"},
{arg: "\t \tfoo\t \t \tbar\t \t22\t \t",
subject: "foo", reply: "bar", size: 22, szb: "22"},
{arg: "\t\tfoo\t\t\tbar\t\t2222\t\t",
subject: "foo", reply: "bar", size: 2222, szb: "2222"},
{arg: "\t \tfoo\t \t \t\t\t2222\t \t",
subject: "foo", reply: "", size: 2222, szb: "2222"},
} {
t.Run(test.arg, func(t *testing.T) {
if err := c.processPub([]byte(test.arg)); err != nil {
t.Fatalf("Unexpected parse error: %v\n", err)
}
if !bytes.Equal(c.pa.subject, []byte(test.subject)) {
t.Fatalf("Mismatched subject: '%s'\n", c.pa.subject)
}
if !bytes.Equal(c.pa.reply, []byte(test.reply)) {
t.Fatalf("Mismatched reply subject: '%s'\n", c.pa.reply)
}
if !bytes.Equal(c.pa.szb, []byte(test.szb)) {
t.Fatalf("Bad size buf: '%s'\n", c.pa.szb)
}
if c.pa.size != test.size {
t.Fatalf("Bad size: %d\n", c.pa.size)
}
})
}
}
func TestParsePubBadSize(t *testing.T) {
c := dummyClient()
// Setup localized max payload
c.mpay = 32768
if err := c.processPub([]byte("foo 2222222222222222")); err == nil {
t.Fatalf("Expected parse error for size too large")
}
}
func TestParseMsg(t *testing.T) {
c := dummyRouteClient()
pub := []byte("MSG foo RSID:1:2 5\r\nhello\r")
err := c.parse(pub)
if err != nil || c.state != MSG_END {
t.Fatalf("Unexpected: %d : %v\n", c.state, err)
}
if !bytes.Equal(c.pa.subject, []byte("foo")) {
t.Fatalf("Did not parse subject correctly: 'foo' vs '%s'\n", c.pa.subject)
}
if c.pa.reply != nil {
t.Fatalf("Did not parse reply correctly: 'nil' vs '%s'\n", c.pa.reply)
}
if c.pa.size != 5 {
t.Fatalf("Did not parse msg size correctly: 5 vs %d\n", c.pa.size)
}
if !bytes.Equal(c.pa.sid, []byte("RSID:1:2")) {
t.Fatalf("Did not parse sid correctly: 'RSID:1:2' vs '%s'\n", c.pa.sid)
}
// Clear snapshots
c.argBuf, c.msgBuf, c.state = nil, nil, OP_START
pub = []byte("MSG foo.bar RSID:1:2 INBOX.22 11\r\nhello world\r")
err = c.parse(pub)
if err != nil || c.state != MSG_END {
t.Fatalf("Unexpected: %d : %v\n", c.state, err)
}
if !bytes.Equal(c.pa.subject, []byte("foo.bar")) {
t.Fatalf("Did not parse subject correctly: 'foo' vs '%s'\n", c.pa.subject)
}
if !bytes.Equal(c.pa.reply, []byte("INBOX.22")) {
t.Fatalf("Did not parse reply correctly: 'INBOX.22' vs '%s'\n", c.pa.reply)
}
if c.pa.size != 11 {
t.Fatalf("Did not parse msg size correctly: 11 vs %d\n", c.pa.size)
}
}
func testMsgArg(c *client, t *testing.T) {
if !bytes.Equal(c.pa.subject, []byte("foobar")) {
t.Fatalf("Mismatched subject: '%s'\n", c.pa.subject)
}
if !bytes.Equal(c.pa.szb, []byte("22")) {
t.Fatalf("Bad size buf: '%s'\n", c.pa.szb)
}
if c.pa.size != 22 {
t.Fatalf("Bad size: %d\n", c.pa.size)
}
if !bytes.Equal(c.pa.sid, []byte("RSID:22:1")) {
t.Fatalf("Bad sid: '%s'\n", c.pa.sid)
}
}
func TestParseMsgArg(t *testing.T) {
c := dummyClient()
if err := c.processMsgArgs([]byte("foobar RSID:22:1 22")); err != nil {
t.Fatalf("Unexpected parse error: %v\n", err)
}
testMsgArg(c, t)
if err := c.processMsgArgs([]byte(" foobar RSID:22:1 22")); err != nil {
t.Fatalf("Unexpected parse error: %v\n", err)
}
testMsgArg(c, t)
if err := c.processMsgArgs([]byte(" foobar RSID:22:1 22 ")); err != nil {
t.Fatalf("Unexpected parse error: %v\n", err)
}
testMsgArg(c, t)
if err := c.processMsgArgs([]byte("foobar RSID:22:1 \t22")); err != nil {
t.Fatalf("Unexpected parse error: %v\n", err)
}
if err := c.processMsgArgs([]byte("foobar\t\tRSID:22:1\t22\r")); err != nil {
t.Fatalf("Unexpected parse error: %v\n", err)
}
testMsgArg(c, t)
}
func TestParseMsgSpace(t *testing.T) {
c := dummyRouteClient()
// Ivan bug he found
if err := c.parse([]byte("MSG \r\n")); err == nil {
t.Fatalf("Expected parse error for MSG <SPC>")
}
c = dummyClient()
// Anything with an M from a client should parse error
if err := c.parse([]byte("M")); err == nil {
t.Fatalf("Expected parse error for M* from a client")
}
}
func TestShouldFail(t *testing.T) {
wrongProtos := []string{
"xxx",
"Px", "PIx", "PINx", " PING",
"POx", "PONx",
"+x", "+Ox",
"-x", "-Ex", "-ERx", "-ERRx",
"Cx", "COx", "CONx", "CONNx", "CONNEx", "CONNECx", "CONNECx", "CONNECT \r\n",
"PUx", "PUB foo\r\n", "PUB \r\n", "PUB foo bar \r\n",
"PUB foo 2\r\nok \r\n", "PUB foo 2\r\nok\r \n",
"Sx", "SUx", "SUB\r\n", "SUB \r\n", "SUB foo\r\n",
"SUB foo bar baz 22\r\n",
"Ux", "UNx", "UNSx", "UNSUx", "UNSUBx", "UNSUBUNSUB 1\r\n", "UNSUB_2\r\n",
"UNSUB_UNSUB_UNSUB 2\r\n", "UNSUB_\t2\r\n", "UNSUB\r\n", "UNSUB \r\n",
"UNSUB \t \r\n",
"Ix", "INx", "INFx", "INFO \r\n",
}
for _, proto := range wrongProtos {
c := dummyClient()
if err := c.parse([]byte(proto)); err == nil {
t.Fatalf("Should have received a parse error for: %v", proto)
}
}
// Special case for MSG, type needs to not be client.
wrongProtos = []string{"Mx", "MSx", "MSGx", "MSG \r\n"}
for _, proto := range wrongProtos {
c := dummyClient()
c.typ = ROUTER
if err := c.parse([]byte(proto)); err == nil {
t.Fatalf("Should have received a parse error for: %v", proto)
}
}
}
func TestProtoSnippet(t *testing.T) {
sample := []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
tests := []struct {
input int
expected string
}{
{0, `"abcdefghijklmnopqrstuvwxyzABCDEF"`},
{1, `"bcdefghijklmnopqrstuvwxyzABCDEFG"`},
{2, `"cdefghijklmnopqrstuvwxyzABCDEFGH"`},
{3, `"defghijklmnopqrstuvwxyzABCDEFGHI"`},
{4, `"efghijklmnopqrstuvwxyzABCDEFGHIJ"`},
{5, `"fghijklmnopqrstuvwxyzABCDEFGHIJK"`},
{6, `"ghijklmnopqrstuvwxyzABCDEFGHIJKL"`},
{7, `"hijklmnopqrstuvwxyzABCDEFGHIJKLM"`},
{8, `"ijklmnopqrstuvwxyzABCDEFGHIJKLMN"`},
{9, `"jklmnopqrstuvwxyzABCDEFGHIJKLMNO"`},
{10, `"klmnopqrstuvwxyzABCDEFGHIJKLMNOP"`},
{11, `"lmnopqrstuvwxyzABCDEFGHIJKLMNOPQ"`},
{12, `"mnopqrstuvwxyzABCDEFGHIJKLMNOPQR"`},
{13, `"nopqrstuvwxyzABCDEFGHIJKLMNOPQRS"`},
{14, `"opqrstuvwxyzABCDEFGHIJKLMNOPQRST"`},
{15, `"pqrstuvwxyzABCDEFGHIJKLMNOPQRSTU"`},
{16, `"qrstuvwxyzABCDEFGHIJKLMNOPQRSTUV"`},
{17, `"rstuvwxyzABCDEFGHIJKLMNOPQRSTUVW"`},
{18, `"stuvwxyzABCDEFGHIJKLMNOPQRSTUVWX"`},
{19, `"tuvwxyzABCDEFGHIJKLMNOPQRSTUVWXY"`},
{20, `"uvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"`},
{21, `"vwxyzABCDEFGHIJKLMNOPQRSTUVWXY"`},
{22, `"wxyzABCDEFGHIJKLMNOPQRSTUVWXY"`},
{23, `"xyzABCDEFGHIJKLMNOPQRSTUVWXY"`},
{24, `"yzABCDEFGHIJKLMNOPQRSTUVWXY"`},
{25, `"zABCDEFGHIJKLMNOPQRSTUVWXY"`},
{26, `"ABCDEFGHIJKLMNOPQRSTUVWXY"`},
{27, `"BCDEFGHIJKLMNOPQRSTUVWXY"`},
{28, `"CDEFGHIJKLMNOPQRSTUVWXY"`},
{29, `"DEFGHIJKLMNOPQRSTUVWXY"`},
{30, `"EFGHIJKLMNOPQRSTUVWXY"`},
{31, `"FGHIJKLMNOPQRSTUVWXY"`},
{32, `"GHIJKLMNOPQRSTUVWXY"`},
{33, `"HIJKLMNOPQRSTUVWXY"`},
{34, `"IJKLMNOPQRSTUVWXY"`},
{35, `"JKLMNOPQRSTUVWXY"`},
{36, `"KLMNOPQRSTUVWXY"`},
{37, `"LMNOPQRSTUVWXY"`},
{38, `"MNOPQRSTUVWXY"`},
{39, `"NOPQRSTUVWXY"`},
{40, `"OPQRSTUVWXY"`},
{41, `"PQRSTUVWXY"`},
{42, `"QRSTUVWXY"`},
{43, `"RSTUVWXY"`},
{44, `"STUVWXY"`},
{45, `"TUVWXY"`},
{46, `"UVWXY"`},
{47, `"VWXY"`},
{48, `"WXY"`},
{49, `"XY"`},
{50, `"Y"`},
{51, `""`},
{52, `""`},
{53, `""`},
{54, `""`},
}
for _, tt := range tests {
got := protoSnippet(tt.input, sample)
if tt.expected != got {
t.Errorf("Expected protocol snippet to be %s when start=%d but got %s\n", tt.expected, tt.input, got)
}
}
}
func TestParseOK(t *testing.T) {
c := dummyClient()
if c.state != OP_START {
t.Fatalf("Expected OP_START vs %d\n", c.state)
}
okProto := []byte("+OK\r\n")
err := c.parse(okProto[:1])
if err != nil || c.state != OP_PLUS {
t.Fatalf("Unexpected: %d : %v\n", c.state, err)
}
err = c.parse(okProto[1:2])
if err != nil || c.state != OP_PLUS_O {
t.Fatalf("Unexpected: %d : %v\n", c.state, err)
}
err = c.parse(okProto[2:3])
if err != nil || c.state != OP_PLUS_OK {
t.Fatalf("Unexpected: %d : %v\n", c.state, err)
}
err = c.parse(okProto[3:4])
if err != nil || c.state != OP_PLUS_OK {
t.Fatalf("Unexpected: %d : %v\n", c.state, err)
}
err = c.parse(okProto[4:5])
if err != nil || c.state != OP_START {
t.Fatalf("Unexpected: %d : %v\n", c.state, err)
}
}

44
vendor/github.com/nats-io/gnatsd/server/ping_test.go generated vendored Normal file
View File

@ -0,0 +1,44 @@
// Copyright 2015-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package server
import (
"fmt"
"testing"
"time"
"github.com/nats-io/go-nats"
)
const PING_CLIENT_PORT = 11228
var DefaultPingOptions = Options{
Host: "127.0.0.1",
Port: PING_CLIENT_PORT,
NoLog: true,
NoSigs: true,
PingInterval: 5 * time.Millisecond,
}
func TestPing(t *testing.T) {
s := RunServer(&DefaultPingOptions)
defer s.Shutdown()
nc, err := nats.Connect(fmt.Sprintf("nats://127.0.0.1:%d", PING_CLIENT_PORT))
if err != nil {
t.Fatalf("Error creating client: %v\n", err)
}
defer nc.Close()
time.Sleep(10 * time.Millisecond)
}

View File

@ -0,0 +1,34 @@
// Copyright 2015-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pse
import (
"fmt"
"os"
"os/exec"
)
// ProcUsage returns CPU usage
func ProcUsage(pcpu *float64, rss, vss *int64) error {
pidStr := fmt.Sprintf("%d", os.Getpid())
out, err := exec.Command("ps", "o", "pcpu=,rss=,vsz=", "-p", pidStr).Output()
if err != nil {
*rss, *vss = -1, -1
return fmt.Errorf("ps call failed:%v", err)
}
fmt.Sscanf(string(out), "%f %d %d", pcpu, rss, vss)
*rss *= 1024 // 1k blocks, want bytes.
*vss *= 1024 // 1k blocks, want bytes.
return nil
}

View File

@ -0,0 +1,83 @@
// Copyright 2015-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pse
/*
#include <sys/types.h>
#include <sys/sysctl.h>
#include <sys/user.h>
#include <stddef.h>
#include <unistd.h>
long pagetok(long size)
{
int pageshift, pagesize;
pagesize = getpagesize();
pageshift = 0;
while (pagesize > 1) {
pageshift++;
pagesize >>= 1;
}
return (size << pageshift);
}
int getusage(double *pcpu, unsigned int *rss, unsigned int *vss)
{
int mib[4], ret;
size_t len;
struct kinfo_proc kp;
len = 4;
sysctlnametomib("kern.proc.pid", mib, &len);
mib[3] = getpid();
len = sizeof(kp);
ret = sysctl(mib, 4, &kp, &len, NULL, 0);
if (ret != 0) {
return (errno);
}
*rss = pagetok(kp.ki_rssize);
*vss = kp.ki_size;
*pcpu = kp.ki_pctcpu;
return 0;
}
*/
import "C"
import (
"syscall"
)
// This is a placeholder for now.
func ProcUsage(pcpu *float64, rss, vss *int64) error {
var r, v C.uint
var c C.double
if ret := C.getusage(&c, &r, &v); ret != 0 {
return syscall.Errno(ret)
}
*pcpu = float64(c)
*rss = int64(r)
*vss = int64(v)
return nil
}

View File

@ -0,0 +1,126 @@
// Copyright 2015-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pse
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"sync/atomic"
"syscall"
"time"
)
var (
procStatFile string
ticks int64
lastTotal int64
lastSeconds int64
ipcpu int64
)
const (
utimePos = 13
stimePos = 14
startPos = 21
vssPos = 22
rssPos = 23
)
func init() {
// Avoiding to generate docker image without CGO
ticks = 100 // int64(C.sysconf(C._SC_CLK_TCK))
procStatFile = fmt.Sprintf("/proc/%d/stat", os.Getpid())
periodic()
}
// Sampling function to keep pcpu relevant.
func periodic() {
contents, err := ioutil.ReadFile(procStatFile)
if err != nil {
return
}
fields := bytes.Fields(contents)
// PCPU
pstart := parseInt64(fields[startPos])
utime := parseInt64(fields[utimePos])
stime := parseInt64(fields[stimePos])
total := utime + stime
var sysinfo syscall.Sysinfo_t
if err := syscall.Sysinfo(&sysinfo); err != nil {
return
}
seconds := int64(sysinfo.Uptime) - (pstart / ticks)
// Save off temps
lt := lastTotal
ls := lastSeconds
// Update last sample
lastTotal = total
lastSeconds = seconds
// Adjust to current time window
total -= lt
seconds -= ls
if seconds > 0 {
atomic.StoreInt64(&ipcpu, (total*1000/ticks)/seconds)
}
time.AfterFunc(1*time.Second, periodic)
}
func ProcUsage(pcpu *float64, rss, vss *int64) error {
contents, err := ioutil.ReadFile(procStatFile)
if err != nil {
return err
}
fields := bytes.Fields(contents)
// Memory
*rss = (parseInt64(fields[rssPos])) << 12
*vss = parseInt64(fields[vssPos])
// PCPU
// We track this with periodic sampling, so just load and go.
*pcpu = float64(atomic.LoadInt64(&ipcpu)) / 10.0
return nil
}
// Ascii numbers 0-9
const (
asciiZero = 48
asciiNine = 57
)
// parseInt64 expects decimal positive numbers. We
// return -1 to signal error
func parseInt64(d []byte) (n int64) {
if len(d) == 0 {
return -1
}
for _, dec := range d {
if dec < asciiZero || dec > asciiNine {
return -1
}
n = n*10 + (int64(dec) - asciiZero)
}
return n
}

View File

@ -0,0 +1,36 @@
// Copyright 2015-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copied from pse_darwin.go
package pse
import (
"fmt"
"os"
"os/exec"
)
// ProcUsage returns CPU usage
func ProcUsage(pcpu *float64, rss, vss *int64) error {
pidStr := fmt.Sprintf("%d", os.Getpid())
out, err := exec.Command("ps", "o", "pcpu=,rss=,vsz=", "-p", pidStr).Output()
if err != nil {
*rss, *vss = -1, -1
return fmt.Errorf("ps call failed:%v", err)
}
fmt.Sscanf(string(out), "%f %d %d", pcpu, rss, vss)
*rss *= 1024 // 1k blocks, want bytes.
*vss *= 1024 // 1k blocks, want bytes.
return nil
}

View File

@ -0,0 +1,25 @@
// Copyright 2015-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build rumprun
package pse
// This is a placeholder for now.
func ProcUsage(pcpu *float64, rss, vss *int64) error {
*pcpu = 0.0
*rss = 0
*vss = 0
return nil
}

View File

@ -0,0 +1,23 @@
// Copyright 2015-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pse
// This is a placeholder for now.
func ProcUsage(pcpu *float64, rss, vss *int64) error {
*pcpu = 0.0
*rss = 0
*vss = 0
return nil
}

View File

@ -0,0 +1,67 @@
// Copyright 2015-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pse
import (
"fmt"
"os"
"os/exec"
"runtime"
"testing"
)
func TestPSEmulation(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skipf("Skipping this test on Windows")
}
var rss, vss, psRss, psVss int64
var pcpu, psPcpu float64
runtime.GC()
// PS version first
pidStr := fmt.Sprintf("%d", os.Getpid())
out, err := exec.Command("ps", "o", "pcpu=,rss=,vsz=", "-p", pidStr).Output()
if err != nil {
t.Fatalf("Failed to execute ps command: %v\n", err)
}
fmt.Sscanf(string(out), "%f %d %d", &psPcpu, &psRss, &psVss)
psRss *= 1024 // 1k blocks, want bytes.
psVss *= 1024 // 1k blocks, want bytes.
runtime.GC()
// Our internal version
ProcUsage(&pcpu, &rss, &vss)
if pcpu != psPcpu {
delta := int64(pcpu - psPcpu)
if delta < 0 {
delta = -delta
}
if delta > 30 { // 30%?
t.Fatalf("CPUs did not match close enough: %f vs %f", pcpu, psPcpu)
}
}
if rss != psRss {
delta := rss - psRss
if delta < 0 {
delta = -delta
}
if delta > 1024*1024 { // 1MB
t.Fatalf("RSSs did not match close enough: %d vs %d", rss, psRss)
}
}
}

View File

@ -0,0 +1,280 @@
// Copyright 2015-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build windows
package pse
import (
"fmt"
"os"
"path/filepath"
"strings"
"sync"
"syscall"
"time"
"unsafe"
)
var (
pdh = syscall.NewLazyDLL("pdh.dll")
winPdhOpenQuery = pdh.NewProc("PdhOpenQuery")
winPdhAddCounter = pdh.NewProc("PdhAddCounterW")
winPdhCollectQueryData = pdh.NewProc("PdhCollectQueryData")
winPdhGetFormattedCounterValue = pdh.NewProc("PdhGetFormattedCounterValue")
winPdhGetFormattedCounterArray = pdh.NewProc("PdhGetFormattedCounterArrayW")
)
// global performance counter query handle and counters
var (
pcHandle PDH_HQUERY
pidCounter, cpuCounter, rssCounter, vssCounter PDH_HCOUNTER
prevCPU float64
prevRss int64
prevVss int64
lastSampleTime time.Time
processPid int
pcQueryLock sync.Mutex
initialSample = true
)
// maxQuerySize is the number of values to return from a query.
// It represents the maximum # of servers that can be queried
// simultaneously running on a machine.
const maxQuerySize = 512
// Keep static memory around to reuse; this works best for passing
// into the pdh API.
var counterResults [maxQuerySize]PDH_FMT_COUNTERVALUE_ITEM_DOUBLE
// PDH Types
type (
PDH_HQUERY syscall.Handle
PDH_HCOUNTER syscall.Handle
)
// PDH constants used here
const (
PDH_FMT_DOUBLE = 0x00000200
PDH_INVALID_DATA = 0xC0000BC6
PDH_MORE_DATA = 0x800007D2
)
// PDH_FMT_COUNTERVALUE_DOUBLE - double value
type PDH_FMT_COUNTERVALUE_DOUBLE struct {
CStatus uint32
DoubleValue float64
}
// PDH_FMT_COUNTERVALUE_ITEM_DOUBLE is an array
// element of a double value
type PDH_FMT_COUNTERVALUE_ITEM_DOUBLE struct {
SzName *uint16 // pointer to a string
FmtValue PDH_FMT_COUNTERVALUE_DOUBLE
}
func pdhAddCounter(hQuery PDH_HQUERY, szFullCounterPath string, dwUserData uintptr, phCounter *PDH_HCOUNTER) error {
ptxt, _ := syscall.UTF16PtrFromString(szFullCounterPath)
r0, _, _ := winPdhAddCounter.Call(
uintptr(hQuery),
uintptr(unsafe.Pointer(ptxt)),
dwUserData,
uintptr(unsafe.Pointer(phCounter)))
if r0 != 0 {
return fmt.Errorf("pdhAddCounter failed. %d", r0)
}
return nil
}
func pdhOpenQuery(datasrc *uint16, userdata uint32, query *PDH_HQUERY) error {
r0, _, _ := syscall.Syscall(winPdhOpenQuery.Addr(), 3, 0, uintptr(userdata), uintptr(unsafe.Pointer(query)))
if r0 != 0 {
return fmt.Errorf("pdhOpenQuery failed - %d", r0)
}
return nil
}
func pdhCollectQueryData(hQuery PDH_HQUERY) error {
r0, _, _ := winPdhCollectQueryData.Call(uintptr(hQuery))
if r0 != 0 {
return fmt.Errorf("pdhCollectQueryData failed - %d", r0)
}
return nil
}
// pdhGetFormattedCounterArrayDouble returns the value of return code
// rather than error, to easily check return codes
func pdhGetFormattedCounterArrayDouble(hCounter PDH_HCOUNTER, lpdwBufferSize *uint32, lpdwBufferCount *uint32, itemBuffer *PDH_FMT_COUNTERVALUE_ITEM_DOUBLE) uint32 {
ret, _, _ := winPdhGetFormattedCounterArray.Call(
uintptr(hCounter),
uintptr(PDH_FMT_DOUBLE),
uintptr(unsafe.Pointer(lpdwBufferSize)),
uintptr(unsafe.Pointer(lpdwBufferCount)),
uintptr(unsafe.Pointer(itemBuffer)))
return uint32(ret)
}
func getCounterArrayData(counter PDH_HCOUNTER) ([]float64, error) {
var bufSize uint32
var bufCount uint32
// Retrieving array data requires two calls, the first which
// requires an addressable empty buffer, and sets size fields.
// The second call returns the data.
initialBuf := make([]PDH_FMT_COUNTERVALUE_ITEM_DOUBLE, 1)
ret := pdhGetFormattedCounterArrayDouble(counter, &bufSize, &bufCount, &initialBuf[0])
if ret == PDH_MORE_DATA {
// we'll likely never get here, but be safe.
if bufCount > maxQuerySize {
bufCount = maxQuerySize
}
ret = pdhGetFormattedCounterArrayDouble(counter, &bufSize, &bufCount, &counterResults[0])
if ret == 0 {
rv := make([]float64, bufCount)
for i := 0; i < int(bufCount); i++ {
rv[i] = counterResults[i].FmtValue.DoubleValue
}
return rv, nil
}
}
if ret != 0 {
return nil, fmt.Errorf("getCounterArrayData failed - %d", ret)
}
return nil, nil
}
// getProcessImageName returns the name of the process image, as expected by
// the performance counter API.
func getProcessImageName() (name string) {
name = filepath.Base(os.Args[0])
name = strings.TrimRight(name, ".exe")
return
}
// initialize our counters
func initCounters() (err error) {
processPid = os.Getpid()
// require an addressible nil pointer
var source uint16
if err := pdhOpenQuery(&source, 0, &pcHandle); err != nil {
return err
}
// setup the performance counters, search for all server instances
name := fmt.Sprintf("%s*", getProcessImageName())
pidQuery := fmt.Sprintf("\\Process(%s)\\ID Process", name)
cpuQuery := fmt.Sprintf("\\Process(%s)\\%% Processor Time", name)
rssQuery := fmt.Sprintf("\\Process(%s)\\Working Set - Private", name)
vssQuery := fmt.Sprintf("\\Process(%s)\\Virtual Bytes", name)
if err = pdhAddCounter(pcHandle, pidQuery, 0, &pidCounter); err != nil {
return err
}
if err = pdhAddCounter(pcHandle, cpuQuery, 0, &cpuCounter); err != nil {
return err
}
if err = pdhAddCounter(pcHandle, rssQuery, 0, &rssCounter); err != nil {
return err
}
if err = pdhAddCounter(pcHandle, vssQuery, 0, &vssCounter); err != nil {
return err
}
// prime the counters by collecting once, and sleep to get somewhat
// useful information the first request. Counters for the CPU require
// at least two collect calls.
if err = pdhCollectQueryData(pcHandle); err != nil {
return err
}
time.Sleep(50)
return nil
}
// ProcUsage returns process CPU and memory statistics
func ProcUsage(pcpu *float64, rss, vss *int64) error {
var err error
// For simplicity, protect the entire call.
// Most simultaneous requests will immediately return
// with cached values.
pcQueryLock.Lock()
defer pcQueryLock.Unlock()
// First time through, initialize counters.
if initialSample {
if err = initCounters(); err != nil {
return err
}
initialSample = false
} else if time.Since(lastSampleTime) < (2 * time.Second) {
// only refresh every two seconds as to minimize impact
// on the server.
*pcpu = prevCPU
*rss = prevRss
*vss = prevVss
return nil
}
// always save the sample time, even on errors.
defer func() {
lastSampleTime = time.Now()
}()
// refresh the performance counter data
if err = pdhCollectQueryData(pcHandle); err != nil {
return err
}
// retrieve the data
var pidAry, cpuAry, rssAry, vssAry []float64
if pidAry, err = getCounterArrayData(pidCounter); err != nil {
return err
}
if cpuAry, err = getCounterArrayData(cpuCounter); err != nil {
return err
}
if rssAry, err = getCounterArrayData(rssCounter); err != nil {
return err
}
if vssAry, err = getCounterArrayData(vssCounter); err != nil {
return err
}
// find the index of the entry for this process
idx := int(-1)
for i := range pidAry {
if int(pidAry[i]) == processPid {
idx = i
break
}
}
// no pid found...
if idx < 0 {
return fmt.Errorf("could not find pid in performance counter results")
}
// assign values from the performance counters
*pcpu = cpuAry[idx]
*rss = int64(rssAry[idx])
*vss = int64(vssAry[idx])
// save off cache values
prevCPU = *pcpu
prevRss = *rss
prevVss = *vss
return nil
}

View File

@ -0,0 +1,97 @@
// Copyright 2015-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build windows
package pse
import (
"fmt"
"os/exec"
"runtime"
"strconv"
"strings"
"testing"
)
func checkValues(t *testing.T, pcpu, tPcpu float64, rss, tRss int64) {
if pcpu != tPcpu {
delta := int64(pcpu - tPcpu)
if delta < 0 {
delta = -delta
}
if delta > 30 { // 30%?
t.Fatalf("CPUs did not match close enough: %f vs %f", pcpu, tPcpu)
}
}
if rss != tRss {
delta := rss - tRss
if delta < 0 {
delta = -delta
}
if delta > 1024*1024 { // 1MB
t.Fatalf("RSSs did not match close enough: %d vs %d", rss, tRss)
}
}
}
func TestPSEmulationWin(t *testing.T) {
var pcpu, tPcpu float64
var rss, vss, tRss int64
runtime.GC()
if err := ProcUsage(&pcpu, &rss, &vss); err != nil {
t.Fatalf("Error: %v", err)
}
runtime.GC()
imageName := getProcessImageName()
// query the counters using typeperf
out, err := exec.Command("typeperf.exe",
fmt.Sprintf("\\Process(%s)\\%% Processor Time", imageName),
fmt.Sprintf("\\Process(%s)\\Working Set - Private", imageName),
fmt.Sprintf("\\Process(%s)\\Virtual Bytes", imageName),
"-sc", "1").Output()
if err != nil {
t.Fatal("unable to run command", err)
}
// parse out results - refer to comments in procUsage for detail
results := strings.Split(string(out), "\r\n")
values := strings.Split(results[2], ",")
// parse pcpu
tPcpu, err = strconv.ParseFloat(strings.Trim(values[1], "\""), 64)
if err != nil {
t.Fatalf("Unable to parse percent cpu: %s", values[1])
}
// parse private bytes (rss)
fval, err := strconv.ParseFloat(strings.Trim(values[2], "\""), 64)
if err != nil {
t.Fatalf("Unable to parse private bytes: %s", values[2])
}
tRss = int64(fval)
checkValues(t, pcpu, tPcpu, rss, tRss)
runtime.GC()
// Again to test caching
if err = ProcUsage(&pcpu, &rss, &vss); err != nil {
t.Fatalf("Error: %v", err)
}
checkValues(t, pcpu, tPcpu, rss, tRss)
}

714
vendor/github.com/nats-io/gnatsd/server/reload.go generated vendored Normal file
View File

@ -0,0 +1,714 @@
// Copyright 2017-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package server
import (
"crypto/tls"
"errors"
"fmt"
"net/url"
"reflect"
"strings"
"sync/atomic"
"time"
)
// FlagSnapshot captures the server options as specified by CLI flags at
// startup. This should not be modified once the server has started.
var FlagSnapshot *Options
// option is a hot-swappable configuration setting.
type option interface {
// Apply the server option.
Apply(server *Server)
// IsLoggingChange indicates if this option requires reloading the logger.
IsLoggingChange() bool
// IsAuthChange indicates if this option requires reloading authorization.
IsAuthChange() bool
}
// loggingOption is a base struct that provides default option behaviors for
// logging-related options.
type loggingOption struct{}
func (l loggingOption) IsLoggingChange() bool {
return true
}
func (l loggingOption) IsAuthChange() bool {
return false
}
// traceOption implements the option interface for the `trace` setting.
type traceOption struct {
loggingOption
newValue bool
}
// Apply is a no-op because logging will be reloaded after options are applied.
func (t *traceOption) Apply(server *Server) {
server.Noticef("Reloaded: trace = %v", t.newValue)
}
// debugOption implements the option interface for the `debug` setting.
type debugOption struct {
loggingOption
newValue bool
}
// Apply is a no-op because logging will be reloaded after options are applied.
func (d *debugOption) Apply(server *Server) {
server.Noticef("Reloaded: debug = %v", d.newValue)
}
// logtimeOption implements the option interface for the `logtime` setting.
type logtimeOption struct {
loggingOption
newValue bool
}
// Apply is a no-op because logging will be reloaded after options are applied.
func (l *logtimeOption) Apply(server *Server) {
server.Noticef("Reloaded: logtime = %v", l.newValue)
}
// logfileOption implements the option interface for the `log_file` setting.
type logfileOption struct {
loggingOption
newValue string
}
// Apply is a no-op because logging will be reloaded after options are applied.
func (l *logfileOption) Apply(server *Server) {
server.Noticef("Reloaded: log_file = %v", l.newValue)
}
// syslogOption implements the option interface for the `syslog` setting.
type syslogOption struct {
loggingOption
newValue bool
}
// Apply is a no-op because logging will be reloaded after options are applied.
func (s *syslogOption) Apply(server *Server) {
server.Noticef("Reloaded: syslog = %v", s.newValue)
}
// remoteSyslogOption implements the option interface for the `remote_syslog`
// setting.
type remoteSyslogOption struct {
loggingOption
newValue string
}
// Apply is a no-op because logging will be reloaded after options are applied.
func (r *remoteSyslogOption) Apply(server *Server) {
server.Noticef("Reloaded: remote_syslog = %v", r.newValue)
}
// noopOption is a base struct that provides default no-op behaviors.
type noopOption struct{}
func (n noopOption) IsLoggingChange() bool {
return false
}
func (n noopOption) IsAuthChange() bool {
return false
}
// tlsOption implements the option interface for the `tls` setting.
type tlsOption struct {
noopOption
newValue *tls.Config
}
// Apply the tls change.
func (t *tlsOption) Apply(server *Server) {
server.mu.Lock()
tlsRequired := t.newValue != nil
server.info.TLSRequired = tlsRequired
message := "disabled"
if tlsRequired {
server.info.TLSVerify = (t.newValue.ClientAuth == tls.RequireAndVerifyClientCert)
message = "enabled"
}
server.mu.Unlock()
server.Noticef("Reloaded: tls = %s", message)
}
// tlsTimeoutOption implements the option interface for the tls `timeout`
// setting.
type tlsTimeoutOption struct {
noopOption
newValue float64
}
// Apply is a no-op because the timeout will be reloaded after options are
// applied.
func (t *tlsTimeoutOption) Apply(server *Server) {
server.Noticef("Reloaded: tls timeout = %v", t.newValue)
}
// authOption is a base struct that provides default option behaviors.
type authOption struct{}
func (o authOption) IsLoggingChange() bool {
return false
}
func (o authOption) IsAuthChange() bool {
return true
}
// usernameOption implements the option interface for the `username` setting.
type usernameOption struct {
authOption
}
// Apply is a no-op because authorization will be reloaded after options are
// applied.
func (u *usernameOption) Apply(server *Server) {
server.Noticef("Reloaded: authorization username")
}
// passwordOption implements the option interface for the `password` setting.
type passwordOption struct {
authOption
}
// Apply is a no-op because authorization will be reloaded after options are
// applied.
func (p *passwordOption) Apply(server *Server) {
server.Noticef("Reloaded: authorization password")
}
// authorizationOption implements the option interface for the `token`
// authorization setting.
type authorizationOption struct {
authOption
}
// Apply is a no-op because authorization will be reloaded after options are
// applied.
func (a *authorizationOption) Apply(server *Server) {
server.Noticef("Reloaded: authorization token")
}
// authTimeoutOption implements the option interface for the authorization
// `timeout` setting.
type authTimeoutOption struct {
noopOption // Not authOption because this is a no-op; will be reloaded with options.
newValue float64
}
// Apply is a no-op because the timeout will be reloaded after options are
// applied.
func (a *authTimeoutOption) Apply(server *Server) {
server.Noticef("Reloaded: authorization timeout = %v", a.newValue)
}
// usersOption implements the option interface for the authorization `users`
// setting.
type usersOption struct {
authOption
newValue []*User
}
func (u *usersOption) Apply(server *Server) {
server.Noticef("Reloaded: authorization users")
}
// clusterOption implements the option interface for the `cluster` setting.
type clusterOption struct {
authOption
newValue ClusterOpts
}
// Apply the cluster change.
func (c *clusterOption) Apply(server *Server) {
// TODO: support enabling/disabling clustering.
server.mu.Lock()
tlsRequired := c.newValue.TLSConfig != nil
server.routeInfo.TLSRequired = tlsRequired
server.routeInfo.TLSVerify = tlsRequired
server.routeInfo.AuthRequired = c.newValue.Username != ""
if c.newValue.NoAdvertise {
server.routeInfo.ClientConnectURLs = nil
} else {
server.routeInfo.ClientConnectURLs = server.clientConnectURLs
}
server.setRouteInfoHostPortAndIP()
server.mu.Unlock()
server.Noticef("Reloaded: cluster")
}
// routesOption implements the option interface for the cluster `routes`
// setting.
type routesOption struct {
noopOption
add []*url.URL
remove []*url.URL
}
// Apply the route changes by adding and removing the necessary routes.
func (r *routesOption) Apply(server *Server) {
server.mu.Lock()
routes := make([]*client, len(server.routes))
i := 0
for _, client := range server.routes {
routes[i] = client
i++
}
server.mu.Unlock()
// Remove routes.
for _, remove := range r.remove {
for _, client := range routes {
if client.route.url == remove {
// Do not attempt to reconnect when route is removed.
client.setRouteNoReconnectOnClose()
client.closeConnection(RouteRemoved)
server.Noticef("Removed route %v", remove)
}
}
}
// Add routes.
server.solicitRoutes(r.add)
server.Noticef("Reloaded: cluster routes")
}
// maxConnOption implements the option interface for the `max_connections`
// setting.
type maxConnOption struct {
noopOption
newValue int
}
// Apply the max connections change by closing random connections til we are
// below the limit if necessary.
func (m *maxConnOption) Apply(server *Server) {
server.mu.Lock()
var (
clients = make([]*client, len(server.clients))
i = 0
)
// Map iteration is random, which allows us to close random connections.
for _, client := range server.clients {
clients[i] = client
i++
}
server.mu.Unlock()
if m.newValue > 0 && len(clients) > m.newValue {
// Close connections til we are within the limit.
var (
numClose = len(clients) - m.newValue
closed = 0
)
for _, client := range clients {
client.maxConnExceeded()
closed++
if closed >= numClose {
break
}
}
server.Noticef("Closed %d connections to fall within max_connections", closed)
}
server.Noticef("Reloaded: max_connections = %v", m.newValue)
}
// pidFileOption implements the option interface for the `pid_file` setting.
type pidFileOption struct {
noopOption
newValue string
}
// Apply the setting by logging the pid to the new file.
func (p *pidFileOption) Apply(server *Server) {
if p.newValue == "" {
return
}
if err := server.logPid(); err != nil {
server.Errorf("Failed to write pidfile: %v", err)
}
server.Noticef("Reloaded: pid_file = %v", p.newValue)
}
// portsFileDirOption implements the option interface for the `portFileDir` setting.
type portsFileDirOption struct {
noopOption
oldValue string
newValue string
}
func (p *portsFileDirOption) Apply(server *Server) {
server.deletePortsFile(p.oldValue)
server.logPorts()
server.Noticef("Reloaded: ports_file_dir = %v", p.newValue)
}
// maxControlLineOption implements the option interface for the
// `max_control_line` setting.
type maxControlLineOption struct {
noopOption
newValue int
}
// Apply is a no-op because the max control line will be reloaded after options
// are applied
func (m *maxControlLineOption) Apply(server *Server) {
server.Noticef("Reloaded: max_control_line = %d", m.newValue)
}
// maxPayloadOption implements the option interface for the `max_payload`
// setting.
type maxPayloadOption struct {
noopOption
newValue int
}
// Apply the setting by updating the server info and each client.
func (m *maxPayloadOption) Apply(server *Server) {
server.mu.Lock()
server.info.MaxPayload = m.newValue
for _, client := range server.clients {
atomic.StoreInt64(&client.mpay, int64(m.newValue))
}
server.mu.Unlock()
server.Noticef("Reloaded: max_payload = %d", m.newValue)
}
// pingIntervalOption implements the option interface for the `ping_interval`
// setting.
type pingIntervalOption struct {
noopOption
newValue time.Duration
}
// Apply is a no-op because the ping interval will be reloaded after options
// are applied.
func (p *pingIntervalOption) Apply(server *Server) {
server.Noticef("Reloaded: ping_interval = %s", p.newValue)
}
// maxPingsOutOption implements the option interface for the `ping_max`
// setting.
type maxPingsOutOption struct {
noopOption
newValue int
}
// Apply is a no-op because the ping interval will be reloaded after options
// are applied.
func (m *maxPingsOutOption) Apply(server *Server) {
server.Noticef("Reloaded: ping_max = %d", m.newValue)
}
// writeDeadlineOption implements the option interface for the `write_deadline`
// setting.
type writeDeadlineOption struct {
noopOption
newValue time.Duration
}
// Apply is a no-op because the write deadline will be reloaded after options
// are applied.
func (w *writeDeadlineOption) Apply(server *Server) {
server.Noticef("Reloaded: write_deadline = %s", w.newValue)
}
// clientAdvertiseOption implements the option interface for the `client_advertise` setting.
type clientAdvertiseOption struct {
noopOption
newValue string
}
// Apply the setting by updating the server info and regenerate the infoJSON byte array.
func (c *clientAdvertiseOption) Apply(server *Server) {
server.mu.Lock()
server.setInfoHostPortAndGenerateJSON()
server.mu.Unlock()
server.Noticef("Reload: client_advertise = %s", c.newValue)
}
// Reload reads the current configuration file and applies any supported
// changes. This returns an error if the server was not started with a config
// file or an option which doesn't support hot-swapping was changed.
func (s *Server) Reload() error {
s.mu.Lock()
if s.configFile == "" {
s.mu.Unlock()
return errors.New("Can only reload config when a file is provided using -c or --config")
}
newOpts, err := ProcessConfigFile(s.configFile)
if err != nil {
s.mu.Unlock()
// TODO: Dump previous good config to a .bak file?
return err
}
clientOrgPort := s.clientActualPort
clusterOrgPort := s.clusterActualPort
s.mu.Unlock()
// Apply flags over config file settings.
newOpts = MergeOptions(newOpts, FlagSnapshot)
processOptions(newOpts)
// processOptions sets Port to 0 if set to -1 (RANDOM port)
// If that's the case, set it to the saved value when the accept loop was
// created.
if newOpts.Port == 0 {
newOpts.Port = clientOrgPort
}
// We don't do that for cluster, so check against -1.
if newOpts.Cluster.Port == -1 {
newOpts.Cluster.Port = clusterOrgPort
}
if err := s.reloadOptions(newOpts); err != nil {
return err
}
s.mu.Lock()
s.configTime = time.Now()
s.mu.Unlock()
return nil
}
// reloadOptions reloads the server config with the provided options. If an
// option that doesn't support hot-swapping is changed, this returns an error.
func (s *Server) reloadOptions(newOpts *Options) error {
changed, err := s.diffOptions(newOpts)
if err != nil {
return err
}
s.setOpts(newOpts)
s.applyOptions(changed)
return nil
}
// diffOptions returns a slice containing options which have been changed. If
// an option that doesn't support hot-swapping is changed, this returns an
// error.
func (s *Server) diffOptions(newOpts *Options) ([]option, error) {
var (
oldConfig = reflect.ValueOf(s.getOpts()).Elem()
newConfig = reflect.ValueOf(newOpts).Elem()
diffOpts = []option{}
)
for i := 0; i < oldConfig.NumField(); i++ {
var (
field = oldConfig.Type().Field(i)
oldValue = oldConfig.Field(i).Interface()
newValue = newConfig.Field(i).Interface()
changed = !reflect.DeepEqual(oldValue, newValue)
)
if !changed {
continue
}
switch strings.ToLower(field.Name) {
case "trace":
diffOpts = append(diffOpts, &traceOption{newValue: newValue.(bool)})
case "debug":
diffOpts = append(diffOpts, &debugOption{newValue: newValue.(bool)})
case "logtime":
diffOpts = append(diffOpts, &logtimeOption{newValue: newValue.(bool)})
case "logfile":
diffOpts = append(diffOpts, &logfileOption{newValue: newValue.(string)})
case "syslog":
diffOpts = append(diffOpts, &syslogOption{newValue: newValue.(bool)})
case "remotesyslog":
diffOpts = append(diffOpts, &remoteSyslogOption{newValue: newValue.(string)})
case "tlsconfig":
diffOpts = append(diffOpts, &tlsOption{newValue: newValue.(*tls.Config)})
case "tlstimeout":
diffOpts = append(diffOpts, &tlsTimeoutOption{newValue: newValue.(float64)})
case "username":
diffOpts = append(diffOpts, &usernameOption{})
case "password":
diffOpts = append(diffOpts, &passwordOption{})
case "authorization":
diffOpts = append(diffOpts, &authorizationOption{})
case "authtimeout":
diffOpts = append(diffOpts, &authTimeoutOption{newValue: newValue.(float64)})
case "users":
diffOpts = append(diffOpts, &usersOption{newValue: newValue.([]*User)})
case "cluster":
newClusterOpts := newValue.(ClusterOpts)
if err := validateClusterOpts(oldValue.(ClusterOpts), newClusterOpts); err != nil {
return nil, err
}
diffOpts = append(diffOpts, &clusterOption{newValue: newClusterOpts})
case "routes":
add, remove := diffRoutes(oldValue.([]*url.URL), newValue.([]*url.URL))
diffOpts = append(diffOpts, &routesOption{add: add, remove: remove})
case "maxconn":
diffOpts = append(diffOpts, &maxConnOption{newValue: newValue.(int)})
case "pidfile":
diffOpts = append(diffOpts, &pidFileOption{newValue: newValue.(string)})
case "portsfiledir":
diffOpts = append(diffOpts, &portsFileDirOption{newValue: newValue.(string), oldValue: oldValue.(string)})
case "maxcontrolline":
diffOpts = append(diffOpts, &maxControlLineOption{newValue: newValue.(int)})
case "maxpayload":
diffOpts = append(diffOpts, &maxPayloadOption{newValue: newValue.(int)})
case "pinginterval":
diffOpts = append(diffOpts, &pingIntervalOption{newValue: newValue.(time.Duration)})
case "maxpingsout":
diffOpts = append(diffOpts, &maxPingsOutOption{newValue: newValue.(int)})
case "writedeadline":
diffOpts = append(diffOpts, &writeDeadlineOption{newValue: newValue.(time.Duration)})
case "clientadvertise":
cliAdv := newValue.(string)
if cliAdv != "" {
// Validate ClientAdvertise syntax
if _, _, err := parseHostPort(cliAdv, 0); err != nil {
return nil, fmt.Errorf("invalid ClientAdvertise value of %s, err=%v", cliAdv, err)
}
}
diffOpts = append(diffOpts, &clientAdvertiseOption{newValue: cliAdv})
case "nolog", "nosigs":
// Ignore NoLog and NoSigs options since they are not parsed and only used in
// testing.
continue
case "port":
// check to see if newValue == 0 and continue if so.
if newValue == 0 {
// ignore RANDOM_PORT
continue
}
fallthrough
default:
// Bail out if attempting to reload any unsupported options.
return nil, fmt.Errorf("Config reload not supported for %s: old=%v, new=%v",
field.Name, oldValue, newValue)
}
}
return diffOpts, nil
}
func (s *Server) applyOptions(opts []option) {
var (
reloadLogging = false
reloadAuth = false
)
for _, opt := range opts {
opt.Apply(s)
if opt.IsLoggingChange() {
reloadLogging = true
}
if opt.IsAuthChange() {
reloadAuth = true
}
}
if reloadLogging {
s.ConfigureLogger()
}
if reloadAuth {
s.reloadAuthorization()
}
s.Noticef("Reloaded server configuration")
}
// reloadAuthorization reconfigures the server authorization settings,
// disconnects any clients who are no longer authorized, and removes any
// unauthorized subscriptions.
func (s *Server) reloadAuthorization() {
s.mu.Lock()
s.configureAuthorization()
clients := make(map[uint64]*client, len(s.clients))
for i, client := range s.clients {
clients[i] = client
}
routes := make(map[uint64]*client, len(s.routes))
for i, route := range s.routes {
routes[i] = route
}
s.mu.Unlock()
for _, client := range clients {
// Disconnect any unauthorized clients.
if !s.isClientAuthorized(client) {
client.authViolation()
continue
}
// Remove any unauthorized subscriptions.
s.removeUnauthorizedSubs(client)
}
for _, client := range routes {
// Disconnect any unauthorized routes.
if !s.isRouterAuthorized(client) {
client.setRouteNoReconnectOnClose()
client.authViolation()
}
}
}
// validateClusterOpts ensures the new ClusterOpts does not change host or
// port, which do not support reload.
func validateClusterOpts(old, new ClusterOpts) error {
if old.Host != new.Host {
return fmt.Errorf("Config reload not supported for cluster host: old=%s, new=%s",
old.Host, new.Host)
}
if old.Port != new.Port {
return fmt.Errorf("Config reload not supported for cluster port: old=%d, new=%d",
old.Port, new.Port)
}
// Validate Cluster.Advertise syntax
if new.Advertise != "" {
if _, _, err := parseHostPort(new.Advertise, 0); err != nil {
return fmt.Errorf("invalid Cluster.Advertise value of %s, err=%v", new.Advertise, err)
}
}
return nil
}
// diffRoutes diffs the old routes and the new routes and returns the ones that
// should be added and removed from the server.
func diffRoutes(old, new []*url.URL) (add, remove []*url.URL) {
// Find routes to remove.
removeLoop:
for _, oldRoute := range old {
for _, newRoute := range new {
if oldRoute == newRoute {
continue removeLoop
}
}
remove = append(remove, oldRoute)
}
// Find routes to add.
addLoop:
for _, newRoute := range new {
for _, oldRoute := range old {
if oldRoute == newRoute {
continue addLoop
}
}
add = append(add, newRoute)
}
return add, remove
}

1901
vendor/github.com/nats-io/gnatsd/server/reload_test.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

75
vendor/github.com/nats-io/gnatsd/server/ring.go generated vendored Normal file
View File

@ -0,0 +1,75 @@
// Copyright 2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package server
// We wrap to hold onto optional items for /connz.
type closedClient struct {
ConnInfo
subs []string
user string
}
// Fixed sized ringbuffer for closed connections.
type closedRingBuffer struct {
total uint64
conns []*closedClient
}
// Create a new ring buffer with at most max items.
func newClosedRingBuffer(max int) *closedRingBuffer {
rb := &closedRingBuffer{}
rb.conns = make([]*closedClient, max)
return rb
}
// Adds in a new closed connection. If there is no more room,
// remove the oldest.
func (rb *closedRingBuffer) append(cc *closedClient) {
rb.conns[rb.next()] = cc
rb.total++
}
func (rb *closedRingBuffer) next() int {
return int(rb.total % uint64(cap(rb.conns)))
}
func (rb *closedRingBuffer) len() int {
if rb.total > uint64(cap(rb.conns)) {
return cap(rb.conns)
}
return int(rb.total)
}
func (rb *closedRingBuffer) totalConns() uint64 {
return rb.total
}
// This will not be sorted. Will return a copy of the list
// which recipient can modify. If the contents of the client
// itself need to be modified, meaning swapping in any optional items,
// a copy should be made. We could introduce a new lock and hold that
// but since we return this list inside monitor which allows programatic
// access, we do not know when it would be done.
func (rb *closedRingBuffer) closedClients() []*closedClient {
dup := make([]*closedClient, rb.len())
if rb.total <= uint64(cap(rb.conns)) {
copy(dup, rb.conns[:rb.len()])
} else {
first := rb.next()
next := cap(rb.conns) - first
copy(dup, rb.conns[first:])
copy(dup[next:], rb.conns[:next])
}
return dup
}

1103
vendor/github.com/nats-io/gnatsd/server/route.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

1000
vendor/github.com/nats-io/gnatsd/server/routes_test.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

1420
vendor/github.com/nats-io/gnatsd/server/server.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

700
vendor/github.com/nats-io/gnatsd/server/server_test.go generated vendored Normal file
View File

@ -0,0 +1,700 @@
// Copyright 2012-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package server
import (
"flag"
"fmt"
"net"
"os"
"strings"
"testing"
"time"
"github.com/nats-io/go-nats"
)
func checkFor(t *testing.T, totalWait, sleepDur time.Duration, f func() error) {
t.Helper()
timeout := time.Now().Add(totalWait)
var err error
for time.Now().Before(timeout) {
err = f()
if err == nil {
return
}
time.Sleep(sleepDur)
}
if err != nil {
t.Fatal(err.Error())
}
}
func DefaultOptions() *Options {
return &Options{
Host: "127.0.0.1",
Port: -1,
HTTPPort: -1,
Cluster: ClusterOpts{Port: -1},
NoLog: true,
NoSigs: true,
Debug: true,
Trace: true,
}
}
// New Go Routine based server
func RunServer(opts *Options) *Server {
if opts == nil {
opts = DefaultOptions()
}
s := New(opts)
if s == nil {
panic("No NATS Server object returned.")
}
if !opts.NoLog {
s.ConfigureLogger()
}
// Run server in Go routine.
go s.Start()
// Wait for accept loop(s) to be started
if !s.ReadyForConnections(10 * time.Second) {
panic("Unable to start NATS Server in Go Routine")
}
return s
}
// LoadConfig loads a configuration from a filename
func LoadConfig(configFile string) (opts *Options) {
opts, err := ProcessConfigFile(configFile)
if err != nil {
panic(fmt.Sprintf("Error processing configuration file: %v", err))
}
opts.NoSigs, opts.NoLog = true, true
return
}
// RunServerWithConfig starts a new Go routine based server with a configuration file.
func RunServerWithConfig(configFile string) (srv *Server, opts *Options) {
opts = LoadConfig(configFile)
srv = RunServer(opts)
return
}
func TestVersionMatchesTag(t *testing.T) {
tag := os.Getenv("TRAVIS_TAG")
if tag == "" {
t.SkipNow()
}
// We expect a tag of the form vX.Y.Z. If that's not the case,
// we need someone to have a look. So fail if first letter is not
// a `v`
if tag[0] != 'v' {
t.Fatalf("Expect tag to start with `v`, tag is: %s", tag)
}
// Strip the `v` from the tag for the version comparison.
if VERSION != tag[1:] {
t.Fatalf("Version (%s) does not match tag (%s)", VERSION, tag[1:])
}
}
func TestStartProfiler(t *testing.T) {
s := New(DefaultOptions())
s.StartProfiler()
s.mu.Lock()
s.profiler.Close()
s.mu.Unlock()
}
func TestStartupAndShutdown(t *testing.T) {
opts := DefaultOptions()
s := RunServer(opts)
defer s.Shutdown()
if !s.isRunning() {
t.Fatal("Could not run server")
}
// Debug stuff.
numRoutes := s.NumRoutes()
if numRoutes != 0 {
t.Fatalf("Expected numRoutes to be 0 vs %d\n", numRoutes)
}
numRemotes := s.NumRemotes()
if numRemotes != 0 {
t.Fatalf("Expected numRemotes to be 0 vs %d\n", numRemotes)
}
numClients := s.NumClients()
if numClients != 0 && numClients != 1 {
t.Fatalf("Expected numClients to be 1 or 0 vs %d\n", numClients)
}
numSubscriptions := s.NumSubscriptions()
if numSubscriptions != 0 {
t.Fatalf("Expected numSubscriptions to be 0 vs %d\n", numSubscriptions)
}
}
func TestTlsCipher(t *testing.T) {
if strings.Compare(tlsCipher(0x0005), "TLS_RSA_WITH_RC4_128_SHA") != 0 {
t.Fatalf("Invalid tls cipher")
}
if strings.Compare(tlsCipher(0x000a), "TLS_RSA_WITH_3DES_EDE_CBC_SHA") != 0 {
t.Fatalf("Invalid tls cipher")
}
if strings.Compare(tlsCipher(0x002f), "TLS_RSA_WITH_AES_128_CBC_SHA") != 0 {
t.Fatalf("Invalid tls cipher")
}
if strings.Compare(tlsCipher(0x0035), "TLS_RSA_WITH_AES_256_CBC_SHA") != 0 {
t.Fatalf("Invalid tls cipher")
}
if strings.Compare(tlsCipher(0xc007), "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA") != 0 {
t.Fatalf("Invalid tls cipher")
}
if strings.Compare(tlsCipher(0xc009), "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA") != 0 {
t.Fatalf("Invalid tls cipher")
}
if strings.Compare(tlsCipher(0xc00a), "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA") != 0 {
t.Fatalf("Invalid tls cipher")
}
if strings.Compare(tlsCipher(0xc011), "TLS_ECDHE_RSA_WITH_RC4_128_SHA") != 0 {
t.Fatalf("Invalid tls cipher")
}
if strings.Compare(tlsCipher(0xc012), "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA") != 0 {
t.Fatalf("Invalid tls cipher")
}
if strings.Compare(tlsCipher(0xc013), "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA") != 0 {
t.Fatalf("Invalid tls cipher")
}
if strings.Compare(tlsCipher(0xc014), "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA") != 0 {
t.Fatalf("IUnknownnvalid tls cipher")
}
if strings.Compare(tlsCipher(0xc02f), "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256") != 0 {
t.Fatalf("Invalid tls cipher")
}
if strings.Compare(tlsCipher(0xc02b), "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256") != 0 {
t.Fatalf("Invalid tls cipher")
}
if strings.Compare(tlsCipher(0xc030), "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384") != 0 {
t.Fatalf("Invalid tls cipher")
}
if strings.Compare(tlsCipher(0xc02c), "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384") != 0 {
t.Fatalf("Invalid tls cipher")
}
if !strings.Contains(tlsCipher(0x9999), "Unknown") {
t.Fatalf("Expected an unknown cipher.")
}
}
func TestGetConnectURLs(t *testing.T) {
opts := DefaultOptions()
opts.Port = 4222
var globalIP net.IP
checkGlobalConnectURLs := func() {
s := New(opts)
defer s.Shutdown()
s.mu.Lock()
urls := s.getClientConnectURLs()
s.mu.Unlock()
if len(urls) == 0 {
t.Fatalf("Expected to get a list of urls, got none for listen addr: %v", opts.Host)
}
for _, u := range urls {
tcpaddr, err := net.ResolveTCPAddr("tcp", u)
if err != nil {
t.Fatalf("Error resolving: %v", err)
}
ip := tcpaddr.IP
if !ip.IsGlobalUnicast() {
t.Fatalf("IP %v is not global", ip.String())
}
if ip.IsUnspecified() {
t.Fatalf("IP %v is unspecified", ip.String())
}
addr := strings.TrimSuffix(u, ":4222")
if addr == opts.Host {
t.Fatalf("Returned url is not right: %v", u)
}
if globalIP == nil {
globalIP = ip
}
}
}
listenAddrs := []string{"0.0.0.0", "::"}
for _, listenAddr := range listenAddrs {
opts.Host = listenAddr
checkGlobalConnectURLs()
}
checkConnectURLsHasOnlyOne := func() {
s := New(opts)
defer s.Shutdown()
s.mu.Lock()
urls := s.getClientConnectURLs()
s.mu.Unlock()
if len(urls) != 1 {
t.Fatalf("Expected one URL, got %v", urls)
}
tcpaddr, err := net.ResolveTCPAddr("tcp", urls[0])
if err != nil {
t.Fatalf("Error resolving: %v", err)
}
ip := tcpaddr.IP
if ip.String() != opts.Host {
t.Fatalf("Expected connect URL to be %v, got %v", opts.Host, ip.String())
}
}
singleConnectReturned := []string{"127.0.0.1", "::1"}
if globalIP != nil {
singleConnectReturned = append(singleConnectReturned, globalIP.String())
}
for _, listenAddr := range singleConnectReturned {
opts.Host = listenAddr
checkConnectURLsHasOnlyOne()
}
}
func TestClientAdvertiseConnectURL(t *testing.T) {
opts := DefaultOptions()
opts.Port = 4222
opts.ClientAdvertise = "nats.example.com"
s := New(opts)
defer s.Shutdown()
s.mu.Lock()
urls := s.getClientConnectURLs()
s.mu.Unlock()
if len(urls) != 1 {
t.Fatalf("Expected to get one url, got none: %v with ClientAdvertise %v",
opts.Host, opts.ClientAdvertise)
}
if urls[0] != "nats.example.com:4222" {
t.Fatalf("Expected to get '%s', got: '%v'", "nats.example.com:4222", urls[0])
}
s.Shutdown()
opts.ClientAdvertise = "nats.example.com:7777"
s = New(opts)
s.mu.Lock()
urls = s.getClientConnectURLs()
s.mu.Unlock()
if len(urls) != 1 {
t.Fatalf("Expected to get one url, got none: %v with ClientAdvertise %v",
opts.Host, opts.ClientAdvertise)
}
if urls[0] != "nats.example.com:7777" {
t.Fatalf("Expected 'nats.example.com:7777', got: '%v'", urls[0])
}
if s.info.Host != "nats.example.com" {
t.Fatalf("Expected host to be set to nats.example.com")
}
if s.info.Port != 7777 {
t.Fatalf("Expected port to be set to 7777")
}
s.Shutdown()
opts = DefaultOptions()
opts.Port = 0
opts.ClientAdvertise = "nats.example.com:7777"
s = New(opts)
if s.info.Host != "nats.example.com" && s.info.Port != 7777 {
t.Fatalf("Expected Client Advertise Host:Port to be nats.example.com:7777, got: %s:%d",
s.info.Host, s.info.Port)
}
s.Shutdown()
}
func TestClientAdvertiseErrorOnStartup(t *testing.T) {
opts := DefaultOptions()
// Set invalid address
opts.ClientAdvertise = "addr:::123"
s := New(opts)
defer s.Shutdown()
dl := &DummyLogger{}
s.SetLogger(dl, false, false)
// Expect this to return due to failure
s.Start()
dl.Lock()
msg := dl.msg
dl.Unlock()
if !strings.Contains(msg, "ClientAdvertise") {
t.Fatalf("Unexpected error: %v", msg)
}
}
func TestNoDeadlockOnStartFailure(t *testing.T) {
opts := DefaultOptions()
opts.Host = "x.x.x.x" // bad host
opts.Port = 4222
opts.HTTPHost = opts.Host
opts.Cluster.Host = "127.0.0.1"
opts.Cluster.Port = -1
opts.ProfPort = -1
s := New(opts)
// This should return since it should fail to start a listener
// on x.x.x.x:4222
s.Start()
// We should be able to shutdown
s.Shutdown()
}
func TestMaxConnections(t *testing.T) {
opts := DefaultOptions()
opts.MaxConn = 1
s := RunServer(opts)
defer s.Shutdown()
addr := fmt.Sprintf("nats://%s:%d", opts.Host, opts.Port)
nc, err := nats.Connect(addr)
if err != nil {
t.Fatalf("Error creating client: %v\n", err)
}
defer nc.Close()
nc2, err := nats.Connect(addr)
if err == nil {
nc2.Close()
t.Fatal("Expected connection to fail")
}
}
func TestMaxSubscriptions(t *testing.T) {
opts := DefaultOptions()
opts.MaxSubs = 10
s := RunServer(opts)
defer s.Shutdown()
addr := fmt.Sprintf("nats://%s:%d", opts.Host, opts.Port)
nc, err := nats.Connect(addr)
if err != nil {
t.Fatalf("Error creating client: %v\n", err)
}
defer nc.Close()
for i := 0; i < 10; i++ {
_, err := nc.Subscribe(fmt.Sprintf("foo.%d", i), func(*nats.Msg) {})
if err != nil {
t.Fatalf("Error subscribing: %v\n", err)
}
}
// This should cause the error.
nc.Subscribe("foo.22", func(*nats.Msg) {})
nc.Flush()
if err := nc.LastError(); err == nil {
t.Fatal("Expected an error but got none\n")
}
}
func TestProcessCommandLineArgs(t *testing.T) {
var host string
var port int
cmd := flag.NewFlagSet("gnatsd", flag.ExitOnError)
cmd.StringVar(&host, "a", "0.0.0.0", "Host.")
cmd.IntVar(&port, "p", 4222, "Port.")
cmd.Parse([]string{"-a", "127.0.0.1", "-p", "9090"})
showVersion, showHelp, err := ProcessCommandLineArgs(cmd)
if err != nil {
t.Errorf("Expected no errors, got: %s", err)
}
if showVersion || showHelp {
t.Errorf("Expected not having to handle subcommands")
}
cmd.Parse([]string{"version"})
showVersion, showHelp, err = ProcessCommandLineArgs(cmd)
if err != nil {
t.Errorf("Expected no errors, got: %s", err)
}
if !showVersion {
t.Errorf("Expected having to handle version command")
}
if showHelp {
t.Errorf("Expected not having to handle help command")
}
cmd.Parse([]string{"help"})
showVersion, showHelp, err = ProcessCommandLineArgs(cmd)
if err != nil {
t.Errorf("Expected no errors, got: %s", err)
}
if showVersion {
t.Errorf("Expected not having to handle version command")
}
if !showHelp {
t.Errorf("Expected having to handle help command")
}
cmd.Parse([]string{"foo", "-p", "9090"})
_, _, err = ProcessCommandLineArgs(cmd)
if err == nil {
t.Errorf("Expected an error handling the command arguments")
}
}
func TestWriteDeadline(t *testing.T) {
opts := DefaultOptions()
opts.WriteDeadline = 30 * time.Millisecond
s := RunServer(opts)
defer s.Shutdown()
c, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", opts.Host, opts.Port), 3*time.Second)
if err != nil {
t.Fatalf("Error on connect: %v", err)
}
defer c.Close()
if _, err := c.Write([]byte("CONNECT {}\r\nPING\r\nSUB foo 1\r\n")); err != nil {
t.Fatalf("Error sending protocols to server: %v", err)
}
// Reduce socket buffer to increase reliability of getting
// write deadline errors.
c.(*net.TCPConn).SetReadBuffer(4)
url := fmt.Sprintf("nats://%s:%d", opts.Host, opts.Port)
sender, err := nats.Connect(url)
if err != nil {
t.Fatalf("Error on connect: %v", err)
}
defer sender.Close()
payload := make([]byte, 1000000)
for i := 0; i < 10; i++ {
if err := sender.Publish("foo", payload); err != nil {
t.Fatalf("Error on publish: %v", err)
}
}
// Flush sender connection to ensure that all data has been sent.
if err := sender.Flush(); err != nil {
t.Fatalf("Error on flush: %v", err)
}
// At this point server should have closed connection c.
// On certain platforms, it may take more than one call before
// getting the error.
for i := 0; i < 100; i++ {
if _, err := c.Write([]byte("PUB bar 5\r\nhello\r\n")); err != nil {
// ok
return
}
}
t.Fatal("Connection should have been closed")
}
func TestSlowConsumerPendingBytes(t *testing.T) {
opts := DefaultOptions()
opts.WriteDeadline = 30 * time.Second // Wait for long time so write deadline does not trigger slow consumer.
opts.MaxPending = 1 * 1024 * 1024 // Set to low value (1MB) to allow SC to trip.
s := RunServer(opts)
defer s.Shutdown()
c, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", opts.Host, opts.Port), 3*time.Second)
if err != nil {
t.Fatalf("Error on connect: %v", err)
}
defer c.Close()
if _, err := c.Write([]byte("CONNECT {}\r\nPING\r\nSUB foo 1\r\n")); err != nil {
t.Fatalf("Error sending protocols to server: %v", err)
}
// Reduce socket buffer to increase reliability of data backing up in the server destined
// for our subscribed client.
c.(*net.TCPConn).SetReadBuffer(128)
url := fmt.Sprintf("nats://%s:%d", opts.Host, opts.Port)
sender, err := nats.Connect(url)
if err != nil {
t.Fatalf("Error on connect: %v", err)
}
defer sender.Close()
payload := make([]byte, 1024*1024)
for i := 0; i < 100; i++ {
if err := sender.Publish("foo", payload); err != nil {
t.Fatalf("Error on publish: %v", err)
}
}
// Flush sender connection to ensure that all data has been sent.
if err := sender.Flush(); err != nil {
t.Fatalf("Error on flush: %v", err)
}
// At this point server should have closed connection c.
// On certain platforms, it may take more than one call before
// getting the error.
for i := 0; i < 100; i++ {
if _, err := c.Write([]byte("PUB bar 5\r\nhello\r\n")); err != nil {
// ok
return
}
}
t.Fatal("Connection should have been closed")
}
func TestRandomPorts(t *testing.T) {
opts := DefaultOptions()
opts.HTTPPort = -1
opts.Port = -1
s := RunServer(opts)
defer s.Shutdown()
if s.Addr() == nil || s.Addr().(*net.TCPAddr).Port <= 0 {
t.Fatal("Should have dynamically assigned server port.")
}
if s.Addr() == nil || s.Addr().(*net.TCPAddr).Port == 4222 {
t.Fatal("Should not have dynamically assigned default port: 4222.")
}
if s.MonitorAddr() == nil || s.MonitorAddr().Port <= 0 {
t.Fatal("Should have dynamically assigned monitoring port.")
}
}
func TestNilMonitoringPort(t *testing.T) {
opts := DefaultOptions()
opts.HTTPPort = 0
opts.HTTPSPort = 0
s := RunServer(opts)
defer s.Shutdown()
if s.MonitorAddr() != nil {
t.Fatal("HttpAddr should be nil.")
}
}
type DummyAuth struct{}
func (d *DummyAuth) Check(c ClientAuthentication) bool {
return c.GetOpts().Username == "valid"
}
func TestCustomClientAuthentication(t *testing.T) {
var clientAuth DummyAuth
opts := DefaultOptions()
opts.CustomClientAuthentication = &clientAuth
s := RunServer(opts)
defer s.Shutdown()
addr := fmt.Sprintf("nats://%s:%d", opts.Host, opts.Port)
nc, err := nats.Connect(addr, nats.UserInfo("valid", ""))
if err != nil {
t.Fatalf("Expected client to connect, got: %s", err)
}
nc.Close()
if _, err := nats.Connect(addr, nats.UserInfo("invalid", "")); err == nil {
t.Fatal("Expected client to fail to connect")
}
}
func TestCustomRouterAuthentication(t *testing.T) {
opts := DefaultOptions()
opts.CustomRouterAuthentication = &DummyAuth{}
opts.Cluster.Host = "127.0.0.1"
s := RunServer(opts)
defer s.Shutdown()
clusterPort := s.ClusterAddr().Port
opts2 := DefaultOptions()
opts2.Cluster.Host = "127.0.0.1"
opts2.Routes = RoutesFromStr(fmt.Sprintf("nats://invalid@127.0.0.1:%d", clusterPort))
s2 := RunServer(opts2)
defer s2.Shutdown()
// s2 will attempt to connect to s, which should reject.
// Keep in mind that s2 will try again...
time.Sleep(50 * time.Millisecond)
checkNumRoutes(t, s2, 0)
opts3 := DefaultOptions()
opts3.Cluster.Host = "127.0.0.1"
opts3.Routes = RoutesFromStr(fmt.Sprintf("nats://valid@127.0.0.1:%d", clusterPort))
s3 := RunServer(opts3)
defer s3.Shutdown()
checkClusterFormed(t, s, s3)
checkNumRoutes(t, s3, 1)
}
func TestMonitoringNoTimeout(t *testing.T) {
s := runMonitorServer()
defer s.Shutdown()
s.mu.Lock()
srv := s.monitoringServer
s.mu.Unlock()
if srv == nil {
t.Fatalf("Monitoring server not set")
}
if srv.ReadTimeout != 0 {
t.Fatalf("ReadTimeout should not be set, was set to %v", srv.ReadTimeout)
}
if srv.WriteTimeout != 0 {
t.Fatalf("WriteTimeout should not be set, was set to %v", srv.WriteTimeout)
}
}
func TestProfilingNoTimeout(t *testing.T) {
opts := DefaultOptions()
opts.ProfPort = -1
s := RunServer(opts)
defer s.Shutdown()
paddr := s.ProfilerAddr()
if paddr == nil {
t.Fatalf("Profiler not started")
}
pport := paddr.Port
if pport <= 0 {
t.Fatalf("Expected profiler port to be set, got %v", pport)
}
s.mu.Lock()
srv := s.profilingServer
s.mu.Unlock()
if srv == nil {
t.Fatalf("Profiling server not set")
}
if srv.ReadTimeout != 0 {
t.Fatalf("ReadTimeout should not be set, was set to %v", srv.ReadTimeout)
}
if srv.WriteTimeout != 0 {
t.Fatalf("WriteTimeout should not be set, was set to %v", srv.WriteTimeout)
}
}

28
vendor/github.com/nats-io/gnatsd/server/service.go generated vendored Normal file
View File

@ -0,0 +1,28 @@
// Copyright 2012-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build !windows
package server
// Run starts the NATS server. This wrapper function allows Windows to add a
// hook for running NATS as a service.
func Run(server *Server) error {
server.Start()
return nil
}
// isWindowsService indicates if NATS is running as a Windows service.
func isWindowsService() bool {
return false
}

View File

@ -0,0 +1,53 @@
// Copyright 2012-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build !windows
package server
import (
"errors"
"testing"
"time"
)
func TestRun(t *testing.T) {
var (
s = New(DefaultOptions())
started = make(chan error, 1)
errC = make(chan error, 1)
)
go func() {
errC <- Run(s)
}()
go func() {
if !s.ReadyForConnections(time.Second) {
started <- errors.New("failed to start in time")
return
}
s.Shutdown()
close(started)
}()
select {
case err := <-errC:
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
case <-time.After(2 * time.Second):
t.Fatal("Timed out")
}
if err := <-started; err != nil {
t.Fatalf("Unexpected error: %v", err)
}
}

View File

@ -0,0 +1,121 @@
// Copyright 2012-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package server
import (
"os"
"time"
"golang.org/x/sys/windows/svc"
"golang.org/x/sys/windows/svc/debug"
)
const (
serviceName = "gnatsd"
reopenLogCode = 128
reopenLogCmd = svc.Cmd(reopenLogCode)
acceptReopenLog = svc.Accepted(reopenLogCode)
)
// winServiceWrapper implements the svc.Handler interface for implementing
// gnatsd as a Windows service.
type winServiceWrapper struct {
server *Server
}
var dockerized = false
func init() {
if v, exists := os.LookupEnv("NATS_DOCKERIZED"); exists && v == "1" {
dockerized = true
}
}
// Execute will be called by the package code at the start of
// the service, and the service will exit once Execute completes.
// Inside Execute you must read service change requests from r and
// act accordingly. You must keep service control manager up to date
// about state of your service by writing into s as required.
// args contains service name followed by argument strings passed
// to the service.
// You can provide service exit code in exitCode return parameter,
// with 0 being "no error". You can also indicate if exit code,
// if any, is service specific or not by using svcSpecificEC
// parameter.
func (w *winServiceWrapper) Execute(args []string, changes <-chan svc.ChangeRequest,
status chan<- svc.Status) (bool, uint32) {
status <- svc.Status{State: svc.StartPending}
go w.server.Start()
// Wait for accept loop(s) to be started
if !w.server.ReadyForConnections(10 * time.Second) {
// Failed to start.
return false, 1
}
status <- svc.Status{
State: svc.Running,
Accepts: svc.AcceptStop | svc.AcceptShutdown | svc.AcceptParamChange | acceptReopenLog,
}
loop:
for change := range changes {
switch change.Cmd {
case svc.Interrogate:
status <- change.CurrentStatus
case svc.Stop, svc.Shutdown:
w.server.Shutdown()
break loop
case reopenLogCmd:
// File log re-open for rotating file logs.
w.server.ReOpenLogFile()
case svc.ParamChange:
if err := w.server.Reload(); err != nil {
w.server.Errorf("Failed to reload server configuration: %s", err)
}
default:
w.server.Debugf("Unexpected control request: %v", change.Cmd)
}
}
status <- svc.Status{State: svc.StopPending}
return false, 0
}
// Run starts the NATS server as a Windows service.
func Run(server *Server) error {
if dockerized {
server.Start()
return nil
}
run := svc.Run
isInteractive, err := svc.IsAnInteractiveSession()
if err != nil {
return err
}
if isInteractive {
run = debug.Run
}
return run(serviceName, &winServiceWrapper{server})
}
// isWindowsService indicates if NATS is running as a Windows service.
func isWindowsService() bool {
if dockerized {
return false
}
isInteractive, _ := svc.IsAnInteractiveSession()
return !isInteractive
}

158
vendor/github.com/nats-io/gnatsd/server/signal.go generated vendored Normal file
View File

@ -0,0 +1,158 @@
// Copyright 2012-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build !windows
package server
import (
"errors"
"fmt"
"os"
"os/exec"
"os/signal"
"strconv"
"strings"
"syscall"
)
const processName = "gnatsd"
// Signal Handling
func (s *Server) handleSignals() {
if s.getOpts().NoSigs {
return
}
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGINT, syscall.SIGUSR1, syscall.SIGHUP)
s.grWG.Add(1)
go func() {
defer s.grWG.Done()
for {
select {
case sig := <-c:
s.Debugf("Trapped %q signal", sig)
switch sig {
case syscall.SIGINT:
s.Noticef("Server Exiting..")
os.Exit(0)
case syscall.SIGUSR1:
// File log re-open for rotating file logs.
s.ReOpenLogFile()
case syscall.SIGHUP:
// Config reload.
if err := s.Reload(); err != nil {
s.Errorf("Failed to reload server configuration: %s", err)
}
}
case <-s.quitCh:
return
}
}
}()
}
// ProcessSignal sends the given signal command to the given process. If pidStr
// is empty, this will send the signal to the single running instance of
// gnatsd. If multiple instances are running, it returns an error. This returns
// an error if the given process is not running or the command is invalid.
func ProcessSignal(command Command, pidStr string) error {
var pid int
if pidStr == "" {
pids, err := resolvePids()
if err != nil {
return err
}
if len(pids) == 0 {
return errors.New("no gnatsd processes running")
}
if len(pids) > 1 {
errStr := "multiple gnatsd processes running:\n"
prefix := ""
for _, p := range pids {
errStr += fmt.Sprintf("%s%d", prefix, p)
prefix = "\n"
}
return errors.New(errStr)
}
pid = pids[0]
} else {
p, err := strconv.Atoi(pidStr)
if err != nil {
return fmt.Errorf("invalid pid: %s", pidStr)
}
pid = p
}
var err error
switch command {
case CommandStop:
err = kill(pid, syscall.SIGKILL)
case CommandQuit:
err = kill(pid, syscall.SIGINT)
case CommandReopen:
err = kill(pid, syscall.SIGUSR1)
case CommandReload:
err = kill(pid, syscall.SIGHUP)
default:
err = fmt.Errorf("unknown signal %q", command)
}
return err
}
// resolvePids returns the pids for all running gnatsd processes.
func resolvePids() ([]int, error) {
// If pgrep isn't available, this will just bail out and the user will be
// required to specify a pid.
output, err := pgrep()
if err != nil {
switch err.(type) {
case *exec.ExitError:
// ExitError indicates non-zero exit code, meaning no processes
// found.
break
default:
return nil, errors.New("unable to resolve pid, try providing one")
}
}
var (
myPid = os.Getpid()
pidStrs = strings.Split(string(output), "\n")
pids = make([]int, 0, len(pidStrs))
)
for _, pidStr := range pidStrs {
if pidStr == "" {
continue
}
pid, err := strconv.Atoi(pidStr)
if err != nil {
return nil, errors.New("unable to resolve pid, try providing one")
}
// Ignore the current process.
if pid == myPid {
continue
}
pids = append(pids, pid)
}
return pids, nil
}
var kill = func(pid int, signal syscall.Signal) error {
return syscall.Kill(pid, signal)
}
var pgrep = func() ([]byte, error) {
return exec.Command("pgrep", processName).Output()
}

314
vendor/github.com/nats-io/gnatsd/server/signal_test.go generated vendored Normal file
View File

@ -0,0 +1,314 @@
// Copyright 2012-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build !windows
package server
import (
"errors"
"fmt"
"io/ioutil"
"os"
"os/exec"
"strings"
"syscall"
"testing"
"time"
"github.com/nats-io/gnatsd/logger"
)
func TestSignalToReOpenLogFile(t *testing.T) {
logFile := "test.log"
defer os.Remove(logFile)
defer os.Remove(logFile + ".bak")
opts := &Options{
Host: "127.0.0.1",
Port: -1,
NoSigs: false,
LogFile: logFile,
}
s := RunServer(opts)
defer s.SetLogger(nil, false, false)
defer s.Shutdown()
// Set the file log
fileLog := logger.NewFileLogger(s.opts.LogFile, s.opts.Logtime, s.opts.Debug, s.opts.Trace, true)
s.SetLogger(fileLog, false, false)
// Add a trace
expectedStr := "This is a Notice"
s.Noticef(expectedStr)
buf, err := ioutil.ReadFile(logFile)
if err != nil {
t.Fatalf("Error reading file: %v", err)
}
if !strings.Contains(string(buf), expectedStr) {
t.Fatalf("Expected log to contain %q, got %q", expectedStr, string(buf))
}
// Rename the file
if err := os.Rename(logFile, logFile+".bak"); err != nil {
t.Fatalf("Unable to rename file: %v", err)
}
// This should cause file to be reopened.
syscall.Kill(syscall.Getpid(), syscall.SIGUSR1)
// Wait a bit for action to be performed
time.Sleep(500 * time.Millisecond)
buf, err = ioutil.ReadFile(logFile)
if err != nil {
t.Fatalf("Error reading file: %v", err)
}
expectedStr = "File log re-opened"
if !strings.Contains(string(buf), expectedStr) {
t.Fatalf("Expected log to contain %q, got %q", expectedStr, string(buf))
}
}
func TestSignalToReloadConfig(t *testing.T) {
opts, err := ProcessConfigFile("./configs/reload/basic.conf")
if err != nil {
t.Fatalf("Error processing config file: %v", err)
}
opts.NoLog = true
s := RunServer(opts)
defer s.Shutdown()
// Repeat test to make sure that server services signals more than once...
for i := 0; i < 2; i++ {
loaded := s.ConfigTime()
// Wait a bit to ensure ConfigTime changes.
time.Sleep(5 * time.Millisecond)
// This should cause config to be reloaded.
syscall.Kill(syscall.Getpid(), syscall.SIGHUP)
// Wait a bit for action to be performed
time.Sleep(500 * time.Millisecond)
if reloaded := s.ConfigTime(); !reloaded.After(loaded) {
t.Fatalf("ConfigTime is incorrect.\nexpected greater than: %s\ngot: %s", loaded, reloaded)
}
}
}
func TestProcessSignalNoProcesses(t *testing.T) {
pgrepBefore := pgrep
pgrep = func() ([]byte, error) {
return nil, &exec.ExitError{}
}
defer func() {
pgrep = pgrepBefore
}()
err := ProcessSignal(CommandStop, "")
if err == nil {
t.Fatal("Expected error")
}
expectedStr := "no gnatsd processes running"
if err.Error() != expectedStr {
t.Fatalf("Error is incorrect.\nexpected: %s\ngot: %s", expectedStr, err.Error())
}
}
func TestProcessSignalMultipleProcesses(t *testing.T) {
pid := os.Getpid()
pgrepBefore := pgrep
pgrep = func() ([]byte, error) {
return []byte(fmt.Sprintf("123\n456\n%d\n", pid)), nil
}
defer func() {
pgrep = pgrepBefore
}()
err := ProcessSignal(CommandStop, "")
if err == nil {
t.Fatal("Expected error")
}
expectedStr := "multiple gnatsd processes running:\n123\n456"
if err.Error() != expectedStr {
t.Fatalf("Error is incorrect.\nexpected: %s\ngot: %s", expectedStr, err.Error())
}
}
func TestProcessSignalPgrepError(t *testing.T) {
pgrepBefore := pgrep
pgrep = func() ([]byte, error) {
return nil, errors.New("error")
}
defer func() {
pgrep = pgrepBefore
}()
err := ProcessSignal(CommandStop, "")
if err == nil {
t.Fatal("Expected error")
}
expectedStr := "unable to resolve pid, try providing one"
if err.Error() != expectedStr {
t.Fatalf("Error is incorrect.\nexpected: %s\ngot: %s", expectedStr, err.Error())
}
}
func TestProcessSignalPgrepMangled(t *testing.T) {
pgrepBefore := pgrep
pgrep = func() ([]byte, error) {
return []byte("12x"), nil
}
defer func() {
pgrep = pgrepBefore
}()
err := ProcessSignal(CommandStop, "")
if err == nil {
t.Fatal("Expected error")
}
expectedStr := "unable to resolve pid, try providing one"
if err.Error() != expectedStr {
t.Fatalf("Error is incorrect.\nexpected: %s\ngot: %s", expectedStr, err.Error())
}
}
func TestProcessSignalResolveSingleProcess(t *testing.T) {
pid := os.Getpid()
pgrepBefore := pgrep
pgrep = func() ([]byte, error) {
return []byte(fmt.Sprintf("123\n%d\n", pid)), nil
}
defer func() {
pgrep = pgrepBefore
}()
killBefore := kill
called := false
kill = func(pid int, signal syscall.Signal) error {
called = true
if pid != 123 {
t.Fatalf("pid is incorrect.\nexpected: 123\ngot: %d", pid)
}
if signal != syscall.SIGKILL {
t.Fatalf("signal is incorrect.\nexpected: killed\ngot: %v", signal)
}
return nil
}
defer func() {
kill = killBefore
}()
if err := ProcessSignal(CommandStop, ""); err != nil {
t.Fatalf("ProcessSignal failed: %v", err)
}
if !called {
t.Fatal("Expected kill to be called")
}
}
func TestProcessSignalInvalidCommand(t *testing.T) {
err := ProcessSignal(Command("invalid"), "123")
if err == nil {
t.Fatal("Expected error")
}
expectedStr := "unknown signal \"invalid\""
if err.Error() != expectedStr {
t.Fatalf("Error is incorrect.\nexpected: %s\ngot: %s", expectedStr, err.Error())
}
}
func TestProcessSignalInvalidPid(t *testing.T) {
err := ProcessSignal(CommandStop, "abc")
if err == nil {
t.Fatal("Expected error")
}
expectedStr := "invalid pid: abc"
if err.Error() != expectedStr {
t.Fatalf("Error is incorrect.\nexpected: %s\ngot: %s", expectedStr, err.Error())
}
}
func TestProcessSignalQuitProcess(t *testing.T) {
killBefore := kill
called := false
kill = func(pid int, signal syscall.Signal) error {
called = true
if pid != 123 {
t.Fatalf("pid is incorrect.\nexpected: 123\ngot: %d", pid)
}
if signal != syscall.SIGINT {
t.Fatalf("signal is incorrect.\nexpected: interrupt\ngot: %v", signal)
}
return nil
}
defer func() {
kill = killBefore
}()
if err := ProcessSignal(CommandQuit, "123"); err != nil {
t.Fatalf("ProcessSignal failed: %v", err)
}
if !called {
t.Fatal("Expected kill to be called")
}
}
func TestProcessSignalReopenProcess(t *testing.T) {
killBefore := kill
called := false
kill = func(pid int, signal syscall.Signal) error {
called = true
if pid != 123 {
t.Fatalf("pid is incorrect.\nexpected: 123\ngot: %d", pid)
}
if signal != syscall.SIGUSR1 {
t.Fatalf("signal is incorrect.\nexpected: user defined signal 1\ngot: %v", signal)
}
return nil
}
defer func() {
kill = killBefore
}()
if err := ProcessSignal(CommandReopen, "123"); err != nil {
t.Fatalf("ProcessSignal failed: %v", err)
}
if !called {
t.Fatal("Expected kill to be called")
}
}
func TestProcessSignalReloadProcess(t *testing.T) {
killBefore := kill
called := false
kill = func(pid int, signal syscall.Signal) error {
called = true
if pid != 123 {
t.Fatalf("pid is incorrect.\nexpected: 123\ngot: %d", pid)
}
if signal != syscall.SIGHUP {
t.Fatalf("signal is incorrect.\nexpected: hangup\ngot: %v", signal)
}
return nil
}
defer func() {
kill = killBefore
}()
if err := ProcessSignal(CommandReload, "123"); err != nil {
t.Fatalf("ProcessSignal failed: %v", err)
}
if !called {
t.Fatal("Expected kill to be called")
}
}

View File

@ -0,0 +1,101 @@
// Copyright 2012-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package server
import (
"fmt"
"os"
"os/signal"
"time"
"golang.org/x/sys/windows/svc"
"golang.org/x/sys/windows/svc/mgr"
)
// Signal Handling
func (s *Server) handleSignals() {
if s.getOpts().NoSigs {
return
}
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
go func() {
for sig := range c {
s.Debugf("Trapped %q signal", sig)
s.Noticef("Server Exiting..")
os.Exit(0)
}
}()
}
// ProcessSignal sends the given signal command to the running gnatsd service.
// If service is empty, this signals the "gnatsd" service. This returns an
// error is the given service is not running or the command is invalid.
func ProcessSignal(command Command, service string) error {
if service == "" {
service = serviceName
}
m, err := mgr.Connect()
if err != nil {
return err
}
defer m.Disconnect()
s, err := m.OpenService(service)
if err != nil {
return fmt.Errorf("could not access service: %v", err)
}
defer s.Close()
var (
cmd svc.Cmd
to svc.State
)
switch command {
case CommandStop, CommandQuit:
cmd = svc.Stop
to = svc.Stopped
case CommandReopen:
cmd = reopenLogCmd
to = svc.Running
case CommandReload:
cmd = svc.ParamChange
to = svc.Running
default:
return fmt.Errorf("unknown signal %q", command)
}
status, err := s.Control(cmd)
if err != nil {
return fmt.Errorf("could not send control=%d: %v", cmd, err)
}
timeout := time.Now().Add(10 * time.Second)
for status.State != to {
if timeout.Before(time.Now()) {
return fmt.Errorf("timeout waiting for service to go to state=%d", to)
}
time.Sleep(300 * time.Millisecond)
status, err = s.Query()
if err != nil {
return fmt.Errorf("could not retrieve service status: %v", err)
}
}
return nil
}

517
vendor/github.com/nats-io/gnatsd/server/split_test.go generated vendored Normal file
View File

@ -0,0 +1,517 @@
// Copyright 2012-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package server
import (
"bytes"
"net"
"testing"
)
func TestSplitBufferSubOp(t *testing.T) {
cli, trash := net.Pipe()
defer cli.Close()
defer trash.Close()
s := &Server{sl: NewSublist()}
c := &client{srv: s, subs: make(map[string]*subscription), nc: cli}
subop := []byte("SUB foo 1\r\n")
subop1 := subop[:6]
subop2 := subop[6:]
if err := c.parse(subop1); err != nil {
t.Fatalf("Unexpected parse error: %v\n", err)
}
if c.state != SUB_ARG {
t.Fatalf("Expected SUB_ARG state vs %d\n", c.state)
}
if err := c.parse(subop2); err != nil {
t.Fatalf("Unexpected parse error: %v\n", err)
}
if c.state != OP_START {
t.Fatalf("Expected OP_START state vs %d\n", c.state)
}
r := s.sl.Match("foo")
if r == nil || len(r.psubs) != 1 {
t.Fatalf("Did not match subscription properly: %+v\n", r)
}
sub := r.psubs[0]
if !bytes.Equal(sub.subject, []byte("foo")) {
t.Fatalf("Subject did not match expected 'foo' : '%s'\n", sub.subject)
}
if !bytes.Equal(sub.sid, []byte("1")) {
t.Fatalf("Sid did not match expected '1' : '%s'\n", sub.sid)
}
if sub.queue != nil {
t.Fatalf("Received a non-nil queue: '%s'\n", sub.queue)
}
}
func TestSplitBufferUnsubOp(t *testing.T) {
s := &Server{sl: NewSublist()}
c := &client{srv: s, subs: make(map[string]*subscription)}
subop := []byte("SUB foo 1024\r\n")
if err := c.parse(subop); err != nil {
t.Fatalf("Unexpected parse error: %v\n", err)
}
if c.state != OP_START {
t.Fatalf("Expected OP_START state vs %d\n", c.state)
}
unsubop := []byte("UNSUB 1024\r\n")
unsubop1 := unsubop[:8]
unsubop2 := unsubop[8:]
if err := c.parse(unsubop1); err != nil {
t.Fatalf("Unexpected parse error: %v\n", err)
}
if c.state != UNSUB_ARG {
t.Fatalf("Expected UNSUB_ARG state vs %d\n", c.state)
}
if err := c.parse(unsubop2); err != nil {
t.Fatalf("Unexpected parse error: %v\n", err)
}
if c.state != OP_START {
t.Fatalf("Expected OP_START state vs %d\n", c.state)
}
r := s.sl.Match("foo")
if r != nil && len(r.psubs) != 0 {
t.Fatalf("Should be no subscriptions in results: %+v\n", r)
}
}
func TestSplitBufferPubOp(t *testing.T) {
c := &client{subs: make(map[string]*subscription)}
pub := []byte("PUB foo.bar INBOX.22 11\r\nhello world\r")
pub1 := pub[:2]
pub2 := pub[2:9]
pub3 := pub[9:15]
pub4 := pub[15:22]
pub5 := pub[22:25]
pub6 := pub[25:33]
pub7 := pub[33:]
if err := c.parse(pub1); err != nil {
t.Fatalf("Unexpected parse error: %v\n", err)
}
if c.state != OP_PU {
t.Fatalf("Expected OP_PU state vs %d\n", c.state)
}
if err := c.parse(pub2); err != nil {
t.Fatalf("Unexpected parse error: %v\n", err)
}
if c.state != PUB_ARG {
t.Fatalf("Expected OP_PU state vs %d\n", c.state)
}
if err := c.parse(pub3); err != nil {
t.Fatalf("Unexpected parse error: %v\n", err)
}
if c.state != PUB_ARG {
t.Fatalf("Expected OP_PU state vs %d\n", c.state)
}
if err := c.parse(pub4); err != nil {
t.Fatalf("Unexpected parse error: %v\n", err)
}
if c.state != PUB_ARG {
t.Fatalf("Expected PUB_ARG state vs %d\n", c.state)
}
if err := c.parse(pub5); err != nil {
t.Fatalf("Unexpected parse error: %v\n", err)
}
if c.state != MSG_PAYLOAD {
t.Fatalf("Expected MSG_PAYLOAD state vs %d\n", c.state)
}
// Check c.pa
if !bytes.Equal(c.pa.subject, []byte("foo.bar")) {
t.Fatalf("PUB arg subject incorrect: '%s'\n", c.pa.subject)
}
if !bytes.Equal(c.pa.reply, []byte("INBOX.22")) {
t.Fatalf("PUB arg reply subject incorrect: '%s'\n", c.pa.reply)
}
if c.pa.size != 11 {
t.Fatalf("PUB arg msg size incorrect: %d\n", c.pa.size)
}
if err := c.parse(pub6); err != nil {
t.Fatalf("Unexpected parse error: %v\n", err)
}
if c.state != MSG_PAYLOAD {
t.Fatalf("Expected MSG_PAYLOAD state vs %d\n", c.state)
}
if err := c.parse(pub7); err != nil {
t.Fatalf("Unexpected parse error: %v\n", err)
}
if c.state != MSG_END {
t.Fatalf("Expected MSG_END state vs %d\n", c.state)
}
}
func TestSplitBufferPubOp2(t *testing.T) {
c := &client{subs: make(map[string]*subscription)}
pub := []byte("PUB foo.bar INBOX.22 11\r\nhello world\r\n")
pub1 := pub[:30]
pub2 := pub[30:]
if err := c.parse(pub1); err != nil {
t.Fatalf("Unexpected parse error: %v\n", err)
}
if c.state != MSG_PAYLOAD {
t.Fatalf("Expected MSG_PAYLOAD state vs %d\n", c.state)
}
if err := c.parse(pub2); err != nil {
t.Fatalf("Unexpected parse error: %v\n", err)
}
if c.state != OP_START {
t.Fatalf("Expected OP_START state vs %d\n", c.state)
}
}
func TestSplitBufferPubOp3(t *testing.T) {
c := &client{subs: make(map[string]*subscription)}
pubAll := []byte("PUB foo bar 11\r\nhello world\r\n")
pub := pubAll[:16]
if err := c.parse(pub); err != nil {
t.Fatalf("Unexpected parse error: %v\n", err)
}
if !bytes.Equal(c.pa.subject, []byte("foo")) {
t.Fatalf("Unexpected subject: '%s' vs '%s'\n", c.pa.subject, "foo")
}
// Simulate next read of network, make sure pub state is saved
// until msg payload has cleared.
copy(pubAll, "XXXXXXXXXXXXXXXX")
if !bytes.Equal(c.pa.subject, []byte("foo")) {
t.Fatalf("Unexpected subject: '%s' vs '%s'\n", c.pa.subject, "foo")
}
if !bytes.Equal(c.pa.reply, []byte("bar")) {
t.Fatalf("Unexpected reply: '%s' vs '%s'\n", c.pa.reply, "bar")
}
if !bytes.Equal(c.pa.szb, []byte("11")) {
t.Fatalf("Unexpected size bytes: '%s' vs '%s'\n", c.pa.szb, "11")
}
}
func TestSplitBufferPubOp4(t *testing.T) {
c := &client{subs: make(map[string]*subscription)}
pubAll := []byte("PUB foo 11\r\nhello world\r\n")
pub := pubAll[:12]
if err := c.parse(pub); err != nil {
t.Fatalf("Unexpected parse error: %v\n", err)
}
if !bytes.Equal(c.pa.subject, []byte("foo")) {
t.Fatalf("Unexpected subject: '%s' vs '%s'\n", c.pa.subject, "foo")
}
// Simulate next read of network, make sure pub state is saved
// until msg payload has cleared.
copy(pubAll, "XXXXXXXXXXXX")
if !bytes.Equal(c.pa.subject, []byte("foo")) {
t.Fatalf("Unexpected subject: '%s' vs '%s'\n", c.pa.subject, "foo")
}
if !bytes.Equal(c.pa.reply, []byte("")) {
t.Fatalf("Unexpected reply: '%s' vs '%s'\n", c.pa.reply, "")
}
if !bytes.Equal(c.pa.szb, []byte("11")) {
t.Fatalf("Unexpected size bytes: '%s' vs '%s'\n", c.pa.szb, "11")
}
}
func TestSplitBufferPubOp5(t *testing.T) {
c := &client{subs: make(map[string]*subscription)}
pubAll := []byte("PUB foo 11\r\nhello world\r\n")
// Splits need to be on MSG_END now too, so make sure we check that.
// Split between \r and \n
pub := pubAll[:len(pubAll)-1]
if err := c.parse(pub); err != nil {
t.Fatalf("Unexpected parse error: %v\n", err)
}
if c.msgBuf == nil {
t.Fatalf("msgBuf should not be nil!\n")
}
if !bytes.Equal(c.msgBuf, []byte("hello world\r")) {
t.Fatalf("c.msgBuf did not snaphot the msg")
}
}
func TestSplitConnectArg(t *testing.T) {
c := &client{subs: make(map[string]*subscription)}
connectAll := []byte("CONNECT {\"verbose\":false,\"tls_required\":false," +
"\"user\":\"test\",\"pedantic\":true,\"pass\":\"pass\"}\r\n")
argJSON := connectAll[8:]
c1 := connectAll[:5]
c2 := connectAll[5:22]
c3 := connectAll[22 : len(connectAll)-2]
c4 := connectAll[len(connectAll)-2:]
if err := c.parse(c1); err != nil {
t.Fatalf("Unexpected parse error: %v\n", err)
}
if c.argBuf != nil {
t.Fatalf("Unexpected argBug placeholder.\n")
}
if err := c.parse(c2); err != nil {
t.Fatalf("Unexpected parse error: %v\n", err)
}
if c.argBuf == nil {
t.Fatalf("Expected argBug to not be nil.\n")
}
if !bytes.Equal(c.argBuf, argJSON[:14]) {
t.Fatalf("argBuf not correct, received %q, wanted %q\n", argJSON[:14], c.argBuf)
}
if err := c.parse(c3); err != nil {
t.Fatalf("Unexpected parse error: %v\n", err)
}
if c.argBuf == nil {
t.Fatalf("Expected argBug to not be nil.\n")
}
if !bytes.Equal(c.argBuf, argJSON[:len(argJSON)-2]) {
t.Fatalf("argBuf not correct, received %q, wanted %q\n",
argJSON[:len(argJSON)-2], c.argBuf)
}
if err := c.parse(c4); err != nil {
t.Fatalf("Unexpected parse error: %v\n", err)
}
if c.argBuf != nil {
t.Fatalf("Unexpected argBuf placeholder.\n")
}
}
func TestSplitDanglingArgBuf(t *testing.T) {
s := New(&defaultServerOptions)
c := &client{srv: s, subs: make(map[string]*subscription)}
// We test to make sure we do not dangle any argBufs after processing
// since that could lead to performance issues.
// SUB
subop := []byte("SUB foo 1\r\n")
c.parse(subop[:6])
c.parse(subop[6:])
if c.argBuf != nil {
t.Fatalf("Expected c.argBuf to be nil: %q\n", c.argBuf)
}
// UNSUB
unsubop := []byte("UNSUB 1024\r\n")
c.parse(unsubop[:8])
c.parse(unsubop[8:])
if c.argBuf != nil {
t.Fatalf("Expected c.argBuf to be nil: %q\n", c.argBuf)
}
// PUB
pubop := []byte("PUB foo.bar INBOX.22 11\r\nhello world\r\n")
c.parse(pubop[:22])
c.parse(pubop[22:25])
if c.argBuf == nil {
t.Fatal("Expected a non-nil argBuf!")
}
c.parse(pubop[25:])
if c.argBuf != nil {
t.Fatalf("Expected c.argBuf to be nil: %q\n", c.argBuf)
}
// MINUS_ERR
errop := []byte("-ERR Too Long\r\n")
c.parse(errop[:8])
c.parse(errop[8:])
if c.argBuf != nil {
t.Fatalf("Expected c.argBuf to be nil: %q\n", c.argBuf)
}
// CONNECT_ARG
connop := []byte("CONNECT {\"verbose\":false,\"tls_required\":false," +
"\"user\":\"test\",\"pedantic\":true,\"pass\":\"pass\"}\r\n")
c.parse(connop[:22])
c.parse(connop[22:])
if c.argBuf != nil {
t.Fatalf("Expected c.argBuf to be nil: %q\n", c.argBuf)
}
// INFO_ARG
infoop := []byte("INFO {\"server_id\":\"id\"}\r\n")
c.parse(infoop[:8])
c.parse(infoop[8:])
if c.argBuf != nil {
t.Fatalf("Expected c.argBuf to be nil: %q\n", c.argBuf)
}
// MSG (the client has to be a ROUTE)
c = &client{subs: make(map[string]*subscription), typ: ROUTER}
msgop := []byte("MSG foo RSID:2:1 5\r\nhello\r\n")
c.parse(msgop[:5])
c.parse(msgop[5:10])
if c.argBuf == nil {
t.Fatal("Expected a non-nil argBuf")
}
if string(c.argBuf) != "foo RS" {
t.Fatalf("Expected argBuf to be \"foo 1 \", got %q", string(c.argBuf))
}
c.parse(msgop[10:])
if c.argBuf != nil {
t.Fatalf("Expected argBuf to be nil: %q", c.argBuf)
}
if c.msgBuf != nil {
t.Fatalf("Expected msgBuf to be nil: %q", c.msgBuf)
}
c.state = OP_START
// Parse up-to somewhere in the middle of the payload.
// Verify that we have saved the MSG_ARG info
c.parse(msgop[:23])
if c.argBuf == nil {
t.Fatal("Expected a non-nil argBuf")
}
if string(c.pa.subject) != "foo" {
t.Fatalf("Expected subject to be \"foo\", got %q", c.pa.subject)
}
if string(c.pa.reply) != "" {
t.Fatalf("Expected reply to be \"\", got %q", c.pa.reply)
}
if string(c.pa.sid) != "RSID:2:1" {
t.Fatalf("Expected sid to \"RSID:2:1\", got %q", c.pa.sid)
}
if c.pa.size != 5 {
t.Fatalf("Expected sid to 5, got %v", c.pa.size)
}
// msg buffer should be
if c.msgBuf == nil || string(c.msgBuf) != "hel" {
t.Fatalf("Expected msgBuf to be \"hel\", got %q", c.msgBuf)
}
c.parse(msgop[23:])
// At the end, we should have cleaned-up both arg and msg buffers.
if c.argBuf != nil {
t.Fatalf("Expected argBuf to be nil: %q", c.argBuf)
}
if c.msgBuf != nil {
t.Fatalf("Expected msgBuf to be nil: %q", c.msgBuf)
}
}
func TestSplitMsgArg(t *testing.T) {
_, c, _ := setupClient()
// Allow parser to process MSG
c.typ = ROUTER
b := make([]byte, 1024)
copy(b, []byte("MSG hello.world RSID:14:8 6040\r\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"))
c.parse(b)
copy(b, []byte("BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\r\n"))
c.parse(b)
wantSubject := "hello.world"
wantSid := "RSID:14:8"
wantSzb := "6040"
if string(c.pa.subject) != wantSubject {
t.Fatalf("Incorrect subject: want %q, got %q", wantSubject, c.pa.subject)
}
if string(c.pa.sid) != wantSid {
t.Fatalf("Incorrect sid: want %q, got %q", wantSid, c.pa.sid)
}
if string(c.pa.szb) != wantSzb {
t.Fatalf("Incorrect szb: want %q, got %q", wantSzb, c.pa.szb)
}
}
func TestSplitBufferMsgOp(t *testing.T) {
c := &client{subs: make(map[string]*subscription), typ: ROUTER}
msg := []byte("MSG foo.bar QRSID:15:3 _INBOX.22 11\r\nhello world\r")
msg1 := msg[:2]
msg2 := msg[2:9]
msg3 := msg[9:15]
msg4 := msg[15:22]
msg5 := msg[22:25]
msg6 := msg[25:37]
msg7 := msg[37:42]
msg8 := msg[42:]
if err := c.parse(msg1); err != nil {
t.Fatalf("Unexpected parse error: %v\n", err)
}
if c.state != OP_MS {
t.Fatalf("Expected OP_MS state vs %d\n", c.state)
}
if err := c.parse(msg2); err != nil {
t.Fatalf("Unexpected parse error: %v\n", err)
}
if c.state != MSG_ARG {
t.Fatalf("Expected MSG_ARG state vs %d\n", c.state)
}
if err := c.parse(msg3); err != nil {
t.Fatalf("Unexpected parse error: %v\n", err)
}
if c.state != MSG_ARG {
t.Fatalf("Expected MSG_ARG state vs %d\n", c.state)
}
if err := c.parse(msg4); err != nil {
t.Fatalf("Unexpected parse error: %v\n", err)
}
if c.state != MSG_ARG {
t.Fatalf("Expected MSG_ARG state vs %d\n", c.state)
}
if err := c.parse(msg5); err != nil {
t.Fatalf("Unexpected parse error: %v\n", err)
}
if c.state != MSG_ARG {
t.Fatalf("Expected MSG_ARG state vs %d\n", c.state)
}
if err := c.parse(msg6); err != nil {
t.Fatalf("Unexpected parse error: %v\n", err)
}
if c.state != MSG_PAYLOAD {
t.Fatalf("Expected MSG_PAYLOAD state vs %d\n", c.state)
}
// Check c.pa
if !bytes.Equal(c.pa.subject, []byte("foo.bar")) {
t.Fatalf("MSG arg subject incorrect: '%s'\n", c.pa.subject)
}
if !bytes.Equal(c.pa.sid, []byte("QRSID:15:3")) {
t.Fatalf("MSG arg sid incorrect: '%s'\n", c.pa.sid)
}
if !bytes.Equal(c.pa.reply, []byte("_INBOX.22")) {
t.Fatalf("MSG arg reply subject incorrect: '%s'\n", c.pa.reply)
}
if c.pa.size != 11 {
t.Fatalf("MSG arg msg size incorrect: %d\n", c.pa.size)
}
if err := c.parse(msg7); err != nil {
t.Fatalf("Unexpected parse error: %v\n", err)
}
if c.state != MSG_PAYLOAD {
t.Fatalf("Expected MSG_PAYLOAD state vs %d\n", c.state)
}
if err := c.parse(msg8); err != nil {
t.Fatalf("Unexpected parse error: %v\n", err)
}
if c.state != MSG_END {
t.Fatalf("Expected MSG_END state vs %d\n", c.state)
}
}

775
vendor/github.com/nats-io/gnatsd/server/sublist.go generated vendored Normal file
View File

@ -0,0 +1,775 @@
// Copyright 2016-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package sublist is a routing mechanism to handle subject distribution
// and provides a facility to match subjects from published messages to
// interested subscribers. Subscribers can have wildcard subjects to match
// multiple published subjects.
package server
import (
"bytes"
"errors"
"strings"
"sync"
"sync/atomic"
)
// Common byte variables for wildcards and token separator.
const (
pwc = '*'
fwc = '>'
tsep = "."
btsep = '.'
)
// Sublist related errors
var (
ErrInvalidSubject = errors.New("sublist: Invalid Subject")
ErrNotFound = errors.New("sublist: No Matches Found")
)
const (
// cacheMax is used to bound limit the frontend cache
slCacheMax = 1024
// plistMin is our lower bounds to create a fast plist for Match.
plistMin = 256
)
// A result structure better optimized for queue subs.
type SublistResult struct {
psubs []*subscription
qsubs [][]*subscription // don't make this a map, too expensive to iterate
}
// A Sublist stores and efficiently retrieves subscriptions.
type Sublist struct {
sync.RWMutex
genid uint64
matches uint64
cacheHits uint64
inserts uint64
removes uint64
cache map[string]*SublistResult
root *level
count uint32
}
// A node contains subscriptions and a pointer to the next level.
type node struct {
next *level
psubs map[*subscription]*subscription
qsubs map[string](map[*subscription]*subscription)
plist []*subscription
}
// A level represents a group of nodes and special pointers to
// wildcard nodes.
type level struct {
nodes map[string]*node
pwc, fwc *node
}
// Create a new default node.
func newNode() *node {
return &node{psubs: make(map[*subscription]*subscription)}
}
// Create a new default level.
func newLevel() *level {
return &level{nodes: make(map[string]*node)}
}
// New will create a default sublist
func NewSublist() *Sublist {
return &Sublist{root: newLevel(), cache: make(map[string]*SublistResult)}
}
// Insert adds a subscription into the sublist
func (s *Sublist) Insert(sub *subscription) error {
// copy the subject since we hold this and this might be part of a large byte slice.
subject := string(sub.subject)
tsa := [32]string{}
tokens := tsa[:0]
start := 0
for i := 0; i < len(subject); i++ {
if subject[i] == btsep {
tokens = append(tokens, subject[start:i])
start = i + 1
}
}
tokens = append(tokens, subject[start:])
s.Lock()
sfwc := false
l := s.root
var n *node
for _, t := range tokens {
lt := len(t)
if lt == 0 || sfwc {
s.Unlock()
return ErrInvalidSubject
}
if lt > 1 {
n = l.nodes[t]
} else {
switch t[0] {
case pwc:
n = l.pwc
case fwc:
n = l.fwc
sfwc = true
default:
n = l.nodes[t]
}
}
if n == nil {
n = newNode()
if lt > 1 {
l.nodes[t] = n
} else {
switch t[0] {
case pwc:
l.pwc = n
case fwc:
l.fwc = n
default:
l.nodes[t] = n
}
}
}
if n.next == nil {
n.next = newLevel()
}
l = n.next
}
if sub.queue == nil {
n.psubs[sub] = sub
if n.plist != nil {
n.plist = append(n.plist, sub)
} else if len(n.psubs) > plistMin {
n.plist = make([]*subscription, 0, len(n.psubs))
// Populate
for _, psub := range n.psubs {
n.plist = append(n.plist, psub)
}
}
} else {
if n.qsubs == nil {
n.qsubs = make(map[string]map[*subscription]*subscription)
}
qname := string(sub.queue)
// This is a queue subscription
subs, ok := n.qsubs[qname]
if !ok {
subs = make(map[*subscription]*subscription)
n.qsubs[qname] = subs
}
subs[sub] = sub
}
s.count++
s.inserts++
s.addToCache(subject, sub)
atomic.AddUint64(&s.genid, 1)
s.Unlock()
return nil
}
// Deep copy
func copyResult(r *SublistResult) *SublistResult {
nr := &SublistResult{}
nr.psubs = append([]*subscription(nil), r.psubs...)
for _, qr := range r.qsubs {
nqr := append([]*subscription(nil), qr...)
nr.qsubs = append(nr.qsubs, nqr)
}
return nr
}
// addToCache will add the new entry to existing cache
// entries if needed. Assumes write lock is held.
func (s *Sublist) addToCache(subject string, sub *subscription) {
for k, r := range s.cache {
if matchLiteral(k, subject) {
// Copy since others may have a reference.
nr := copyResult(r)
if sub.queue == nil {
nr.psubs = append(nr.psubs, sub)
} else {
if i := findQSliceForSub(sub, nr.qsubs); i >= 0 {
nr.qsubs[i] = append(nr.qsubs[i], sub)
} else {
nr.qsubs = append(nr.qsubs, []*subscription{sub})
}
}
s.cache[k] = nr
}
}
}
// removeFromCache will remove the sub from any active cache entries.
// Assumes write lock is held.
func (s *Sublist) removeFromCache(subject string, sub *subscription) {
for k := range s.cache {
if !matchLiteral(k, subject) {
continue
}
// Since someone else may be referecing, can't modify the list
// safely, just let it re-populate.
delete(s.cache, k)
}
}
// Match will match all entries to the literal subject.
// It will return a set of results for both normal and queue subscribers.
func (s *Sublist) Match(subject string) *SublistResult {
s.RLock()
atomic.AddUint64(&s.matches, 1)
rc, ok := s.cache[subject]
s.RUnlock()
if ok {
atomic.AddUint64(&s.cacheHits, 1)
return rc
}
tsa := [32]string{}
tokens := tsa[:0]
start := 0
for i := 0; i < len(subject); i++ {
if subject[i] == btsep {
tokens = append(tokens, subject[start:i])
start = i + 1
}
}
tokens = append(tokens, subject[start:])
// FIXME(dlc) - Make shared pool between sublist and client readLoop?
result := &SublistResult{}
s.Lock()
matchLevel(s.root, tokens, result)
// Add to our cache
s.cache[subject] = result
// Bound the number of entries to sublistMaxCache
if len(s.cache) > slCacheMax {
for k := range s.cache {
delete(s.cache, k)
break
}
}
s.Unlock()
return result
}
// This will add in a node's results to the total results.
func addNodeToResults(n *node, results *SublistResult) {
// Normal subscriptions
if n.plist != nil {
results.psubs = append(results.psubs, n.plist...)
} else {
for _, psub := range n.psubs {
results.psubs = append(results.psubs, psub)
}
}
// Queue subscriptions
for qname, qr := range n.qsubs {
if len(qr) == 0 {
continue
}
tsub := &subscription{subject: nil, queue: []byte(qname)}
// Need to find matching list in results
if i := findQSliceForSub(tsub, results.qsubs); i >= 0 {
for _, sub := range qr {
results.qsubs[i] = append(results.qsubs[i], sub)
}
} else {
var nqsub []*subscription
for _, sub := range qr {
nqsub = append(nqsub, sub)
}
results.qsubs = append(results.qsubs, nqsub)
}
}
}
// We do not use a map here since we want iteration to be past when
// processing publishes in L1 on client. So we need to walk sequentially
// for now. Keep an eye on this in case we start getting large number of
// different queue subscribers for the same subject.
func findQSliceForSub(sub *subscription, qsl [][]*subscription) int {
if sub.queue == nil {
return -1
}
for i, qr := range qsl {
if len(qr) > 0 && bytes.Equal(sub.queue, qr[0].queue) {
return i
}
}
return -1
}
// matchLevel is used to recursively descend into the trie.
func matchLevel(l *level, toks []string, results *SublistResult) {
var pwc, n *node
for i, t := range toks {
if l == nil {
return
}
if l.fwc != nil {
addNodeToResults(l.fwc, results)
}
if pwc = l.pwc; pwc != nil {
matchLevel(pwc.next, toks[i+1:], results)
}
n = l.nodes[t]
if n != nil {
l = n.next
} else {
l = nil
}
}
if n != nil {
addNodeToResults(n, results)
}
if pwc != nil {
addNodeToResults(pwc, results)
}
}
// lnt is used to track descent into levels for a removal for pruning.
type lnt struct {
l *level
n *node
t string
}
// Raw low level remove, can do batches with lock held outside.
func (s *Sublist) remove(sub *subscription, shouldLock bool) error {
subject := string(sub.subject)
tsa := [32]string{}
tokens := tsa[:0]
start := 0
for i := 0; i < len(subject); i++ {
if subject[i] == btsep {
tokens = append(tokens, subject[start:i])
start = i + 1
}
}
tokens = append(tokens, subject[start:])
if shouldLock {
s.Lock()
defer s.Unlock()
}
sfwc := false
l := s.root
var n *node
// Track levels for pruning
var lnts [32]lnt
levels := lnts[:0]
for _, t := range tokens {
lt := len(t)
if lt == 0 || sfwc {
return ErrInvalidSubject
}
if l == nil {
return ErrNotFound
}
if lt > 1 {
n = l.nodes[t]
} else {
switch t[0] {
case pwc:
n = l.pwc
case fwc:
n = l.fwc
sfwc = true
default:
n = l.nodes[t]
}
}
if n != nil {
levels = append(levels, lnt{l, n, t})
l = n.next
} else {
l = nil
}
}
if !s.removeFromNode(n, sub) {
return ErrNotFound
}
s.count--
s.removes++
for i := len(levels) - 1; i >= 0; i-- {
l, n, t := levels[i].l, levels[i].n, levels[i].t
if n.isEmpty() {
l.pruneNode(n, t)
}
}
s.removeFromCache(subject, sub)
atomic.AddUint64(&s.genid, 1)
return nil
}
// Remove will remove a subscription.
func (s *Sublist) Remove(sub *subscription) error {
return s.remove(sub, true)
}
// RemoveBatch will remove a list of subscriptions.
func (s *Sublist) RemoveBatch(subs []*subscription) error {
s.Lock()
defer s.Unlock()
for _, sub := range subs {
if err := s.remove(sub, false); err != nil {
return err
}
}
return nil
}
// pruneNode is used to prune an empty node from the tree.
func (l *level) pruneNode(n *node, t string) {
if n == nil {
return
}
if n == l.fwc {
l.fwc = nil
} else if n == l.pwc {
l.pwc = nil
} else {
delete(l.nodes, t)
}
}
// isEmpty will test if the node has any entries. Used
// in pruning.
func (n *node) isEmpty() bool {
if len(n.psubs) == 0 && len(n.qsubs) == 0 {
if n.next == nil || n.next.numNodes() == 0 {
return true
}
}
return false
}
// Return the number of nodes for the given level.
func (l *level) numNodes() int {
num := len(l.nodes)
if l.pwc != nil {
num++
}
if l.fwc != nil {
num++
}
return num
}
// Remove the sub for the given node.
func (s *Sublist) removeFromNode(n *node, sub *subscription) (found bool) {
if n == nil {
return false
}
if sub.queue == nil {
_, found = n.psubs[sub]
delete(n.psubs, sub)
if found && n.plist != nil {
// This will brute force remove the plist to perform
// correct behavior. Will get repopulated on a call
//to Match as needed.
n.plist = nil
}
return found
}
// We have a queue group subscription here
qname := string(sub.queue)
qsub := n.qsubs[qname]
_, found = qsub[sub]
delete(qsub, sub)
if len(qsub) == 0 {
delete(n.qsubs, qname)
}
return found
}
// Count returns the number of subscriptions.
func (s *Sublist) Count() uint32 {
s.RLock()
defer s.RUnlock()
return s.count
}
// CacheCount returns the number of result sets in the cache.
func (s *Sublist) CacheCount() int {
s.RLock()
defer s.RUnlock()
return len(s.cache)
}
// Public stats for the sublist
type SublistStats struct {
NumSubs uint32 `json:"num_subscriptions"`
NumCache uint32 `json:"num_cache"`
NumInserts uint64 `json:"num_inserts"`
NumRemoves uint64 `json:"num_removes"`
NumMatches uint64 `json:"num_matches"`
CacheHitRate float64 `json:"cache_hit_rate"`
MaxFanout uint32 `json:"max_fanout"`
AvgFanout float64 `json:"avg_fanout"`
}
// Stats will return a stats structure for the current state.
func (s *Sublist) Stats() *SublistStats {
s.Lock()
defer s.Unlock()
st := &SublistStats{}
st.NumSubs = s.count
st.NumCache = uint32(len(s.cache))
st.NumInserts = s.inserts
st.NumRemoves = s.removes
st.NumMatches = atomic.LoadUint64(&s.matches)
if st.NumMatches > 0 {
st.CacheHitRate = float64(atomic.LoadUint64(&s.cacheHits)) / float64(st.NumMatches)
}
// whip through cache for fanout stats
tot, max := 0, 0
for _, r := range s.cache {
l := len(r.psubs) + len(r.qsubs)
tot += l
if l > max {
max = l
}
}
st.MaxFanout = uint32(max)
if tot > 0 {
st.AvgFanout = float64(tot) / float64(len(s.cache))
}
return st
}
// numLevels will return the maximum number of levels
// contained in the Sublist tree.
func (s *Sublist) numLevels() int {
return visitLevel(s.root, 0)
}
// visitLevel is used to descend the Sublist tree structure
// recursively.
func visitLevel(l *level, depth int) int {
if l == nil || l.numNodes() == 0 {
return depth
}
depth++
maxDepth := depth
for _, n := range l.nodes {
if n == nil {
continue
}
newDepth := visitLevel(n.next, depth)
if newDepth > maxDepth {
maxDepth = newDepth
}
}
if l.pwc != nil {
pwcDepth := visitLevel(l.pwc.next, depth)
if pwcDepth > maxDepth {
maxDepth = pwcDepth
}
}
if l.fwc != nil {
fwcDepth := visitLevel(l.fwc.next, depth)
if fwcDepth > maxDepth {
maxDepth = fwcDepth
}
}
return maxDepth
}
// IsValidSubject returns true if a subject is valid, false otherwise
func IsValidSubject(subject string) bool {
if subject == "" {
return false
}
sfwc := false
tokens := strings.Split(subject, tsep)
for _, t := range tokens {
if len(t) == 0 || sfwc {
return false
}
if len(t) > 1 {
continue
}
switch t[0] {
case fwc:
sfwc = true
}
}
return true
}
// IsValidLiteralSubject returns true if a subject is valid and literal (no wildcards), false otherwise
func IsValidLiteralSubject(subject string) bool {
tokens := strings.Split(subject, tsep)
for _, t := range tokens {
if len(t) == 0 {
return false
}
if len(t) > 1 {
continue
}
switch t[0] {
case pwc, fwc:
return false
}
}
return true
}
// matchLiteral is used to test literal subjects, those that do not have any
// wildcards, with a target subject. This is used in the cache layer.
func matchLiteral(literal, subject string) bool {
li := 0
ll := len(literal)
ls := len(subject)
for i := 0; i < ls; i++ {
if li >= ll {
return false
}
// This function has been optimized for speed.
// For instance, do not set b:=subject[i] here since
// we may bump `i` in this loop to avoid `continue` or
// skiping common test in a particular test.
// Run Benchmark_SublistMatchLiteral before making any change.
switch subject[i] {
case pwc:
// NOTE: This is not testing validity of a subject, instead ensures
// that wildcards are treated as such if they follow some basic rules,
// namely that they are a token on their own.
if i == 0 || subject[i-1] == btsep {
if i == ls-1 {
// There is no more token in the subject after this wildcard.
// Skip token in literal and expect to not find a separator.
for {
// End of literal, this is a match.
if li >= ll {
return true
}
// Presence of separator, this can't be a match.
if literal[li] == btsep {
return false
}
li++
}
} else if subject[i+1] == btsep {
// There is another token in the subject after this wildcard.
// Skip token in literal and expect to get a separator.
for {
// We found the end of the literal before finding a separator,
// this can't be a match.
if li >= ll {
return false
}
if literal[li] == btsep {
break
}
li++
}
// Bump `i` since we know there is a `.` following, we are
// safe. The common test below is going to check `.` with `.`
// which is good. A `continue` here is too costly.
i++
}
}
case fwc:
// For `>` to be a wildcard, it means being the only or last character
// in the string preceded by a `.`
if (i == 0 || subject[i-1] == btsep) && i == ls-1 {
return true
}
}
if subject[i] != literal[li] {
return false
}
li++
}
// Make sure we have processed all of the literal's chars..
return li >= ll
}
func addLocalSub(sub *subscription, subs *[]*subscription) {
if sub != nil && sub.client != nil && sub.client.typ == CLIENT {
*subs = append(*subs, sub)
}
}
func (s *Sublist) addNodeToSubs(n *node, subs *[]*subscription) {
// Normal subscriptions
if n.plist != nil {
for _, sub := range n.plist {
addLocalSub(sub, subs)
}
} else {
for _, sub := range n.psubs {
addLocalSub(sub, subs)
}
}
// Queue subscriptions
for _, qr := range n.qsubs {
for _, sub := range qr {
addLocalSub(sub, subs)
}
}
}
func (s *Sublist) collectLocalSubs(l *level, subs *[]*subscription) {
if len(l.nodes) > 0 {
for _, n := range l.nodes {
s.addNodeToSubs(n, subs)
s.collectLocalSubs(n.next, subs)
}
}
if l.pwc != nil {
s.addNodeToSubs(l.pwc, subs)
s.collectLocalSubs(l.pwc.next, subs)
}
if l.fwc != nil {
s.addNodeToSubs(l.fwc, subs)
s.collectLocalSubs(l.fwc.next, subs)
}
}
// Return all local client subscriptions. Use the supplied slice.
func (s *Sublist) localSubs(subs *[]*subscription) {
s.RLock()
s.collectLocalSubs(s.root, subs)
s.RUnlock()
}

1019
vendor/github.com/nats-io/gnatsd/server/sublist_test.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

111
vendor/github.com/nats-io/gnatsd/server/util.go generated vendored Normal file
View File

@ -0,0 +1,111 @@
// Copyright 2012-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package server
import (
"errors"
"fmt"
"net"
"strconv"
"strings"
"time"
"github.com/nats-io/nuid"
)
// Use nuid.
func genID() string {
return nuid.Next()
}
// Ascii numbers 0-9
const (
asciiZero = 48
asciiNine = 57
)
// parseSize expects decimal positive numbers. We
// return -1 to signal error.
func parseSize(d []byte) (n int) {
l := len(d)
if l == 0 {
return -1
}
var (
i int
dec byte
)
// Note: Use `goto` here to avoid for loop in order
// to have the function be inlined.
// See: https://github.com/golang/go/issues/14768
loop:
dec = d[i]
if dec < asciiZero || dec > asciiNine {
return -1
}
n = n*10 + (int(dec) - asciiZero)
i++
if i < l {
goto loop
}
return n
}
// parseInt64 expects decimal positive numbers. We
// return -1 to signal error
func parseInt64(d []byte) (n int64) {
if len(d) == 0 {
return -1
}
for _, dec := range d {
if dec < asciiZero || dec > asciiNine {
return -1
}
n = n*10 + (int64(dec) - asciiZero)
}
return n
}
// Helper to move from float seconds to time.Duration
func secondsToDuration(seconds float64) time.Duration {
ttl := seconds * float64(time.Second)
return time.Duration(ttl)
}
// Parse a host/port string with a default port to use
// if none (or 0 or -1) is specified in `hostPort` string.
func parseHostPort(hostPort string, defaultPort int) (host string, port int, err error) {
if hostPort != "" {
host, sPort, err := net.SplitHostPort(hostPort)
switch err.(type) {
case *net.AddrError:
// try appending the current port
host, sPort, err = net.SplitHostPort(fmt.Sprintf("%s:%d", hostPort, defaultPort))
}
if err != nil {
return "", -1, err
}
port, err = strconv.Atoi(strings.TrimSpace(sPort))
if err != nil {
return "", -1, err
}
if port == 0 || port == -1 {
port = defaultPort
}
return strings.TrimSpace(host), port, nil
}
return "", -1, errors.New("No hostport specified")
}

168
vendor/github.com/nats-io/gnatsd/server/util_test.go generated vendored Normal file
View File

@ -0,0 +1,168 @@
// Copyright 2012-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package server
import (
"math/rand"
"strconv"
"sync"
"testing"
"time"
)
func TestParseSize(t *testing.T) {
if parseSize(nil) != -1 {
t.Fatal("Should error on nil byte slice")
}
n := []byte("12345678")
if pn := parseSize(n); pn != 12345678 {
t.Fatalf("Did not parse %q correctly, res=%d\n", n, pn)
}
}
func TestParseSInt64(t *testing.T) {
if parseInt64(nil) != -1 {
t.Fatal("Should error on nil byte slice")
}
n := []byte("12345678")
if pn := parseInt64(n); pn != 12345678 {
t.Fatalf("Did not parse %q correctly, res=%d\n", n, pn)
}
}
func TestParseHostPort(t *testing.T) {
check := func(hostPort string, defaultPort int, expectedHost string, expectedPort int, expectedErr bool) {
h, p, err := parseHostPort(hostPort, defaultPort)
if expectedErr {
if err == nil {
stackFatalf(t, "Expected an error, did not get one")
}
// expected error, so we are done
return
}
if !expectedErr && err != nil {
stackFatalf(t, "Unexpected error: %v", err)
}
if expectedHost != h {
stackFatalf(t, "Expected host %q, got %q", expectedHost, h)
}
if expectedPort != p {
stackFatalf(t, "Expected port %d, got %d", expectedPort, p)
}
}
check("addr:1234", 5678, "addr", 1234, false)
check(" addr:1234 ", 5678, "addr", 1234, false)
check(" addr : 1234 ", 5678, "addr", 1234, false)
check("addr", 5678, "addr", 5678, false)
check(" addr ", 5678, "addr", 5678, false)
check("addr:-1", 5678, "addr", 5678, false)
check(" addr:-1 ", 5678, "addr", 5678, false)
check(" addr : -1 ", 5678, "addr", 5678, false)
check("addr:0", 5678, "addr", 5678, false)
check(" addr:0 ", 5678, "addr", 5678, false)
check(" addr : 0 ", 5678, "addr", 5678, false)
check("addr:addr", 0, "", 0, true)
check("addr:::1234", 0, "", 0, true)
check("", 0, "", 0, true)
}
func BenchmarkParseInt(b *testing.B) {
b.SetBytes(1)
n := "12345678"
for i := 0; i < b.N; i++ {
strconv.ParseInt(n, 10, 0)
}
}
func BenchmarkParseSize(b *testing.B) {
b.SetBytes(1)
n := []byte("12345678")
for i := 0; i < b.N; i++ {
parseSize(n)
}
}
func deferUnlock(mu *sync.Mutex) {
mu.Lock()
defer mu.Unlock()
// see noDeferUnlock
if false {
return
}
}
func BenchmarkDeferMutex(b *testing.B) {
var mu sync.Mutex
b.SetBytes(1)
for i := 0; i < b.N; i++ {
deferUnlock(&mu)
}
}
func noDeferUnlock(mu *sync.Mutex) {
mu.Lock()
// prevent staticcheck warning about empty critical section
if false {
return
}
mu.Unlock()
}
func BenchmarkNoDeferMutex(b *testing.B) {
var mu sync.Mutex
b.SetBytes(1)
for i := 0; i < b.N; i++ {
noDeferUnlock(&mu)
}
}
func createTestSub() *subscription {
return &subscription{
subject: []byte("foo"),
queue: []byte("bar"),
sid: []byte("22"),
}
}
func BenchmarkArrayRand(b *testing.B) {
b.StopTimer()
r := rand.New(rand.NewSource(time.Now().UnixNano()))
// Create an array of 10 items
subs := []*subscription{}
for i := 0; i < 10; i++ {
subs = append(subs, createTestSub())
}
b.StartTimer()
for i := 0; i < b.N; i++ {
index := r.Intn(len(subs))
_ = subs[index]
}
}
func BenchmarkMapRange(b *testing.B) {
b.StopTimer()
// Create an map of 10 items
subs := map[int]*subscription{}
for i := 0; i < 10; i++ {
subs[i] = createTestSub()
}
b.StartTimer()
for i := 0; i < b.N; i++ {
for range subs {
break
}
}
}

236
vendor/github.com/nats-io/gnatsd/test/auth_test.go generated vendored Normal file
View File

@ -0,0 +1,236 @@
// Copyright 2012-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package test
import (
"encoding/json"
"fmt"
"net"
"testing"
"time"
"github.com/nats-io/gnatsd/server"
)
func doAuthConnect(t tLogger, c net.Conn, token, user, pass string) {
cs := fmt.Sprintf("CONNECT {\"verbose\":true,\"auth_token\":\"%s\",\"user\":\"%s\",\"pass\":\"%s\"}\r\n", token, user, pass)
sendProto(t, c, cs)
}
func testInfoForAuth(t tLogger, infojs []byte) bool {
var sinfo server.Info
err := json.Unmarshal(infojs, &sinfo)
if err != nil {
t.Fatalf("Could not unmarshal INFO json: %v\n", err)
}
return sinfo.AuthRequired
}
func expectAuthRequired(t tLogger, c net.Conn) {
buf := expectResult(t, c, infoRe)
infojs := infoRe.FindAllSubmatch(buf, 1)[0][1]
if !testInfoForAuth(t, infojs) {
t.Fatalf("Expected server to require authorization: '%s'", infojs)
}
}
////////////////////////////////////////////////////////////
// The authorization token version
////////////////////////////////////////////////////////////
const AUTH_PORT = 10422
const AUTH_TOKEN = "_YZZ22_"
func runAuthServerWithToken() *server.Server {
opts := DefaultTestOptions
opts.Port = AUTH_PORT
opts.Authorization = AUTH_TOKEN
return RunServer(&opts)
}
func TestNoAuthClient(t *testing.T) {
s := runAuthServerWithToken()
defer s.Shutdown()
c := createClientConn(t, "127.0.0.1", AUTH_PORT)
defer c.Close()
expectAuthRequired(t, c)
doAuthConnect(t, c, "", "", "")
expectResult(t, c, errRe)
}
func TestAuthClientBadToken(t *testing.T) {
s := runAuthServerWithToken()
defer s.Shutdown()
c := createClientConn(t, "127.0.0.1", AUTH_PORT)
defer c.Close()
expectAuthRequired(t, c)
doAuthConnect(t, c, "ZZZ", "", "")
expectResult(t, c, errRe)
}
func TestAuthClientNoConnect(t *testing.T) {
s := runAuthServerWithToken()
defer s.Shutdown()
c := createClientConn(t, "127.0.0.1", AUTH_PORT)
defer c.Close()
expectAuthRequired(t, c)
// This is timing dependent..
time.Sleep(server.AUTH_TIMEOUT)
expectResult(t, c, errRe)
}
func TestAuthClientGoodConnect(t *testing.T) {
s := runAuthServerWithToken()
defer s.Shutdown()
c := createClientConn(t, "127.0.0.1", AUTH_PORT)
defer c.Close()
expectAuthRequired(t, c)
doAuthConnect(t, c, AUTH_TOKEN, "", "")
expectResult(t, c, okRe)
}
func TestAuthClientFailOnEverythingElse(t *testing.T) {
s := runAuthServerWithToken()
defer s.Shutdown()
c := createClientConn(t, "127.0.0.1", AUTH_PORT)
defer c.Close()
expectAuthRequired(t, c)
sendProto(t, c, "PUB foo 2\r\nok\r\n")
expectResult(t, c, errRe)
}
////////////////////////////////////////////////////////////
// The username/password version
////////////////////////////////////////////////////////////
const AUTH_USER = "derek"
const AUTH_PASS = "foobar"
func runAuthServerWithUserPass() *server.Server {
opts := DefaultTestOptions
opts.Port = AUTH_PORT
opts.Username = AUTH_USER
opts.Password = AUTH_PASS
return RunServer(&opts)
}
func TestNoUserOrPasswordClient(t *testing.T) {
s := runAuthServerWithUserPass()
defer s.Shutdown()
c := createClientConn(t, "127.0.0.1", AUTH_PORT)
defer c.Close()
expectAuthRequired(t, c)
doAuthConnect(t, c, "", "", "")
expectResult(t, c, errRe)
}
func TestBadUserClient(t *testing.T) {
s := runAuthServerWithUserPass()
defer s.Shutdown()
c := createClientConn(t, "127.0.0.1", AUTH_PORT)
defer c.Close()
expectAuthRequired(t, c)
doAuthConnect(t, c, "", "derekzz", AUTH_PASS)
expectResult(t, c, errRe)
}
func TestBadPasswordClient(t *testing.T) {
s := runAuthServerWithUserPass()
defer s.Shutdown()
c := createClientConn(t, "127.0.0.1", AUTH_PORT)
defer c.Close()
expectAuthRequired(t, c)
doAuthConnect(t, c, "", AUTH_USER, "ZZ")
expectResult(t, c, errRe)
}
func TestPasswordClientGoodConnect(t *testing.T) {
s := runAuthServerWithUserPass()
defer s.Shutdown()
c := createClientConn(t, "127.0.0.1", AUTH_PORT)
defer c.Close()
expectAuthRequired(t, c)
doAuthConnect(t, c, "", AUTH_USER, AUTH_PASS)
expectResult(t, c, okRe)
}
////////////////////////////////////////////////////////////
// The bcrypt username/password version
////////////////////////////////////////////////////////////
// Generated with util/mkpasswd (Cost 4 because of cost of --race, default is 11)
const BCRYPT_AUTH_PASS = "IW@$6v(y1(t@fhPDvf!5^%"
const BCRYPT_AUTH_HASH = "$2a$04$Q.CgCP2Sl9pkcTXEZHazaeMwPaAkSHk7AI51HkyMt5iJQQyUA4qxq"
func runAuthServerWithBcryptUserPass() *server.Server {
opts := DefaultTestOptions
opts.Port = AUTH_PORT
opts.Username = AUTH_USER
opts.Password = BCRYPT_AUTH_HASH
return RunServer(&opts)
}
func TestBadBcryptPassword(t *testing.T) {
s := runAuthServerWithBcryptUserPass()
defer s.Shutdown()
c := createClientConn(t, "127.0.0.1", AUTH_PORT)
defer c.Close()
expectAuthRequired(t, c)
doAuthConnect(t, c, "", AUTH_USER, BCRYPT_AUTH_HASH)
expectResult(t, c, errRe)
}
func TestGoodBcryptPassword(t *testing.T) {
s := runAuthServerWithBcryptUserPass()
defer s.Shutdown()
c := createClientConn(t, "127.0.0.1", AUTH_PORT)
defer c.Close()
expectAuthRequired(t, c)
doAuthConnect(t, c, "", AUTH_USER, BCRYPT_AUTH_PASS)
expectResult(t, c, okRe)
}
////////////////////////////////////////////////////////////
// The bcrypt authorization token version
////////////////////////////////////////////////////////////
const BCRYPT_AUTH_TOKEN = "0uhJOSr3GW7xvHvtd^K6pa"
const BCRYPT_AUTH_TOKEN_HASH = "$2a$04$u5ZClXpcjHgpfc61Ee0VKuwI1K3vTC4zq7SjphjnlHMeb1Llkb5Y6"
func runAuthServerWithBcryptToken() *server.Server {
opts := DefaultTestOptions
opts.Port = AUTH_PORT
opts.Authorization = BCRYPT_AUTH_TOKEN_HASH
return RunServer(&opts)
}
func TestBadBcryptToken(t *testing.T) {
s := runAuthServerWithBcryptToken()
defer s.Shutdown()
c := createClientConn(t, "127.0.0.1", AUTH_PORT)
defer c.Close()
expectAuthRequired(t, c)
doAuthConnect(t, c, BCRYPT_AUTH_TOKEN_HASH, "", "")
expectResult(t, c, errRe)
}
func TestGoodBcryptToken(t *testing.T) {
s := runAuthServerWithBcryptToken()
defer s.Shutdown()
c := createClientConn(t, "127.0.0.1", AUTH_PORT)
defer c.Close()
expectAuthRequired(t, c)
doAuthConnect(t, c, BCRYPT_AUTH_TOKEN, "", "")
expectResult(t, c, okRe)
}

View File

@ -0,0 +1,79 @@
2017 iMac Pro 3Ghz (Turbo 4Ghz) 10-Core Skylake
OSX High Sierra 10.13.2
===================
Go version go1.9.2
===================
Benchmark_____Pub0b_Payload-20 30000000 55.1 ns/op 199.78 MB/s
Benchmark_____Pub8b_Payload-20 30000000 55.8 ns/op 340.21 MB/s
Benchmark____Pub32b_Payload-20 20000000 63.4 ns/op 694.34 MB/s
Benchmark___Pub128B_Payload-20 20000000 79.8 ns/op 1766.47 MB/s
Benchmark___Pub256B_Payload-20 20000000 98.1 ns/op 2741.51 MB/s
Benchmark_____Pub1K_Payload-20 5000000 283 ns/op 3660.72 MB/s
Benchmark_____Pub4K_Payload-20 1000000 1395 ns/op 2945.30 MB/s
Benchmark_____Pub8K_Payload-20 500000 2846 ns/op 2882.35 MB/s
Benchmark_AuthPub0b_Payload-20 10000000 126 ns/op 86.82 MB/s
Benchmark____________PubSub-20 10000000 135 ns/op
Benchmark____PubSubTwoConns-20 10000000 136 ns/op
Benchmark____PubTwoQueueSub-20 10000000 152 ns/op
Benchmark___PubFourQueueSub-20 10000000 152 ns/op
Benchmark__PubEightQueueSub-20 10000000 152 ns/op
Benchmark___RoutedPubSub_0b-20 5000000 385 ns/op
Benchmark___RoutedPubSub_1K-20 1000000 1076 ns/op
Benchmark_RoutedPubSub_100K-20 20000 78501 ns/op
2015 iMac5k 4Ghz i7 Haswell
OSX El Capitan 10.11.3
===================
Go version go1.6
===================
Benchmark____PubNo_Payload-8 20000000 88.6 ns/op 124.11 MB/s
Benchmark____Pub8b_Payload-8 20000000 89.8 ns/op 211.63 MB/s
Benchmark___Pub32b_Payload-8 20000000 97.3 ns/op 452.20 MB/s
Benchmark__Pub256B_Payload-8 10000000 129 ns/op 2078.43 MB/s
Benchmark____Pub1K_Payload-8 5000000 216 ns/op 4791.00 MB/s
Benchmark____Pub4K_Payload-8 1000000 1123 ns/op 3657.53 MB/s
Benchmark____Pub8K_Payload-8 500000 2309 ns/op 3553.09 MB/s
Benchmark___________PubSub-8 10000000 210 ns/op
Benchmark___PubSubTwoConns-8 10000000 205 ns/op
Benchmark___PubTwoQueueSub-8 10000000 231 ns/op
Benchmark__PubFourQueueSub-8 10000000 233 ns/op
Benchmark_PubEightQueueSub-8 5000000 231 ns/op
OSX Yosemite 10.10.5
===================
Go version go1.4.2
===================
Benchmark___PubNo_Payload 10000000 133 ns/op 82.44 MB/s
Benchmark___Pub8b_Payload 10000000 135 ns/op 140.27 MB/s
Benchmark__Pub32b_Payload 10000000 147 ns/op 297.56 MB/s
Benchmark_Pub256B_Payload 10000000 211 ns/op 1273.82 MB/s
Benchmark___Pub1K_Payload 3000000 447 ns/op 2321.55 MB/s
Benchmark___Pub4K_Payload 1000000 1677 ns/op 2450.43 MB/s
Benchmark___Pub8K_Payload 300000 3670 ns/op 2235.80 MB/s
Benchmark__________PubSub 5000000 263 ns/op
Benchmark__PubSubTwoConns 5000000 268 ns/op
Benchmark__PubTwoQueueSub 2000000 936 ns/op
Benchmark_PubFourQueueSub 1000000 1103 ns/op
===================
Go version go1.5.0
===================
Benchmark___PubNo_Payload-8 10000000 122 ns/op 89.94 MB/s
Benchmark___Pub8b_Payload-8 10000000 124 ns/op 152.72 MB/s
Benchmark__Pub32b_Payload-8 10000000 135 ns/op 325.73 MB/s
Benchmark_Pub256B_Payload-8 10000000 159 ns/op 1685.78 MB/s
Benchmark___Pub1K_Payload-8 5000000 256 ns/op 4047.90 MB/s
Benchmark___Pub4K_Payload-8 1000000 1164 ns/op 3530.77 MB/s
Benchmark___Pub8K_Payload-8 500000 2444 ns/op 3357.34 MB/s
Benchmark__________PubSub-8 5000000 254 ns/op
Benchmark__PubSubTwoConns-8 5000000 245 ns/op
Benchmark__PubTwoQueueSub-8 2000000 845 ns/op
Benchmark_PubFourQueueSub-8 1000000 1004 ns/op

674
vendor/github.com/nats-io/gnatsd/test/bench_test.go generated vendored Normal file
View File

@ -0,0 +1,674 @@
// Copyright 2012-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package test
import (
"bufio"
"fmt"
"math/rand"
"net"
"testing"
"time"
"github.com/nats-io/gnatsd/server"
)
const PERF_PORT = 8422
// For Go routine based server.
func runBenchServer() *server.Server {
opts := DefaultTestOptions
opts.Port = PERF_PORT
return RunServer(&opts)
}
const defaultRecBufSize = 32768
const defaultSendBufSize = 32768
func flushConnection(b *testing.B, c net.Conn) {
buf := make([]byte, 32)
c.Write([]byte("PING\r\n"))
c.SetReadDeadline(time.Now().Add(5 * time.Second))
n, err := c.Read(buf)
c.SetReadDeadline(time.Time{})
if err != nil {
b.Fatalf("Failed read: %v\n", err)
}
if n != 6 && buf[0] != 'P' && buf[1] != 'O' {
b.Fatalf("Failed read of PONG: %s\n", buf)
}
}
func benchPub(b *testing.B, subject, payload string) {
b.StopTimer()
s := runBenchServer()
c := createClientConn(b, "127.0.0.1", PERF_PORT)
doDefaultConnect(b, c)
bw := bufio.NewWriterSize(c, defaultSendBufSize)
sendOp := []byte(fmt.Sprintf("PUB %s %d\r\n%s\r\n", subject, len(payload), payload))
b.SetBytes(int64(len(sendOp)))
b.StartTimer()
for i := 0; i < b.N; i++ {
bw.Write(sendOp)
}
bw.Flush()
flushConnection(b, c)
b.StopTimer()
c.Close()
s.Shutdown()
}
var ch = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@$#%^&*()")
func sizedBytes(sz int) []byte {
b := make([]byte, sz)
for i := range b {
b[i] = ch[rand.Intn(len(ch))]
}
return b
}
func sizedString(sz int) string {
return string(sizedBytes(sz))
}
// Publish subject for pub benchmarks.
var psub = "a"
func Benchmark______Pub0b_Payload(b *testing.B) {
benchPub(b, psub, "")
}
func Benchmark______Pub8b_Payload(b *testing.B) {
b.StopTimer()
s := sizedString(8)
benchPub(b, psub, s)
}
func Benchmark_____Pub32b_Payload(b *testing.B) {
b.StopTimer()
s := sizedString(32)
benchPub(b, psub, s)
}
func Benchmark____Pub128B_Payload(b *testing.B) {
b.StopTimer()
s := sizedString(128)
benchPub(b, psub, s)
}
func Benchmark____Pub256B_Payload(b *testing.B) {
b.StopTimer()
s := sizedString(256)
benchPub(b, psub, s)
}
func Benchmark______Pub1K_Payload(b *testing.B) {
b.StopTimer()
s := sizedString(1024)
benchPub(b, psub, s)
}
func Benchmark______Pub4K_Payload(b *testing.B) {
b.StopTimer()
s := sizedString(4 * 1024)
benchPub(b, psub, s)
}
func Benchmark______Pub8K_Payload(b *testing.B) {
b.StopTimer()
s := sizedString(8 * 1024)
benchPub(b, psub, s)
}
func Benchmark______Pub32K_Payload(b *testing.B) {
b.StopTimer()
s := sizedString(32 * 1024)
benchPub(b, psub, s)
}
func drainConnection(b *testing.B, c net.Conn, ch chan bool, expected int) {
buf := make([]byte, defaultRecBufSize)
bytes := 0
for {
c.SetReadDeadline(time.Now().Add(30 * time.Second))
n, err := c.Read(buf)
if err != nil {
b.Errorf("Error on read: %v\n", err)
break
}
bytes += n
if bytes >= expected {
break
}
}
if bytes != expected {
b.Errorf("Did not receive all bytes: %d vs %d\n", bytes, expected)
}
ch <- true
}
// Benchmark the authorization code path.
func Benchmark__AuthPub0b_Payload(b *testing.B) {
b.StopTimer()
srv, opts := RunServerWithConfig("./configs/authorization.conf")
defer srv.Shutdown()
c := createClientConn(b, opts.Host, opts.Port)
defer c.Close()
expectAuthRequired(b, c)
cs := fmt.Sprintf("CONNECT {\"verbose\":false,\"user\":\"%s\",\"pass\":\"%s\"}\r\n", "bench", DefaultPass)
sendProto(b, c, cs)
bw := bufio.NewWriterSize(c, defaultSendBufSize)
sendOp := []byte("PUB a 0\r\n\r\n")
b.SetBytes(int64(len(sendOp)))
b.StartTimer()
for i := 0; i < b.N; i++ {
bw.Write(sendOp)
}
bw.Flush()
flushConnection(b, c)
b.StopTimer()
}
func Benchmark_____________PubSub(b *testing.B) {
b.StopTimer()
s := runBenchServer()
c := createClientConn(b, "127.0.0.1", PERF_PORT)
doDefaultConnect(b, c)
sendProto(b, c, "SUB foo 1\r\n")
bw := bufio.NewWriterSize(c, defaultSendBufSize)
sendOp := []byte(fmt.Sprintf("PUB foo 2\r\nok\r\n"))
ch := make(chan bool)
expected := len("MSG foo 1 2\r\nok\r\n") * b.N
go drainConnection(b, c, ch, expected)
b.StartTimer()
for i := 0; i < b.N; i++ {
_, err := bw.Write(sendOp)
if err != nil {
b.Errorf("Received error on PUB write: %v\n", err)
}
}
err := bw.Flush()
if err != nil {
b.Errorf("Received error on FLUSH write: %v\n", err)
}
// Wait for connection to be drained
<-ch
b.StopTimer()
c.Close()
s.Shutdown()
}
func Benchmark_____PubSubTwoConns(b *testing.B) {
b.StopTimer()
s := runBenchServer()
c := createClientConn(b, "127.0.0.1", PERF_PORT)
doDefaultConnect(b, c)
bw := bufio.NewWriterSize(c, defaultSendBufSize)
c2 := createClientConn(b, "127.0.0.1", PERF_PORT)
doDefaultConnect(b, c2)
sendProto(b, c2, "SUB foo 1\r\n")
flushConnection(b, c2)
sendOp := []byte(fmt.Sprintf("PUB foo 2\r\nok\r\n"))
ch := make(chan bool)
expected := len("MSG foo 1 2\r\nok\r\n") * b.N
go drainConnection(b, c2, ch, expected)
b.StartTimer()
for i := 0; i < b.N; i++ {
bw.Write(sendOp)
}
err := bw.Flush()
if err != nil {
b.Errorf("Received error on FLUSH write: %v\n", err)
}
// Wait for connection to be drained
<-ch
b.StopTimer()
c.Close()
c2.Close()
s.Shutdown()
}
func Benchmark_PubSub512kTwoConns(b *testing.B) {
b.StopTimer()
s := runBenchServer()
c := createClientConn(b, "127.0.0.1", PERF_PORT)
doDefaultConnect(b, c)
bw := bufio.NewWriterSize(c, defaultSendBufSize)
c2 := createClientConn(b, "127.0.0.1", PERF_PORT)
doDefaultConnect(b, c2)
sendProto(b, c2, "SUB foo 1\r\n")
flushConnection(b, c2)
sz := 1024 * 512
payload := sizedString(sz)
sendOp := []byte(fmt.Sprintf("PUB foo %d\r\n%s\r\n", sz, payload))
ch := make(chan bool)
expected := len(fmt.Sprintf("MSG foo 1 %d\r\n%s\r\n", sz, payload)) * b.N
go drainConnection(b, c2, ch, expected)
b.StartTimer()
for i := 0; i < b.N; i++ {
bw.Write(sendOp)
}
err := bw.Flush()
if err != nil {
b.Errorf("Received error on FLUSH write: %v\n", err)
}
// Wait for connection to be drained
<-ch
b.StopTimer()
c.Close()
c2.Close()
s.Shutdown()
}
func Benchmark_____PubTwoQueueSub(b *testing.B) {
b.StopTimer()
s := runBenchServer()
c := createClientConn(b, "127.0.0.1", PERF_PORT)
doDefaultConnect(b, c)
sendProto(b, c, "SUB foo group1 1\r\n")
sendProto(b, c, "SUB foo group1 2\r\n")
bw := bufio.NewWriterSize(c, defaultSendBufSize)
sendOp := []byte(fmt.Sprintf("PUB foo 2\r\nok\r\n"))
ch := make(chan bool)
expected := len("MSG foo 1 2\r\nok\r\n") * b.N
go drainConnection(b, c, ch, expected)
b.StartTimer()
for i := 0; i < b.N; i++ {
_, err := bw.Write(sendOp)
if err != nil {
b.Fatalf("Received error on PUB write: %v\n", err)
}
}
err := bw.Flush()
if err != nil {
b.Fatalf("Received error on FLUSH write: %v\n", err)
}
// Wait for connection to be drained
<-ch
b.StopTimer()
c.Close()
s.Shutdown()
}
func Benchmark____PubFourQueueSub(b *testing.B) {
b.StopTimer()
s := runBenchServer()
c := createClientConn(b, "127.0.0.1", PERF_PORT)
doDefaultConnect(b, c)
sendProto(b, c, "SUB foo group1 1\r\n")
sendProto(b, c, "SUB foo group1 2\r\n")
sendProto(b, c, "SUB foo group1 3\r\n")
sendProto(b, c, "SUB foo group1 4\r\n")
bw := bufio.NewWriterSize(c, defaultSendBufSize)
sendOp := []byte(fmt.Sprintf("PUB foo 2\r\nok\r\n"))
ch := make(chan bool)
expected := len("MSG foo 1 2\r\nok\r\n") * b.N
go drainConnection(b, c, ch, expected)
b.StartTimer()
for i := 0; i < b.N; i++ {
_, err := bw.Write(sendOp)
if err != nil {
b.Fatalf("Received error on PUB write: %v\n", err)
}
}
err := bw.Flush()
if err != nil {
b.Fatalf("Received error on FLUSH write: %v\n", err)
}
// Wait for connection to be drained
<-ch
b.StopTimer()
c.Close()
s.Shutdown()
}
func Benchmark___PubEightQueueSub(b *testing.B) {
b.StopTimer()
s := runBenchServer()
c := createClientConn(b, "127.0.0.1", PERF_PORT)
doDefaultConnect(b, c)
sendProto(b, c, "SUB foo group1 1\r\n")
sendProto(b, c, "SUB foo group1 2\r\n")
sendProto(b, c, "SUB foo group1 3\r\n")
sendProto(b, c, "SUB foo group1 4\r\n")
sendProto(b, c, "SUB foo group1 5\r\n")
sendProto(b, c, "SUB foo group1 6\r\n")
sendProto(b, c, "SUB foo group1 7\r\n")
sendProto(b, c, "SUB foo group1 8\r\n")
bw := bufio.NewWriterSize(c, defaultSendBufSize)
sendOp := []byte(fmt.Sprintf("PUB foo 2\r\nok\r\n"))
ch := make(chan bool)
expected := len("MSG foo 1 2\r\nok\r\n") * b.N
go drainConnection(b, c, ch, expected)
b.StartTimer()
for i := 0; i < b.N; i++ {
_, err := bw.Write(sendOp)
if err != nil {
b.Fatalf("Received error on PUB write: %v\n", err)
}
}
err := bw.Flush()
if err != nil {
b.Fatalf("Received error on FLUSH write: %v\n", err)
}
// Wait for connection to be drained
<-ch
b.StopTimer()
c.Close()
s.Shutdown()
}
func routePubSub(b *testing.B, size int) {
b.StopTimer()
s1, o1 := RunServerWithConfig("./configs/srv_a.conf")
defer s1.Shutdown()
s2, o2 := RunServerWithConfig("./configs/srv_b.conf")
defer s2.Shutdown()
sub := createClientConn(b, o1.Host, o1.Port)
doDefaultConnect(b, sub)
sendProto(b, sub, "SUB foo 1\r\n")
flushConnection(b, sub)
payload := sizedString(size)
pub := createClientConn(b, o2.Host, o2.Port)
doDefaultConnect(b, pub)
bw := bufio.NewWriterSize(pub, defaultSendBufSize)
ch := make(chan bool)
sendOp := []byte(fmt.Sprintf("PUB foo %d\r\n%s\r\n", len(payload), payload))
expected := len(fmt.Sprintf("MSG foo 1 %d\r\n%s\r\n", len(payload), payload)) * b.N
go drainConnection(b, sub, ch, expected)
b.StartTimer()
for i := 0; i < b.N; i++ {
_, err := bw.Write(sendOp)
if err != nil {
b.Fatalf("Received error on PUB write: %v\n", err)
}
}
err := bw.Flush()
if err != nil {
b.Errorf("Received error on FLUSH write: %v\n", err)
}
// Wait for connection to be drained
<-ch
b.StopTimer()
pub.Close()
sub.Close()
}
func Benchmark____RoutedPubSub_0b(b *testing.B) {
routePubSub(b, 2)
}
func Benchmark____RoutedPubSub_1K(b *testing.B) {
routePubSub(b, 1024)
}
func Benchmark__RoutedPubSub_100K(b *testing.B) {
routePubSub(b, 100*1024)
}
func routeQueue(b *testing.B, numQueueSubs, size int) {
b.StopTimer()
s1, o1 := RunServerWithConfig("./configs/srv_a.conf")
defer s1.Shutdown()
s2, o2 := RunServerWithConfig("./configs/srv_b.conf")
defer s2.Shutdown()
sub := createClientConn(b, o1.Host, o1.Port)
doDefaultConnect(b, sub)
for i := 0; i < numQueueSubs; i++ {
sendProto(b, sub, fmt.Sprintf("SUB foo bar %d\r\n", 100+i))
}
flushConnection(b, sub)
payload := sizedString(size)
pub := createClientConn(b, o2.Host, o2.Port)
doDefaultConnect(b, pub)
bw := bufio.NewWriterSize(pub, defaultSendBufSize)
ch := make(chan bool)
sendOp := []byte(fmt.Sprintf("PUB foo %d\r\n%s\r\n", len(payload), payload))
expected := len(fmt.Sprintf("MSG foo 100 %d\r\n%s\r\n", len(payload), payload)) * b.N
go drainConnection(b, sub, ch, expected)
b.StartTimer()
for i := 0; i < b.N; i++ {
_, err := bw.Write(sendOp)
if err != nil {
b.Fatalf("Received error on PUB write: %v\n", err)
}
}
err := bw.Flush()
if err != nil {
b.Errorf("Received error on FLUSH write: %v\n", err)
}
// Wait for connection to be drained
<-ch
b.StopTimer()
pub.Close()
sub.Close()
}
func Benchmark____Routed2QueueSub(b *testing.B) {
routeQueue(b, 2, 2)
}
func Benchmark____Routed4QueueSub(b *testing.B) {
routeQueue(b, 4, 2)
}
func Benchmark____Routed8QueueSub(b *testing.B) {
routeQueue(b, 8, 2)
}
func Benchmark___Routed16QueueSub(b *testing.B) {
routeQueue(b, 16, 2)
}
func doFanout(b *testing.B, numServers, numConnections, subsPerConnection int, subject, payload string) {
var s1, s2 *server.Server
var o1, o2 *server.Options
switch numServers {
case 1:
s1, o1 = RunServerWithConfig("./configs/srv_a.conf")
defer s1.Shutdown()
s2, o2 = s1, o1
case 2:
s1, o1 = RunServerWithConfig("./configs/srv_a.conf")
defer s1.Shutdown()
s2, o2 = RunServerWithConfig("./configs/srv_b.conf")
defer s2.Shutdown()
default:
b.Fatalf("%d servers not supported for this test\n", numServers)
}
// To get a consistent length sid in MSG sent to us for drainConnection.
var sidFloor int
switch {
case subsPerConnection <= 100:
sidFloor = 100
case subsPerConnection <= 1000:
sidFloor = 1000
case subsPerConnection <= 10000:
sidFloor = 10000
default:
b.Fatalf("Unsupported SubsPerConnection argument of %d\n", subsPerConnection)
}
msgOp := fmt.Sprintf("MSG %s %d %d\r\n%s\r\n", subject, sidFloor, len(payload), payload)
expected := len(msgOp) * subsPerConnection * b.N
// Client connections and subscriptions.
clients := make([]chan bool, 0, numConnections)
for i := 0; i < numConnections; i++ {
c := createClientConn(b, o2.Host, o2.Port)
doDefaultConnect(b, c)
defer c.Close()
ch := make(chan bool)
clients = append(clients, ch)
for s := 0; s < subsPerConnection; s++ {
subOp := fmt.Sprintf("SUB %s %d\r\n", subject, sidFloor+s)
sendProto(b, c, subOp)
}
flushConnection(b, c)
go drainConnection(b, c, ch, expected)
}
// Publish Connection
c := createClientConn(b, o1.Host, o1.Port)
doDefaultConnect(b, c)
bw := bufio.NewWriterSize(c, defaultSendBufSize)
sendOp := []byte(fmt.Sprintf("PUB %s %d\r\n%s\r\n", subject, len(payload), payload))
flushConnection(b, c)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := bw.Write(sendOp)
if err != nil {
b.Errorf("Received error on PUB write: %v\n", err)
}
}
err := bw.Flush()
if err != nil {
b.Errorf("Received error on FLUSH write: %v\n", err)
}
// Wait for connections to be drained
for i := 0; i < numConnections; i++ {
<-clients[i]
}
b.StopTimer()
}
var sub = "x"
var payload = "12345678"
func Benchmark___FanOut_512x1kx1k(b *testing.B) {
doFanout(b, 1, 1000, 1000, sub, sizedString(512))
}
func Benchmark__FanOut_8x1000x100(b *testing.B) {
doFanout(b, 1, 1000, 100, sub, payload)
}
func Benchmark______FanOut_8x1x10(b *testing.B) {
doFanout(b, 1, 1, 10, sub, payload)
}
func Benchmark_____FanOut_8x1x100(b *testing.B) {
doFanout(b, 1, 1, 100, sub, payload)
}
func Benchmark____FanOut_8x10x100(b *testing.B) {
doFanout(b, 1, 10, 100, sub, payload)
}
func Benchmark___FanOut_8x10x1000(b *testing.B) {
doFanout(b, 1, 10, 1000, sub, payload)
}
func Benchmark___FanOut_8x100x100(b *testing.B) {
doFanout(b, 1, 100, 100, sub, payload)
}
func Benchmark__FanOut_8x100x1000(b *testing.B) {
doFanout(b, 1, 100, 1000, sub, payload)
}
func Benchmark__FanOut_8x10x10000(b *testing.B) {
doFanout(b, 1, 10, 10000, sub, payload)
}
func Benchmark__FanOut_1kx10x1000(b *testing.B) {
doFanout(b, 1, 10, 1000, sub, sizedString(1024))
}
func Benchmark_____RFanOut_8x1x10(b *testing.B) {
doFanout(b, 2, 1, 10, sub, payload)
}
func Benchmark____RFanOut_8x1x100(b *testing.B) {
doFanout(b, 2, 1, 100, sub, payload)
}
func Benchmark___RFanOut_8x10x100(b *testing.B) {
doFanout(b, 2, 10, 100, sub, payload)
}
func Benchmark__RFanOut_8x10x1000(b *testing.B) {
doFanout(b, 2, 10, 1000, sub, payload)
}
func Benchmark__RFanOut_8x100x100(b *testing.B) {
doFanout(b, 2, 100, 100, sub, payload)
}
func Benchmark_RFanOut_8x100x1000(b *testing.B) {
doFanout(b, 2, 100, 1000, sub, payload)
}
func Benchmark_RFanOut_8x10x10000(b *testing.B) {
doFanout(b, 2, 10, 10000, sub, payload)
}
func Benchmark_RFanOut_1kx10x1000(b *testing.B) {
doFanout(b, 2, 10, 1000, sub, sizedString(1024))
}

View File

@ -0,0 +1,92 @@
// Copyright 2016-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package test
import (
"fmt"
"io/ioutil"
"os"
"testing"
"github.com/nats-io/go-nats"
)
func TestMultipleUserAuth(t *testing.T) {
srv, opts := RunServerWithConfig("./configs/multi_user.conf")
defer srv.Shutdown()
if opts.Users == nil {
t.Fatal("Expected a user array that is not nil")
}
if len(opts.Users) != 2 {
t.Fatal("Expected a user array that had 2 users")
}
// Test first user
url := fmt.Sprintf("nats://%s:%s@%s:%d/",
opts.Users[0].Username,
opts.Users[0].Password,
opts.Host, opts.Port)
nc, err := nats.Connect(url)
if err != nil {
t.Fatalf("Expected a successful connect, got %v\n", err)
}
defer nc.Close()
if !nc.AuthRequired() {
t.Fatal("Expected auth to be required for the server")
}
// Test second user
url = fmt.Sprintf("nats://%s:%s@%s:%d/",
opts.Users[1].Username,
opts.Users[1].Password,
opts.Host, opts.Port)
nc, err = nats.Connect(url)
if err != nil {
t.Fatalf("Expected a successful connect, got %v\n", err)
}
defer nc.Close()
}
// Resolves to "test"
const testToken = "$2a$05$3sSWEVA1eMCbV0hWavDjXOx.ClBjI6u1CuUdLqf22cbJjXsnzz8/."
func TestTokenInConfig(t *testing.T) {
confFileName := "test.conf"
defer os.Remove(confFileName)
content := `
listen: 127.0.0.1:4567
authorization={
token: ` + testToken + `
timeout: 5
}`
if err := ioutil.WriteFile(confFileName, []byte(content), 0666); err != nil {
t.Fatalf("Error writing config file: %v", err)
}
s, opts := RunServerWithConfig(confFileName)
defer s.Shutdown()
url := fmt.Sprintf("nats://test@%s:%d/", opts.Host, opts.Port)
nc, err := nats.Connect(url)
if err != nil {
t.Fatalf("Expected a successful connect, got %v\n", err)
}
defer nc.Close()
if !nc.AuthRequired() {
t.Fatal("Expected auth to be required for the server")
}
}

View File

@ -0,0 +1,377 @@
// Copyright 2013-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package test
import (
"fmt"
"math/rand"
"sync"
"sync/atomic"
"testing"
"time"
"github.com/nats-io/go-nats"
)
func TestServerRestartReSliceIssue(t *testing.T) {
srvA, srvB, optsA, optsB := runServers(t)
defer srvA.Shutdown()
urlA := fmt.Sprintf("nats://%s:%d/", optsA.Host, optsA.Port)
urlB := fmt.Sprintf("nats://%s:%d/", optsB.Host, optsB.Port)
// msg to send..
msg := []byte("Hello World")
servers := []string{urlA, urlB}
opts := nats.GetDefaultOptions()
opts.Timeout = (5 * time.Second)
opts.ReconnectWait = (50 * time.Millisecond)
opts.MaxReconnect = 1000
numClients := 20
reconnects := int32(0)
reconnectsDone := make(chan bool, numClients)
opts.ReconnectedCB = func(nc *nats.Conn) {
atomic.AddInt32(&reconnects, 1)
reconnectsDone <- true
}
clients := make([]*nats.Conn, numClients)
// Create 20 random clients.
// Half connected to A and half to B..
for i := 0; i < numClients; i++ {
opts.Url = servers[i%2]
nc, err := opts.Connect()
if err != nil {
t.Fatalf("Failed to create connection: %v\n", err)
}
clients[i] = nc
defer nc.Close()
// Create 10 subscriptions each..
for x := 0; x < 10; x++ {
subject := fmt.Sprintf("foo.%d", (rand.Int()%50)+1)
nc.Subscribe(subject, func(m *nats.Msg) {
// Just eat it..
})
}
// Pick one subject to send to..
subject := fmt.Sprintf("foo.%d", (rand.Int()%50)+1)
go func() {
time.Sleep(10 * time.Millisecond)
for i := 1; i <= 100; i++ {
if err := nc.Publish(subject, msg); err != nil {
return
}
if i%10 == 0 {
time.Sleep(time.Millisecond)
}
}
}()
}
// Wait for a short bit..
time.Sleep(20 * time.Millisecond)
// Restart SrvB
srvB.Shutdown()
srvB = RunServer(optsB)
defer srvB.Shutdown()
// Check that all expected clients have reconnected
done := false
for i := 0; i < numClients/2 && !done; i++ {
select {
case <-reconnectsDone:
done = true
case <-time.After(3 * time.Second):
t.Fatalf("Expected %d reconnects, got %d\n", numClients/2, reconnects)
}
}
// Since srvB was restarted, its defer Shutdown() was last, so will
// exectue first, which would cause clients that have reconnected to
// it to try to reconnect (causing delays on Windows). So let's
// explicitly close them here.
// NOTE: With fix of NATS GO client (reconnect loop yields to Close()),
// this change would not be required, however, it still speeeds up
// the test, from more than 7s to less than one.
for i := 0; i < numClients; i++ {
nc := clients[i]
nc.Close()
}
}
// This will test queue subscriber semantics across a cluster in the presence
// of server restarts.
func TestServerRestartAndQueueSubs(t *testing.T) {
srvA, srvB, optsA, optsB := runServers(t)
urlA := fmt.Sprintf("nats://%s:%d/", optsA.Host, optsA.Port)
urlB := fmt.Sprintf("nats://%s:%d/", optsB.Host, optsB.Port)
// Client options
opts := nats.GetDefaultOptions()
opts.Timeout = (5 * time.Second)
opts.ReconnectWait = (50 * time.Millisecond)
opts.MaxReconnect = 1000
opts.NoRandomize = true
// Allow us to block on a reconnect completion.
reconnectsDone := make(chan bool)
opts.ReconnectedCB = func(nc *nats.Conn) {
reconnectsDone <- true
}
// Helper to wait on a reconnect.
waitOnReconnect := func() {
var rcs int64
for {
select {
case <-reconnectsDone:
atomic.AddInt64(&rcs, 1)
if rcs >= 2 {
return
}
case <-time.After(2 * time.Second):
t.Fatalf("Expected a reconnect, timedout!\n")
}
}
}
// Create two clients..
opts.Servers = []string{urlA}
nc1, err := opts.Connect()
if err != nil {
t.Fatalf("Failed to create connection for nc1: %v\n", err)
}
opts.Servers = []string{urlB}
nc2, err := opts.Connect()
if err != nil {
t.Fatalf("Failed to create connection for nc2: %v\n", err)
}
c1, _ := nats.NewEncodedConn(nc1, "json")
defer c1.Close()
c2, _ := nats.NewEncodedConn(nc2, "json")
defer c2.Close()
// Flusher helper function.
flush := func() {
// Wait for processing.
c1.Flush()
c2.Flush()
// Wait for a short bit for cluster propagation.
time.Sleep(50 * time.Millisecond)
}
// To hold queue results.
results := make(map[int]int)
var mu sync.Mutex
// This corresponds to the subsriptions below.
const ExpectedMsgCount = 3
// Make sure we got what we needed, 1 msg only and all seqnos accounted for..
checkResults := func(numSent int) {
mu.Lock()
defer mu.Unlock()
for i := 0; i < numSent; i++ {
if results[i] != ExpectedMsgCount {
t.Fatalf("Received incorrect number of messages, [%d] vs [%d] for seq: %d\n", results[i], ExpectedMsgCount, i)
}
}
// Auto reset results map
results = make(map[int]int)
}
subj := "foo.bar"
qgroup := "workers"
cb := func(seqno int) {
mu.Lock()
defer mu.Unlock()
results[seqno] = results[seqno] + 1
}
// Create queue subscribers
c1.QueueSubscribe(subj, qgroup, cb)
c2.QueueSubscribe(subj, qgroup, cb)
// Do a wildcard subscription.
c1.Subscribe("foo.*", cb)
c2.Subscribe("foo.*", cb)
// Wait for processing.
flush()
sendAndCheckMsgs := func(numToSend int) {
for i := 0; i < numToSend; i++ {
if i%2 == 0 {
c1.Publish(subj, i)
} else {
c2.Publish(subj, i)
}
}
// Wait for processing.
flush()
// Check Results
checkResults(numToSend)
}
////////////////////////////////////////////////////////////////////////////
// Base Test
////////////////////////////////////////////////////////////////////////////
// Make sure subscriptions are propagated in the cluster
if err := checkExpectedSubs(4, srvA, srvB); err != nil {
t.Fatalf("%v", err)
}
// Now send 10 messages, from each client..
sendAndCheckMsgs(10)
////////////////////////////////////////////////////////////////////////////
// Now restart SrvA and srvB, re-run test
////////////////////////////////////////////////////////////////////////////
srvA.Shutdown()
srvA = RunServer(optsA)
defer srvA.Shutdown()
srvB.Shutdown()
srvB = RunServer(optsB)
defer srvB.Shutdown()
waitOnReconnect()
// Make sure the cluster is reformed
checkClusterFormed(t, srvA, srvB)
// Make sure subscriptions are propagated in the cluster
if err := checkExpectedSubs(4, srvA, srvB); err != nil {
t.Fatalf("%v", err)
}
// Now send another 10 messages, from each client..
sendAndCheckMsgs(10)
// Since servers are restarted after all client's close defer calls,
// their defer Shutdown() are last, and so will be executed first,
// which would cause clients to try to reconnect on exit, causing
// delays on Windows. So let's explicitly close them here.
c1.Close()
c2.Close()
}
// This will test request semantics across a route
func TestRequestsAcrossRoutes(t *testing.T) {
srvA, srvB, optsA, optsB := runServers(t)
defer srvA.Shutdown()
defer srvB.Shutdown()
urlA := fmt.Sprintf("nats://%s:%d/", optsA.Host, optsA.Port)
urlB := fmt.Sprintf("nats://%s:%d/", optsB.Host, optsB.Port)
nc1, err := nats.Connect(urlA)
if err != nil {
t.Fatalf("Failed to create connection for nc1: %v\n", err)
}
defer nc1.Close()
nc2, err := nats.Connect(urlB)
if err != nil {
t.Fatalf("Failed to create connection for nc2: %v\n", err)
}
defer nc2.Close()
ec2, _ := nats.NewEncodedConn(nc2, nats.JSON_ENCODER)
response := []byte("I will help you")
// Connect responder to srvA
nc1.Subscribe("foo-req", func(m *nats.Msg) {
nc1.Publish(m.Reply, response)
})
// Make sure the route and the subscription are propagated.
nc1.Flush()
var resp string
for i := 0; i < 100; i++ {
if err := ec2.Request("foo-req", i, &resp, 100*time.Millisecond); err != nil {
t.Fatalf("Received an error on Request test [%d]: %s", i, err)
}
}
}
// This will test request semantics across a route to queues
func TestRequestsAcrossRoutesToQueues(t *testing.T) {
srvA, srvB, optsA, optsB := runServers(t)
defer srvA.Shutdown()
defer srvB.Shutdown()
urlA := fmt.Sprintf("nats://%s:%d/", optsA.Host, optsA.Port)
urlB := fmt.Sprintf("nats://%s:%d/", optsB.Host, optsB.Port)
nc1, err := nats.Connect(urlA)
if err != nil {
t.Fatalf("Failed to create connection for nc1: %v\n", err)
}
defer nc1.Close()
nc2, err := nats.Connect(urlB)
if err != nil {
t.Fatalf("Failed to create connection for nc2: %v\n", err)
}
defer nc2.Close()
ec1, _ := nats.NewEncodedConn(nc1, nats.JSON_ENCODER)
ec2, _ := nats.NewEncodedConn(nc2, nats.JSON_ENCODER)
response := []byte("I will help you")
// Connect one responder to srvA
nc1.QueueSubscribe("foo-req", "booboo", func(m *nats.Msg) {
nc1.Publish(m.Reply, response)
})
// Make sure the route and the subscription are propagated.
nc1.Flush()
// Connect the other responder to srvB
nc2.QueueSubscribe("foo-req", "booboo", func(m *nats.Msg) {
nc2.Publish(m.Reply, response)
})
var resp string
for i := 0; i < 100; i++ {
if err := ec2.Request("foo-req", i, &resp, 500*time.Millisecond); err != nil {
t.Fatalf("Received an error on Request test [%d]: %s", i, err)
}
}
for i := 0; i < 100; i++ {
if err := ec1.Request("foo-req", i, &resp, 500*time.Millisecond); err != nil {
t.Fatalf("Received an error on Request test [%d]: %s", i, err)
}
}
}

491
vendor/github.com/nats-io/gnatsd/test/cluster_test.go generated vendored Normal file
View File

@ -0,0 +1,491 @@
// Copyright 2013-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package test
import (
"errors"
"fmt"
"runtime"
"testing"
"time"
"github.com/nats-io/gnatsd/server"
)
// Helper function to check that a cluster is formed
func checkClusterFormed(t *testing.T, servers ...*server.Server) {
t.Helper()
expectedNumRoutes := len(servers) - 1
checkFor(t, 10*time.Second, 100*time.Millisecond, func() error {
for _, s := range servers {
if numRoutes := s.NumRoutes(); numRoutes != expectedNumRoutes {
return fmt.Errorf("Expected %d routes for server %q, got %d", expectedNumRoutes, s.ID(), numRoutes)
}
}
return nil
})
}
func checkNumRoutes(t *testing.T, s *server.Server, expected int) {
t.Helper()
checkFor(t, 5*time.Second, 15*time.Millisecond, func() error {
if nr := s.NumRoutes(); nr != expected {
return fmt.Errorf("Expected %v routes, got %v", expected, nr)
}
return nil
})
}
// Helper function to check that a server (or list of servers) have the
// expected number of subscriptions.
func checkExpectedSubs(expected int, servers ...*server.Server) error {
var err string
maxTime := time.Now().Add(10 * time.Second)
for time.Now().Before(maxTime) {
err = ""
for _, s := range servers {
if numSubs := int(s.NumSubscriptions()); numSubs != expected {
err = fmt.Sprintf("Expected %d subscriptions for server %q, got %d", expected, s.ID(), numSubs)
break
}
}
if err != "" {
time.Sleep(10 * time.Millisecond)
} else {
break
}
}
if err != "" {
return errors.New(err)
}
return nil
}
func runServers(t *testing.T) (srvA, srvB *server.Server, optsA, optsB *server.Options) {
srvA, optsA = RunServerWithConfig("./configs/srv_a.conf")
srvB, optsB = RunServerWithConfig("./configs/srv_b.conf")
checkClusterFormed(t, srvA, srvB)
return
}
func TestProperServerWithRoutesShutdown(t *testing.T) {
before := runtime.NumGoroutine()
srvA, srvB, _, _ := runServers(t)
srvA.Shutdown()
srvB.Shutdown()
time.Sleep(100 * time.Millisecond)
after := runtime.NumGoroutine()
delta := after - before
// There may be some finalizers or IO, but in general more than
// 2 as a delta represents a problem.
if delta > 2 {
t.Fatalf("Expected same number of goroutines, %d vs %d\n", before, after)
}
}
func TestDoubleRouteConfig(t *testing.T) {
srvA, srvB, _, _ := runServers(t)
defer srvA.Shutdown()
defer srvB.Shutdown()
}
func TestBasicClusterPubSub(t *testing.T) {
srvA, srvB, optsA, optsB := runServers(t)
defer srvA.Shutdown()
defer srvB.Shutdown()
clientA := createClientConn(t, optsA.Host, optsA.Port)
defer clientA.Close()
clientB := createClientConn(t, optsB.Host, optsB.Port)
defer clientB.Close()
sendA, expectA := setupConn(t, clientA)
sendA("SUB foo 22\r\n")
sendA("PING\r\n")
expectA(pongRe)
if err := checkExpectedSubs(1, srvA, srvB); err != nil {
t.Fatalf("%v", err)
}
sendB, expectB := setupConn(t, clientB)
sendB("PUB foo 2\r\nok\r\n")
sendB("PING\r\n")
expectB(pongRe)
expectMsgs := expectMsgsCommand(t, expectA)
matches := expectMsgs(1)
checkMsg(t, matches[0], "foo", "22", "", "2", "ok")
}
func TestClusterQueueSubs(t *testing.T) {
srvA, srvB, optsA, optsB := runServers(t)
defer srvA.Shutdown()
defer srvB.Shutdown()
clientA := createClientConn(t, optsA.Host, optsA.Port)
defer clientA.Close()
clientB := createClientConn(t, optsB.Host, optsB.Port)
defer clientB.Close()
sendA, expectA := setupConn(t, clientA)
sendB, expectB := setupConn(t, clientB)
expectMsgsA := expectMsgsCommand(t, expectA)
expectMsgsB := expectMsgsCommand(t, expectB)
// Capture sids for checking later.
qg1SidsA := []string{"1", "2", "3"}
// Three queue subscribers
for _, sid := range qg1SidsA {
sendA(fmt.Sprintf("SUB foo qg1 %s\r\n", sid))
}
sendA("PING\r\n")
expectA(pongRe)
// Make sure the subs have propagated to srvB before continuing
if err := checkExpectedSubs(len(qg1SidsA), srvB); err != nil {
t.Fatalf("%v", err)
}
sendB("PUB foo 2\r\nok\r\n")
sendB("PING\r\n")
expectB(pongRe)
// Make sure we get only 1.
matches := expectMsgsA(1)
checkMsg(t, matches[0], "foo", "", "", "2", "ok")
// Capture sids for checking later.
pSids := []string{"4", "5", "6"}
// Create 3 normal subscribers
for _, sid := range pSids {
sendA(fmt.Sprintf("SUB foo %s\r\n", sid))
}
// Create a FWC Subscriber
pSids = append(pSids, "7")
sendA("SUB > 7\r\n")
sendA("PING\r\n")
expectA(pongRe)
// Make sure the subs have propagated to srvB before continuing
if err := checkExpectedSubs(len(qg1SidsA)+len(pSids), srvB); err != nil {
t.Fatalf("%v", err)
}
// Send to B
sendB("PUB foo 2\r\nok\r\n")
sendB("PING\r\n")
expectB(pongRe)
// Should receive 5.
matches = expectMsgsA(5)
checkForQueueSid(t, matches, qg1SidsA)
checkForPubSids(t, matches, pSids)
// Send to A
sendA("PUB foo 2\r\nok\r\n")
// Should receive 5.
matches = expectMsgsA(5)
checkForQueueSid(t, matches, qg1SidsA)
checkForPubSids(t, matches, pSids)
// Now add queue subscribers to B
qg2SidsB := []string{"1", "2", "3"}
for _, sid := range qg2SidsB {
sendB(fmt.Sprintf("SUB foo qg2 %s\r\n", sid))
}
sendB("PING\r\n")
expectB(pongRe)
// Make sure the subs have propagated to srvA before continuing
if err := checkExpectedSubs(len(qg1SidsA)+len(pSids)+len(qg2SidsB), srvA); err != nil {
t.Fatalf("%v", err)
}
// Send to B
sendB("PUB foo 2\r\nok\r\n")
// Should receive 1 from B.
matches = expectMsgsB(1)
checkForQueueSid(t, matches, qg2SidsB)
// Should receive 5 still from A.
matches = expectMsgsA(5)
checkForQueueSid(t, matches, qg1SidsA)
checkForPubSids(t, matches, pSids)
// Now drop queue subscribers from A
for _, sid := range qg1SidsA {
sendA(fmt.Sprintf("UNSUB %s\r\n", sid))
}
sendA("PING\r\n")
expectA(pongRe)
// Make sure the subs have propagated to srvB before continuing
if err := checkExpectedSubs(len(pSids)+len(qg2SidsB), srvB); err != nil {
t.Fatalf("%v", err)
}
// Send to B
sendB("PUB foo 2\r\nok\r\n")
// Should receive 1 from B.
matches = expectMsgsB(1)
checkForQueueSid(t, matches, qg2SidsB)
sendB("PING\r\n")
expectB(pongRe)
// Should receive 4 now.
matches = expectMsgsA(4)
checkForPubSids(t, matches, pSids)
// Send to A
sendA("PUB foo 2\r\nok\r\n")
// Should receive 4 now.
matches = expectMsgsA(4)
checkForPubSids(t, matches, pSids)
}
// Issue #22
func TestClusterDoubleMsgs(t *testing.T) {
srvA, srvB, optsA, optsB := runServers(t)
defer srvA.Shutdown()
defer srvB.Shutdown()
clientA1 := createClientConn(t, optsA.Host, optsA.Port)
defer clientA1.Close()
clientA2 := createClientConn(t, optsA.Host, optsA.Port)
defer clientA2.Close()
clientB := createClientConn(t, optsB.Host, optsB.Port)
defer clientB.Close()
sendA1, expectA1 := setupConn(t, clientA1)
sendA2, expectA2 := setupConn(t, clientA2)
sendB, expectB := setupConn(t, clientB)
expectMsgsA1 := expectMsgsCommand(t, expectA1)
expectMsgsA2 := expectMsgsCommand(t, expectA2)
// Capture sids for checking later.
qg1SidsA := []string{"1", "2", "3"}
// Three queue subscribers
for _, sid := range qg1SidsA {
sendA1(fmt.Sprintf("SUB foo qg1 %s\r\n", sid))
}
sendA1("PING\r\n")
expectA1(pongRe)
// Make sure the subs have propagated to srvB before continuing
if err := checkExpectedSubs(len(qg1SidsA), srvB); err != nil {
t.Fatalf("%v", err)
}
sendB("PUB foo 2\r\nok\r\n")
sendB("PING\r\n")
expectB(pongRe)
// Make sure we get only 1.
matches := expectMsgsA1(1)
checkMsg(t, matches[0], "foo", "", "", "2", "ok")
checkForQueueSid(t, matches, qg1SidsA)
// Add a FWC subscriber on A2
sendA2("SUB > 1\r\n")
sendA2("SUB foo 2\r\n")
sendA2("PING\r\n")
expectA2(pongRe)
pSids := []string{"1", "2"}
// Make sure the subs have propagated to srvB before continuing
if err := checkExpectedSubs(len(qg1SidsA)+2, srvB); err != nil {
t.Fatalf("%v", err)
}
sendB("PUB foo 2\r\nok\r\n")
sendB("PING\r\n")
expectB(pongRe)
matches = expectMsgsA1(1)
checkMsg(t, matches[0], "foo", "", "", "2", "ok")
checkForQueueSid(t, matches, qg1SidsA)
matches = expectMsgsA2(2)
checkMsg(t, matches[0], "foo", "", "", "2", "ok")
checkForPubSids(t, matches, pSids)
// Close ClientA1
clientA1.Close()
sendB("PUB foo 2\r\nok\r\n")
sendB("PING\r\n")
expectB(pongRe)
matches = expectMsgsA2(2)
checkMsg(t, matches[0], "foo", "", "", "2", "ok")
checkForPubSids(t, matches, pSids)
}
// This will test that we drop remote sids correctly.
func TestClusterDropsRemoteSids(t *testing.T) {
srvA, srvB, optsA, _ := runServers(t)
defer srvA.Shutdown()
defer srvB.Shutdown()
clientA := createClientConn(t, optsA.Host, optsA.Port)
defer clientA.Close()
sendA, expectA := setupConn(t, clientA)
// Add a subscription
sendA("SUB foo 1\r\n")
sendA("PING\r\n")
expectA(pongRe)
// Wait for propagation.
time.Sleep(100 * time.Millisecond)
if sc := srvA.NumSubscriptions(); sc != 1 {
t.Fatalf("Expected one subscription for srvA, got %d\n", sc)
}
if sc := srvB.NumSubscriptions(); sc != 1 {
t.Fatalf("Expected one subscription for srvB, got %d\n", sc)
}
// Add another subscription
sendA("SUB bar 2\r\n")
sendA("PING\r\n")
expectA(pongRe)
// Wait for propagation.
time.Sleep(100 * time.Millisecond)
if sc := srvA.NumSubscriptions(); sc != 2 {
t.Fatalf("Expected two subscriptions for srvA, got %d\n", sc)
}
if sc := srvB.NumSubscriptions(); sc != 2 {
t.Fatalf("Expected two subscriptions for srvB, got %d\n", sc)
}
// unsubscription
sendA("UNSUB 1\r\n")
sendA("PING\r\n")
expectA(pongRe)
// Wait for propagation.
time.Sleep(100 * time.Millisecond)
if sc := srvA.NumSubscriptions(); sc != 1 {
t.Fatalf("Expected one subscription for srvA, got %d\n", sc)
}
if sc := srvB.NumSubscriptions(); sc != 1 {
t.Fatalf("Expected one subscription for srvB, got %d\n", sc)
}
// Close the client and make sure we remove subscription state.
clientA.Close()
// Wait for propagation.
time.Sleep(100 * time.Millisecond)
if sc := srvA.NumSubscriptions(); sc != 0 {
t.Fatalf("Expected no subscriptions for srvA, got %d\n", sc)
}
if sc := srvB.NumSubscriptions(); sc != 0 {
t.Fatalf("Expected no subscriptions for srvB, got %d\n", sc)
}
}
// This will test that we drop remote sids correctly.
func TestAutoUnsubscribePropagation(t *testing.T) {
srvA, srvB, optsA, _ := runServers(t)
defer srvA.Shutdown()
defer srvB.Shutdown()
clientA := createClientConn(t, optsA.Host, optsA.Port)
defer clientA.Close()
sendA, expectA := setupConn(t, clientA)
expectMsgs := expectMsgsCommand(t, expectA)
// We will create subscriptions that will auto-unsubscribe and make sure
// we are not accumulating orphan subscriptions on the other side.
for i := 1; i <= 100; i++ {
sub := fmt.Sprintf("SUB foo %d\r\n", i)
auto := fmt.Sprintf("UNSUB %d 1\r\n", i)
sendA(sub)
sendA(auto)
// This will trip the auto-unsubscribe
sendA("PUB foo 2\r\nok\r\n")
expectMsgs(1)
}
sendA("PING\r\n")
expectA(pongRe)
time.Sleep(50 * time.Millisecond)
// Make sure number of subscriptions on B is correct
if subs := srvB.NumSubscriptions(); subs != 0 {
t.Fatalf("Expected no subscriptions on remote server, got %d\n", subs)
}
}
func TestAutoUnsubscribePropagationOnClientDisconnect(t *testing.T) {
srvA, srvB, optsA, _ := runServers(t)
defer srvA.Shutdown()
defer srvB.Shutdown()
cluster := []*server.Server{srvA, srvB}
clientA := createClientConn(t, optsA.Host, optsA.Port)
defer clientA.Close()
sendA, expectA := setupConn(t, clientA)
// No subscriptions. Ready to test.
if err := checkExpectedSubs(0, cluster...); err != nil {
t.Fatalf("%v", err)
}
sendA("SUB foo 1\r\n")
sendA("UNSUB 1 1\r\n")
sendA("PING\r\n")
expectA(pongRe)
// Waiting cluster subs propagation
if err := checkExpectedSubs(1, cluster...); err != nil {
t.Fatalf("%v", err)
}
clientA.Close()
// No subs should be on the cluster when all clients is disconnected
if err := checkExpectedSubs(0, cluster...); err != nil {
t.Fatalf("%v", err)
}
}

View File

@ -0,0 +1,64 @@
// Copyright 2013-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package test
import (
"testing"
"github.com/nats-io/gnatsd/server"
)
func runTLSServers(t *testing.T) (srvA, srvB *server.Server, optsA, optsB *server.Options) {
srvA, optsA = RunServerWithConfig("./configs/srv_a_tls.conf")
srvB, optsB = RunServerWithConfig("./configs/srv_b_tls.conf")
checkClusterFormed(t, srvA, srvB)
return
}
func TestTLSClusterConfig(t *testing.T) {
srvA, srvB, _, _ := runTLSServers(t)
defer srvA.Shutdown()
defer srvB.Shutdown()
}
func TestBasicTLSClusterPubSub(t *testing.T) {
srvA, srvB, optsA, optsB := runTLSServers(t)
defer srvA.Shutdown()
defer srvB.Shutdown()
clientA := createClientConn(t, optsA.Host, optsA.Port)
defer clientA.Close()
clientB := createClientConn(t, optsB.Host, optsB.Port)
defer clientB.Close()
sendA, expectA := setupConn(t, clientA)
sendA("SUB foo 22\r\n")
sendA("PING\r\n")
expectA(pongRe)
sendB, expectB := setupConn(t, clientB)
sendB("PUB foo 2\r\nok\r\n")
sendB("PING\r\n")
expectB(pongRe)
if err := checkExpectedSubs(1, srvA, srvB); err != nil {
t.Fatalf("%v", err)
}
expectMsgs := expectMsgsCommand(t, expectA)
matches := expectMsgs(1)
checkMsg(t, matches[0], "foo", "22", "", "2", "ok")
}

128
vendor/github.com/nats-io/gnatsd/test/fanout_test.go generated vendored Normal file
View File

@ -0,0 +1,128 @@
// Copyright 2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build !race
package test
import (
"fmt"
"sync"
"testing"
"github.com/nats-io/gnatsd/server"
"github.com/nats-io/go-nats"
)
// IMPORTANT: Tests in this file are not executed when running with the -race flag.
// As we look to improve high fanout situations make sure we
// have a test that checks ordering for all subscriptions from a single subscriber.
func TestHighFanoutOrdering(t *testing.T) {
opts := &server.Options{Host: "127.0.0.1", Port: server.RANDOM_PORT}
s := RunServer(opts)
defer s.Shutdown()
url := fmt.Sprintf("nats://%s", s.Addr())
const (
nconns = 100
nsubs = 100
npubs = 500
)
// make unique
subj := nats.NewInbox()
var wg sync.WaitGroup
wg.Add(nconns * nsubs)
for i := 0; i < nconns; i++ {
nc, err := nats.Connect(url)
if err != nil {
t.Fatalf("Expected a successful connect on %d, got %v\n", i, err)
}
nc.SetErrorHandler(func(c *nats.Conn, s *nats.Subscription, e error) {
t.Fatalf("Got an error %v for %+v\n", s, err)
})
ec, _ := nats.NewEncodedConn(nc, nats.DEFAULT_ENCODER)
for y := 0; y < nsubs; y++ {
expected := 0
ec.Subscribe(subj, func(n int) {
if n != expected {
t.Fatalf("Expected %d but received %d\n", expected, n)
}
expected++
if expected >= npubs {
wg.Done()
}
})
}
ec.Flush()
defer ec.Close()
}
nc, _ := nats.Connect(url)
ec, _ := nats.NewEncodedConn(nc, nats.DEFAULT_ENCODER)
for i := 0; i < npubs; i++ {
ec.Publish(subj, i)
}
defer ec.Close()
wg.Wait()
}
func TestRouteFormTimeWithHighSubscriptions(t *testing.T) {
srvA, optsA := RunServerWithConfig("./configs/srv_a.conf")
defer srvA.Shutdown()
clientA := createClientConn(t, optsA.Host, optsA.Port)
defer clientA.Close()
sendA, expectA := setupConn(t, clientA)
// Now add lots of subscriptions. These will need to be forwarded
// to new routes when they are added.
subsTotal := 100000
for i := 0; i < subsTotal; i++ {
subject := fmt.Sprintf("FOO.BAR.BAZ.%d", i)
sendA(fmt.Sprintf("SUB %s %d\r\n", subject, i))
}
sendA("PING\r\n")
expectA(pongRe)
srvB, _ := RunServerWithConfig("./configs/srv_b.conf")
defer srvB.Shutdown()
checkClusterFormed(t, srvA, srvB)
// Now wait for all subscriptions to be processed.
if err := checkExpectedSubs(subsTotal, srvB); err != nil {
// Make sure we are not a slow consumer
// Check for slow consumer status
if srvA.NumSlowConsumers() > 0 {
t.Fatal("Did not receive all subscriptions due to slow consumer")
} else {
t.Fatalf("%v", err)
}
}
// Just double check the slow consumer status.
if srvA.NumSlowConsumers() > 0 {
t.Fatalf("Received a slow consumer notification: %d", srvA.NumSlowConsumers())
}
}

62
vendor/github.com/nats-io/gnatsd/test/gosrv_test.go generated vendored Normal file
View File

@ -0,0 +1,62 @@
// Copyright 2012-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package test
import (
"net"
"runtime"
"testing"
"time"
)
func TestSimpleGoServerShutdown(t *testing.T) {
base := runtime.NumGoroutine()
opts := DefaultTestOptions
opts.Port = -1
s := RunServer(&opts)
s.Shutdown()
time.Sleep(100 * time.Millisecond)
delta := (runtime.NumGoroutine() - base)
if delta > 1 {
t.Fatalf("%d Go routines still exist post Shutdown()", delta)
}
}
func TestGoServerShutdownWithClients(t *testing.T) {
base := runtime.NumGoroutine()
opts := DefaultTestOptions
opts.Port = -1
s := RunServer(&opts)
addr := s.Addr().(*net.TCPAddr)
for i := 0; i < 50; i++ {
createClientConn(t, "127.0.0.1", addr.Port)
}
s.Shutdown()
// Wait longer for client connections
time.Sleep(1 * time.Second)
delta := (runtime.NumGoroutine() - base)
// There may be some finalizers or IO, but in general more than
// 2 as a delta represents a problem.
if delta > 2 {
t.Fatalf("%d Go routines still exist post Shutdown()", delta)
}
}
func TestGoServerMultiShutdown(t *testing.T) {
opts := DefaultTestOptions
opts.Port = -1
s := RunServer(&opts)
s.Shutdown()
s.Shutdown()
}

View File

@ -0,0 +1,99 @@
// Copyright 2015-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package test
import (
"fmt"
"net"
"runtime"
"strings"
"testing"
"time"
"github.com/nats-io/go-nats"
)
func TestMaxPayload(t *testing.T) {
srv, opts := RunServerWithConfig("./configs/override.conf")
defer srv.Shutdown()
endpoint := fmt.Sprintf("%s:%d", opts.Host, opts.Port)
nc, err := nats.Connect(fmt.Sprintf("nats://%s/", endpoint))
if err != nil {
t.Fatalf("Could not connect to server: %v", err)
}
defer nc.Close()
size := 4 * 1024 * 1024
big := sizedBytes(size)
err = nc.Publish("foo", big)
if err != nats.ErrMaxPayload {
t.Fatalf("Expected a Max Payload error")
}
conn, err := net.DialTimeout("tcp", endpoint, nc.Opts.Timeout)
if err != nil {
t.Fatalf("Could not make a raw connection to the server: %v", err)
}
defer conn.Close()
info := make([]byte, 512)
_, err = conn.Read(info)
if err != nil {
t.Fatalf("Expected an info message to be sent by the server: %s", err)
}
pub := fmt.Sprintf("PUB bar %d\r\n", size)
conn.Write([]byte(pub))
if err != nil {
t.Fatalf("Could not publish event to the server: %s", err)
}
errMsg := make([]byte, 35)
_, err = conn.Read(errMsg)
if err != nil {
t.Fatalf("Expected an error message to be sent by the server: %s", err)
}
if !strings.Contains(string(errMsg), "Maximum Payload Violation") {
t.Errorf("Received wrong error message (%v)\n", string(errMsg))
}
// Client proactively omits sending the message so server
// does not close the connection.
if nc.IsClosed() {
t.Errorf("Expected connection to not be closed.")
}
// On the other hand client which did not proactively omitted
// publishing the bytes following what is suggested by server
// in the info message has its connection closed.
_, err = conn.Write(big)
if err == nil && runtime.GOOS != "windows" {
t.Errorf("Expected error due to maximum payload transgression.")
}
// On windows, the previous write will not fail because the connection
// is not fully closed at this stage.
if runtime.GOOS == "windows" {
// Issuing a PING and not expecting the PONG.
_, err = conn.Write([]byte("PING\r\n"))
if err == nil {
conn.SetReadDeadline(time.Now().Add(500 * time.Millisecond))
_, err = conn.Read(big)
if err == nil {
t.Errorf("Expected closed connection due to maximum payload transgression.")
}
}
}
}

739
vendor/github.com/nats-io/gnatsd/test/monitor_test.go generated vendored Normal file
View File

@ -0,0 +1,739 @@
// Copyright 2012-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package test
import (
"crypto/tls"
"crypto/x509"
"encoding/json"
"fmt"
"io/ioutil"
"net"
"net/http"
"strings"
"sync"
"sync/atomic"
"testing"
"time"
"github.com/nats-io/gnatsd/server"
"github.com/nats-io/go-nats"
)
const CLIENT_PORT = 11422
const MONITOR_PORT = 11522
func runMonitorServer() *server.Server {
resetPreviousHTTPConnections()
opts := DefaultTestOptions
opts.Port = CLIENT_PORT
opts.HTTPPort = MONITOR_PORT
opts.HTTPHost = "127.0.0.1"
return RunServer(&opts)
}
// Runs a clustered pair of monitor servers for testing the /routez endpoint
func runMonitorServerClusteredPair(t *testing.T) (*server.Server, *server.Server) {
resetPreviousHTTPConnections()
opts := DefaultTestOptions
opts.Port = CLIENT_PORT
opts.HTTPPort = MONITOR_PORT
opts.HTTPHost = "127.0.0.1"
opts.Cluster = server.ClusterOpts{Host: "127.0.0.1", Port: 10223}
opts.Routes = server.RoutesFromStr("nats-route://127.0.0.1:10222")
s1 := RunServer(&opts)
opts2 := DefaultTestOptions
opts2.Port = CLIENT_PORT + 1
opts2.HTTPPort = MONITOR_PORT + 1
opts2.HTTPHost = "127.0.0.1"
opts2.Cluster = server.ClusterOpts{Host: "127.0.0.1", Port: 10222}
opts2.Routes = server.RoutesFromStr("nats-route://127.0.0.1:10223")
s2 := RunServer(&opts2)
checkClusterFormed(t, s1, s2)
return s1, s2
}
func runMonitorServerNoHTTPPort() *server.Server {
resetPreviousHTTPConnections()
opts := DefaultTestOptions
opts.Port = CLIENT_PORT
opts.HTTPPort = 0
return RunServer(&opts)
}
func resetPreviousHTTPConnections() {
http.DefaultTransport.(*http.Transport).CloseIdleConnections()
}
// Make sure that we do not run the http server for monitoring unless asked.
func TestNoMonitorPort(t *testing.T) {
s := runMonitorServerNoHTTPPort()
defer s.Shutdown()
url := fmt.Sprintf("http://127.0.0.1:%d/", MONITOR_PORT)
if resp, err := http.Get(url + "varz"); err == nil {
t.Fatalf("Expected error: Got %+v\n", resp)
}
if resp, err := http.Get(url + "healthz"); err == nil {
t.Fatalf("Expected error: Got %+v\n", resp)
}
if resp, err := http.Get(url + "connz"); err == nil {
t.Fatalf("Expected error: Got %+v\n", resp)
}
}
// testEndpointDataRace tests a monitoring endpoint for data races by polling
// while client code acts to ensure statistics are updated. It is designed to
// run under the -race flag to catch violations. The caller must start the
// NATS server.
func testEndpointDataRace(endpoint string, t *testing.T) {
var doneWg sync.WaitGroup
url := fmt.Sprintf("http://127.0.0.1:%d/", MONITOR_PORT)
// Poll as fast as we can, while creating connections, publishing,
// and subscribing.
clientDone := int64(0)
doneWg.Add(1)
go func() {
for atomic.LoadInt64(&clientDone) == 0 {
resp, err := http.Get(url + endpoint)
if err != nil {
t.Errorf("Expected no error: Got %v\n", err)
} else {
resp.Body.Close()
}
}
doneWg.Done()
}()
// create connections, subscriptions, and publish messages to
// update the monitor variables.
var conns []net.Conn
for i := 0; i < 50; i++ {
cl := createClientConnSubscribeAndPublish(t)
// keep a few connections around to test monitor variables.
if i%10 == 0 {
conns = append(conns, cl)
} else {
cl.Close()
}
}
atomic.AddInt64(&clientDone, 1)
// wait for the endpoint polling goroutine to exit
doneWg.Wait()
// cleanup the conns
for _, cl := range conns {
cl.Close()
}
}
func TestEndpointDataRaces(t *testing.T) {
// setup a small cluster to test /routez
s1, s2 := runMonitorServerClusteredPair(t)
defer s1.Shutdown()
defer s2.Shutdown()
// test all of our endpoints
testEndpointDataRace("varz", t)
testEndpointDataRace("connz", t)
testEndpointDataRace("routez", t)
testEndpointDataRace("subsz", t)
testEndpointDataRace("stacksz", t)
}
func TestVarz(t *testing.T) {
s := runMonitorServer()
defer s.Shutdown()
url := fmt.Sprintf("http://127.0.0.1:%d/", MONITOR_PORT)
resp, err := http.Get(url + "varz")
if err != nil {
t.Fatalf("Expected no error: Got %v\n", err)
}
if resp.StatusCode != 200 {
t.Fatalf("Expected a 200 response, got %d\n", resp.StatusCode)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatalf("Got an error reading the body: %v\n", err)
}
v := server.Varz{}
if err := json.Unmarshal(body, &v); err != nil {
t.Fatalf("Got an error unmarshalling the body: %v\n", err)
}
// Do some sanity checks on values
if time.Since(v.Start) > 10*time.Second {
t.Fatal("Expected start time to be within 10 seconds.")
}
cl := createClientConnSubscribeAndPublish(t)
defer cl.Close()
resp, err = http.Get(url + "varz")
if err != nil {
t.Fatalf("Expected no error: Got %v\n", err)
}
if resp.StatusCode != 200 {
t.Fatalf("Expected a 200 response, got %d\n", resp.StatusCode)
}
defer resp.Body.Close()
body, err = ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatalf("Got an error reading the body: %v\n", err)
}
if strings.Contains(string(body), "cluster_port") {
t.Fatal("Varz body contains cluster information when no cluster is defined.")
}
v = server.Varz{}
if err := json.Unmarshal(body, &v); err != nil {
t.Fatalf("Got an error unmarshalling the body: %v\n", err)
}
if v.Connections != 1 {
t.Fatalf("Expected Connections of 1, got %v\n", v.Connections)
}
if v.InMsgs != 1 {
t.Fatalf("Expected InMsgs of 1, got %v\n", v.InMsgs)
}
if v.OutMsgs != 1 {
t.Fatalf("Expected OutMsgs of 1, got %v\n", v.OutMsgs)
}
if v.InBytes != 5 {
t.Fatalf("Expected InBytes of 5, got %v\n", v.InBytes)
}
if v.OutBytes != 5 {
t.Fatalf("Expected OutBytes of 5, got %v\n", v.OutBytes)
}
if v.MaxPending != server.MAX_PENDING_SIZE {
t.Fatalf("Expected MaxPending of %d, got %v\n",
server.MAX_PENDING_SIZE, v.MaxPending)
}
if v.WriteDeadline != server.DEFAULT_FLUSH_DEADLINE {
t.Fatalf("Expected WriteDeadline of %d, got %v\n",
server.DEFAULT_FLUSH_DEADLINE, v.WriteDeadline)
}
}
func TestConnz(t *testing.T) {
s := runMonitorServer()
defer s.Shutdown()
url := fmt.Sprintf("http://127.0.0.1:%d/", MONITOR_PORT)
resp, err := http.Get(url + "connz")
if err != nil {
t.Fatalf("Expected no error: Got %v\n", err)
}
if resp.StatusCode != 200 {
t.Fatalf("Expected a 200 response, got %d\n", resp.StatusCode)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatalf("Got an error reading the body: %v\n", err)
}
c := server.Connz{}
if err := json.Unmarshal(body, &c); err != nil {
t.Fatalf("Got an error unmarshalling the body: %v\n", err)
}
// Test contents..
if c.NumConns != 0 {
t.Fatalf("Expected 0 connections, got %d\n", c.NumConns)
}
if c.Total != 0 {
t.Fatalf("Expected 0 live connections, got %d\n", c.Total)
}
if c.Conns == nil || len(c.Conns) != 0 {
t.Fatalf("Expected 0 connections in array, got %p\n", c.Conns)
}
cl := createClientConnSubscribeAndPublish(t)
defer cl.Close()
resp, err = http.Get(url + "connz")
if err != nil {
t.Fatalf("Expected no error: Got %v\n", err)
}
if resp.StatusCode != 200 {
t.Fatalf("Expected a 200 response, got %d\n", resp.StatusCode)
}
defer resp.Body.Close()
body, err = ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatalf("Got an error reading the body: %v\n", err)
}
if err := json.Unmarshal(body, &c); err != nil {
t.Fatalf("Got an error unmarshalling the body: %v\n", err)
}
if c.NumConns != 1 {
t.Fatalf("Expected 1 connection, got %d\n", c.NumConns)
}
if c.Total != 1 {
t.Fatalf("Expected 1 live connection, got %d\n", c.Total)
}
if c.Conns == nil || len(c.Conns) != 1 {
t.Fatalf("Expected 1 connection in array, got %p\n", c.Conns)
}
if c.Limit != server.DefaultConnListSize {
t.Fatalf("Expected limit of %d, got %v\n", server.DefaultConnListSize, c.Limit)
}
if c.Offset != 0 {
t.Fatalf("Expected offset of 0, got %v\n", c.Offset)
}
// Test inside details of each connection
ci := c.Conns[0]
if ci.Cid == 0 {
t.Fatalf("Expected non-zero cid, got %v\n", ci.Cid)
}
if ci.IP != "127.0.0.1" {
t.Fatalf("Expected \"127.0.0.1\" for IP, got %v\n", ci.IP)
}
if ci.Port == 0 {
t.Fatalf("Expected non-zero port, got %v\n", ci.Port)
}
if ci.NumSubs != 1 {
t.Fatalf("Expected num_subs of 1, got %v\n", ci.NumSubs)
}
if len(ci.Subs) != 0 {
t.Fatalf("Expected subs of 0, got %v\n", ci.Subs)
}
if ci.InMsgs != 1 {
t.Fatalf("Expected InMsgs of 1, got %v\n", ci.InMsgs)
}
if ci.OutMsgs != 1 {
t.Fatalf("Expected OutMsgs of 1, got %v\n", ci.OutMsgs)
}
if ci.InBytes != 5 {
t.Fatalf("Expected InBytes of 1, got %v\n", ci.InBytes)
}
if ci.OutBytes != 5 {
t.Fatalf("Expected OutBytes of 1, got %v\n", ci.OutBytes)
}
}
func TestTLSConnz(t *testing.T) {
srv, opts := RunServerWithConfig("./configs/tls.conf")
defer srv.Shutdown()
rootCAFile := "./configs/certs/ca.pem"
clientCertFile := "./configs/certs/client-cert.pem"
clientKeyFile := "./configs/certs/client-key.pem"
// Test with secure connection
endpoint := fmt.Sprintf("%s:%d", opts.Host, opts.Port)
nurl := fmt.Sprintf("tls://%s:%s@%s/", opts.Username, opts.Password, endpoint)
nc, err := nats.Connect(nurl, nats.RootCAs(rootCAFile))
if err != nil {
t.Fatalf("Got an error on Connect with Secure Options: %+v\n", err)
}
defer nc.Close()
ch := make(chan struct{})
nc.Subscribe("foo", func(m *nats.Msg) { ch <- struct{}{} })
nc.Publish("foo", []byte("Hello"))
// Wait for message
<-ch
url := fmt.Sprintf("https://127.0.0.1:%d/", opts.HTTPSPort)
tlsConfig := &tls.Config{}
caCert, err := ioutil.ReadFile(rootCAFile)
if err != nil {
t.Fatalf("Got error reading RootCA file: %s", err)
}
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)
tlsConfig.RootCAs = caCertPool
cert, err := tls.LoadX509KeyPair(clientCertFile, clientKeyFile)
if err != nil {
t.Fatalf("Got error reading client certificates: %s", err)
}
tlsConfig.Certificates = []tls.Certificate{cert}
transport := &http.Transport{TLSClientConfig: tlsConfig}
httpClient := &http.Client{Transport: transport}
resp, err := httpClient.Get(url + "connz")
if err != nil {
t.Fatalf("Expected no error: Got %v\n", err)
}
if resp.StatusCode != 200 {
t.Fatalf("Expected a 200 response, got %d\n", resp.StatusCode)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatalf("Got an error reading the body: %v\n", err)
}
c := server.Connz{}
if err := json.Unmarshal(body, &c); err != nil {
t.Fatalf("Got an error unmarshalling the body: %v\n", err)
}
if c.NumConns != 1 {
t.Fatalf("Expected 1 connection, got %d\n", c.NumConns)
}
if c.Total != 1 {
t.Fatalf("Expected 1 live connection, got %d\n", c.Total)
}
if c.Conns == nil || len(c.Conns) != 1 {
t.Fatalf("Expected 1 connection in array, got %d\n", len(c.Conns))
}
// Test inside details of each connection
ci := c.Conns[0]
if ci.Cid == 0 {
t.Fatalf("Expected non-zero cid, got %v\n", ci.Cid)
}
if ci.IP != "127.0.0.1" {
t.Fatalf("Expected \"127.0.0.1\" for IP, got %v\n", ci.IP)
}
if ci.Port == 0 {
t.Fatalf("Expected non-zero port, got %v\n", ci.Port)
}
if ci.NumSubs != 1 {
t.Fatalf("Expected num_subs of 1, got %v\n", ci.NumSubs)
}
if len(ci.Subs) != 0 {
t.Fatalf("Expected subs of 0, got %v\n", ci.Subs)
}
if ci.InMsgs != 1 {
t.Fatalf("Expected InMsgs of 1, got %v\n", ci.InMsgs)
}
if ci.OutMsgs != 1 {
t.Fatalf("Expected OutMsgs of 1, got %v\n", ci.OutMsgs)
}
if ci.InBytes != 5 {
t.Fatalf("Expected InBytes of 1, got %v\n", ci.InBytes)
}
if ci.OutBytes != 5 {
t.Fatalf("Expected OutBytes of 1, got %v\n", ci.OutBytes)
}
if ci.Start.IsZero() {
t.Fatalf("Expected Start to be valid\n")
}
if ci.Uptime == "" {
t.Fatalf("Expected Uptime to be valid\n")
}
if ci.LastActivity.IsZero() {
t.Fatalf("Expected LastActivity to be valid\n")
}
if ci.LastActivity.UnixNano() < ci.Start.UnixNano() {
t.Fatalf("Expected LastActivity [%v] to be > Start [%v]\n", ci.LastActivity, ci.Start)
}
if ci.Idle == "" {
t.Fatalf("Expected Idle to be valid\n")
}
}
func TestConnzWithSubs(t *testing.T) {
s := runMonitorServer()
defer s.Shutdown()
cl := createClientConnSubscribeAndPublish(t)
defer cl.Close()
url := fmt.Sprintf("http://127.0.0.1:%d/", MONITOR_PORT)
resp, err := http.Get(url + "connz?subs=1")
if err != nil {
t.Fatalf("Expected no error: Got %v\n", err)
}
if resp.StatusCode != 200 {
t.Fatalf("Expected a 200 response, got %d\n", resp.StatusCode)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatalf("Got an error reading the body: %v\n", err)
}
c := server.Connz{}
if err := json.Unmarshal(body, &c); err != nil {
t.Fatalf("Got an error unmarshalling the body: %v\n", err)
}
// Test inside details of each connection
ci := c.Conns[0]
if len(ci.Subs) != 1 || ci.Subs[0] != "foo" {
t.Fatalf("Expected subs of 1, got %v\n", ci.Subs)
}
}
func TestConnzWithAuth(t *testing.T) {
srv, opts := RunServerWithConfig("./configs/multi_user.conf")
defer srv.Shutdown()
endpoint := fmt.Sprintf("%s:%d", opts.Host, opts.Port)
curl := fmt.Sprintf("nats://%s:%s@%s/", opts.Users[0].Username, opts.Users[0].Password, endpoint)
nc, err := nats.Connect(curl)
if err != nil {
t.Fatalf("Got an error on Connect: %+v\n", err)
}
defer nc.Close()
ch := make(chan struct{})
nc.Subscribe("foo", func(m *nats.Msg) { ch <- struct{}{} })
nc.Publish("foo", []byte("Hello"))
// Wait for message
<-ch
url := fmt.Sprintf("http://127.0.0.1:%d/", opts.HTTPPort)
resp, err := http.Get(url + "connz?auth=1")
if err != nil {
t.Fatalf("Expected no error: Got %v\n", err)
}
if resp.StatusCode != 200 {
t.Fatalf("Expected a 200 response, got %d\n", resp.StatusCode)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatalf("Got an error reading the body: %v\n", err)
}
c := server.Connz{}
if err := json.Unmarshal(body, &c); err != nil {
t.Fatalf("Got an error unmarshalling the body: %v\n", err)
}
// Test that we have authorized_user and its Alice.
ci := c.Conns[0]
if ci.AuthorizedUser != opts.Users[0].Username {
t.Fatalf("Expected authorized_user to be %q, got %q\n",
opts.Users[0].Username, ci.AuthorizedUser)
}
}
func TestConnzWithOffsetAndLimit(t *testing.T) {
s := runMonitorServer()
defer s.Shutdown()
cl1 := createClientConnSubscribeAndPublish(t)
defer cl1.Close()
cl2 := createClientConnSubscribeAndPublish(t)
defer cl2.Close()
url := fmt.Sprintf("http://127.0.0.1:%d/", MONITOR_PORT)
resp, err := http.Get(url + "connz?offset=1&limit=1")
if err != nil {
t.Fatalf("Expected no error: Got %v\n", err)
}
if resp.StatusCode != 200 {
t.Fatalf("Expected a 200 response, got %d\n", resp.StatusCode)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatalf("Got an error reading the body: %v\n", err)
}
c := server.Connz{}
if err := json.Unmarshal(body, &c); err != nil {
t.Fatalf("Got an error unmarshalling the body: %v\n", err)
}
if c.Limit != 1 {
t.Fatalf("Expected limit of 1, got %v\n", c.Limit)
}
if c.Offset != 1 {
t.Fatalf("Expected offset of 1, got %v\n", c.Offset)
}
if len(c.Conns) != 1 {
t.Fatalf("Expected conns of 1, got %v\n", len(c.Conns))
}
}
func TestSubsz(t *testing.T) {
s := runMonitorServer()
defer s.Shutdown()
cl := createClientConnSubscribeAndPublish(t)
defer cl.Close()
url := fmt.Sprintf("http://127.0.0.1:%d/", MONITOR_PORT)
resp, err := http.Get(url + "subscriptionsz")
if err != nil {
t.Fatalf("Expected no error: Got %v\n", err)
}
if resp.StatusCode != 200 {
t.Fatalf("Expected a 200 response, got %d\n", resp.StatusCode)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatalf("Got an error reading the body: %v\n", err)
}
su := server.Subsz{}
if err := json.Unmarshal(body, &su); err != nil {
t.Fatalf("Got an error unmarshalling the body: %v\n", err)
}
// Do some sanity checks on values
if su.NumSubs != 1 {
t.Fatalf("Expected num_subs of 1, got %v\n", su.NumSubs)
}
}
func TestHTTPHost(t *testing.T) {
s := runMonitorServer()
defer s.Shutdown()
// Grab non-127.0.0.1 address and try to use that to connect.
// Should fail.
var ip net.IP
ifaces, _ := net.Interfaces()
for _, i := range ifaces {
addrs, _ := i.Addrs()
for _, addr := range addrs {
switch v := addr.(type) {
case *net.IPNet:
ip = v.IP
case *net.IPAddr:
ip = v.IP
}
// Skip loopback/127.0.0.1 or any ipv6 for now.
if ip.IsLoopback() || ip.To4() == nil {
ip = nil
continue
}
break
}
if ip != nil {
break
}
}
if ip == nil {
t.Fatalf("Could not find non-loopback IPV4 address")
}
url := fmt.Sprintf("http://%v:%d/", ip, MONITOR_PORT)
if resp, err := http.Get(url + "varz"); err == nil {
t.Fatalf("Expected error: Got %+v\n", resp)
}
}
// Create a connection to test ConnInfo
func createClientConnSubscribeAndPublish(t *testing.T) net.Conn {
cl := createClientConn(t, "127.0.0.1", CLIENT_PORT)
send, expect := setupConn(t, cl)
expectMsgs := expectMsgsCommand(t, expect)
send("SUB foo 1\r\nPUB foo 5\r\nhello\r\n")
expectMsgs(1)
return cl
}
func TestMonitorNoTLSConfig(t *testing.T) {
opts := DefaultTestOptions
opts.Port = CLIENT_PORT
opts.HTTPHost = "127.0.0.1"
opts.HTTPSPort = MONITOR_PORT
s := server.New(&opts)
defer s.Shutdown()
// Check with manually starting the monitoring, which should return an error
if err := s.StartMonitoring(); err == nil || !strings.Contains(err.Error(), "TLS") {
t.Fatalf("Expected error about missing TLS config, got %v", err)
}
// Also check by calling Start(), which should produce a fatal error
dl := &dummyLogger{}
s.SetLogger(dl, false, false)
defer s.SetLogger(nil, false, false)
s.Start()
if !strings.Contains(dl.msg, "TLS") {
t.Fatalf("Expected error about missing TLS config, got %v", dl.msg)
}
}
func TestMonitorErrorOnListen(t *testing.T) {
s := runMonitorServer()
defer s.Shutdown()
opts := DefaultTestOptions
opts.Port = CLIENT_PORT + 1
opts.HTTPHost = "127.0.0.1"
opts.HTTPPort = MONITOR_PORT
s2 := server.New(&opts)
defer s2.Shutdown()
if err := s2.StartMonitoring(); err == nil || !strings.Contains(err.Error(), "listen") {
t.Fatalf("Expected error about not able to start listener, got %v", err)
}
}
func TestMonitorBothPortsConfigured(t *testing.T) {
opts := DefaultTestOptions
opts.Port = CLIENT_PORT
opts.HTTPHost = "127.0.0.1"
opts.HTTPPort = MONITOR_PORT
opts.HTTPSPort = MONITOR_PORT + 1
s := server.New(&opts)
defer s.Shutdown()
if err := s.StartMonitoring(); err == nil || !strings.Contains(err.Error(), "specify both") {
t.Fatalf("Expected error about ports configured, got %v", err)
}
}
func TestMonitorStop(t *testing.T) {
resetPreviousHTTPConnections()
opts := DefaultTestOptions
opts.Port = CLIENT_PORT
opts.HTTPHost = "127.0.0.1"
opts.HTTPPort = MONITOR_PORT
url := fmt.Sprintf("http://%v:%d/", opts.HTTPHost, MONITOR_PORT)
// Create a server instance and start only the monitoring http server.
s := server.New(&opts)
if err := s.StartMonitoring(); err != nil {
t.Fatalf("Error starting monitoring: %v", err)
}
// Make sure http server is started
resp, err := http.Get(url + "varz")
if err != nil {
t.Fatalf("Error on http request: %v", err)
}
resp.Body.Close()
// Although the server itself was not started (we did not call s.Start()),
// Shutdown() should stop the http server.
s.Shutdown()
// HTTP request should now fail
if resp, err := http.Get(url + "varz"); err == nil {
t.Fatalf("Expected error: Got %+v\n", resp)
}
}

43
vendor/github.com/nats-io/gnatsd/test/opts_test.go generated vendored Normal file
View File

@ -0,0 +1,43 @@
// Copyright 2015-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package test
import "testing"
func TestServerConfig(t *testing.T) {
srv, opts := RunServerWithConfig("./configs/override.conf")
defer srv.Shutdown()
c := createClientConn(t, opts.Host, opts.Port)
defer c.Close()
sinfo := checkInfoMsg(t, c)
if sinfo.MaxPayload != opts.MaxPayload {
t.Fatalf("Expected max_payload from server, got %d vs %d",
opts.MaxPayload, sinfo.MaxPayload)
}
}
func TestTLSConfig(t *testing.T) {
srv, opts := RunServerWithConfig("./configs/tls.conf")
defer srv.Shutdown()
c := createClientConn(t, opts.Host, opts.Port)
defer c.Close()
sinfo := checkInfoMsg(t, c)
if !sinfo.TLSRequired {
t.Fatal("Expected TLSRequired to be true when configured")
}
}

109
vendor/github.com/nats-io/gnatsd/test/pedantic_test.go generated vendored Normal file
View File

@ -0,0 +1,109 @@
// Copyright 2012-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package test
import (
"testing"
"github.com/nats-io/gnatsd/server"
)
func runPedanticServer() *server.Server {
opts := DefaultTestOptions
opts.NoLog = false
opts.Trace = true
opts.Port = PROTO_TEST_PORT
return RunServer(&opts)
}
func TestPedanticSub(t *testing.T) {
s := runPedanticServer()
defer s.Shutdown()
c := createClientConn(t, "127.0.0.1", PROTO_TEST_PORT)
defer c.Close()
send := sendCommand(t, c)
expect := expectCommand(t, c)
doConnect(t, c, false, true, false)
// Ping should still be same
send("PING\r\n")
expect(pongRe)
// Test malformed subjects for SUB
// Sub can contain wildcards, but
// subject must still be legit.
// Empty terminal token
send("SUB foo. 1\r\n")
expect(errRe)
// Empty beginning token
send("SUB .foo. 1\r\n")
expect(errRe)
// Empty middle token
send("SUB foo..bar 1\r\n")
expect(errRe)
// Bad non-terminal FWC
send("SUB foo.>.bar 1\r\n")
buf := expect(errRe)
// Check that itr is 'Invalid Subject'
matches := errRe.FindAllSubmatch(buf, -1)
if len(matches) != 1 {
t.Fatal("Wanted one overall match")
}
if string(matches[0][1]) != "'Invalid Subject'" {
t.Fatalf("Expected 'Invalid Subject', got %s", string(matches[0][1]))
}
}
func TestPedanticPub(t *testing.T) {
s := runPedanticServer()
defer s.Shutdown()
c := createClientConn(t, "127.0.0.1", PROTO_TEST_PORT)
defer c.Close()
send := sendCommand(t, c)
expect := expectCommand(t, c)
doConnect(t, c, false, true, false)
// Ping should still be same
send("PING\r\n")
expect(pongRe)
// Test malformed subjects for PUB
// PUB subjects can not have wildcards
// This will error in pedantic mode
send("PUB foo.* 2\r\nok\r\n")
expect(errRe)
send("PUB foo.> 2\r\nok\r\n")
expect(errRe)
send("PUB foo. 2\r\nok\r\n")
expect(errRe)
send("PUB .foo 2\r\nok\r\n")
expect(errRe)
send("PUB foo..* 2\r\nok\r\n")
expect(errRe)
}

55
vendor/github.com/nats-io/gnatsd/test/pid_test.go generated vendored Normal file
View File

@ -0,0 +1,55 @@
// Copyright 2012-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package test
import (
"fmt"
"io/ioutil"
"os"
"testing"
)
func TestPidFile(t *testing.T) {
opts := DefaultTestOptions
tmpDir, err := ioutil.TempDir("", "_gnatsd")
if err != nil {
t.Fatal("Could not create tmp dir")
}
defer os.RemoveAll(tmpDir)
file, err := ioutil.TempFile(tmpDir, "gnatsd:pid_")
if err != nil {
t.Fatalf("Unable to create temp file: %v", err)
}
file.Close()
opts.PidFile = file.Name()
s := RunServer(&opts)
s.Shutdown()
buf, err := ioutil.ReadFile(opts.PidFile)
if err != nil {
t.Fatalf("Could not read pid_file: %v", err)
}
if len(buf) <= 0 {
t.Fatal("Expected a non-zero length pid_file")
}
pid := 0
fmt.Sscanf(string(buf), "%d", &pid)
if pid != os.Getpid() {
t.Fatalf("Expected pid to be %d, got %d\n", os.Getpid(), pid)
}
}

189
vendor/github.com/nats-io/gnatsd/test/ping_test.go generated vendored Normal file
View File

@ -0,0 +1,189 @@
// Copyright 2012-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package test
import (
"crypto/tls"
"fmt"
"net"
"testing"
"time"
"github.com/nats-io/gnatsd/server"
)
const (
PING_TEST_PORT = 9972
PING_INTERVAL = 50 * time.Millisecond
PING_MAX = 2
)
func runPingServer() *server.Server {
opts := DefaultTestOptions
opts.Port = PING_TEST_PORT
opts.PingInterval = PING_INTERVAL
opts.MaxPingsOut = PING_MAX
return RunServer(&opts)
}
func TestPingSentToTLSConnection(t *testing.T) {
opts := DefaultTestOptions
opts.Port = PING_TEST_PORT
opts.PingInterval = PING_INTERVAL
opts.MaxPingsOut = PING_MAX
opts.TLSCert = "configs/certs/server-cert.pem"
opts.TLSKey = "configs/certs/server-key.pem"
opts.TLSCaCert = "configs/certs/ca.pem"
tc := server.TLSConfigOpts{}
tc.CertFile = opts.TLSCert
tc.KeyFile = opts.TLSKey
tc.CaFile = opts.TLSCaCert
opts.TLSConfig, _ = server.GenTLSConfig(&tc)
opts.TLSTimeout = 5
s := RunServer(&opts)
defer s.Shutdown()
c := createClientConn(t, "127.0.0.1", PING_TEST_PORT)
defer c.Close()
checkInfoMsg(t, c)
c = tls.Client(c, &tls.Config{InsecureSkipVerify: true})
tlsConn := c.(*tls.Conn)
tlsConn.Handshake()
cs := fmt.Sprintf("CONNECT {\"verbose\":%v,\"pedantic\":%v,\"tls_required\":%v}\r\n", false, false, true)
sendProto(t, c, cs)
expect := expectCommand(t, c)
// Expect the max to be delivered correctly..
for i := 0; i < PING_MAX; i++ {
time.Sleep(PING_INTERVAL / 2)
expect(pingRe)
}
// We should get an error from the server
time.Sleep(PING_INTERVAL)
expect(errRe)
// Server should close the connection at this point..
time.Sleep(PING_INTERVAL)
c.SetWriteDeadline(time.Now().Add(PING_INTERVAL))
var err error
for {
_, err = c.Write([]byte("PING\r\n"))
if err != nil {
break
}
}
c.SetWriteDeadline(time.Time{})
if err == nil {
t.Fatal("No error: Expected to have connection closed")
}
if ne, ok := err.(net.Error); ok && ne.Timeout() {
t.Fatal("timeout: Expected to have connection closed")
}
}
func TestPingInterval(t *testing.T) {
s := runPingServer()
defer s.Shutdown()
c := createClientConn(t, "127.0.0.1", PING_TEST_PORT)
defer c.Close()
doConnect(t, c, false, false, false)
expect := expectCommand(t, c)
// Expect the max to be delivered correctly..
for i := 0; i < PING_MAX; i++ {
time.Sleep(PING_INTERVAL / 2)
expect(pingRe)
}
// We should get an error from the server
time.Sleep(PING_INTERVAL)
expect(errRe)
// Server should close the connection at this point..
time.Sleep(PING_INTERVAL)
c.SetWriteDeadline(time.Now().Add(PING_INTERVAL))
var err error
for {
_, err = c.Write([]byte("PING\r\n"))
if err != nil {
break
}
}
c.SetWriteDeadline(time.Time{})
if err == nil {
t.Fatal("No error: Expected to have connection closed")
}
if ne, ok := err.(net.Error); ok && ne.Timeout() {
t.Fatal("timeout: Expected to have connection closed")
}
}
func TestUnpromptedPong(t *testing.T) {
s := runPingServer()
defer s.Shutdown()
c := createClientConn(t, "127.0.0.1", PING_TEST_PORT)
defer c.Close()
doConnect(t, c, false, false, false)
expect := expectCommand(t, c)
// Send lots of PONGs in a row...
for i := 0; i < 100; i++ {
c.Write([]byte("PONG\r\n"))
}
// The server should still send the max number of PINGs and then
// close the connection.
for i := 0; i < PING_MAX; i++ {
time.Sleep(PING_INTERVAL / 2)
expect(pingRe)
}
// We should get an error from the server
time.Sleep(PING_INTERVAL)
expect(errRe)
// Server should close the connection at this point..
c.SetWriteDeadline(time.Now().Add(PING_INTERVAL))
var err error
for {
_, err = c.Write([]byte("PING\r\n"))
if err != nil {
break
}
}
c.SetWriteDeadline(time.Time{})
if err == nil {
t.Fatal("No error: Expected to have connection closed")
}
if ne, ok := err.(net.Error); ok && ne.Timeout() {
t.Fatal("timeout: Expected to have connection closed")
}
}

52
vendor/github.com/nats-io/gnatsd/test/port_test.go generated vendored Normal file
View File

@ -0,0 +1,52 @@
// Copyright 2014-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package test
import (
"net"
"strconv"
"testing"
"github.com/nats-io/gnatsd/server"
)
func TestResolveRandomPort(t *testing.T) {
opts := &server.Options{Host: "127.0.0.1", Port: server.RANDOM_PORT, NoSigs: true}
s := RunServer(opts)
defer s.Shutdown()
addr := s.Addr()
_, port, err := net.SplitHostPort(addr.String())
if err != nil {
t.Fatalf("Expected no error: Got %v\n", err)
}
portNum, err := strconv.Atoi(port)
if err != nil {
t.Fatalf("Expected no error: Got %v\n", err)
}
if portNum == server.DEFAULT_PORT {
t.Fatalf("Expected server to choose a random port\nGot: %d", server.DEFAULT_PORT)
}
if portNum == server.RANDOM_PORT {
t.Fatalf("Expected server to choose a random port\nGot: %d", server.RANDOM_PORT)
}
if opts.Port != portNum {
t.Fatalf("Options port (%d) should have been overridden by chosen random port (%d)",
opts.Port, portNum)
}
}

195
vendor/github.com/nats-io/gnatsd/test/ports_test.go generated vendored Normal file
View File

@ -0,0 +1,195 @@
// Copyright 2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package test
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os"
"path"
"strings"
"testing"
"time"
"github.com/nats-io/gnatsd/server"
)
// waits until a calculated list of listeners is resolved or a timeout
func waitForFile(path string, dur time.Duration) ([]byte, error) {
end := time.Now().Add(dur)
for time.Now().Before(end) {
if _, err := os.Stat(path); os.IsNotExist(err) {
time.Sleep(25 * time.Millisecond)
continue
} else {
return ioutil.ReadFile(path)
}
}
return nil, errors.New("Timeout")
}
func portFile(dirname string) string {
return path.Join(dirname, fmt.Sprintf("%s_%d.ports", path.Base(os.Args[0]), os.Getpid()))
}
func TestPortsFile(t *testing.T) {
portFileDir := os.TempDir()
opts := DefaultTestOptions
opts.PortsFileDir = portFileDir
opts.Port = -1
opts.HTTPPort = -1
opts.ProfPort = -1
opts.Cluster.Port = -1
s := RunServer(&opts)
// this for test cleanup in case we fail - will be ignored if server already shutdown
defer s.Shutdown()
ports := s.PortsInfo(5 * time.Second)
if ports == nil {
t.Fatal("services failed to start in 5 seconds")
}
// the pid file should be
portsFile := portFile(portFileDir)
if portsFile == "" {
t.Fatal("Expected a ports file")
}
// try to read a file here - the file should be a json
buf, err := waitForFile(portsFile, 5*time.Second)
if err != nil {
t.Fatalf("Could not read ports file: %v", err)
}
if len(buf) <= 0 {
t.Fatal("Expected a non-zero length ports file")
}
readPorts := server.Ports{}
json.Unmarshal(buf, &readPorts)
if len(readPorts.Nats) == 0 || !strings.HasPrefix(readPorts.Nats[0], "nats://") {
t.Fatal("Expected at least one nats url")
}
if len(readPorts.Monitoring) == 0 || !strings.HasPrefix(readPorts.Monitoring[0], "http://") {
t.Fatal("Expected at least one monitoring url")
}
if len(readPorts.Cluster) == 0 || !strings.HasPrefix(readPorts.Cluster[0], "nats://") {
t.Fatal("Expected at least one cluster listen url")
}
if len(readPorts.Profile) == 0 || !strings.HasPrefix(readPorts.Profile[0], "http://") {
t.Fatal("Expected at least one profile listen url")
}
// testing cleanup
s.Shutdown()
// if we called shutdown, the cleanup code should have kicked
if _, err := os.Stat(portsFile); os.IsNotExist(err) {
// good
} else {
t.Fatalf("the port file %s was not deleted", portsFile)
}
}
// makes a temp directory with two directories 'A' and 'B'
// the location of the ports file is changed from dir A to dir B.
func TestPortsFileReload(t *testing.T) {
// make a temp dir
tempDir, err := ioutil.TempDir("", "")
if err != nil {
t.Fatalf("Error creating temp director (%s): %v", tempDir, err)
}
defer os.RemoveAll(tempDir)
// make child temp dir A
dirA := path.Join(tempDir, "A")
os.MkdirAll(dirA, 0777)
// write the config file with a reference to A
config := fmt.Sprintf("ports_file_dir %s\nport -1", dirA)
confPath := path.Join(tempDir, fmt.Sprintf("%d.conf", os.Getpid()))
if err := ioutil.WriteFile(confPath, []byte(config), 0666); err != nil {
t.Fatalf("Error writing ports file (%s): %v", confPath, err)
}
opts, err := server.ProcessConfigFile(confPath)
if err != nil {
t.Fatalf("Error processing the configuration: %v", err)
}
s := RunServer(opts)
defer s.Shutdown()
ports := s.PortsInfo(5 * time.Second)
if ports == nil {
t.Fatal("services failed to start in 5 seconds")
}
// get the ports file path name
portsFileInA := portFile(dirA)
// the file should be in dirA
if !strings.HasPrefix(portsFileInA, dirA) {
t.Fatalf("expected ports file to be in [%s] but was in [%s]", dirA, portsFileInA)
}
// wait for it
buf, err := waitForFile(portsFileInA, 5*time.Second)
if err != nil {
t.Fatalf("Could not read ports file: %v", err)
}
if len(buf) <= 0 {
t.Fatal("Expected a non-zero length ports file")
}
// change the configuration for the ports file to dirB
dirB := path.Join(tempDir, "B")
os.MkdirAll(dirB, 0777)
config = fmt.Sprintf("ports_file_dir %s\nport -1", dirB)
if err := ioutil.WriteFile(confPath, []byte(config), 0666); err != nil {
t.Fatalf("Error writing ports file (%s): %v", confPath, err)
}
// reload the server
if err := s.Reload(); err != nil {
t.Fatalf("error reloading server: %v", err)
}
// wait for the new file to show up
portsFileInB := portFile(dirB)
buf, err = waitForFile(portsFileInB, 5*time.Second)
if !strings.HasPrefix(portsFileInB, dirB) {
t.Fatalf("expected ports file to be in [%s] but was in [%s]", dirB, portsFileInB)
}
if err != nil {
t.Fatalf("Could not read ports file: %v", err)
}
if len(buf) <= 0 {
t.Fatal("Expected a non-zero length ports file")
}
// the file in dirA should have deleted
if _, err := os.Stat(portsFileInA); os.IsNotExist(err) {
// good
} else {
t.Fatalf("the port file %s was not deleted", portsFileInA)
}
}

317
vendor/github.com/nats-io/gnatsd/test/proto_test.go generated vendored Normal file
View File

@ -0,0 +1,317 @@
// Copyright 2012-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package test
import (
"encoding/json"
"testing"
"time"
"github.com/nats-io/gnatsd/server"
)
const PROTO_TEST_PORT = 9922
func runProtoServer() *server.Server {
opts := DefaultTestOptions
opts.Port = PROTO_TEST_PORT
return RunServer(&opts)
}
func TestProtoBasics(t *testing.T) {
s := runProtoServer()
defer s.Shutdown()
c := createClientConn(t, "127.0.0.1", PROTO_TEST_PORT)
defer c.Close()
send, expect := setupConn(t, c)
expectMsgs := expectMsgsCommand(t, expect)
// Ping
send("PING\r\n")
expect(pongRe)
// Single Msg
send("SUB foo 1\r\nPUB foo 5\r\nhello\r\n")
matches := expectMsgs(1)
checkMsg(t, matches[0], "foo", "1", "", "5", "hello")
// 2 Messages
send("SUB * 2\r\nPUB foo 2\r\nok\r\n")
matches = expectMsgs(2)
// Could arrive in any order
checkMsg(t, matches[0], "foo", "", "", "2", "ok")
checkMsg(t, matches[1], "foo", "", "", "2", "ok")
}
func TestProtoErr(t *testing.T) {
s := runProtoServer()
defer s.Shutdown()
c := createClientConn(t, "127.0.0.1", PROTO_TEST_PORT)
defer c.Close()
send, expect := setupConn(t, c)
// Make sure we get an error on bad proto
send("ZZZ")
expect(errRe)
}
func TestUnsubMax(t *testing.T) {
s := runProtoServer()
defer s.Shutdown()
c := createClientConn(t, "127.0.0.1", PROTO_TEST_PORT)
defer c.Close()
send, expect := setupConn(t, c)
expectMsgs := expectMsgsCommand(t, expect)
send("SUB foo 22\r\n")
send("UNSUB 22 2\r\n")
for i := 0; i < 100; i++ {
send("PUB foo 2\r\nok\r\n")
}
time.Sleep(50 * time.Millisecond)
matches := expectMsgs(2)
checkMsg(t, matches[0], "foo", "22", "", "2", "ok")
checkMsg(t, matches[1], "foo", "22", "", "2", "ok")
}
func TestQueueSub(t *testing.T) {
s := runProtoServer()
defer s.Shutdown()
c := createClientConn(t, "127.0.0.1", PROTO_TEST_PORT)
defer c.Close()
send, expect := setupConn(t, c)
expectMsgs := expectMsgsCommand(t, expect)
sent := 100
send("SUB foo qgroup1 22\r\n")
send("SUB foo qgroup1 32\r\n")
for i := 0; i < sent; i++ {
send("PUB foo 2\r\nok\r\n")
}
// Wait for responses
time.Sleep(250 * time.Millisecond)
matches := expectMsgs(sent)
sids := make(map[string]int)
for _, m := range matches {
sids[string(m[sidIndex])]++
}
if len(sids) != 2 {
t.Fatalf("Expected only 2 sids, got %d\n", len(sids))
}
for k, c := range sids {
if c < 35 {
t.Fatalf("Expected ~50 (+-15) msgs for sid:'%s', got %d\n", k, c)
}
}
}
func TestMultipleQueueSub(t *testing.T) {
s := runProtoServer()
defer s.Shutdown()
c := createClientConn(t, "127.0.0.1", PROTO_TEST_PORT)
defer c.Close()
send, expect := setupConn(t, c)
expectMsgs := expectMsgsCommand(t, expect)
sent := 100
send("SUB foo g1 1\r\n")
send("SUB foo g1 2\r\n")
send("SUB foo g2 3\r\n")
send("SUB foo g2 4\r\n")
for i := 0; i < sent; i++ {
send("PUB foo 2\r\nok\r\n")
}
// Wait for responses
time.Sleep(250 * time.Millisecond)
matches := expectMsgs(sent * 2)
sids := make(map[string]int)
for _, m := range matches {
sids[string(m[sidIndex])]++
}
if len(sids) != 4 {
t.Fatalf("Expected 4 sids, got %d\n", len(sids))
}
for k, c := range sids {
if c < 35 {
t.Fatalf("Expected ~50 (+-15) msgs for '%s', got %d\n", k, c)
}
}
}
func TestPubToArgState(t *testing.T) {
s := runProtoServer()
defer s.Shutdown()
c := createClientConn(t, "127.0.0.1", PROTO_TEST_PORT)
defer c.Close()
send, expect := setupConn(t, c)
send("PUBS foo 2\r\nok\r\n")
expect(errRe)
}
func TestSubToArgState(t *testing.T) {
s := runProtoServer()
defer s.Shutdown()
c := createClientConn(t, "127.0.0.1", PROTO_TEST_PORT)
defer c.Close()
send, expect := setupConn(t, c)
send("SUBZZZ foo 1\r\n")
expect(errRe)
}
// Issue #63
func TestProtoCrash(t *testing.T) {
s := runProtoServer()
defer s.Shutdown()
c := createClientConn(t, "127.0.0.1", PROTO_TEST_PORT)
defer c.Close()
send, expect := sendCommand(t, c), expectCommand(t, c)
checkInfoMsg(t, c)
send("CONNECT {\"verbose\":true,\"tls_required\":false,\"user\":\"test\",\"pedantic\":true,\"pass\":\"password\"}")
time.Sleep(100 * time.Millisecond)
send("\r\n")
expect(okRe)
}
// Issue #136
func TestDuplicateProtoSub(t *testing.T) {
s := runProtoServer()
defer s.Shutdown()
c := createClientConn(t, "127.0.0.1", PROTO_TEST_PORT)
defer c.Close()
send, expect := setupConn(t, c)
send("PING\r\n")
expect(pongRe)
send("SUB foo 1\r\n")
send("SUB foo 1\r\n")
ns := 0
for i := 0; i < 5; i++ {
ns = int(s.NumSubscriptions())
if ns == 0 {
time.Sleep(50 * time.Millisecond)
} else {
break
}
}
if ns != 1 {
t.Fatalf("Expected 1 subscription, got %d\n", ns)
}
}
func TestIncompletePubArg(t *testing.T) {
s := runProtoServer()
defer s.Shutdown()
c := createClientConn(t, "127.0.0.1", PROTO_TEST_PORT)
defer c.Close()
send, expect := setupConn(t, c)
size := 10000
goodBuf := ""
for i := 0; i < size; i++ {
goodBuf += "A"
}
goodBuf += "\r\n"
badSize := 3371
badBuf := ""
for i := 0; i < badSize; i++ {
badBuf += "B"
}
// Message is corrupted and since we are still reading from client,
// next PUB accidentally becomes part of the payload of the
// incomplete message thus breaking the protocol.
badBuf2 := ""
for i := 0; i < size; i++ {
badBuf2 += "C"
}
badBuf2 += "\r\n"
pub := "PUB example 10000\r\n"
send(pub + goodBuf + pub + goodBuf + pub + badBuf + pub + badBuf2)
expect(errRe)
}
func TestControlLineMaximums(t *testing.T) {
s := runProtoServer()
defer s.Shutdown()
c := createClientConn(t, "127.0.0.1", PROTO_TEST_PORT)
defer c.Close()
send, expect := setupConn(t, c)
pubTooLong := "PUB foo "
for i := 0; i < 32; i++ {
pubTooLong += "2222222222"
}
send(pubTooLong)
expect(errRe)
}
func TestServerInfoWithClientAdvertise(t *testing.T) {
opts := DefaultTestOptions
opts.Port = PROTO_TEST_PORT
opts.ClientAdvertise = "me:1"
s := RunServer(&opts)
defer s.Shutdown()
c := createClientConn(t, opts.Host, PROTO_TEST_PORT)
defer c.Close()
buf := expectResult(t, c, infoRe)
js := infoRe.FindAllSubmatch(buf, 1)[0][1]
var sinfo server.Info
err := json.Unmarshal(js, &sinfo)
if err != nil {
t.Fatalf("Could not unmarshal INFO json: %v\n", err)
}
if sinfo.Host != "me" || sinfo.Port != 1 {
t.Fatalf("Expected INFO Host:Port to be me:1, got %s:%d", sinfo.Host, sinfo.Port)
}
}

View File

@ -0,0 +1,665 @@
// Copyright 2015-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package test
import (
"bufio"
"encoding/json"
"fmt"
"io/ioutil"
"net"
"net/http"
"runtime"
"strconv"
"strings"
"testing"
"time"
"github.com/nats-io/gnatsd/server"
)
func runSeedServer(t *testing.T) (*server.Server, *server.Options) {
return RunServerWithConfig("./configs/seed.conf")
}
func runAuthSeedServer(t *testing.T) (*server.Server, *server.Options) {
return RunServerWithConfig("./configs/auth_seed.conf")
}
func TestSeedFirstRouteInfo(t *testing.T) {
s, opts := runSeedServer(t)
defer s.Shutdown()
rc := createRouteConn(t, opts.Cluster.Host, opts.Cluster.Port)
defer rc.Close()
_, routeExpect := setupRoute(t, rc, opts)
buf := routeExpect(infoRe)
info := server.Info{}
if err := json.Unmarshal(buf[4:], &info); err != nil {
t.Fatalf("Could not unmarshal route info: %v", err)
}
if info.ID != s.ID() {
t.Fatalf("Expected seed's ID %q, got %q", s.ID(), info.ID)
}
}
func TestSeedMultipleRouteInfo(t *testing.T) {
s, opts := runSeedServer(t)
defer s.Shutdown()
rc1 := createRouteConn(t, opts.Cluster.Host, opts.Cluster.Port)
defer rc1.Close()
rc1ID := "2222"
rc1Port := 22
rc1Host := "127.0.0.1"
routeSend1, route1Expect := setupRouteEx(t, rc1, opts, rc1ID)
route1Expect(infoRe)
// register ourselves via INFO
r1Info := server.Info{ID: rc1ID, Host: rc1Host, Port: rc1Port}
b, _ := json.Marshal(r1Info)
infoJSON := fmt.Sprintf(server.InfoProto, b)
routeSend1(infoJSON)
routeSend1("PING\r\n")
route1Expect(pongRe)
rc2 := createRouteConn(t, opts.Cluster.Host, opts.Cluster.Port)
defer rc2.Close()
rc2ID := "2224"
rc2Port := 24
rc2Host := "127.0.0.1"
routeSend2, route2Expect := setupRouteEx(t, rc2, opts, rc2ID)
hp2 := fmt.Sprintf("nats-route://%s/", net.JoinHostPort(rc2Host, strconv.Itoa(rc2Port)))
// register ourselves via INFO
r2Info := server.Info{ID: rc2ID, Host: rc2Host, Port: rc2Port}
b, _ = json.Marshal(r2Info)
infoJSON = fmt.Sprintf(server.InfoProto, b)
routeSend2(infoJSON)
// Now read back the second INFO route1 should receive letting
// it know about route2
buf := route1Expect(infoRe)
info := server.Info{}
if err := json.Unmarshal(buf[4:], &info); err != nil {
t.Fatalf("Could not unmarshal route info: %v", err)
}
if info.ID != rc2ID {
t.Fatalf("Expected info.ID to be %q, got %q", rc2ID, info.ID)
}
if info.IP == "" {
t.Fatalf("Expected a IP for the implicit route")
}
if info.IP != hp2 {
t.Fatalf("Expected IP Host of %s, got %s\n", hp2, info.IP)
}
route2Expect(infoRe)
routeSend2("PING\r\n")
route2Expect(pongRe)
// Now let's do a third.
rc3 := createRouteConn(t, opts.Cluster.Host, opts.Cluster.Port)
defer rc3.Close()
rc3ID := "2226"
rc3Port := 26
rc3Host := "127.0.0.1"
routeSend3, _ := setupRouteEx(t, rc3, opts, rc3ID)
// register ourselves via INFO
r3Info := server.Info{ID: rc3ID, Host: rc3Host, Port: rc3Port}
b, _ = json.Marshal(r3Info)
infoJSON = fmt.Sprintf(server.InfoProto, b)
routeSend3(infoJSON)
// Now read back out the info from the seed route
buf = route1Expect(infoRe)
info = server.Info{}
if err := json.Unmarshal(buf[4:], &info); err != nil {
t.Fatalf("Could not unmarshal route info: %v", err)
}
if info.ID != rc3ID {
t.Fatalf("Expected info.ID to be %q, got %q", rc3ID, info.ID)
}
// Now read back out the info from the seed route
buf = route2Expect(infoRe)
info = server.Info{}
if err := json.Unmarshal(buf[4:], &info); err != nil {
t.Fatalf("Could not unmarshal route info: %v", err)
}
if info.ID != rc3ID {
t.Fatalf("Expected info.ID to be %q, got %q", rc3ID, info.ID)
}
}
func TestSeedSolicitWorks(t *testing.T) {
s1, opts := runSeedServer(t)
defer s1.Shutdown()
// Create the routes string for others to connect to the seed.
routesStr := fmt.Sprintf("nats-route://%s:%d/", opts.Cluster.Host, opts.Cluster.Port)
// Run Server #2
s2Opts := nextServerOpts(opts)
s2Opts.Routes = server.RoutesFromStr(routesStr)
s2 := RunServer(s2Opts)
defer s2.Shutdown()
// Run Server #3
s3Opts := nextServerOpts(s2Opts)
s3 := RunServer(s3Opts)
defer s3.Shutdown()
// Wait for a bit for graph to connect
time.Sleep(500 * time.Millisecond)
// Grab Routez from monitor ports, make sure we are fully connected
url := fmt.Sprintf("http://%s:%d/", opts.Host, opts.HTTPPort)
rz := readHTTPRoutez(t, url)
ris := expectRids(t, rz, []string{s2.ID(), s3.ID()})
if ris[s2.ID()].IsConfigured {
t.Fatalf("Expected server not to be configured\n")
}
if ris[s3.ID()].IsConfigured {
t.Fatalf("Expected server not to be configured\n")
}
url = fmt.Sprintf("http://%s:%d/", s2Opts.Host, s2Opts.HTTPPort)
rz = readHTTPRoutez(t, url)
ris = expectRids(t, rz, []string{s1.ID(), s3.ID()})
if !ris[s1.ID()].IsConfigured {
t.Fatalf("Expected seed server to be configured\n")
}
if ris[s3.ID()].IsConfigured {
t.Fatalf("Expected server not to be configured\n")
}
url = fmt.Sprintf("http://%s:%d/", s3Opts.Host, s3Opts.HTTPPort)
rz = readHTTPRoutez(t, url)
ris = expectRids(t, rz, []string{s1.ID(), s2.ID()})
if !ris[s1.ID()].IsConfigured {
t.Fatalf("Expected seed server to be configured\n")
}
if ris[s2.ID()].IsConfigured {
t.Fatalf("Expected server not to be configured\n")
}
}
type serverInfo struct {
server *server.Server
opts *server.Options
}
func checkConnected(t *testing.T, servers []serverInfo, current int, oneSeed bool) error {
s := servers[current]
// Grab Routez from monitor ports, make sure we are fully connected
url := fmt.Sprintf("http://%s:%d/", s.opts.Host, s.opts.HTTPPort)
rz := readHTTPRoutez(t, url)
total := len(servers)
var ids []string
for i := 0; i < total; i++ {
if i == current {
continue
}
ids = append(ids, servers[i].server.ID())
}
ris, err := expectRidsNoFatal(t, true, rz, ids)
if err != nil {
return err
}
for i := 0; i < total; i++ {
if i == current {
continue
}
s := servers[i]
if current == 0 || ((oneSeed && i > 0) || (!oneSeed && (i != current-1))) {
if ris[s.server.ID()].IsConfigured {
return fmt.Errorf("Expected server %s:%d not to be configured", s.opts.Host, s.opts.Port)
}
} else if oneSeed || (i == current-1) {
if !ris[s.server.ID()].IsConfigured {
return fmt.Errorf("Expected server %s:%d to be configured", s.opts.Host, s.opts.Port)
}
}
}
return nil
}
func TestStressSeedSolicitWorks(t *testing.T) {
s1, opts := runSeedServer(t)
defer s1.Shutdown()
// Create the routes string for others to connect to the seed.
routesStr := fmt.Sprintf("nats-route://%s:%d/", opts.Cluster.Host, opts.Cluster.Port)
s2Opts := nextServerOpts(opts)
s2Opts.Routes = server.RoutesFromStr(routesStr)
s3Opts := nextServerOpts(s2Opts)
s4Opts := nextServerOpts(s3Opts)
for i := 0; i < 10; i++ {
func() {
// Run these servers manually, because we want them to start and
// connect to s1 as fast as possible.
s2 := server.New(s2Opts)
if s2 == nil {
panic("No NATS Server object returned.")
}
defer s2.Shutdown()
go s2.Start()
s3 := server.New(s3Opts)
if s3 == nil {
panic("No NATS Server object returned.")
}
defer s3.Shutdown()
go s3.Start()
s4 := server.New(s4Opts)
if s4 == nil {
panic("No NATS Server object returned.")
}
defer s4.Shutdown()
go s4.Start()
serversInfo := []serverInfo{{s1, opts}, {s2, s2Opts}, {s3, s3Opts}, {s4, s4Opts}}
checkFor(t, 5*time.Second, 100*time.Millisecond, func() error {
for j := 0; j < len(serversInfo); j++ {
if err := checkConnected(t, serversInfo, j, true); err != nil {
return err
}
}
return nil
})
}()
checkNumRoutes(t, s1, 0)
}
}
func TestChainedSolicitWorks(t *testing.T) {
s1, opts := runSeedServer(t)
defer s1.Shutdown()
// Create the routes string for others to connect to the seed.
routesStr := fmt.Sprintf("nats-route://%s:%d/", opts.Cluster.Host, opts.Cluster.Port)
// Run Server #2
s2Opts := nextServerOpts(opts)
s2Opts.Routes = server.RoutesFromStr(routesStr)
s2 := RunServer(s2Opts)
defer s2.Shutdown()
// Run Server #3
s3Opts := nextServerOpts(s2Opts)
// We will have s3 connect to s2, not the seed.
routesStr = fmt.Sprintf("nats-route://%s:%d/", s2Opts.Cluster.Host, s2Opts.Cluster.Port)
s3Opts.Routes = server.RoutesFromStr(routesStr)
s3 := RunServer(s3Opts)
defer s3.Shutdown()
// Wait for a bit for graph to connect
time.Sleep(500 * time.Millisecond)
// Grab Routez from monitor ports, make sure we are fully connected
url := fmt.Sprintf("http://%s:%d/", opts.Host, opts.HTTPPort)
rz := readHTTPRoutez(t, url)
ris := expectRids(t, rz, []string{s2.ID(), s3.ID()})
if ris[s2.ID()].IsConfigured {
t.Fatalf("Expected server not to be configured\n")
}
if ris[s3.ID()].IsConfigured {
t.Fatalf("Expected server not to be configured\n")
}
url = fmt.Sprintf("http://%s:%d/", s2Opts.Host, s2Opts.HTTPPort)
rz = readHTTPRoutez(t, url)
ris = expectRids(t, rz, []string{s1.ID(), s3.ID()})
if !ris[s1.ID()].IsConfigured {
t.Fatalf("Expected seed server to be configured\n")
}
if ris[s3.ID()].IsConfigured {
t.Fatalf("Expected server not to be configured\n")
}
url = fmt.Sprintf("http://%s:%d/", s3Opts.Host, s3Opts.HTTPPort)
rz = readHTTPRoutez(t, url)
ris = expectRids(t, rz, []string{s1.ID(), s2.ID()})
if !ris[s2.ID()].IsConfigured {
t.Fatalf("Expected s2 server to be configured\n")
}
if ris[s1.ID()].IsConfigured {
t.Fatalf("Expected seed server not to be configured\n")
}
}
func TestStressChainedSolicitWorks(t *testing.T) {
s1, opts := runSeedServer(t)
defer s1.Shutdown()
// Create the routes string for s2 to connect to the seed
routesStr := fmt.Sprintf("nats-route://%s:%d/", opts.Cluster.Host, opts.Cluster.Port)
s2Opts := nextServerOpts(opts)
s2Opts.Routes = server.RoutesFromStr(routesStr)
s3Opts := nextServerOpts(s2Opts)
// Create the routes string for s3 to connect to s2
routesStr = fmt.Sprintf("nats-route://%s:%d/", s2Opts.Cluster.Host, s2Opts.Cluster.Port)
s3Opts.Routes = server.RoutesFromStr(routesStr)
s4Opts := nextServerOpts(s3Opts)
// Create the routes string for s4 to connect to s3
routesStr = fmt.Sprintf("nats-route://%s:%d/", s3Opts.Cluster.Host, s3Opts.Cluster.Port)
s4Opts.Routes = server.RoutesFromStr(routesStr)
for i := 0; i < 10; i++ {
func() {
// Run these servers manually, because we want them to start and
// connect to s1 as fast as possible.
s2 := server.New(s2Opts)
if s2 == nil {
panic("No NATS Server object returned.")
}
defer s2.Shutdown()
go s2.Start()
s3 := server.New(s3Opts)
if s3 == nil {
panic("No NATS Server object returned.")
}
defer s3.Shutdown()
go s3.Start()
s4 := server.New(s4Opts)
if s4 == nil {
panic("No NATS Server object returned.")
}
defer s4.Shutdown()
go s4.Start()
serversInfo := []serverInfo{{s1, opts}, {s2, s2Opts}, {s3, s3Opts}, {s4, s4Opts}}
checkFor(t, 5*time.Second, 100*time.Millisecond, func() error {
for j := 0; j < len(serversInfo); j++ {
if err := checkConnected(t, serversInfo, j, false); err != nil {
return err
}
}
return nil
})
}()
checkNumRoutes(t, s1, 0)
}
}
func TestAuthSeedSolicitWorks(t *testing.T) {
s1, opts := runAuthSeedServer(t)
defer s1.Shutdown()
// Create the routes string for others to connect to the seed.
routesStr := fmt.Sprintf("nats-route://%s:%s@%s:%d/", opts.Cluster.Username, opts.Cluster.Password, opts.Cluster.Host, opts.Cluster.Port)
// Run Server #2
s2Opts := nextServerOpts(opts)
s2Opts.Routes = server.RoutesFromStr(routesStr)
s2 := RunServer(s2Opts)
defer s2.Shutdown()
// Run Server #3
s3Opts := nextServerOpts(s2Opts)
s3 := RunServer(s3Opts)
defer s3.Shutdown()
// Wait for a bit for graph to connect
time.Sleep(500 * time.Millisecond)
// Grab Routez from monitor ports, make sure we are fully connected
url := fmt.Sprintf("http://%s:%d/", opts.Host, opts.HTTPPort)
rz := readHTTPRoutez(t, url)
ris := expectRids(t, rz, []string{s2.ID(), s3.ID()})
if ris[s2.ID()].IsConfigured {
t.Fatalf("Expected server not to be configured\n")
}
if ris[s3.ID()].IsConfigured {
t.Fatalf("Expected server not to be configured\n")
}
url = fmt.Sprintf("http://%s:%d/", s2Opts.Host, s2Opts.HTTPPort)
rz = readHTTPRoutez(t, url)
ris = expectRids(t, rz, []string{s1.ID(), s3.ID()})
if !ris[s1.ID()].IsConfigured {
t.Fatalf("Expected seed server to be configured\n")
}
if ris[s3.ID()].IsConfigured {
t.Fatalf("Expected server not to be configured\n")
}
url = fmt.Sprintf("http://%s:%d/", s3Opts.Host, s3Opts.HTTPPort)
rz = readHTTPRoutez(t, url)
ris = expectRids(t, rz, []string{s1.ID(), s2.ID()})
if !ris[s1.ID()].IsConfigured {
t.Fatalf("Expected seed server to be configured\n")
}
if ris[s2.ID()].IsConfigured {
t.Fatalf("Expected server not to be configured\n")
}
}
// Helper to check for correct route memberships
func expectRids(t *testing.T, rz *server.Routez, rids []string) map[string]*server.RouteInfo {
ri, err := expectRidsNoFatal(t, false, rz, rids)
if err != nil {
t.Fatalf("%v", err)
}
return ri
}
func expectRidsNoFatal(t *testing.T, direct bool, rz *server.Routez, rids []string) (map[string]*server.RouteInfo, error) {
caller := 1
if !direct {
caller++
}
if len(rids) != rz.NumRoutes {
_, fn, line, _ := runtime.Caller(caller)
return nil, fmt.Errorf("[%s:%d] Expecting %d routes, got %d\n", fn, line, len(rids), rz.NumRoutes)
}
set := make(map[string]bool)
for _, v := range rids {
set[v] = true
}
// Make result map for additional checking
ri := make(map[string]*server.RouteInfo)
for _, r := range rz.Routes {
if !set[r.RemoteID] {
_, fn, line, _ := runtime.Caller(caller)
return nil, fmt.Errorf("[%s:%d] Route with rid %s unexpected, expected %+v\n", fn, line, r.RemoteID, rids)
}
ri[r.RemoteID] = r
}
return ri, nil
}
// Helper to easily grab routez info.
func readHTTPRoutez(t *testing.T, url string) *server.Routez {
resetPreviousHTTPConnections()
resp, err := http.Get(url + "routez")
if err != nil {
stackFatalf(t, "Expected no error: Got %v\n", err)
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
stackFatalf(t, "Expected a 200 response, got %d\n", resp.StatusCode)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
stackFatalf(t, "Got an error reading the body: %v\n", err)
}
r := server.Routez{}
if err := json.Unmarshal(body, &r); err != nil {
stackFatalf(t, "Got an error unmarshalling the body: %v\n", err)
}
return &r
}
func TestSeedReturnIPInInfo(t *testing.T) {
s, opts := runSeedServer(t)
defer s.Shutdown()
rc1 := createRouteConn(t, opts.Cluster.Host, opts.Cluster.Port)
defer rc1.Close()
rc1ID := "2222"
rc1Port := 22
rc1Host := "127.0.0.1"
routeSend1, route1Expect := setupRouteEx(t, rc1, opts, rc1ID)
route1Expect(infoRe)
// register ourselves via INFO
r1Info := server.Info{ID: rc1ID, Host: rc1Host, Port: rc1Port}
b, _ := json.Marshal(r1Info)
infoJSON := fmt.Sprintf(server.InfoProto, b)
routeSend1(infoJSON)
routeSend1("PING\r\n")
route1Expect(pongRe)
rc2 := createRouteConn(t, opts.Cluster.Host, opts.Cluster.Port)
defer rc2.Close()
rc2ID := "2224"
rc2Port := 24
rc2Host := "127.0.0.1"
routeSend2, _ := setupRouteEx(t, rc2, opts, rc2ID)
// register ourselves via INFO
r2Info := server.Info{ID: rc2ID, Host: rc2Host, Port: rc2Port}
b, _ = json.Marshal(r2Info)
infoJSON = fmt.Sprintf(server.InfoProto, b)
routeSend2(infoJSON)
// Now read info that route1 should have received from the seed
buf := route1Expect(infoRe)
info := server.Info{}
if err := json.Unmarshal(buf[4:], &info); err != nil {
t.Fatalf("Could not unmarshal route info: %v", err)
}
if info.IP == "" {
t.Fatal("Expected to have IP in INFO")
}
rip, _, err := net.SplitHostPort(strings.TrimPrefix(info.IP, "nats-route://"))
if err != nil {
t.Fatalf("Error parsing url: %v", err)
}
addr, ok := rc1.RemoteAddr().(*net.TCPAddr)
if !ok {
t.Fatal("Unable to get IP address from route")
}
s1 := strings.ToLower(addr.IP.String())
s2 := strings.ToLower(rip)
if s1 != s2 {
t.Fatalf("Expected IP %s, got %s", s1, s2)
}
}
func TestImplicitRouteRetry(t *testing.T) {
srvSeed, optsSeed := runSeedServer(t)
defer srvSeed.Shutdown()
optsA := nextServerOpts(optsSeed)
optsA.Routes = server.RoutesFromStr(fmt.Sprintf("nats://%s:%d", optsSeed.Cluster.Host, optsSeed.Cluster.Port))
optsA.Cluster.ConnectRetries = 5
srvA := RunServer(optsA)
defer srvA.Shutdown()
optsB := nextServerOpts(optsA)
rcb := createRouteConn(t, optsSeed.Cluster.Host, optsSeed.Cluster.Port)
defer rcb.Close()
rcbID := "ServerB"
routeBSend, routeBExpect := setupRouteEx(t, rcb, optsB, rcbID)
routeBExpect(infoRe)
// register ourselves via INFO
rbInfo := server.Info{ID: rcbID, Host: optsB.Cluster.Host, Port: optsB.Cluster.Port}
b, _ := json.Marshal(rbInfo)
infoJSON := fmt.Sprintf(server.InfoProto, b)
routeBSend(infoJSON)
routeBSend("PING\r\n")
routeBExpect(pongRe)
// srvA should try to connect. Wait to make sure that it fails.
time.Sleep(1200 * time.Millisecond)
// Setup a fake route listen for routeB
rbListen, err := net.Listen("tcp", fmt.Sprintf("%s:%d", optsB.Cluster.Host, optsB.Cluster.Port))
if err != nil {
t.Fatalf("Error during listen: %v", err)
}
c, err := rbListen.Accept()
if err != nil {
t.Fatalf("Error during accept: %v", err)
}
defer c.Close()
br := bufio.NewReaderSize(c, 32768)
// Consume CONNECT and INFO
for i := 0; i < 2; i++ {
c.SetReadDeadline(time.Now().Add(2 * time.Second))
buf, _, err := br.ReadLine()
c.SetReadDeadline(time.Time{})
if err != nil {
t.Fatalf("Error reading: %v", err)
}
if i == 0 {
continue
}
buf = buf[len("INFO "):]
info := &server.Info{}
if err := json.Unmarshal(buf, info); err != nil {
t.Fatalf("Error during unmarshal: %v", err)
}
// Check INFO is from server A.
if info.ID != srvA.ID() {
t.Fatalf("Expected CONNECT from %v, got CONNECT from %v", srvA.ID(), info.ID)
}
}
}

1052
vendor/github.com/nats-io/gnatsd/test/routes_test.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

378
vendor/github.com/nats-io/gnatsd/test/test.go generated vendored Normal file
View File

@ -0,0 +1,378 @@
// Copyright 2012-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package test
import (
"crypto/rand"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"net"
"regexp"
"runtime"
"strings"
"time"
"github.com/nats-io/gnatsd/server"
)
// So we can pass tests and benchmarks..
type tLogger interface {
Fatalf(format string, args ...interface{})
Errorf(format string, args ...interface{})
}
// DefaultTestOptions are default options for the unit tests.
var DefaultTestOptions = server.Options{
Host: "127.0.0.1",
Port: 4222,
NoLog: true,
NoSigs: true,
MaxControlLine: 256,
}
// RunDefaultServer starts a new Go routine based server using the default options
func RunDefaultServer() *server.Server {
return RunServer(&DefaultTestOptions)
}
// RunServer starts a new Go routine based server
func RunServer(opts *server.Options) *server.Server {
if opts == nil {
opts = &DefaultTestOptions
}
s := server.New(opts)
if s == nil {
panic("No NATS Server object returned.")
}
// Run server in Go routine.
go s.Start()
// Wait for accept loop(s) to be started
if !s.ReadyForConnections(10 * time.Second) {
panic("Unable to start NATS Server in Go Routine")
}
return s
}
// LoadConfig loads a configuration from a filename
func LoadConfig(configFile string) (opts *server.Options) {
opts, err := server.ProcessConfigFile(configFile)
if err != nil {
panic(fmt.Sprintf("Error processing configuration file: %v", err))
}
opts.NoSigs, opts.NoLog = true, true
return
}
// RunServerWithConfig starts a new Go routine based server with a configuration file.
func RunServerWithConfig(configFile string) (srv *server.Server, opts *server.Options) {
opts = LoadConfig(configFile)
srv = RunServer(opts)
return
}
func stackFatalf(t tLogger, f string, args ...interface{}) {
lines := make([]string, 0, 32)
msg := fmt.Sprintf(f, args...)
lines = append(lines, msg)
// Ignore ourselves
_, testFile, _, _ := runtime.Caller(0)
// Generate the Stack of callers:
for i := 0; true; i++ {
_, file, line, ok := runtime.Caller(i)
if !ok {
break
}
if file == testFile {
continue
}
msg := fmt.Sprintf("%d - %s:%d", i, file, line)
lines = append(lines, msg)
}
t.Fatalf("%s", strings.Join(lines, "\n"))
}
func acceptRouteConn(t tLogger, host string, timeout time.Duration) net.Conn {
l, e := net.Listen("tcp", host)
if e != nil {
stackFatalf(t, "Error listening for route connection on %v: %v", host, e)
}
defer l.Close()
tl := l.(*net.TCPListener)
tl.SetDeadline(time.Now().Add(timeout))
conn, err := l.Accept()
tl.SetDeadline(time.Time{})
if err != nil {
stackFatalf(t, "Did not receive a route connection request: %v", err)
}
return conn
}
func createRouteConn(t tLogger, host string, port int) net.Conn {
return createClientConn(t, host, port)
}
func createClientConn(t tLogger, host string, port int) net.Conn {
addr := fmt.Sprintf("%s:%d", host, port)
c, err := net.DialTimeout("tcp", addr, 3*time.Second)
if err != nil {
stackFatalf(t, "Could not connect to server: %v\n", err)
}
return c
}
func checkSocket(t tLogger, addr string, wait time.Duration) {
end := time.Now().Add(wait)
for time.Now().Before(end) {
conn, err := net.Dial("tcp", addr)
if err != nil {
// Retry after 50ms
time.Sleep(50 * time.Millisecond)
continue
}
conn.Close()
// Wait a bit to give a chance to the server to remove this
// "client" from its state, which may otherwise interfere with
// some tests.
time.Sleep(25 * time.Millisecond)
return
}
// We have failed to bind the socket in the time allowed.
t.Fatalf("Failed to connect to the socket: %q", addr)
}
func checkInfoMsg(t tLogger, c net.Conn) server.Info {
buf := expectResult(t, c, infoRe)
js := infoRe.FindAllSubmatch(buf, 1)[0][1]
var sinfo server.Info
err := json.Unmarshal(js, &sinfo)
if err != nil {
stackFatalf(t, "Could not unmarshal INFO json: %v\n", err)
}
return sinfo
}
func doConnect(t tLogger, c net.Conn, verbose, pedantic, ssl bool) {
checkInfoMsg(t, c)
cs := fmt.Sprintf("CONNECT {\"verbose\":%v,\"pedantic\":%v,\"tls_required\":%v}\r\n", verbose, pedantic, ssl)
sendProto(t, c, cs)
}
func doDefaultConnect(t tLogger, c net.Conn) {
// Basic Connect
doConnect(t, c, false, false, false)
}
const connectProto = "CONNECT {\"verbose\":false,\"user\":\"%s\",\"pass\":\"%s\",\"name\":\"%s\"}\r\n"
func doRouteAuthConnect(t tLogger, c net.Conn, user, pass, id string) {
cs := fmt.Sprintf(connectProto, user, pass, id)
sendProto(t, c, cs)
}
func setupRouteEx(t tLogger, c net.Conn, opts *server.Options, id string) (sendFun, expectFun) {
user := opts.Cluster.Username
pass := opts.Cluster.Password
doRouteAuthConnect(t, c, user, pass, id)
return sendCommand(t, c), expectCommand(t, c)
}
func setupRoute(t tLogger, c net.Conn, opts *server.Options) (sendFun, expectFun) {
u := make([]byte, 16)
io.ReadFull(rand.Reader, u)
id := fmt.Sprintf("ROUTER:%s", hex.EncodeToString(u))
return setupRouteEx(t, c, opts, id)
}
func setupConn(t tLogger, c net.Conn) (sendFun, expectFun) {
doDefaultConnect(t, c)
return sendCommand(t, c), expectCommand(t, c)
}
func setupConnWithProto(t tLogger, c net.Conn, proto int) (sendFun, expectFun) {
checkInfoMsg(t, c)
cs := fmt.Sprintf("CONNECT {\"verbose\":%v,\"pedantic\":%v,\"tls_required\":%v,\"protocol\":%d}\r\n", false, false, false, proto)
sendProto(t, c, cs)
return sendCommand(t, c), expectCommand(t, c)
}
type sendFun func(string)
type expectFun func(*regexp.Regexp) []byte
// Closure version for easier reading
func sendCommand(t tLogger, c net.Conn) sendFun {
return func(op string) {
sendProto(t, c, op)
}
}
// Closure version for easier reading
func expectCommand(t tLogger, c net.Conn) expectFun {
return func(re *regexp.Regexp) []byte {
return expectResult(t, c, re)
}
}
// Send the protocol command to the server.
func sendProto(t tLogger, c net.Conn, op string) {
n, err := c.Write([]byte(op))
if err != nil {
stackFatalf(t, "Error writing command to conn: %v\n", err)
}
if n != len(op) {
stackFatalf(t, "Partial write: %d vs %d\n", n, len(op))
}
}
var (
infoRe = regexp.MustCompile(`INFO\s+([^\r\n]+)\r\n`)
pingRe = regexp.MustCompile(`PING\r\n`)
pongRe = regexp.MustCompile(`PONG\r\n`)
msgRe = regexp.MustCompile(`(?:(?:MSG\s+([^\s]+)\s+([^\s]+)\s+(([^\s]+)[^\S\r\n]+)?(\d+)\s*\r\n([^\\r\\n]*?)\r\n)+?)`)
okRe = regexp.MustCompile(`\A\+OK\r\n`)
errRe = regexp.MustCompile(`\A\-ERR\s+([^\r\n]+)\r\n`)
subRe = regexp.MustCompile(`SUB\s+([^\s]+)((\s+)([^\s]+))?\s+([^\s]+)\r\n`)
unsubRe = regexp.MustCompile(`UNSUB\s+([^\s]+)(\s+(\d+))?\r\n`)
connectRe = regexp.MustCompile(`CONNECT\s+([^\r\n]+)\r\n`)
)
const (
subIndex = 1
sidIndex = 2
replyIndex = 4
lenIndex = 5
msgIndex = 6
)
// Test result from server against regexp
func expectResult(t tLogger, c net.Conn, re *regexp.Regexp) []byte {
expBuf := make([]byte, 32768)
// Wait for commands to be processed and results queued for read
c.SetReadDeadline(time.Now().Add(5 * time.Second))
n, err := c.Read(expBuf)
c.SetReadDeadline(time.Time{})
if n <= 0 && err != nil {
stackFatalf(t, "Error reading from conn: %v\n", err)
}
buf := expBuf[:n]
if !re.Match(buf) {
stackFatalf(t, "Response did not match expected: \n\tReceived:'%q'\n\tExpected:'%s'\n", buf, re)
}
return buf
}
func expectNothing(t tLogger, c net.Conn) {
expBuf := make([]byte, 32)
c.SetReadDeadline(time.Now().Add(100 * time.Millisecond))
n, err := c.Read(expBuf)
c.SetReadDeadline(time.Time{})
if err == nil && n > 0 {
stackFatalf(t, "Expected nothing, received: '%q'\n", expBuf[:n])
}
}
// This will check that we got what we expected.
func checkMsg(t tLogger, m [][]byte, subject, sid, reply, len, msg string) {
if string(m[subIndex]) != subject {
stackFatalf(t, "Did not get correct subject: expected '%s' got '%s'\n", subject, m[subIndex])
}
if sid != "" && string(m[sidIndex]) != sid {
stackFatalf(t, "Did not get correct sid: expected '%s' got '%s'\n", sid, m[sidIndex])
}
if string(m[replyIndex]) != reply {
stackFatalf(t, "Did not get correct reply: expected '%s' got '%s'\n", reply, m[replyIndex])
}
if string(m[lenIndex]) != len {
stackFatalf(t, "Did not get correct msg length: expected '%s' got '%s'\n", len, m[lenIndex])
}
if string(m[msgIndex]) != msg {
stackFatalf(t, "Did not get correct msg: expected '%s' got '%s'\n", msg, m[msgIndex])
}
}
// Closure for expectMsgs
func expectMsgsCommand(t tLogger, ef expectFun) func(int) [][][]byte {
return func(expected int) [][][]byte {
buf := ef(msgRe)
matches := msgRe.FindAllSubmatch(buf, -1)
if len(matches) != expected {
stackFatalf(t, "Did not get correct # msgs: %d vs %d\n", len(matches), expected)
}
return matches
}
}
// This will check that the matches include at least one of the sids. Useful for checking
// that we received messages on a certain queue group.
func checkForQueueSid(t tLogger, matches [][][]byte, sids []string) {
seen := make(map[string]int, len(sids))
for _, sid := range sids {
seen[sid] = 0
}
for _, m := range matches {
sid := string(m[sidIndex])
if _, ok := seen[sid]; ok {
seen[sid]++
}
}
// Make sure we only see one and exactly one.
total := 0
for _, n := range seen {
total += n
}
if total != 1 {
stackFatalf(t, "Did not get a msg for queue sids group: expected 1 got %d\n", total)
}
}
// This will check that the matches include all of the sids. Useful for checking
// that we received messages on all subscribers.
func checkForPubSids(t tLogger, matches [][][]byte, sids []string) {
seen := make(map[string]int, len(sids))
for _, sid := range sids {
seen[sid] = 0
}
for _, m := range matches {
sid := string(m[sidIndex])
if _, ok := seen[sid]; ok {
seen[sid]++
}
}
// Make sure we only see one and exactly one for each sid.
for sid, n := range seen {
if n != 1 {
stackFatalf(t, "Did not get a msg for sid[%s]: expected 1 got %d\n", sid, n)
}
}
}
// Helper function to generate next opts to make sure no port conflicts etc.
func nextServerOpts(opts *server.Options) *server.Options {
nopts := opts.Clone()
nopts.Port++
nopts.Cluster.Port++
nopts.HTTPPort++
return nopts
}

72
vendor/github.com/nats-io/gnatsd/test/test_test.go generated vendored Normal file
View File

@ -0,0 +1,72 @@
// Copyright 2016-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package test
import (
"fmt"
"strings"
"sync"
"testing"
"time"
)
func checkFor(t *testing.T, totalWait, sleepDur time.Duration, f func() error) {
t.Helper()
timeout := time.Now().Add(totalWait)
var err error
for time.Now().Before(timeout) {
err = f()
if err == nil {
return
}
time.Sleep(sleepDur)
}
if err != nil {
t.Fatal(err.Error())
}
}
type dummyLogger struct {
sync.Mutex
msg string
}
func (d *dummyLogger) Fatalf(format string, args ...interface{}) {
d.Lock()
d.msg = fmt.Sprintf(format, args...)
d.Unlock()
}
func (d *dummyLogger) Errorf(format string, args ...interface{}) {
}
func (d *dummyLogger) Debugf(format string, args ...interface{}) {
}
func (d *dummyLogger) Tracef(format string, args ...interface{}) {
}
func (d *dummyLogger) Noticef(format string, args ...interface{}) {
}
func TestStackFatal(t *testing.T) {
d := &dummyLogger{}
stackFatalf(d, "test stack %d", 1)
if !strings.HasPrefix(d.msg, "test stack 1") {
t.Fatalf("Unexpected start of stack: %v", d.msg)
}
if !strings.Contains(d.msg, "test_test.go") {
t.Fatalf("Unexpected stack: %v", d.msg)
}
}

334
vendor/github.com/nats-io/gnatsd/test/tls_test.go generated vendored Normal file
View File

@ -0,0 +1,334 @@
// Copyright 2015-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package test
import (
"bufio"
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"net"
"strings"
"sync"
"testing"
"time"
"github.com/nats-io/gnatsd/server"
"github.com/nats-io/go-nats"
)
func TestTLSConnection(t *testing.T) {
srv, opts := RunServerWithConfig("./configs/tls.conf")
defer srv.Shutdown()
endpoint := fmt.Sprintf("%s:%d", opts.Host, opts.Port)
nurl := fmt.Sprintf("tls://%s:%s@%s/", opts.Username, opts.Password, endpoint)
nc, err := nats.Connect(nurl)
if err == nil {
nc.Close()
t.Fatalf("Expected error trying to connect to secure server")
}
// Do simple SecureConnect
nc, err = nats.Connect(fmt.Sprintf("tls://%s/", endpoint))
if err == nil {
nc.Close()
t.Fatalf("Expected error trying to connect to secure server with no auth")
}
// Now do more advanced checking, verifying servername and using rootCA.
nc, err = nats.Connect(nurl, nats.RootCAs("./configs/certs/ca.pem"))
if err != nil {
t.Fatalf("Got an error on Connect with Secure Options: %+v\n", err)
}
defer nc.Close()
subj := "foo-tls"
sub, _ := nc.SubscribeSync(subj)
nc.Publish(subj, []byte("We are Secure!"))
nc.Flush()
nmsgs, _ := sub.QueuedMsgs()
if nmsgs != 1 {
t.Fatalf("Expected to receive a message over the TLS connection")
}
}
func TestTLSClientCertificate(t *testing.T) {
srv, opts := RunServerWithConfig("./configs/tlsverify.conf")
defer srv.Shutdown()
nurl := fmt.Sprintf("tls://%s:%d", opts.Host, opts.Port)
_, err := nats.Connect(nurl)
if err == nil {
t.Fatalf("Expected error trying to connect to secure server without a certificate")
}
// Load client certificate to successfully connect.
certFile := "./configs/certs/client-cert.pem"
keyFile := "./configs/certs/client-key.pem"
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
t.Fatalf("error parsing X509 certificate/key pair: %v", err)
}
// Load in root CA for server verification
rootPEM, err := ioutil.ReadFile("./configs/certs/ca.pem")
if err != nil || rootPEM == nil {
t.Fatalf("failed to read root certificate")
}
pool := x509.NewCertPool()
ok := pool.AppendCertsFromPEM([]byte(rootPEM))
if !ok {
t.Fatalf("failed to parse root certificate")
}
config := &tls.Config{
Certificates: []tls.Certificate{cert},
ServerName: opts.Host,
RootCAs: pool,
MinVersion: tls.VersionTLS12,
}
copts := nats.GetDefaultOptions()
copts.Url = nurl
copts.Secure = true
copts.TLSConfig = config
nc, err := copts.Connect()
if err != nil {
t.Fatalf("Got an error on Connect with Secure Options: %+v\n", err)
}
nc.Flush()
defer nc.Close()
}
func TestTLSVerifyClientCertificate(t *testing.T) {
srv, opts := RunServerWithConfig("./configs/tlsverify_noca.conf")
defer srv.Shutdown()
nurl := fmt.Sprintf("tls://%s:%d", opts.Host, opts.Port)
// The client is configured properly, but the server has no CA
// to verify the client certificate. Connection should fail.
nc, err := nats.Connect(nurl,
nats.ClientCert("./configs/certs/client-cert.pem", "./configs/certs/client-key.pem"),
nats.RootCAs("./configs/certs/ca.pem"))
if err == nil {
nc.Close()
t.Fatal("Expected failure to connect, did not")
}
}
func TestTLSConnectionTimeout(t *testing.T) {
opts := LoadConfig("./configs/tls.conf")
opts.TLSTimeout = 0.25
srv := RunServer(opts)
defer srv.Shutdown()
// Dial with normal TCP
endpoint := fmt.Sprintf("%s:%d", opts.Host, opts.Port)
conn, err := net.Dial("tcp", endpoint)
if err != nil {
t.Fatalf("Could not connect to %q", endpoint)
}
defer conn.Close()
// Read deadlines
conn.SetReadDeadline(time.Now().Add(2 * time.Second))
// Read the INFO string.
br := bufio.NewReader(conn)
info, err := br.ReadString('\n')
if err != nil {
t.Fatalf("Failed to read INFO - %v", err)
}
if !strings.HasPrefix(info, "INFO ") {
t.Fatalf("INFO response incorrect: %s\n", info)
}
wait := time.Duration(opts.TLSTimeout * float64(time.Second))
time.Sleep(wait)
// Read deadlines
conn.SetReadDeadline(time.Now().Add(2 * time.Second))
tlsErr, err := br.ReadString('\n')
if err == nil && !strings.Contains(tlsErr, "-ERR 'Secure Connection - TLS Required") {
t.Fatalf("TLS Timeout response incorrect: %q\n", tlsErr)
}
}
// Ensure there is no race between authorization timeout and TLS handshake.
func TestTLSAuthorizationShortTimeout(t *testing.T) {
opts := LoadConfig("./configs/tls.conf")
opts.AuthTimeout = 0.001
srv := RunServer(opts)
defer srv.Shutdown()
endpoint := fmt.Sprintf("%s:%d", opts.Host, opts.Port)
nurl := fmt.Sprintf("tls://%s:%s@%s/", opts.Username, opts.Password, endpoint)
// Expect an error here (no CA) but not a TLS oversized record error which
// indicates the authorization timeout fired too soon.
_, err := nats.Connect(nurl)
if err == nil {
t.Fatal("Expected error trying to connect to secure server")
}
if strings.Contains(err.Error(), "oversized record") {
t.Fatal("Corrupted TLS handshake:", err)
}
}
func stressConnect(t *testing.T, wg *sync.WaitGroup, errCh chan error, url string, index int) {
defer wg.Done()
subName := fmt.Sprintf("foo.%d", index)
for i := 0; i < 33; i++ {
nc, err := nats.Connect(url, nats.RootCAs("./configs/certs/ca.pem"))
if err != nil {
errCh <- fmt.Errorf("Unable to create TLS connection: %v\n", err)
return
}
defer nc.Close()
sub, err := nc.SubscribeSync(subName)
if err != nil {
errCh <- fmt.Errorf("Unable to subscribe on '%s': %v\n", subName, err)
return
}
if err := nc.Publish(subName, []byte("secure data")); err != nil {
errCh <- fmt.Errorf("Unable to send on '%s': %v\n", subName, err)
}
if _, err := sub.NextMsg(2 * time.Second); err != nil {
errCh <- fmt.Errorf("Unable to get next message: %v\n", err)
}
nc.Close()
}
errCh <- nil
}
func TestTLSStressConnect(t *testing.T) {
opts, err := server.ProcessConfigFile("./configs/tls.conf")
if err != nil {
panic(fmt.Sprintf("Error processing configuration file: %v", err))
}
opts.NoSigs, opts.NoLog = true, true
// For this test, remove the authorization
opts.Username = ""
opts.Password = ""
// Increase ssl timeout
opts.TLSTimeout = 2.0
srv := RunServer(opts)
defer srv.Shutdown()
nurl := fmt.Sprintf("tls://%s:%d", opts.Host, opts.Port)
threadCount := 3
errCh := make(chan error, threadCount)
var wg sync.WaitGroup
wg.Add(threadCount)
for i := 0; i < threadCount; i++ {
go stressConnect(t, &wg, errCh, nurl, i)
}
wg.Wait()
var lastError error
for i := 0; i < threadCount; i++ {
err := <-errCh
if err != nil {
lastError = err
}
}
if lastError != nil {
t.Fatalf("%v\n", lastError)
}
}
func TestTLSBadAuthError(t *testing.T) {
srv, opts := RunServerWithConfig("./configs/tls.conf")
defer srv.Shutdown()
endpoint := fmt.Sprintf("%s:%d", opts.Host, opts.Port)
nurl := fmt.Sprintf("tls://%s:%s@%s/", opts.Username, "NOT_THE_PASSWORD", endpoint)
_, err := nats.Connect(nurl, nats.RootCAs("./configs/certs/ca.pem"))
if err == nil {
t.Fatalf("Expected error trying to connect to secure server")
}
if err.Error() != nats.ErrAuthorization.Error() {
t.Fatalf("Excpected and auth violation, got %v\n", err)
}
}
func TestTLSConnectionCurvePref(t *testing.T) {
srv, opts := RunServerWithConfig("./configs/tls_curve_pref.conf")
defer srv.Shutdown()
if len(opts.TLSConfig.CurvePreferences) != 1 {
t.Fatal("Invalid curve preference loaded.")
}
if opts.TLSConfig.CurvePreferences[0] != tls.CurveP256 {
t.Fatalf("Invalid curve preference loaded [%v].", opts.TLSConfig.CurvePreferences[0])
}
endpoint := fmt.Sprintf("%s:%d", opts.Host, opts.Port)
nurl := fmt.Sprintf("tls://%s:%s@%s/", opts.Username, opts.Password, endpoint)
nc, err := nats.Connect(nurl)
if err == nil {
nc.Close()
t.Fatalf("Expected error trying to connect to secure server")
}
// Do simple SecureConnect
nc, err = nats.Connect(fmt.Sprintf("tls://%s/", endpoint))
if err == nil {
nc.Close()
t.Fatalf("Expected error trying to connect to secure server with no auth")
}
// Now do more advanced checking, verifying servername and using rootCA.
nc, err = nats.Connect(nurl, nats.RootCAs("./configs/certs/ca.pem"))
if err != nil {
t.Fatalf("Got an error on Connect with Secure Options: %+v\n", err)
}
defer nc.Close()
subj := "foo-tls"
sub, _ := nc.SubscribeSync(subj)
nc.Publish(subj, []byte("We are Secure!"))
nc.Flush()
nmsgs, _ := sub.QueuedMsgs()
if nmsgs != 1 {
t.Fatalf("Expected to receive a message over the TLS connection")
}
}

View File

@ -0,0 +1,101 @@
// Copyright 2016-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package test
import (
"regexp"
"testing"
)
const DefaultPass = "foo"
var permErrRe = regexp.MustCompile(`\A\-ERR\s+'Permissions Violation([^\r\n]+)\r\n`)
func TestUserAuthorizationProto(t *testing.T) {
srv, opts := RunServerWithConfig("./configs/authorization.conf")
defer srv.Shutdown()
// Alice can do anything, check a few for OK result.
c := createClientConn(t, opts.Host, opts.Port)
defer c.Close()
expectAuthRequired(t, c)
doAuthConnect(t, c, "", "alice", DefaultPass)
expectResult(t, c, okRe)
sendProto(t, c, "PUB foo 2\r\nok\r\n")
expectResult(t, c, okRe)
sendProto(t, c, "SUB foo 1\r\n")
expectResult(t, c, okRe)
// Check that we now reserve _SYS.> though for internal, so no clients.
sendProto(t, c, "PUB _SYS.HB 2\r\nok\r\n")
expectResult(t, c, permErrRe)
// Check that _ is ok
sendProto(t, c, "PUB _ 2\r\nok\r\n")
expectResult(t, c, okRe)
c.Close()
// Bob is a requestor only, e.g. req.foo, req.bar for publish, subscribe only to INBOXes.
c = createClientConn(t, opts.Host, opts.Port)
defer c.Close()
expectAuthRequired(t, c)
doAuthConnect(t, c, "", "bob", DefaultPass)
expectResult(t, c, okRe)
// These should error.
sendProto(t, c, "SUB foo 1\r\n")
expectResult(t, c, permErrRe)
sendProto(t, c, "PUB foo 2\r\nok\r\n")
expectResult(t, c, permErrRe)
// These should work ok.
sendProto(t, c, "SUB _INBOX.abcd 1\r\n")
expectResult(t, c, okRe)
sendProto(t, c, "PUB req.foo 2\r\nok\r\n")
expectResult(t, c, okRe)
sendProto(t, c, "PUB req.bar 2\r\nok\r\n")
expectResult(t, c, okRe)
c.Close()
// Joe is a default user
c = createClientConn(t, opts.Host, opts.Port)
defer c.Close()
expectAuthRequired(t, c)
doAuthConnect(t, c, "", "joe", DefaultPass)
expectResult(t, c, okRe)
// These should error.
sendProto(t, c, "SUB foo.bar.* 1\r\n")
expectResult(t, c, permErrRe)
sendProto(t, c, "PUB foo.bar.baz 2\r\nok\r\n")
expectResult(t, c, permErrRe)
// These should work ok.
sendProto(t, c, "SUB _INBOX.abcd 1\r\n")
expectResult(t, c, okRe)
sendProto(t, c, "SUB PUBLIC.abcd 1\r\n")
expectResult(t, c, okRe)
sendProto(t, c, "PUB SANDBOX.foo 2\r\nok\r\n")
expectResult(t, c, okRe)
sendProto(t, c, "PUB SANDBOX.bar 2\r\nok\r\n")
expectResult(t, c, okRe)
// Since only PWC, this should fail (too many tokens).
sendProto(t, c, "PUB SANDBOX.foo.bar 2\r\nok\r\n")
expectResult(t, c, permErrRe)
c.Close()
}

82
vendor/github.com/nats-io/gnatsd/test/verbose_test.go generated vendored Normal file
View File

@ -0,0 +1,82 @@
// Copyright 2012-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package test
import (
"testing"
)
func TestVerbosePing(t *testing.T) {
s := runProtoServer()
defer s.Shutdown()
c := createClientConn(t, "127.0.0.1", PROTO_TEST_PORT)
defer c.Close()
doConnect(t, c, true, false, false)
send := sendCommand(t, c)
expect := expectCommand(t, c)
expect(okRe)
// Ping should still be same
send("PING\r\n")
expect(pongRe)
}
func TestVerboseConnect(t *testing.T) {
s := runProtoServer()
defer s.Shutdown()
c := createClientConn(t, "127.0.0.1", PROTO_TEST_PORT)
defer c.Close()
doConnect(t, c, true, false, false)
send := sendCommand(t, c)
expect := expectCommand(t, c)
expect(okRe)
// Connect
send("CONNECT {\"verbose\":true,\"pedantic\":true,\"tls_required\":false}\r\n")
expect(okRe)
}
func TestVerbosePubSub(t *testing.T) {
s := runProtoServer()
defer s.Shutdown()
c := createClientConn(t, "127.0.0.1", PROTO_TEST_PORT)
defer c.Close()
doConnect(t, c, true, false, false)
send := sendCommand(t, c)
expect := expectCommand(t, c)
expect(okRe)
// Pub
send("PUB foo 2\r\nok\r\n")
expect(okRe)
// Sub
send("SUB foo 1\r\n")
expect(okRe)
// UnSub
send("UNSUB 1\r\n")
expect(okRe)
}

16
vendor/github.com/nats-io/gnatsd/util/gnatsd.service generated vendored Normal file
View File

@ -0,0 +1,16 @@
[Unit]
Description=NATS messaging server
After=network.target
[Service]
PrivateTmp=true
Type=simple
ExecStart=/usr/sbin/gnatsd -c /etc/gnatsd.conf
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s SIGINT $MAINPID
User=gnatsd
Group=gnatsd
[Install]
WantedBy=multi-user.target

25
vendor/github.com/nats-io/gnatsd/util/tls.go generated vendored Normal file
View File

@ -0,0 +1,25 @@
// Copyright 2017-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build go1.8
package util
import (
"crypto/tls"
)
// CloneTLSConfig returns a copy of c.
func CloneTLSConfig(c *tls.Config) *tls.Config {
return c.Clone()
}

47
vendor/github.com/nats-io/gnatsd/util/tls_pre17.go generated vendored Normal file
View File

@ -0,0 +1,47 @@
// Copyright 2017-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build go1.5,!go1.7
package util
import (
"crypto/tls"
)
// CloneTLSConfig returns a copy of c. Only the exported fields are copied.
// This is temporary, until this is provided by the language.
// https://go-review.googlesource.com/#/c/28075/
func CloneTLSConfig(c *tls.Config) *tls.Config {
return &tls.Config{
Rand: c.Rand,
Time: c.Time,
Certificates: c.Certificates,
NameToCertificate: c.NameToCertificate,
GetCertificate: c.GetCertificate,
RootCAs: c.RootCAs,
NextProtos: c.NextProtos,
ServerName: c.ServerName,
ClientAuth: c.ClientAuth,
ClientCAs: c.ClientCAs,
InsecureSkipVerify: c.InsecureSkipVerify,
CipherSuites: c.CipherSuites,
PreferServerCipherSuites: c.PreferServerCipherSuites,
SessionTicketsDisabled: c.SessionTicketsDisabled,
SessionTicketKey: c.SessionTicketKey,
ClientSessionCache: c.ClientSessionCache,
MinVersion: c.MinVersion,
MaxVersion: c.MaxVersion,
CurvePreferences: c.CurvePreferences,
}
}

49
vendor/github.com/nats-io/gnatsd/util/tls_pre18.go generated vendored Normal file
View File

@ -0,0 +1,49 @@
// Copyright 2017-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build go1.7,!go1.8
package util
import (
"crypto/tls"
)
// CloneTLSConfig returns a copy of c. Only the exported fields are copied.
// This is temporary, until this is provided by the language.
// https://go-review.googlesource.com/#/c/28075/
func CloneTLSConfig(c *tls.Config) *tls.Config {
return &tls.Config{
Rand: c.Rand,
Time: c.Time,
Certificates: c.Certificates,
NameToCertificate: c.NameToCertificate,
GetCertificate: c.GetCertificate,
RootCAs: c.RootCAs,
NextProtos: c.NextProtos,
ServerName: c.ServerName,
ClientAuth: c.ClientAuth,
ClientCAs: c.ClientCAs,
InsecureSkipVerify: c.InsecureSkipVerify,
CipherSuites: c.CipherSuites,
PreferServerCipherSuites: c.PreferServerCipherSuites,
SessionTicketsDisabled: c.SessionTicketsDisabled,
SessionTicketKey: c.SessionTicketKey,
ClientSessionCache: c.ClientSessionCache,
MinVersion: c.MinVersion,
MaxVersion: c.MaxVersion,
CurvePreferences: c.CurvePreferences,
DynamicRecordSizingDisabled: c.DynamicRecordSizingDisabled,
Renegotiation: c.Renegotiation,
}
}

3
vendor/github.com/nats-io/go-nats/GOVERNANCE.md generated vendored Normal file
View File

@ -0,0 +1,3 @@
# NATS Go Client Governance
NATS Go Client (go-nats) is part of the NATS project and is subject to the [NATS Governance](https://github.com/nats-io/nats-general/blob/master/GOVERNANCE.md).

201
vendor/github.com/nats-io/go-nats/LICENSE generated vendored Normal file
View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

10
vendor/github.com/nats-io/go-nats/MAINTAINERS.md generated vendored Normal file
View File

@ -0,0 +1,10 @@
# Maintainers
Maintainership is on a per project basis.
### Core-maintainers
- Derek Collison <derek@nats.io> [@derekcollison](https://github.com/derekcollison)
- Ivan Kozlovic <ivan@nats.io> [@kozlovic](https://github.com/kozlovic)
### Maintainers
- Waldemar Quevedo <wally@nats.io> [@wallyqs](https://github.com/wallyqs)

334
vendor/github.com/nats-io/go-nats/README.md generated vendored Normal file
View File

@ -0,0 +1,334 @@
# NATS - Go Client
A [Go](http://golang.org) client for the [NATS messaging system](https://nats.io).
[![License Apache 2](https://img.shields.io/badge/License-Apache2-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0)
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fnats-io%2Fgo-nats.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fnats-io%2Fgo-nats?ref=badge_shield)
[![Go Report Card](https://goreportcard.com/badge/github.com/nats-io/go-nats)](https://goreportcard.com/report/github.com/nats-io/go-nats) [![Build Status](https://travis-ci.org/nats-io/go-nats.svg?branch=master)](http://travis-ci.org/nats-io/go-nats) [![GoDoc](https://godoc.org/github.com/nats-io/go-nats?status.svg)](http://godoc.org/github.com/nats-io/go-nats) [![Coverage Status](https://coveralls.io/repos/nats-io/go-nats/badge.svg?branch=master)](https://coveralls.io/r/nats-io/go-nats?branch=master)
## Installation
```bash
# Go client
go get github.com/nats-io/go-nats
# Server
go get github.com/nats-io/gnatsd
```
## Basic Usage
```go
nc, _ := nats.Connect(nats.DefaultURL)
// Simple Publisher
nc.Publish("foo", []byte("Hello World"))
// Simple Async Subscriber
nc.Subscribe("foo", func(m *nats.Msg) {
fmt.Printf("Received a message: %s\n", string(m.Data))
})
// Simple Sync Subscriber
sub, err := nc.SubscribeSync("foo")
m, err := sub.NextMsg(timeout)
// Channel Subscriber
ch := make(chan *nats.Msg, 64)
sub, err := nc.ChanSubscribe("foo", ch)
msg := <- ch
// Unsubscribe
sub.Unsubscribe()
// Requests
msg, err := nc.Request("help", []byte("help me"), 10*time.Millisecond)
// Replies
nc.Subscribe("help", func(m *Msg) {
nc.Publish(m.Reply, []byte("I can help!"))
})
// Close connection
nc, _ := nats.Connect("nats://localhost:4222")
nc.Close();
```
## Encoded Connections
```go
nc, _ := nats.Connect(nats.DefaultURL)
c, _ := nats.NewEncodedConn(nc, nats.JSON_ENCODER)
defer c.Close()
// Simple Publisher
c.Publish("foo", "Hello World")
// Simple Async Subscriber
c.Subscribe("foo", func(s string) {
fmt.Printf("Received a message: %s\n", s)
})
// EncodedConn can Publish any raw Go type using the registered Encoder
type person struct {
Name string
Address string
Age int
}
// Go type Subscriber
c.Subscribe("hello", func(p *person) {
fmt.Printf("Received a person: %+v\n", p)
})
me := &person{Name: "derek", Age: 22, Address: "140 New Montgomery Street, San Francisco, CA"}
// Go type Publisher
c.Publish("hello", me)
// Unsubscribe
sub, err := c.Subscribe("foo", nil)
...
sub.Unsubscribe()
// Requests
var response string
err := c.Request("help", "help me", &response, 10*time.Millisecond)
if err != nil {
fmt.Printf("Request failed: %v\n", err)
}
// Replying
c.Subscribe("help", func(subj, reply string, msg string) {
c.Publish(reply, "I can help!")
})
// Close connection
c.Close();
```
## TLS
```go
// tls as a scheme will enable secure connections by default. This will also verify the server name.
nc, err := nats.Connect("tls://nats.demo.io:4443")
// If you are using a self-signed certificate, you need to have a tls.Config with RootCAs setup.
// We provide a helper method to make this case easier.
nc, err = nats.Connect("tls://localhost:4443", nats.RootCAs("./configs/certs/ca.pem"))
// If the server requires client certificate, there is an helper function for that too:
cert := nats.ClientCert("./configs/certs/client-cert.pem", "./configs/certs/client-key.pem")
nc, err = nats.Connect("tls://localhost:4443", cert)
// You can also supply a complete tls.Config
certFile := "./configs/certs/client-cert.pem"
keyFile := "./configs/certs/client-key.pem"
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
t.Fatalf("error parsing X509 certificate/key pair: %v", err)
}
config := &tls.Config{
ServerName: opts.Host,
Certificates: []tls.Certificate{cert},
RootCAs: pool,
MinVersion: tls.VersionTLS12,
}
nc, err = nats.Connect("nats://localhost:4443", nats.Secure(config))
if err != nil {
t.Fatalf("Got an error on Connect with Secure Options: %+v\n", err)
}
```
## Using Go Channels (netchan)
```go
nc, _ := nats.Connect(nats.DefaultURL)
ec, _ := nats.NewEncodedConn(nc, nats.JSON_ENCODER)
defer ec.Close()
type person struct {
Name string
Address string
Age int
}
recvCh := make(chan *person)
ec.BindRecvChan("hello", recvCh)
sendCh := make(chan *person)
ec.BindSendChan("hello", sendCh)
me := &person{Name: "derek", Age: 22, Address: "140 New Montgomery Street"}
// Send via Go channels
sendCh <- me
// Receive via Go channels
who := <- recvCh
```
## Wildcard Subscriptions
```go
// "*" matches any token, at any level of the subject.
nc.Subscribe("foo.*.baz", func(m *Msg) {
fmt.Printf("Msg received on [%s] : %s\n", m.Subject, string(m.Data));
})
nc.Subscribe("foo.bar.*", func(m *Msg) {
fmt.Printf("Msg received on [%s] : %s\n", m.Subject, string(m.Data));
})
// ">" matches any length of the tail of a subject, and can only be the last token
// E.g. 'foo.>' will match 'foo.bar', 'foo.bar.baz', 'foo.foo.bar.bax.22'
nc.Subscribe("foo.>", func(m *Msg) {
fmt.Printf("Msg received on [%s] : %s\n", m.Subject, string(m.Data));
})
// Matches all of the above
nc.Publish("foo.bar.baz", []byte("Hello World"))
```
## Queue Groups
```go
// All subscriptions with the same queue name will form a queue group.
// Each message will be delivered to only one subscriber per queue group,
// using queuing semantics. You can have as many queue groups as you wish.
// Normal subscribers will continue to work as expected.
nc.QueueSubscribe("foo", "job_workers", func(_ *Msg) {
received += 1;
})
```
## Advanced Usage
```go
// Flush connection to server, returns when all messages have been processed.
nc.Flush()
fmt.Println("All clear!")
// FlushTimeout specifies a timeout value as well.
err := nc.FlushTimeout(1*time.Second)
if err != nil {
fmt.Println("All clear!")
} else {
fmt.Println("Flushed timed out!")
}
// Auto-unsubscribe after MAX_WANTED messages received
const MAX_WANTED = 10
sub, err := nc.Subscribe("foo")
sub.AutoUnsubscribe(MAX_WANTED)
// Multiple connections
nc1 := nats.Connect("nats://host1:4222")
nc2 := nats.Connect("nats://host2:4222")
nc1.Subscribe("foo", func(m *Msg) {
fmt.Printf("Received a message: %s\n", string(m.Data))
})
nc2.Publish("foo", []byte("Hello World!"));
```
## Clustered Usage
```go
var servers = "nats://localhost:1222, nats://localhost:1223, nats://localhost:1224"
nc, err := nats.Connect(servers)
// Optionally set ReconnectWait and MaxReconnect attempts.
// This example means 10 seconds total per backend.
nc, err = nats.Connect(servers, nats.MaxReconnects(5), nats.ReconnectWait(2 * time.Second))
// Optionally disable randomization of the server pool
nc, err = nats.Connect(servers, nats.DontRandomize())
// Setup callbacks to be notified on disconnects, reconnects and connection closed.
nc, err = nats.Connect(servers,
nats.DisconnectHandler(func(nc *nats.Conn) {
fmt.Printf("Got disconnected!\n")
}),
nats.ReconnectHandler(func(_ *nats.Conn) {
fmt.Printf("Got reconnected to %v!\n", nc.ConnectedUrl())
}),
nats.ClosedHandler(func(nc *nats.Conn) {
fmt.Printf("Connection closed. Reason: %q\n", nc.LastError())
})
)
// When connecting to a mesh of servers with auto-discovery capabilities,
// you may need to provide a username/password or token in order to connect
// to any server in that mesh when authentication is required.
// Instead of providing the credentials in the initial URL, you will use
// new option setters:
nc, err = nats.Connect("nats://localhost:4222", nats.UserInfo("foo", "bar"))
// For token based authentication:
nc, err = nats.Connect("nats://localhost:4222", nats.Token("S3cretT0ken"))
// You can even pass the two at the same time in case one of the server
// in the mesh requires token instead of user name and password.
nc, err = nats.Connect("nats://localhost:4222",
nats.UserInfo("foo", "bar"),
nats.Token("S3cretT0ken"))
// Note that if credentials are specified in the initial URLs, they take
// precedence on the credentials specfied through the options.
// For instance, in the connect call below, the client library will use
// the user "my" and password "pwd" to connect to locahost:4222, however,
// it will use username "foo" and password "bar" when (re)connecting to
// a different server URL that it got as part of the auto-discovery.
nc, err = nats.Connect("nats://my:pwd@localhost:4222", nats.UserInfo("foo", "bar"))
```
## Context support (+Go 1.7)
```go
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
nc, err := nats.Connect(nats.DefaultURL)
// Request with context
msg, err := nc.RequestWithContext(ctx, "foo", []byte("bar"))
// Synchronous subscriber with context
sub, err := nc.SubscribeSync("foo")
msg, err := sub.NextMsgWithContext(ctx)
// Encoded Request with context
c, err := nats.NewEncodedConn(nc, nats.JSON_ENCODER)
type request struct {
Message string `json:"message"`
}
type response struct {
Code int `json:"code"`
}
req := &request{Message: "Hello"}
resp := &response{}
err := c.RequestWithContext(ctx, "foo", req, resp)
```
## License
Unless otherwise noted, the NATS source files are distributed
under the Apache Version 2.0 license found in the LICENSE file.
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fnats-io%2Fgo-nats.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fnats-io%2Fgo-nats?ref=badge_large)

26
vendor/github.com/nats-io/go-nats/TODO.md generated vendored Normal file
View File

@ -0,0 +1,26 @@
- [ ] Better constructors, options handling
- [ ] Functions for callback settings after connection created.
- [ ] Better options for subscriptions. Slow Consumer state settable, Go routines vs Inline.
- [ ] Move off of channels for subscribers, use syncPool linkedLists, etc with highwater.
- [ ] Test for valid subjects on publish and subscribe?
- [ ] SyncSubscriber and Next for EncodedConn
- [ ] Fast Publisher?
- [ ] pooling for structs used? leaky bucket?
- [ ] Timeout 0 should work as no timeout
- [x] Ping timer
- [x] Name in Connect for gnatsd
- [x] Asynchronous error handling
- [x] Parser rewrite
- [x] Reconnect
- [x] Hide Lock
- [x] Easier encoder interface
- [x] QueueSubscribeSync
- [x] Make nats specific errors prefixed with 'nats:'
- [x] API test for closed connection
- [x] TLS/SSL
- [x] Stats collection
- [x] Disconnect detection
- [x] Optimized Publish (coalescing)
- [x] Do Examples via Go style
- [x] Standardized Errors

177
vendor/github.com/nats-io/go-nats/context.go generated vendored Normal file
View File

@ -0,0 +1,177 @@
// Copyright 2016-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build go1.7
// A Go client for the NATS messaging system (https://nats.io).
package nats
import (
"context"
"fmt"
"reflect"
)
// RequestWithContext takes a context, a subject and payload
// in bytes and request expecting a single response.
func (nc *Conn) RequestWithContext(ctx context.Context, subj string, data []byte) (*Msg, error) {
if ctx == nil {
return nil, ErrInvalidContext
}
if nc == nil {
return nil, ErrInvalidConnection
}
nc.mu.Lock()
// If user wants the old style.
if nc.Opts.UseOldRequestStyle {
nc.mu.Unlock()
return nc.oldRequestWithContext(ctx, subj, data)
}
// Do setup for the new style.
if nc.respMap == nil {
// _INBOX wildcard
nc.respSub = fmt.Sprintf("%s.*", NewInbox())
nc.respMap = make(map[string]chan *Msg)
}
// Create literal Inbox and map to a chan msg.
mch := make(chan *Msg, RequestChanLen)
respInbox := nc.newRespInbox()
token := respToken(respInbox)
nc.respMap[token] = mch
createSub := nc.respMux == nil
ginbox := nc.respSub
nc.mu.Unlock()
if createSub {
// Make sure scoped subscription is setup only once.
var err error
nc.respSetup.Do(func() { err = nc.createRespMux(ginbox) })
if err != nil {
return nil, err
}
}
err := nc.PublishRequest(subj, respInbox, data)
if err != nil {
return nil, err
}
var ok bool
var msg *Msg
select {
case msg, ok = <-mch:
if !ok {
return nil, ErrConnectionClosed
}
case <-ctx.Done():
nc.mu.Lock()
delete(nc.respMap, token)
nc.mu.Unlock()
return nil, ctx.Err()
}
return msg, nil
}
// oldRequestWithContext utilizes inbox and subscription per request.
func (nc *Conn) oldRequestWithContext(ctx context.Context, subj string, data []byte) (*Msg, error) {
inbox := NewInbox()
ch := make(chan *Msg, RequestChanLen)
s, err := nc.subscribe(inbox, _EMPTY_, nil, ch)
if err != nil {
return nil, err
}
s.AutoUnsubscribe(1)
defer s.Unsubscribe()
err = nc.PublishRequest(subj, inbox, data)
if err != nil {
return nil, err
}
return s.NextMsgWithContext(ctx)
}
// NextMsgWithContext takes a context and returns the next message
// available to a synchronous subscriber, blocking until it is delivered
// or context gets canceled.
func (s *Subscription) NextMsgWithContext(ctx context.Context) (*Msg, error) {
if ctx == nil {
return nil, ErrInvalidContext
}
if s == nil {
return nil, ErrBadSubscription
}
s.mu.Lock()
err := s.validateNextMsgState()
if err != nil {
s.mu.Unlock()
return nil, err
}
// snapshot
mch := s.mch
s.mu.Unlock()
var ok bool
var msg *Msg
select {
case msg, ok = <-mch:
if !ok {
return nil, ErrConnectionClosed
}
err := s.processNextMsgDelivered(msg)
if err != nil {
return nil, err
}
case <-ctx.Done():
return nil, ctx.Err()
}
return msg, nil
}
// RequestWithContext will create an Inbox and perform a Request
// using the provided cancellation context with the Inbox reply
// for the data v. A response will be decoded into the vPtrResponse.
func (c *EncodedConn) RequestWithContext(ctx context.Context, subject string, v interface{}, vPtr interface{}) error {
if ctx == nil {
return ErrInvalidContext
}
b, err := c.Enc.Encode(subject, v)
if err != nil {
return err
}
m, err := c.Conn.RequestWithContext(ctx, subject, b)
if err != nil {
return err
}
if reflect.TypeOf(vPtr) == emptyMsgType {
mPtr := vPtr.(*Msg)
*mPtr = *m
} else {
err := c.Enc.Decode(m.Subject, m.Data, vPtr)
if err != nil {
return err
}
}
return nil
}

260
vendor/github.com/nats-io/go-nats/enc.go generated vendored Normal file
View File

@ -0,0 +1,260 @@
// Copyright 2012-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package nats
import (
"errors"
"fmt"
"reflect"
"sync"
"time"
// Default Encoders
. "github.com/nats-io/go-nats/encoders/builtin"
)
// Encoder interface is for all register encoders
type Encoder interface {
Encode(subject string, v interface{}) ([]byte, error)
Decode(subject string, data []byte, vPtr interface{}) error
}
var encMap map[string]Encoder
var encLock sync.Mutex
// Indexe names into the Registered Encoders.
const (
JSON_ENCODER = "json"
GOB_ENCODER = "gob"
DEFAULT_ENCODER = "default"
)
func init() {
encMap = make(map[string]Encoder)
// Register json, gob and default encoder
RegisterEncoder(JSON_ENCODER, &JsonEncoder{})
RegisterEncoder(GOB_ENCODER, &GobEncoder{})
RegisterEncoder(DEFAULT_ENCODER, &DefaultEncoder{})
}
// EncodedConn are the preferred way to interface with NATS. They wrap a bare connection to
// a nats server and have an extendable encoder system that will encode and decode messages
// from raw Go types.
type EncodedConn struct {
Conn *Conn
Enc Encoder
}
// NewEncodedConn will wrap an existing Connection and utilize the appropriate registered
// encoder.
func NewEncodedConn(c *Conn, encType string) (*EncodedConn, error) {
if c == nil {
return nil, errors.New("nats: Nil Connection")
}
if c.IsClosed() {
return nil, ErrConnectionClosed
}
ec := &EncodedConn{Conn: c, Enc: EncoderForType(encType)}
if ec.Enc == nil {
return nil, fmt.Errorf("No encoder registered for '%s'", encType)
}
return ec, nil
}
// RegisterEncoder will register the encType with the given Encoder. Useful for customization.
func RegisterEncoder(encType string, enc Encoder) {
encLock.Lock()
defer encLock.Unlock()
encMap[encType] = enc
}
// EncoderForType will return the registered Encoder for the encType.
func EncoderForType(encType string) Encoder {
encLock.Lock()
defer encLock.Unlock()
return encMap[encType]
}
// Publish publishes the data argument to the given subject. The data argument
// will be encoded using the associated encoder.
func (c *EncodedConn) Publish(subject string, v interface{}) error {
b, err := c.Enc.Encode(subject, v)
if err != nil {
return err
}
return c.Conn.publish(subject, _EMPTY_, b)
}
// PublishRequest will perform a Publish() expecting a response on the
// reply subject. Use Request() for automatically waiting for a response
// inline.
func (c *EncodedConn) PublishRequest(subject, reply string, v interface{}) error {
b, err := c.Enc.Encode(subject, v)
if err != nil {
return err
}
return c.Conn.publish(subject, reply, b)
}
// Request will create an Inbox and perform a Request() call
// with the Inbox reply for the data v. A response will be
// decoded into the vPtrResponse.
func (c *EncodedConn) Request(subject string, v interface{}, vPtr interface{}, timeout time.Duration) error {
b, err := c.Enc.Encode(subject, v)
if err != nil {
return err
}
m, err := c.Conn.Request(subject, b, timeout)
if err != nil {
return err
}
if reflect.TypeOf(vPtr) == emptyMsgType {
mPtr := vPtr.(*Msg)
*mPtr = *m
} else {
err = c.Enc.Decode(m.Subject, m.Data, vPtr)
}
return err
}
// Handler is a specific callback used for Subscribe. It is generalized to
// an interface{}, but we will discover its format and arguments at runtime
// and perform the correct callback, including de-marshaling JSON strings
// back into the appropriate struct based on the signature of the Handler.
//
// Handlers are expected to have one of four signatures.
//
// type person struct {
// Name string `json:"name,omitempty"`
// Age uint `json:"age,omitempty"`
// }
//
// handler := func(m *Msg)
// handler := func(p *person)
// handler := func(subject string, o *obj)
// handler := func(subject, reply string, o *obj)
//
// These forms allow a callback to request a raw Msg ptr, where the processing
// of the message from the wire is untouched. Process a JSON representation
// and demarshal it into the given struct, e.g. person.
// There are also variants where the callback wants either the subject, or the
// subject and the reply subject.
type Handler interface{}
// Dissect the cb Handler's signature
func argInfo(cb Handler) (reflect.Type, int) {
cbType := reflect.TypeOf(cb)
if cbType.Kind() != reflect.Func {
panic("nats: Handler needs to be a func")
}
numArgs := cbType.NumIn()
if numArgs == 0 {
return nil, numArgs
}
return cbType.In(numArgs - 1), numArgs
}
var emptyMsgType = reflect.TypeOf(&Msg{})
// Subscribe will create a subscription on the given subject and process incoming
// messages using the specified Handler. The Handler should be a func that matches
// a signature from the description of Handler from above.
func (c *EncodedConn) Subscribe(subject string, cb Handler) (*Subscription, error) {
return c.subscribe(subject, _EMPTY_, cb)
}
// QueueSubscribe will create a queue subscription on the given subject and process
// incoming messages using the specified Handler. The Handler should be a func that
// matches a signature from the description of Handler from above.
func (c *EncodedConn) QueueSubscribe(subject, queue string, cb Handler) (*Subscription, error) {
return c.subscribe(subject, queue, cb)
}
// Internal implementation that all public functions will use.
func (c *EncodedConn) subscribe(subject, queue string, cb Handler) (*Subscription, error) {
if cb == nil {
return nil, errors.New("nats: Handler required for EncodedConn Subscription")
}
argType, numArgs := argInfo(cb)
if argType == nil {
return nil, errors.New("nats: Handler requires at least one argument")
}
cbValue := reflect.ValueOf(cb)
wantsRaw := (argType == emptyMsgType)
natsCB := func(m *Msg) {
var oV []reflect.Value
if wantsRaw {
oV = []reflect.Value{reflect.ValueOf(m)}
} else {
var oPtr reflect.Value
if argType.Kind() != reflect.Ptr {
oPtr = reflect.New(argType)
} else {
oPtr = reflect.New(argType.Elem())
}
if err := c.Enc.Decode(m.Subject, m.Data, oPtr.Interface()); err != nil {
if c.Conn.Opts.AsyncErrorCB != nil {
c.Conn.ach.push(func() {
c.Conn.Opts.AsyncErrorCB(c.Conn, m.Sub, errors.New("nats: Got an error trying to unmarshal: "+err.Error()))
})
}
return
}
if argType.Kind() != reflect.Ptr {
oPtr = reflect.Indirect(oPtr)
}
// Callback Arity
switch numArgs {
case 1:
oV = []reflect.Value{oPtr}
case 2:
subV := reflect.ValueOf(m.Subject)
oV = []reflect.Value{subV, oPtr}
case 3:
subV := reflect.ValueOf(m.Subject)
replyV := reflect.ValueOf(m.Reply)
oV = []reflect.Value{subV, replyV, oPtr}
}
}
cbValue.Call(oV)
}
return c.Conn.subscribe(subject, queue, natsCB, nil)
}
// FlushTimeout allows a Flush operation to have an associated timeout.
func (c *EncodedConn) FlushTimeout(timeout time.Duration) (err error) {
return c.Conn.FlushTimeout(timeout)
}
// Flush will perform a round trip to the server and return when it
// receives the internal reply.
func (c *EncodedConn) Flush() error {
return c.Conn.Flush()
}
// Close will close the connection to the server. This call will release
// all blocking calls, such as Flush(), etc.
func (c *EncodedConn) Close() {
c.Conn.Close()
}
// LastError reports the last error encountered via the Connection.
func (c *EncodedConn) LastError() error {
return c.Conn.err
}

270
vendor/github.com/nats-io/go-nats/enc_test.go generated vendored Normal file
View File

@ -0,0 +1,270 @@
// Copyright 2012-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package nats_test
import (
"fmt"
"testing"
"time"
. "github.com/nats-io/go-nats"
"github.com/nats-io/go-nats/encoders/protobuf"
"github.com/nats-io/go-nats/encoders/protobuf/testdata"
)
// Since we import above nats packages, we need to have a different
// const name than TEST_PORT that we used on the other packages.
const ENC_TEST_PORT = 8268
var options = Options{
Url: fmt.Sprintf("nats://localhost:%d", ENC_TEST_PORT),
AllowReconnect: true,
MaxReconnect: 10,
ReconnectWait: 100 * time.Millisecond,
Timeout: DefaultTimeout,
}
////////////////////////////////////////////////////////////////////////////////
// Encoded connection tests
////////////////////////////////////////////////////////////////////////////////
func TestPublishErrorAfterSubscribeDecodeError(t *testing.T) {
ts := RunServerOnPort(ENC_TEST_PORT)
defer ts.Shutdown()
opts := options
nc, _ := opts.Connect()
defer nc.Close()
c, _ := NewEncodedConn(nc, JSON_ENCODER)
//Test message type
type Message struct {
Message string
}
const testSubj = "test"
c.Subscribe(testSubj, func(msg *Message) {})
//Publish invalid json to catch decode error in subscription callback
c.Publish(testSubj, `foo`)
c.Flush()
//Next publish should be successful
if err := c.Publish(testSubj, Message{"2"}); err != nil {
t.Error("Fail to send correct json message after decode error in subscription")
}
}
func TestPublishErrorAfterInvalidPublishMessage(t *testing.T) {
ts := RunServerOnPort(ENC_TEST_PORT)
defer ts.Shutdown()
opts := options
nc, _ := opts.Connect()
defer nc.Close()
c, _ := NewEncodedConn(nc, protobuf.PROTOBUF_ENCODER)
const testSubj = "test"
c.Publish(testSubj, &testdata.Person{Name: "Anatolii"})
//Publish invalid protobuff message to catch decode error
c.Publish(testSubj, "foo")
//Next publish with valid protobuf message should be successful
if err := c.Publish(testSubj, &testdata.Person{Name: "Anatolii"}); err != nil {
t.Error("Fail to send correct protobuf message after invalid message publishing", err)
}
}
func TestVariousFailureConditions(t *testing.T) {
ts := RunServerOnPort(ENC_TEST_PORT)
defer ts.Shutdown()
dch := make(chan bool)
opts := options
opts.AsyncErrorCB = func(_ *Conn, _ *Subscription, e error) {
dch <- true
}
nc, _ := opts.Connect()
nc.Close()
if _, err := NewEncodedConn(nil, protobuf.PROTOBUF_ENCODER); err == nil {
t.Fatal("Expected an error")
}
if _, err := NewEncodedConn(nc, protobuf.PROTOBUF_ENCODER); err == nil || err != ErrConnectionClosed {
t.Fatalf("Wrong error: %v instead of %v", err, ErrConnectionClosed)
}
nc, _ = opts.Connect()
defer nc.Close()
if _, err := NewEncodedConn(nc, "foo"); err == nil {
t.Fatal("Expected an error")
}
c, err := NewEncodedConn(nc, protobuf.PROTOBUF_ENCODER)
if err != nil {
t.Fatalf("Unable to create encoded connection: %v", err)
}
defer c.Close()
if _, err := c.Subscribe("bar", func(subj, obj string) {}); err != nil {
t.Fatalf("Unable to create subscription: %v", err)
}
if err := c.Publish("bar", &testdata.Person{Name: "Ivan"}); err != nil {
t.Fatalf("Unable to publish: %v", err)
}
if err := Wait(dch); err != nil {
t.Fatal("Did not get the async error callback")
}
if err := c.PublishRequest("foo", "bar", "foo"); err == nil {
t.Fatal("Expected an error")
}
if err := c.Request("foo", "foo", nil, 2*time.Second); err == nil {
t.Fatal("Expected an error")
}
nc.Close()
if err := c.PublishRequest("foo", "bar", &testdata.Person{Name: "Ivan"}); err == nil {
t.Fatal("Expected an error")
}
resp := &testdata.Person{}
if err := c.Request("foo", &testdata.Person{Name: "Ivan"}, resp, 2*time.Second); err == nil {
t.Fatal("Expected an error")
}
if _, err := c.Subscribe("foo", nil); err == nil {
t.Fatal("Expected an error")
}
if _, err := c.Subscribe("foo", func() {}); err == nil {
t.Fatal("Expected an error")
}
func() {
defer func() {
if r := recover(); r == nil {
t.Fatal("Expected an error")
}
}()
if _, err := c.Subscribe("foo", "bar"); err == nil {
t.Fatal("Expected an error")
}
}()
}
func TestRequest(t *testing.T) {
ts := RunServerOnPort(ENC_TEST_PORT)
defer ts.Shutdown()
dch := make(chan bool)
opts := options
nc, _ := opts.Connect()
defer nc.Close()
c, err := NewEncodedConn(nc, protobuf.PROTOBUF_ENCODER)
if err != nil {
t.Fatalf("Unable to create encoded connection: %v", err)
}
defer c.Close()
sentName := "Ivan"
recvName := "Kozlovic"
if _, err := c.Subscribe("foo", func(_, reply string, p *testdata.Person) {
if p.Name != sentName {
t.Fatalf("Got wrong name: %v instead of %v", p.Name, sentName)
}
c.Publish(reply, &testdata.Person{Name: recvName})
dch <- true
}); err != nil {
t.Fatalf("Unable to create subscription: %v", err)
}
if _, err := c.Subscribe("foo", func(_ string, p *testdata.Person) {
if p.Name != sentName {
t.Fatalf("Got wrong name: %v instead of %v", p.Name, sentName)
}
dch <- true
}); err != nil {
t.Fatalf("Unable to create subscription: %v", err)
}
if err := c.Publish("foo", &testdata.Person{Name: sentName}); err != nil {
t.Fatalf("Unable to publish: %v", err)
}
if err := Wait(dch); err != nil {
t.Fatal("Did not get message")
}
if err := Wait(dch); err != nil {
t.Fatal("Did not get message")
}
response := &testdata.Person{}
if err := c.Request("foo", &testdata.Person{Name: sentName}, response, 2*time.Second); err != nil {
t.Fatalf("Unable to publish: %v", err)
}
if response == nil {
t.Fatal("No response received")
} else if response.Name != recvName {
t.Fatalf("Wrong response: %v instead of %v", response.Name, recvName)
}
if err := Wait(dch); err != nil {
t.Fatal("Did not get message")
}
if err := Wait(dch); err != nil {
t.Fatal("Did not get message")
}
c2, err := NewEncodedConn(nc, GOB_ENCODER)
if err != nil {
t.Fatalf("Unable to create encoded connection: %v", err)
}
defer c2.Close()
if _, err := c2.QueueSubscribe("bar", "baz", func(m *Msg) {
response := &Msg{Subject: m.Reply, Data: []byte(recvName)}
c2.Conn.PublishMsg(response)
dch <- true
}); err != nil {
t.Fatalf("Unable to create subscription: %v", err)
}
mReply := Msg{}
if err := c2.Request("bar", &Msg{Data: []byte(sentName)}, &mReply, 2*time.Second); err != nil {
t.Fatalf("Unable to send request: %v", err)
}
if string(mReply.Data) != recvName {
t.Fatalf("Wrong reply: %v instead of %v", string(mReply.Data), recvName)
}
if err := Wait(dch); err != nil {
t.Fatal("Did not get message")
}
if c.LastError() != nil {
t.Fatalf("Unexpected connection error: %v", c.LastError())
}
if c2.LastError() != nil {
t.Fatalf("Unexpected connection error: %v", c2.LastError())
}
}

View File

@ -0,0 +1,117 @@
// Copyright 2012-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package builtin
import (
"bytes"
"fmt"
"reflect"
"strconv"
"unsafe"
)
// DefaultEncoder implementation for EncodedConn.
// This encoder will leave []byte and string untouched, but will attempt to
// turn numbers into appropriate strings that can be decoded. It will also
// propely encoded and decode bools. If will encode a struct, but if you want
// to properly handle structures you should use JsonEncoder.
type DefaultEncoder struct {
// Empty
}
var trueB = []byte("true")
var falseB = []byte("false")
var nilB = []byte("")
// Encode
func (je *DefaultEncoder) Encode(subject string, v interface{}) ([]byte, error) {
switch arg := v.(type) {
case string:
bytes := *(*[]byte)(unsafe.Pointer(&arg))
return bytes, nil
case []byte:
return arg, nil
case bool:
if arg {
return trueB, nil
} else {
return falseB, nil
}
case nil:
return nilB, nil
default:
var buf bytes.Buffer
fmt.Fprintf(&buf, "%+v", arg)
return buf.Bytes(), nil
}
}
// Decode
func (je *DefaultEncoder) Decode(subject string, data []byte, vPtr interface{}) error {
// Figure out what it's pointing to...
sData := *(*string)(unsafe.Pointer(&data))
switch arg := vPtr.(type) {
case *string:
*arg = sData
return nil
case *[]byte:
*arg = data
return nil
case *int:
n, err := strconv.ParseInt(sData, 10, 64)
if err != nil {
return err
}
*arg = int(n)
return nil
case *int32:
n, err := strconv.ParseInt(sData, 10, 64)
if err != nil {
return err
}
*arg = int32(n)
return nil
case *int64:
n, err := strconv.ParseInt(sData, 10, 64)
if err != nil {
return err
}
*arg = int64(n)
return nil
case *float32:
n, err := strconv.ParseFloat(sData, 32)
if err != nil {
return err
}
*arg = float32(n)
return nil
case *float64:
n, err := strconv.ParseFloat(sData, 64)
if err != nil {
return err
}
*arg = float64(n)
return nil
case *bool:
b, err := strconv.ParseBool(sData)
if err != nil {
return err
}
*arg = b
return nil
default:
vt := reflect.TypeOf(arg).Elem()
return fmt.Errorf("nats: Default Encoder can't decode to type %s", vt)
}
}

View File

@ -0,0 +1,45 @@
// Copyright 2013-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package builtin
import (
"bytes"
"encoding/gob"
)
// GobEncoder is a Go specific GOB Encoder implementation for EncodedConn.
// This encoder will use the builtin encoding/gob to Marshal
// and Unmarshal most types, including structs.
type GobEncoder struct {
// Empty
}
// FIXME(dlc) - This could probably be more efficient.
// Encode
func (ge *GobEncoder) Encode(subject string, v interface{}) ([]byte, error) {
b := new(bytes.Buffer)
enc := gob.NewEncoder(b)
if err := enc.Encode(v); err != nil {
return nil, err
}
return b.Bytes(), nil
}
// Decode
func (ge *GobEncoder) Decode(subject string, data []byte, vPtr interface{}) (err error) {
dec := gob.NewDecoder(bytes.NewBuffer(data))
err = dec.Decode(vPtr)
return
}

View File

@ -0,0 +1,56 @@
// Copyright 2012-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package builtin
import (
"encoding/json"
"strings"
)
// JsonEncoder is a JSON Encoder implementation for EncodedConn.
// This encoder will use the builtin encoding/json to Marshal
// and Unmarshal most types, including structs.
type JsonEncoder struct {
// Empty
}
// Encode
func (je *JsonEncoder) Encode(subject string, v interface{}) ([]byte, error) {
b, err := json.Marshal(v)
if err != nil {
return nil, err
}
return b, nil
}
// Decode
func (je *JsonEncoder) Decode(subject string, data []byte, vPtr interface{}) (err error) {
switch arg := vPtr.(type) {
case *string:
// If they want a string and it is a JSON string, strip quotes
// This allows someone to send a struct but receive as a plain string
// This cast should be efficient for Go 1.3 and beyond.
str := string(data)
if strings.HasPrefix(str, `"`) && strings.HasSuffix(str, `"`) {
*arg = str[1 : len(str)-1]
} else {
*arg = str
}
case *[]byte:
*arg = data
default:
err = json.Unmarshal(data, arg)
}
return
}

Some files were not shown because too many files have changed in this diff Show More