Merge branch 'nats-endpoint' of https://github.com/lennycampino/tile38 into lennycampino-nats-endpoint
This commit is contained in:
commit
f17e95c6b6
@ -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
80
pkg/endpoint/nats.go
Normal 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
201
vendor/github.com/nats-io/gnatsd/LICENSE
generated
vendored
Normal 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
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
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
295
vendor/github.com/nats-io/gnatsd/conf/parse.go
generated
vendored
Normal 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
275
vendor/github.com/nats-io/gnatsd/conf/parse_test.go
generated
vendored
Normal 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
6
vendor/github.com/nats-io/gnatsd/conf/simple.conf
generated
vendored
Normal 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
152
vendor/github.com/nats-io/gnatsd/logger/log.go
generated
vendored
Normal 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
185
vendor/github.com/nats-io/gnatsd/logger/log_test.go
generated
vendored
Normal 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
123
vendor/github.com/nats-io/gnatsd/logger/syslog.go
generated
vendored
Normal 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
235
vendor/github.com/nats-io/gnatsd/logger/syslog_test.go
generated
vendored
Normal 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)
|
||||
}
|
104
vendor/github.com/nats-io/gnatsd/logger/syslog_windows.go
generated
vendored
Normal file
104
vendor/github.com/nats-io/gnatsd/logger/syslog_windows.go
generated
vendored
Normal 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...))
|
||||
}
|
||||
}
|
139
vendor/github.com/nats-io/gnatsd/logger/syslog_windows_test.go
generated
vendored
Executable file
139
vendor/github.com/nats-io/gnatsd/logger/syslog_windows_test.go
generated
vendored
Executable 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
249
vendor/github.com/nats-io/gnatsd/server/auth.go
generated
vendored
Normal 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
99
vendor/github.com/nats-io/gnatsd/server/auth_test.go
generated
vendored
Normal 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)
|
||||
}
|
||||
}
|
97
vendor/github.com/nats-io/gnatsd/server/ciphersuites.go
generated
vendored
Normal file
97
vendor/github.com/nats-io/gnatsd/server/ciphersuites.go
generated
vendored
Normal 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
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
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
361
vendor/github.com/nats-io/gnatsd/server/closed_conns_test.go
generated
vendored
Normal file
361
vendor/github.com/nats-io/gnatsd/server/closed_conns_test.go
generated
vendored
Normal 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
124
vendor/github.com/nats-io/gnatsd/server/const.go
generated
vendored
Normal 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
51
vendor/github.com/nats-io/gnatsd/server/errors.go
generated
vendored
Normal 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
184
vendor/github.com/nats-io/gnatsd/server/log.go
generated
vendored
Normal 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
175
vendor/github.com/nats-io/gnatsd/server/log_test.go
generated
vendored
Normal 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
1029
vendor/github.com/nats-io/gnatsd/server/monitor.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
147
vendor/github.com/nats-io/gnatsd/server/monitor_sort_opts.go
generated
vendored
Normal file
147
vendor/github.com/nats-io/gnatsd/server/monitor_sort_opts.go
generated
vendored
Normal 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
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
87
vendor/github.com/nats-io/gnatsd/server/norace_test.go
generated
vendored
Normal 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
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
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
749
vendor/github.com/nats-io/gnatsd/server/parser.go
generated
vendored
Normal 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
546
vendor/github.com/nats-io/gnatsd/server/parser_test.go
generated
vendored
Normal 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
44
vendor/github.com/nats-io/gnatsd/server/ping_test.go
generated
vendored
Normal 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)
|
||||
}
|
34
vendor/github.com/nats-io/gnatsd/server/pse/pse_darwin.go
generated
vendored
Normal file
34
vendor/github.com/nats-io/gnatsd/server/pse/pse_darwin.go
generated
vendored
Normal 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
|
||||
}
|
83
vendor/github.com/nats-io/gnatsd/server/pse/pse_freebsd.go
generated
vendored
Normal file
83
vendor/github.com/nats-io/gnatsd/server/pse/pse_freebsd.go
generated
vendored
Normal 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
|
||||
}
|
126
vendor/github.com/nats-io/gnatsd/server/pse/pse_linux.go
generated
vendored
Normal file
126
vendor/github.com/nats-io/gnatsd/server/pse/pse_linux.go
generated
vendored
Normal 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
|
||||
}
|
36
vendor/github.com/nats-io/gnatsd/server/pse/pse_openbsd.go
generated
vendored
Normal file
36
vendor/github.com/nats-io/gnatsd/server/pse/pse_openbsd.go
generated
vendored
Normal 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
|
||||
}
|
25
vendor/github.com/nats-io/gnatsd/server/pse/pse_rumprun.go
generated
vendored
Normal file
25
vendor/github.com/nats-io/gnatsd/server/pse/pse_rumprun.go
generated
vendored
Normal 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
|
||||
}
|
23
vendor/github.com/nats-io/gnatsd/server/pse/pse_solaris.go
generated
vendored
Normal file
23
vendor/github.com/nats-io/gnatsd/server/pse/pse_solaris.go
generated
vendored
Normal 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
|
||||
}
|
67
vendor/github.com/nats-io/gnatsd/server/pse/pse_test.go
generated
vendored
Normal file
67
vendor/github.com/nats-io/gnatsd/server/pse/pse_test.go
generated
vendored
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
280
vendor/github.com/nats-io/gnatsd/server/pse/pse_windows.go
generated
vendored
Normal file
280
vendor/github.com/nats-io/gnatsd/server/pse/pse_windows.go
generated
vendored
Normal 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
|
||||
}
|
97
vendor/github.com/nats-io/gnatsd/server/pse/pse_windows_test.go
generated
vendored
Normal file
97
vendor/github.com/nats-io/gnatsd/server/pse/pse_windows_test.go
generated
vendored
Normal 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
714
vendor/github.com/nats-io/gnatsd/server/reload.go
generated
vendored
Normal 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
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
75
vendor/github.com/nats-io/gnatsd/server/ring.go
generated
vendored
Normal 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
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
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
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
700
vendor/github.com/nats-io/gnatsd/server/server_test.go
generated
vendored
Normal 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
28
vendor/github.com/nats-io/gnatsd/server/service.go
generated
vendored
Normal 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
|
||||
}
|
53
vendor/github.com/nats-io/gnatsd/server/service_test.go
generated
vendored
Normal file
53
vendor/github.com/nats-io/gnatsd/server/service_test.go
generated
vendored
Normal 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)
|
||||
}
|
||||
}
|
121
vendor/github.com/nats-io/gnatsd/server/service_windows.go
generated
vendored
Normal file
121
vendor/github.com/nats-io/gnatsd/server/service_windows.go
generated
vendored
Normal 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
158
vendor/github.com/nats-io/gnatsd/server/signal.go
generated
vendored
Normal 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
314
vendor/github.com/nats-io/gnatsd/server/signal_test.go
generated
vendored
Normal 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")
|
||||
}
|
||||
}
|
101
vendor/github.com/nats-io/gnatsd/server/signal_windows.go
generated
vendored
Normal file
101
vendor/github.com/nats-io/gnatsd/server/signal_windows.go
generated
vendored
Normal 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
517
vendor/github.com/nats-io/gnatsd/server/split_test.go
generated
vendored
Normal 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
775
vendor/github.com/nats-io/gnatsd/server/sublist.go
generated
vendored
Normal 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
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
111
vendor/github.com/nats-io/gnatsd/server/util.go
generated
vendored
Normal 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
168
vendor/github.com/nats-io/gnatsd/server/util_test.go
generated
vendored
Normal 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
236
vendor/github.com/nats-io/gnatsd/test/auth_test.go
generated
vendored
Normal 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)
|
||||
}
|
79
vendor/github.com/nats-io/gnatsd/test/bench_results.txt
generated
vendored
Normal file
79
vendor/github.com/nats-io/gnatsd/test/bench_results.txt
generated
vendored
Normal 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
674
vendor/github.com/nats-io/gnatsd/test/bench_test.go
generated
vendored
Normal 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))
|
||||
}
|
92
vendor/github.com/nats-io/gnatsd/test/client_auth_test.go
generated
vendored
Normal file
92
vendor/github.com/nats-io/gnatsd/test/client_auth_test.go
generated
vendored
Normal 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")
|
||||
}
|
||||
}
|
377
vendor/github.com/nats-io/gnatsd/test/client_cluster_test.go
generated
vendored
Normal file
377
vendor/github.com/nats-io/gnatsd/test/client_cluster_test.go
generated
vendored
Normal 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
491
vendor/github.com/nats-io/gnatsd/test/cluster_test.go
generated
vendored
Normal 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)
|
||||
}
|
||||
}
|
64
vendor/github.com/nats-io/gnatsd/test/cluster_tls_test.go
generated
vendored
Normal file
64
vendor/github.com/nats-io/gnatsd/test/cluster_tls_test.go
generated
vendored
Normal 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
128
vendor/github.com/nats-io/gnatsd/test/fanout_test.go
generated
vendored
Normal 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
62
vendor/github.com/nats-io/gnatsd/test/gosrv_test.go
generated
vendored
Normal 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()
|
||||
}
|
99
vendor/github.com/nats-io/gnatsd/test/maxpayload_test.go
generated
vendored
Normal file
99
vendor/github.com/nats-io/gnatsd/test/maxpayload_test.go
generated
vendored
Normal 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
739
vendor/github.com/nats-io/gnatsd/test/monitor_test.go
generated
vendored
Normal 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
43
vendor/github.com/nats-io/gnatsd/test/opts_test.go
generated
vendored
Normal 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
109
vendor/github.com/nats-io/gnatsd/test/pedantic_test.go
generated
vendored
Normal 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
55
vendor/github.com/nats-io/gnatsd/test/pid_test.go
generated
vendored
Normal 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
189
vendor/github.com/nats-io/gnatsd/test/ping_test.go
generated
vendored
Normal 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
52
vendor/github.com/nats-io/gnatsd/test/port_test.go
generated
vendored
Normal 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
195
vendor/github.com/nats-io/gnatsd/test/ports_test.go
generated
vendored
Normal 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
317
vendor/github.com/nats-io/gnatsd/test/proto_test.go
generated
vendored
Normal 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)
|
||||
}
|
||||
}
|
665
vendor/github.com/nats-io/gnatsd/test/route_discovery_test.go
generated
vendored
Normal file
665
vendor/github.com/nats-io/gnatsd/test/route_discovery_test.go
generated
vendored
Normal 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
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
378
vendor/github.com/nats-io/gnatsd/test/test.go
generated
vendored
Normal 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
72
vendor/github.com/nats-io/gnatsd/test/test_test.go
generated
vendored
Normal 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
334
vendor/github.com/nats-io/gnatsd/test/tls_test.go
generated
vendored
Normal 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")
|
||||
}
|
||||
}
|
101
vendor/github.com/nats-io/gnatsd/test/user_authorization_test.go
generated
vendored
Normal file
101
vendor/github.com/nats-io/gnatsd/test/user_authorization_test.go
generated
vendored
Normal 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
82
vendor/github.com/nats-io/gnatsd/test/verbose_test.go
generated
vendored
Normal 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
16
vendor/github.com/nats-io/gnatsd/util/gnatsd.service
generated
vendored
Normal 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
25
vendor/github.com/nats-io/gnatsd/util/tls.go
generated
vendored
Normal 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
47
vendor/github.com/nats-io/gnatsd/util/tls_pre17.go
generated
vendored
Normal 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
49
vendor/github.com/nats-io/gnatsd/util/tls_pre18.go
generated
vendored
Normal 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
3
vendor/github.com/nats-io/go-nats/GOVERNANCE.md
generated
vendored
Normal 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
201
vendor/github.com/nats-io/go-nats/LICENSE
generated
vendored
Normal 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
10
vendor/github.com/nats-io/go-nats/MAINTAINERS.md
generated
vendored
Normal 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
334
vendor/github.com/nats-io/go-nats/README.md
generated
vendored
Normal file
@ -0,0 +1,334 @@
|
||||
# NATS - Go Client
|
||||
A [Go](http://golang.org) client for the [NATS messaging system](https://nats.io).
|
||||
|
||||
[](https://www.apache.org/licenses/LICENSE-2.0)
|
||||
[](https://app.fossa.io/projects/git%2Bgithub.com%2Fnats-io%2Fgo-nats?ref=badge_shield)
|
||||
[](https://goreportcard.com/report/github.com/nats-io/go-nats) [](http://travis-ci.org/nats-io/go-nats) [](http://godoc.org/github.com/nats-io/go-nats) [](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.
|
||||
|
||||
[](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
26
vendor/github.com/nats-io/go-nats/TODO.md
generated
vendored
Normal 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
177
vendor/github.com/nats-io/go-nats/context.go
generated
vendored
Normal 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
260
vendor/github.com/nats-io/go-nats/enc.go
generated
vendored
Normal 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
270
vendor/github.com/nats-io/go-nats/enc_test.go
generated
vendored
Normal 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())
|
||||
}
|
||||
}
|
117
vendor/github.com/nats-io/go-nats/encoders/builtin/default_enc.go
generated
vendored
Normal file
117
vendor/github.com/nats-io/go-nats/encoders/builtin/default_enc.go
generated
vendored
Normal 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)
|
||||
}
|
||||
}
|
45
vendor/github.com/nats-io/go-nats/encoders/builtin/gob_enc.go
generated
vendored
Normal file
45
vendor/github.com/nats-io/go-nats/encoders/builtin/gob_enc.go
generated
vendored
Normal 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
|
||||
}
|
56
vendor/github.com/nats-io/go-nats/encoders/builtin/json_enc.go
generated
vendored
Normal file
56
vendor/github.com/nats-io/go-nats/encoders/builtin/json_enc.go
generated
vendored
Normal 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
Loading…
x
Reference in New Issue
Block a user