Add vendoring to containerd master

Initial vendor list validated with empty $GOPATH
and only master checked out; followed by `make`
and verified that all binaries build properly.
Updates require github.com/LK4D4/vndr tool.

Signed-off-by: Phil Estes <estesp@linux.vnet.ibm.com>
This commit is contained in:
Phil Estes 2016-12-16 12:03:35 -05:00
parent 286ea04591
commit dd9309c15e
No known key found for this signature in database
GPG key ID: 0F386284C03A1162
407 changed files with 113562 additions and 0 deletions

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

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2012-2016 Apcera Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

45
vendor/github.com/nats-io/gnatsd/auth/multiuser.go generated vendored Normal file
View file

@ -0,0 +1,45 @@
// Copyright 2016 Apcera Inc. All rights reserved.
package auth
import (
"github.com/nats-io/gnatsd/server"
"golang.org/x/crypto/bcrypt"
)
// Plain authentication is a basic username and password
type MultiUser struct {
users map[string]*server.User
}
// Create a new multi-user
func NewMultiUser(users []*server.User) *MultiUser {
m := &MultiUser{users: make(map[string]*server.User)}
for _, u := range users {
m.users[u.Username] = u
}
return m
}
// Check authenticates the client using a username and password against a list of multiple users.
func (m *MultiUser) Check(c server.ClientAuth) bool {
opts := c.GetOpts()
user, ok := m.users[opts.Username]
if !ok {
return false
}
pass := user.Password
// Check to see if the password is a bcrypt hash
if isBcrypt(pass) {
if err := bcrypt.CompareHashAndPassword([]byte(pass), []byte(opts.Password)); err != nil {
return false
}
} else if pass != opts.Password {
return false
}
c.RegisterUser(user)
return true
}

40
vendor/github.com/nats-io/gnatsd/auth/plain.go generated vendored Normal file
View file

@ -0,0 +1,40 @@
// Copyright 2014-2015 Apcera Inc. All rights reserved.
package auth
import (
"strings"
"github.com/nats-io/gnatsd/server"
"golang.org/x/crypto/bcrypt"
)
const bcryptPrefix = "$2a$"
func isBcrypt(password string) bool {
return strings.HasPrefix(password, bcryptPrefix)
}
// Plain authentication is a basic username and password
type Plain struct {
Username string
Password string
}
// Check authenticates the client using a username and password
func (p *Plain) Check(c server.ClientAuth) bool {
opts := c.GetOpts()
if p.Username != opts.Username {
return false
}
// Check to see if the password is a bcrypt hash
if isBcrypt(p.Password) {
if err := bcrypt.CompareHashAndPassword([]byte(p.Password), []byte(opts.Password)); err != nil {
return false
}
} else if p.Password != opts.Password {
return false
}
return true
}

26
vendor/github.com/nats-io/gnatsd/auth/token.go generated vendored Normal file
View file

@ -0,0 +1,26 @@
package auth
import (
"github.com/nats-io/gnatsd/server"
"golang.org/x/crypto/bcrypt"
)
// Token holds a string token used for authentication
type Token struct {
Token string
}
// Check authenticates a client from a token
func (p *Token) Check(c server.ClientAuth) bool {
opts := c.GetOpts()
// Check to see if the token is a bcrypt hash
if isBcrypt(p.Token) {
if err := bcrypt.CompareHashAndPassword([]byte(p.Token), []byte(opts.Authorization)); err != nil {
return false
}
} else if p.Token != opts.Authorization {
return false
}
return true
}

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

File diff suppressed because it is too large Load diff

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

@ -0,0 +1,284 @@
// Copyright 2013-2016 Apcera Inc. All rights reserved.
// 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) {
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
}
}

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

@ -0,0 +1,128 @@
// Copyright 2012-2015 Apcera Inc. All rights reserved.
//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
}
// 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,
}
setPlainLabelFormats(l)
return l
}
// 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...)
}
}

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

@ -0,0 +1,112 @@
// Copyright 2012-2014 Apcera Inc. All rights reserved.
// +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...))
}
}

View file

@ -0,0 +1,52 @@
// Copyright 2012-2014 Apcera Inc. All rights reserved.
package logger
import (
"fmt"
"log"
"os"
)
type SysLogger struct {
writer *log.Logger
debug bool
trace bool
}
func NewSysLogger(debug, trace bool) *SysLogger {
w := log.New(os.Stdout, "gnatsd", log.LstdFlags)
return &SysLogger{
writer: w,
debug: debug,
trace: trace,
}
}
func NewRemoteSysLogger(fqn string, debug, trace bool) *SysLogger {
return NewSysLogger(debug, trace)
}
func (l *SysLogger) Noticef(format string, v ...interface{}) {
l.writer.Println("NOTICE", fmt.Sprintf(format, v...))
}
func (l *SysLogger) Fatalf(format string, v ...interface{}) {
l.writer.Println("CRITICAL", fmt.Sprintf(format, v...))
}
func (l *SysLogger) Errorf(format string, v ...interface{}) {
l.writer.Println("ERROR", fmt.Sprintf(format, v...))
}
func (l *SysLogger) Debugf(format string, v ...interface{}) {
if l.debug {
l.writer.Println("DEBUG", fmt.Sprintf(format, v...))
}
}
func (l *SysLogger) Tracef(format string, v ...interface{}) {
if l.trace {
l.writer.Println("NOTICE", fmt.Sprintf(format, v...))
}
}

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

@ -0,0 +1,17 @@
// Copyright 2012-2014 Apcera Inc. All rights reserved.
package server
// Auth is an interface for implementing authentication
type Auth interface {
// Check if a client is authorized to connect
Check(c ClientAuth) bool
}
// ClientAuth is an interface for client authentication
type ClientAuth interface {
// Get options associated with a client
GetOpts() *clientOpts
// Optionally map a user after auth.
RegisterUser(*User)
}

View file

@ -0,0 +1,33 @@
// Copyright 2015 Apcera Inc. All rights reserved.
// +build go1.4,!go1.5
package server
import (
"crypto/tls"
)
// Where we maintain all of the available 1.4 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_256_CBC_SHA": tls.TLS_RSA_WITH_AES_256_CBC_SHA,
"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_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,
}
func defaultCipherSuites() []uint16 {
return []uint16{
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
}
}

View file

@ -0,0 +1,38 @@
// Copyright 2015 Apcera Inc. All rights reserved.
// +build go1.5
package server
import (
"crypto/tls"
)
// Where we maintain all of the available 1.5 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_256_CBC_SHA": tls.TLS_RSA_WITH_AES_256_CBC_SHA,
"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_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,
}
func defaultCipherSuites() []uint16 {
return []uint16{
// The SHA384 versions are only in Go1.5
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,
}
}

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

File diff suppressed because it is too large Load diff

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

@ -0,0 +1,82 @@
// Copyright 2012-2016 Apcera Inc. All rights reserved.
package server
import (
"time"
)
const (
// VERSION is the current version for the server.
VERSION = "0.9.6"
// 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)
// 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
)

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

@ -0,0 +1,32 @@
// Copyright 2012-2016 Apcera Inc. All rights reserved.
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")
)

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

@ -0,0 +1,132 @@
// Copyright 2012-2016 Apcera Inc. All rights reserved.
package server
import (
"sync"
"sync/atomic"
"github.com/nats-io/gnatsd/logger"
)
// Package globals for performance checks
var trace int32
var debug int32
var log = struct {
sync.Mutex
logger 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{})
}
// SetLogger sets the logger of the server
func (s *Server) SetLogger(logger Logger, debugFlag, traceFlag bool) {
if debugFlag {
atomic.StoreInt32(&debug, 1)
} else {
atomic.StoreInt32(&debug, 0)
}
if traceFlag {
atomic.StoreInt32(&trace, 1)
} else {
atomic.StoreInt32(&trace, 0)
}
log.Lock()
log.logger = logger
log.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 signalling
// the process to trigger this function.
func (s *Server) ReOpenLogFile() {
// Check to make sure this is a file logger.
log.Lock()
ll := log.logger
log.Unlock()
if ll == nil {
Noticef("File log re-open ignored, no logger")
return
}
if s.opts.LogFile == "" {
Noticef("File log re-open ignored, not a file logger")
} else {
fileLog := logger.NewFileLogger(s.opts.LogFile,
s.opts.Logtime, s.opts.Debug, s.opts.Trace, true)
s.SetLogger(fileLog, s.opts.Debug, s.opts.Trace)
Noticef("File log re-opened")
}
}
// Noticef logs a notice statement
func Noticef(format string, v ...interface{}) {
executeLogCall(func(logger Logger, format string, v ...interface{}) {
logger.Noticef(format, v...)
}, format, v...)
}
// Errorf logs an error
func Errorf(format string, v ...interface{}) {
executeLogCall(func(logger Logger, format string, v ...interface{}) {
logger.Errorf(format, v...)
}, format, v...)
}
// Fatalf logs a fatal error
func Fatalf(format string, v ...interface{}) {
executeLogCall(func(logger Logger, format string, v ...interface{}) {
logger.Fatalf(format, v...)
}, format, v...)
}
// Debugf logs a debug statement
func Debugf(format string, v ...interface{}) {
if atomic.LoadInt32(&debug) == 0 {
return
}
executeLogCall(func(logger Logger, format string, v ...interface{}) {
logger.Debugf(format, v...)
}, format, v...)
}
// Tracef logs a trace statement
func Tracef(format string, v ...interface{}) {
if atomic.LoadInt32(&trace) == 0 {
return
}
executeLogCall(func(logger Logger, format string, v ...interface{}) {
logger.Tracef(format, v...)
}, format, v...)
}
func executeLogCall(f func(logger Logger, format string, v ...interface{}), format string, args ...interface{}) {
log.Lock()
defer log.Unlock()
if log.logger == nil {
return
}
f(log.logger, format, args...)
}

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

@ -0,0 +1,526 @@
// Copyright 2013-2015 Apcera Inc. All rights reserved.
package server
import (
"crypto/tls"
"encoding/json"
"fmt"
"net"
"net/http"
"runtime"
"sort"
"strconv"
"sync/atomic"
"time"
"github.com/nats-io/gnatsd/server/pse"
)
// Snapshot this
var numCores int
func init() {
numCores = runtime.NumCPU()
}
// Connz represents detailed information on current client connections.
type Connz struct {
Now time.Time `json:"now"`
NumConns int `json:"num_connections"`
Total int `json:"total"`
Offset int `json:"offset"`
Limit int `json:"limit"`
Conns []ConnInfo `json:"connections"`
}
// ConnInfo has detailed information on a per connection basis.
type ConnInfo struct {
Cid uint64 `json:"cid"`
IP string `json:"ip"`
Port int `json:"port"`
Start time.Time `json:"start"`
LastActivity time.Time `json:"last_activity"`
Uptime string `json:"uptime"`
Idle string `json:"idle"`
Pending int `json:"pending_bytes"`
InMsgs int64 `json:"in_msgs"`
OutMsgs int64 `json:"out_msgs"`
InBytes int64 `json:"in_bytes"`
OutBytes int64 `json:"out_bytes"`
NumSubs uint32 `json:"subscriptions"`
Name string `json:"name,omitempty"`
Lang string `json:"lang,omitempty"`
Version string `json:"version,omitempty"`
TLSVersion string `json:"tls_version,omitempty"`
TLSCipher string `json:"tls_cipher_suite,omitempty"`
AuthorizedUser string `json:"authorized_user,omitempty"`
Subs []string `json:"subscriptions_list,omitempty"`
}
// DefaultConnListSize is the default size of the connection list.
const DefaultConnListSize = 1024
const defaultStackBufSize = 10000
// HandleConnz process HTTP requests for connection information.
func (s *Server) HandleConnz(w http.ResponseWriter, r *http.Request) {
sortOpt := SortOpt(r.URL.Query().Get("sort"))
// If no sort option given or sort is by uptime, then sort by cid
if sortOpt == "" || sortOpt == byUptime {
sortOpt = byCid
} else if !sortOpt.IsValid() {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(fmt.Sprintf("Invalid sorting option: %s", sortOpt)))
return
}
c := &Connz{}
c.Now = time.Now()
auth, _ := strconv.Atoi(r.URL.Query().Get("auth"))
subs, _ := strconv.Atoi(r.URL.Query().Get("subs"))
c.Offset, _ = strconv.Atoi(r.URL.Query().Get("offset"))
c.Limit, _ = strconv.Atoi(r.URL.Query().Get("limit"))
if c.Limit == 0 {
c.Limit = DefaultConnListSize
}
// Walk the list
s.mu.Lock()
s.httpReqStats[ConnzPath]++
tlsRequired := s.info.TLSRequired
// number total of clients. The resulting ConnInfo array
// may be smaller if pagination is used.
totalClients := len(s.clients)
c.Total = totalClients
i := 0
pairs := make(Pairs, totalClients)
for _, client := range s.clients {
client.mu.Lock()
switch sortOpt {
case byCid:
pairs[i] = Pair{Key: client, Val: int64(client.cid)}
case bySubs:
pairs[i] = Pair{Key: client, Val: int64(len(client.subs))}
case byPending:
pairs[i] = Pair{Key: client, Val: int64(client.bw.Buffered())}
case byOutMsgs:
pairs[i] = Pair{Key: client, Val: client.outMsgs}
case byInMsgs:
pairs[i] = Pair{Key: client, Val: atomic.LoadInt64(&client.inMsgs)}
case byOutBytes:
pairs[i] = Pair{Key: client, Val: client.outBytes}
case byInBytes:
pairs[i] = Pair{Key: client, Val: atomic.LoadInt64(&client.inBytes)}
case byLast:
pairs[i] = Pair{Key: client, Val: client.last.UnixNano()}
case byIdle:
pairs[i] = Pair{Key: client, Val: c.Now.Sub(client.last).Nanoseconds()}
}
client.mu.Unlock()
i++
}
s.mu.Unlock()
if totalClients > 0 {
if sortOpt == byCid {
// Return in ascending order
sort.Sort(pairs)
} else {
// Return in descending order
sort.Sort(sort.Reverse(pairs))
}
}
minoff := c.Offset
maxoff := c.Offset + c.Limit
// Make sure these are sane.
if minoff > totalClients {
minoff = totalClients
}
if maxoff > totalClients {
maxoff = totalClients
}
pairs = pairs[minoff:maxoff]
// Now we have the real number of ConnInfo objects, we can set c.NumConns
// and allocate the array
c.NumConns = len(pairs)
c.Conns = make([]ConnInfo, c.NumConns)
i = 0
for _, pair := range pairs {
client := pair.Key
client.mu.Lock()
// First, fill ConnInfo with current client's values. We will
// then overwrite the field used for the sort with what was stored
// in 'pair'.
ci := &c.Conns[i]
ci.Cid = client.cid
ci.Start = client.start
ci.LastActivity = client.last
ci.Uptime = myUptime(c.Now.Sub(client.start))
ci.Idle = myUptime(c.Now.Sub(client.last))
ci.OutMsgs = client.outMsgs
ci.OutBytes = client.outBytes
ci.NumSubs = uint32(len(client.subs))
ci.Pending = client.bw.Buffered()
ci.Name = client.opts.Name
ci.Lang = client.opts.Lang
ci.Version = client.opts.Version
// inMsgs and inBytes are updated outside of the client's lock, so
// we need to use atomic here.
ci.InMsgs = atomic.LoadInt64(&client.inMsgs)
ci.InBytes = atomic.LoadInt64(&client.inBytes)
// Now overwrite the field that was used as the sort key, so results
// still look sorted even if the value has changed since sort occurred.
sortValue := pair.Val
switch sortOpt {
case bySubs:
ci.NumSubs = uint32(sortValue)
case byPending:
ci.Pending = int(sortValue)
case byOutMsgs:
ci.OutMsgs = sortValue
case byInMsgs:
ci.InMsgs = sortValue
case byOutBytes:
ci.OutBytes = sortValue
case byInBytes:
ci.InBytes = sortValue
case byLast:
ci.LastActivity = time.Unix(0, sortValue)
case byIdle:
ci.Idle = myUptime(time.Duration(sortValue))
}
// If the connection is gone, too bad, we won't set TLSVersion and TLSCipher.
if tlsRequired && client.nc != nil {
conn := client.nc.(*tls.Conn)
cs := conn.ConnectionState()
ci.TLSVersion = tlsVersion(cs.Version)
ci.TLSCipher = tlsCipher(cs.CipherSuite)
}
switch conn := client.nc.(type) {
case *net.TCPConn, *tls.Conn:
addr := conn.RemoteAddr().(*net.TCPAddr)
ci.Port = addr.Port
ci.IP = addr.IP.String()
}
// Fill in subscription data if requested.
if subs == 1 {
sublist := make([]*subscription, 0, len(client.subs))
for _, sub := range client.subs {
sublist = append(sublist, sub)
}
ci.Subs = castToSliceString(sublist)
}
// Fill in user if auth requested.
if auth == 1 {
ci.AuthorizedUser = client.opts.Username
}
client.mu.Unlock()
i++
}
b, err := json.MarshalIndent(c, "", " ")
if err != nil {
Errorf("Error marshalling response to /connz request: %v", err)
}
// Handle response
ResponseHandler(w, r, b)
}
func castToSliceString(input []*subscription) []string {
output := make([]string, 0, len(input))
for _, line := range input {
output = append(output, string(line.subject))
}
return output
}
// Subsz represents detail information on current connections.
type Subsz struct {
*SublistStats
}
// Routez represents detailed information on current client connections.
type Routez struct {
Now time.Time `json:"now"`
NumRoutes int `json:"num_routes"`
Routes []*RouteInfo `json:"routes"`
}
// RouteInfo has detailed information on a per connection basis.
type RouteInfo struct {
Rid uint64 `json:"rid"`
RemoteID string `json:"remote_id"`
DidSolicit bool `json:"did_solicit"`
IsConfigured bool `json:"is_configured"`
IP string `json:"ip"`
Port int `json:"port"`
Pending int `json:"pending_size"`
InMsgs int64 `json:"in_msgs"`
OutMsgs int64 `json:"out_msgs"`
InBytes int64 `json:"in_bytes"`
OutBytes int64 `json:"out_bytes"`
NumSubs uint32 `json:"subscriptions"`
Subs []string `json:"subscriptions_list,omitempty"`
}
// HandleRoutez process HTTP requests for route information.
func (s *Server) HandleRoutez(w http.ResponseWriter, r *http.Request) {
rs := &Routez{Routes: []*RouteInfo{}}
rs.Now = time.Now()
subs, _ := strconv.Atoi(r.URL.Query().Get("subs"))
// Walk the list
s.mu.Lock()
s.httpReqStats[RoutezPath]++
rs.NumRoutes = len(s.routes)
for _, r := range s.routes {
r.mu.Lock()
ri := &RouteInfo{
Rid: r.cid,
RemoteID: r.route.remoteID,
DidSolicit: r.route.didSolicit,
IsConfigured: r.route.routeType == Explicit,
InMsgs: atomic.LoadInt64(&r.inMsgs),
OutMsgs: r.outMsgs,
InBytes: atomic.LoadInt64(&r.inBytes),
OutBytes: r.outBytes,
NumSubs: uint32(len(r.subs)),
}
if subs == 1 {
sublist := make([]*subscription, 0, len(r.subs))
for _, sub := range r.subs {
sublist = append(sublist, sub)
}
ri.Subs = castToSliceString(sublist)
}
r.mu.Unlock()
if ip, ok := r.nc.(*net.TCPConn); ok {
addr := ip.RemoteAddr().(*net.TCPAddr)
ri.Port = addr.Port
ri.IP = addr.IP.String()
}
rs.Routes = append(rs.Routes, ri)
}
s.mu.Unlock()
b, err := json.MarshalIndent(rs, "", " ")
if err != nil {
Errorf("Error marshalling response to /routez request: %v", err)
}
// Handle response
ResponseHandler(w, r, b)
}
// HandleSubsz processes HTTP requests for subjects stats.
func (s *Server) HandleSubsz(w http.ResponseWriter, r *http.Request) {
s.mu.Lock()
s.httpReqStats[SubszPath]++
s.mu.Unlock()
st := &Subsz{s.sl.Stats()}
b, err := json.MarshalIndent(st, "", " ")
if err != nil {
Errorf("Error marshalling response to /subscriptionsz request: %v", err)
}
// Handle response
ResponseHandler(w, r, b)
}
// HandleStacksz processes HTTP requests for getting stacks
func (s *Server) HandleStacksz(w http.ResponseWriter, r *http.Request) {
// Do not get any lock here that would prevent getting the stacks
// if we were to have a deadlock somewhere.
var defaultBuf [defaultStackBufSize]byte
size := defaultStackBufSize
buf := defaultBuf[:size]
n := 0
for {
n = runtime.Stack(buf, true)
if n < size {
break
}
size *= 2
buf = make([]byte, size)
}
// Handle response
ResponseHandler(w, r, buf[:n])
}
// Varz will output server information on the monitoring port at /varz.
type Varz struct {
*Info
*Options
Port int `json:"port"`
MaxPayload int `json:"max_payload"`
Start time.Time `json:"start"`
Now time.Time `json:"now"`
Uptime string `json:"uptime"`
Mem int64 `json:"mem"`
Cores int `json:"cores"`
CPU float64 `json:"cpu"`
Connections int `json:"connections"`
TotalConnections uint64 `json:"total_connections"`
Routes int `json:"routes"`
Remotes int `json:"remotes"`
InMsgs int64 `json:"in_msgs"`
OutMsgs int64 `json:"out_msgs"`
InBytes int64 `json:"in_bytes"`
OutBytes int64 `json:"out_bytes"`
SlowConsumers int64 `json:"slow_consumers"`
Subscriptions uint32 `json:"subscriptions"`
HTTPReqStats map[string]uint64 `json:"http_req_stats"`
}
type usage struct {
CPU float32
Cores int
Mem int64
}
func myUptime(d time.Duration) string {
// Just use total seconds for uptime, and display days / years
tsecs := d / time.Second
tmins := tsecs / 60
thrs := tmins / 60
tdays := thrs / 24
tyrs := tdays / 365
if tyrs > 0 {
return fmt.Sprintf("%dy%dd%dh%dm%ds", tyrs, tdays%365, thrs%24, tmins%60, tsecs%60)
}
if tdays > 0 {
return fmt.Sprintf("%dd%dh%dm%ds", tdays, thrs%24, tmins%60, tsecs%60)
}
if thrs > 0 {
return fmt.Sprintf("%dh%dm%ds", thrs, tmins%60, tsecs%60)
}
if tmins > 0 {
return fmt.Sprintf("%dm%ds", tmins, tsecs%60)
}
return fmt.Sprintf("%ds", tsecs)
}
// HandleRoot will show basic info and links to others handlers.
func (s *Server) HandleRoot(w http.ResponseWriter, r *http.Request) {
// This feels dumb to me, but is required: https://code.google.com/p/go/issues/detail?id=4799
if r.URL.Path != "/" {
http.NotFound(w, r)
return
}
s.mu.Lock()
s.httpReqStats[RootPath]++
s.mu.Unlock()
fmt.Fprintf(w, `<html lang="en">
<head>
<link rel="shortcut icon" href="http://nats.io/img/favicon.ico">
<style type="text/css">
body { font-family: "Century Gothic", CenturyGothic, AppleGothic, sans-serif; font-size: 22; }
a { margin-left: 32px; }
</style>
</head>
<body>
<img src="http://nats.io/img/logo.png" alt="NATS">
<br/>
<a href=/varz>varz</a><br/>
<a href=/connz>connz</a><br/>
<a href=/routez>routez</a><br/>
<a href=/subsz>subsz</a><br/>
<br/>
<a href=http://nats.io/documentation/server/gnatsd-monitoring/>help</a>
</body>
</html>`)
}
// HandleVarz will process HTTP requests for server information.
func (s *Server) HandleVarz(w http.ResponseWriter, r *http.Request) {
v := &Varz{Info: &s.info, Options: s.opts, MaxPayload: s.opts.MaxPayload, Start: s.start}
v.Now = time.Now()
v.Uptime = myUptime(time.Since(s.start))
v.Port = v.Info.Port
updateUsage(v)
s.mu.Lock()
v.Connections = len(s.clients)
v.TotalConnections = s.totalClients
v.Routes = len(s.routes)
v.Remotes = len(s.remotes)
v.InMsgs = s.inMsgs
v.InBytes = s.inBytes
v.OutMsgs = s.outMsgs
v.OutBytes = s.outBytes
v.SlowConsumers = s.slowConsumers
v.Subscriptions = s.sl.Count()
s.httpReqStats[VarzPath]++
// Need a copy here since s.httpReqStas can change while doing
// the marshaling down below.
v.HTTPReqStats = make(map[string]uint64, len(s.httpReqStats))
for key, val := range s.httpReqStats {
v.HTTPReqStats[key] = val
}
s.mu.Unlock()
b, err := json.MarshalIndent(v, "", " ")
if err != nil {
Errorf("Error marshalling response to /varz request: %v", err)
}
// Handle response
ResponseHandler(w, r, b)
}
// Grab RSS and PCPU
func updateUsage(v *Varz) {
var rss, vss int64
var pcpu float64
pse.ProcUsage(&pcpu, &rss, &vss)
v.Mem = rss
v.CPU = pcpu
v.Cores = numCores
}
// ResponseHandler handles responses for monitoring routes
func ResponseHandler(w http.ResponseWriter, r *http.Request, data []byte) {
// Get callback from request
callback := r.URL.Query().Get("callback")
// If callback is not empty then
if callback != "" {
// Response for JSONP
w.Header().Set("Content-Type", "application/javascript")
fmt.Fprintf(w, "%s(%s)", callback, data)
} else {
// Otherwise JSON
w.Header().Set("Content-Type", "application/json")
w.Write(data)
}
}

View file

@ -0,0 +1,50 @@
// Copyright 2013-2016 Apcera Inc. All rights reserved.
package server
// SortOpt is a helper type to sort by ConnInfo values
type SortOpt string
const (
byCid SortOpt = "cid"
bySubs = "subs"
byPending = "pending"
byOutMsgs = "msgs_to"
byInMsgs = "msgs_from"
byOutBytes = "bytes_to"
byInBytes = "bytes_from"
byLast = "last"
byIdle = "idle"
byUptime = "uptime"
)
// IsValid determines if a sort option is valid
func (s SortOpt) IsValid() bool {
switch s {
case "", byCid, bySubs, byPending, byOutMsgs, byInMsgs, byOutBytes, byInBytes, byLast, byIdle, byUptime:
return true
default:
return false
}
}
// Pair type is internally used.
type Pair struct {
Key *client
Val int64
}
// Pairs type is internally used.
type Pairs []Pair
func (d Pairs) Len() int {
return len(d)
}
func (d Pairs) Swap(i, j int) {
d[i], d[j] = d[j], d[i]
}
func (d Pairs) Less(i, j int) bool {
return d[i].Val < d[j].Val
}

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

@ -0,0 +1,802 @@
// Copyright 2012-2016 Apcera Inc. All rights reserved.
package server
import (
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"net"
"net/url"
"os"
"strconv"
"strings"
"time"
"github.com/nats-io/gnatsd/conf"
)
// For multiple accounts/users.
type User struct {
Username string `json:"user"`
Password string `json:"password"`
Permissions *Permissions `json:"permissions"`
}
// Authorization are the allowed subjects on a per
// publish or subscribe basis.
type Permissions struct {
Publish []string `json:"publish"`
Subscribe []string `json:"subscribe"`
}
// Options for clusters.
type ClusterOpts struct {
Host string `json:"addr"`
Port int `json:"cluster_port"`
Username string `json:"-"`
Password string `json:"-"`
AuthTimeout float64 `json:"auth_timeout"`
TLSTimeout float64 `json:"-"`
TLSConfig *tls.Config `json:"-"`
ListenStr string `json:"-"`
NoAdvertise bool `json:"-"`
}
// Options block for gnatsd server.
type Options struct {
Host string `json:"addr"`
Port int `json:"port"`
Trace bool `json:"-"`
Debug bool `json:"-"`
NoLog bool `json:"-"`
NoSigs bool `json:"-"`
Logtime bool `json:"-"`
MaxConn int `json:"max_connections"`
Users []*User `json:"-"`
Username string `json:"-"`
Password string `json:"-"`
Authorization string `json:"-"`
PingInterval time.Duration `json:"ping_interval"`
MaxPingsOut int `json:"ping_max"`
HTTPHost string `json:"http_host"`
HTTPPort int `json:"http_port"`
HTTPSPort int `json:"https_port"`
AuthTimeout float64 `json:"auth_timeout"`
MaxControlLine int `json:"max_control_line"`
MaxPayload int `json:"max_payload"`
Cluster ClusterOpts `json:"cluster"`
ProfPort int `json:"-"`
PidFile string `json:"-"`
LogFile string `json:"-"`
Syslog bool `json:"-"`
RemoteSyslog string `json:"-"`
Routes []*url.URL `json:"-"`
RoutesStr string `json:"-"`
TLSTimeout float64 `json:"tls_timeout"`
TLS bool `json:"-"`
TLSVerify bool `json:"-"`
TLSCert string `json:"-"`
TLSKey string `json:"-"`
TLSCaCert string `json:"-"`
TLSConfig *tls.Config `json:"-"`
}
// Configuration file authorization section.
type authorization struct {
// Singles
user string
pass string
// Multiple Users
users []*User
timeout float64
defaultPermissions *Permissions
}
// TLSConfigOpts holds the parsed tls config information,
// used with flag parsing
type TLSConfigOpts struct {
CertFile string
KeyFile string
CaFile string
Verify bool
Timeout float64
Ciphers []uint16
}
var tlsUsage = `
TLS configuration is specified in the tls section of a configuration file:
e.g.
tls {
cert_file: "./certs/server-cert.pem"
key_file: "./certs/server-key.pem"
ca_file: "./certs/ca.pem"
verify: true
cipher_suites: [
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
]
}
Available cipher suites include:
`
// ProcessConfigFile processes a configuration file.
// FIXME(dlc): Hacky
func ProcessConfigFile(configFile string) (*Options, error) {
opts := &Options{}
if configFile == "" {
return opts, nil
}
m, err := conf.ParseFile(configFile)
if err != nil {
return nil, err
}
for k, v := range m {
switch strings.ToLower(k) {
case "listen":
hp, err := parseListen(v)
if err != nil {
return nil, err
}
opts.Host = hp.host
opts.Port = hp.port
case "port":
opts.Port = int(v.(int64))
case "host", "net":
opts.Host = v.(string)
case "debug":
opts.Debug = v.(bool)
case "trace":
opts.Trace = v.(bool)
case "logtime":
opts.Logtime = v.(bool)
case "authorization":
am := v.(map[string]interface{})
auth, err := parseAuthorization(am)
if err != nil {
return nil, err
}
opts.Username = auth.user
opts.Password = auth.pass
opts.AuthTimeout = auth.timeout
// Check for multiple users defined
if auth.users != nil {
if auth.user != "" {
return nil, fmt.Errorf("Can not have a single user/pass and a users array")
}
opts.Users = auth.users
}
case "http":
hp, err := parseListen(v)
if err != nil {
return nil, err
}
opts.HTTPHost = hp.host
opts.HTTPPort = hp.port
case "https":
hp, err := parseListen(v)
if err != nil {
return nil, err
}
opts.HTTPHost = hp.host
opts.HTTPSPort = hp.port
case "http_port", "monitor_port":
opts.HTTPPort = int(v.(int64))
case "https_port":
opts.HTTPSPort = int(v.(int64))
case "cluster":
cm := v.(map[string]interface{})
if err := parseCluster(cm, opts); err != nil {
return nil, err
}
case "logfile", "log_file":
opts.LogFile = v.(string)
case "syslog":
opts.Syslog = v.(bool)
case "remote_syslog":
opts.RemoteSyslog = v.(string)
case "pidfile", "pid_file":
opts.PidFile = v.(string)
case "prof_port":
opts.ProfPort = int(v.(int64))
case "max_control_line":
opts.MaxControlLine = int(v.(int64))
case "max_payload":
opts.MaxPayload = int(v.(int64))
case "max_connections", "max_conn":
opts.MaxConn = int(v.(int64))
case "ping_interval":
opts.PingInterval = time.Duration(int(v.(int64))) * time.Second
case "ping_max":
opts.MaxPingsOut = int(v.(int64))
case "tls":
tlsm := v.(map[string]interface{})
tc, err := parseTLS(tlsm)
if err != nil {
return nil, err
}
if opts.TLSConfig, err = GenTLSConfig(tc); err != nil {
return nil, err
}
opts.TLSTimeout = tc.Timeout
}
}
return opts, nil
}
// hostPort is simple struct to hold parsed listen/addr strings.
type hostPort struct {
host string
port int
}
// parseListen will parse listen option which is replacing host/net and port
func parseListen(v interface{}) (*hostPort, error) {
hp := &hostPort{}
switch v.(type) {
// Only a port
case int64:
hp.port = int(v.(int64))
case string:
host, port, err := net.SplitHostPort(v.(string))
if err != nil {
return nil, fmt.Errorf("Could not parse address string %q", v)
}
hp.port, err = strconv.Atoi(port)
if err != nil {
return nil, fmt.Errorf("Could not parse port %q", port)
}
hp.host = host
}
return hp, nil
}
// parseCluster will parse the cluster config.
func parseCluster(cm map[string]interface{}, opts *Options) error {
for mk, mv := range cm {
switch strings.ToLower(mk) {
case "listen":
hp, err := parseListen(mv)
if err != nil {
return err
}
opts.Cluster.Host = hp.host
opts.Cluster.Port = hp.port
case "port":
opts.Cluster.Port = int(mv.(int64))
case "host", "net":
opts.Cluster.Host = mv.(string)
case "authorization":
am := mv.(map[string]interface{})
auth, err := parseAuthorization(am)
if err != nil {
return err
}
if auth.users != nil {
return fmt.Errorf("Cluster authorization does not allow multiple users")
}
opts.Cluster.Username = auth.user
opts.Cluster.Password = auth.pass
opts.Cluster.AuthTimeout = auth.timeout
case "routes":
ra := mv.([]interface{})
opts.Routes = make([]*url.URL, 0, len(ra))
for _, r := range ra {
routeURL := r.(string)
url, err := url.Parse(routeURL)
if err != nil {
return fmt.Errorf("error parsing route url [%q]", routeURL)
}
opts.Routes = append(opts.Routes, url)
}
case "tls":
tlsm := mv.(map[string]interface{})
tc, err := parseTLS(tlsm)
if err != nil {
return err
}
if opts.Cluster.TLSConfig, err = GenTLSConfig(tc); err != nil {
return err
}
// For clusters, we will force strict verification. We also act
// as both client and server, so will mirror the rootCA to the
// clientCA pool.
opts.Cluster.TLSConfig.ClientAuth = tls.RequireAndVerifyClientCert
opts.Cluster.TLSConfig.RootCAs = opts.Cluster.TLSConfig.ClientCAs
opts.Cluster.TLSTimeout = tc.Timeout
case "no_advertise":
opts.Cluster.NoAdvertise = mv.(bool)
}
}
return nil
}
// Helper function to parse Authorization configs.
func parseAuthorization(am map[string]interface{}) (*authorization, error) {
auth := &authorization{}
for mk, mv := range am {
switch strings.ToLower(mk) {
case "user", "username":
auth.user = mv.(string)
case "pass", "password":
auth.pass = mv.(string)
case "timeout":
at := float64(1)
switch mv.(type) {
case int64:
at = float64(mv.(int64))
case float64:
at = mv.(float64)
}
auth.timeout = at
case "users":
users, err := parseUsers(mv)
if err != nil {
return nil, err
}
auth.users = users
case "default_permission", "default_permissions":
pm, ok := mv.(map[string]interface{})
if !ok {
return nil, fmt.Errorf("Expected default permissions to be a map/struct, got %+v", mv)
}
permissions, err := parseUserPermissions(pm)
if err != nil {
return nil, err
}
auth.defaultPermissions = permissions
}
// Now check for permission defaults with multiple users, etc.
if auth.users != nil && auth.defaultPermissions != nil {
for _, user := range auth.users {
if user.Permissions == nil {
user.Permissions = auth.defaultPermissions
}
}
}
}
return auth, nil
}
// Helper function to parse multiple users array with optional permissions.
func parseUsers(mv interface{}) ([]*User, error) {
// Make sure we have an array
uv, ok := mv.([]interface{})
if !ok {
return nil, fmt.Errorf("Expected users field to be an array, got %v", mv)
}
users := []*User{}
for _, u := range uv {
// Check its a map/struct
um, ok := u.(map[string]interface{})
if !ok {
return nil, fmt.Errorf("Expected user entry to be a map/struct, got %v", u)
}
user := &User{}
for k, v := range um {
switch strings.ToLower(k) {
case "user", "username":
user.Username = v.(string)
case "pass", "password":
user.Password = v.(string)
case "permission", "permissions", "authroization":
pm, ok := v.(map[string]interface{})
if !ok {
return nil, fmt.Errorf("Expected user permissions to be a map/struct, got %+v", v)
}
permissions, err := parseUserPermissions(pm)
if err != nil {
return nil, err
}
user.Permissions = permissions
}
}
// Check to make sure we have at least username and password
if user.Username == "" || user.Password == "" {
return nil, fmt.Errorf("User entry requires a user and a password")
}
users = append(users, user)
}
return users, nil
}
// Helper function to parse user/account permissions
func parseUserPermissions(pm map[string]interface{}) (*Permissions, error) {
p := &Permissions{}
for k, v := range pm {
switch strings.ToLower(k) {
case "pub", "publish":
subjects, err := parseSubjects(v)
if err != nil {
return nil, err
}
p.Publish = subjects
case "sub", "subscribe":
subjects, err := parseSubjects(v)
if err != nil {
return nil, err
}
p.Subscribe = subjects
default:
return nil, fmt.Errorf("Unknown field %s parsing permissions", k)
}
}
return p, nil
}
// Helper function to parse subject singeltons and/or arrays
func parseSubjects(v interface{}) ([]string, error) {
var subjects []string
switch v.(type) {
case string:
subjects = append(subjects, v.(string))
case []string:
subjects = v.([]string)
case []interface{}:
for _, i := range v.([]interface{}) {
subject, ok := i.(string)
if !ok {
return nil, fmt.Errorf("Subject in permissions array cannot be cast to string")
}
subjects = append(subjects, subject)
}
default:
return nil, fmt.Errorf("Expected subject permissions to be a subject, or array of subjects, got %T", v)
}
return checkSubjectArray(subjects)
}
// Helper function to validate subjects, etc for account permissioning.
func checkSubjectArray(sa []string) ([]string, error) {
for _, s := range sa {
if !IsValidSubject(s) {
return nil, fmt.Errorf("Subject %q is not a valid subject", s)
}
}
return sa, nil
}
// PrintTLSHelpAndDie prints TLS usage and exits.
func PrintTLSHelpAndDie() {
fmt.Printf("%s\n", tlsUsage)
for k := range cipherMap {
fmt.Printf(" %s\n", k)
}
fmt.Printf("\n")
os.Exit(0)
}
func parseCipher(cipherName string) (uint16, error) {
cipher, exists := cipherMap[cipherName]
if !exists {
return 0, fmt.Errorf("Unrecognized cipher %s", cipherName)
}
return cipher, nil
}
// Helper function to parse TLS configs.
func parseTLS(tlsm map[string]interface{}) (*TLSConfigOpts, error) {
tc := TLSConfigOpts{}
for mk, mv := range tlsm {
switch strings.ToLower(mk) {
case "cert_file":
certFile, ok := mv.(string)
if !ok {
return nil, fmt.Errorf("error parsing tls config, expected 'cert_file' to be filename")
}
tc.CertFile = certFile
case "key_file":
keyFile, ok := mv.(string)
if !ok {
return nil, fmt.Errorf("error parsing tls config, expected 'key_file' to be filename")
}
tc.KeyFile = keyFile
case "ca_file":
caFile, ok := mv.(string)
if !ok {
return nil, fmt.Errorf("error parsing tls config, expected 'ca_file' to be filename")
}
tc.CaFile = caFile
case "verify":
verify, ok := mv.(bool)
if !ok {
return nil, fmt.Errorf("error parsing tls config, expected 'verify' to be a boolean")
}
tc.Verify = verify
case "cipher_suites":
ra := mv.([]interface{})
if len(ra) == 0 {
return nil, fmt.Errorf("error parsing tls config, 'cipher_suites' cannot be empty")
}
tc.Ciphers = make([]uint16, 0, len(ra))
for _, r := range ra {
cipher, err := parseCipher(r.(string))
if err != nil {
return nil, err
}
tc.Ciphers = append(tc.Ciphers, cipher)
}
case "timeout":
at := float64(0)
switch mv.(type) {
case int64:
at = float64(mv.(int64))
case float64:
at = mv.(float64)
}
tc.Timeout = at
default:
return nil, fmt.Errorf("error parsing tls config, unknown field [%q]", mk)
}
}
// If cipher suites were not specified then use the defaults
if tc.Ciphers == nil {
tc.Ciphers = defaultCipherSuites()
}
return &tc, nil
}
// GenTLSConfig loads TLS related configuration parameters.
func GenTLSConfig(tc *TLSConfigOpts) (*tls.Config, error) {
// Now load in cert and private key
cert, err := tls.LoadX509KeyPair(tc.CertFile, tc.KeyFile)
if err != nil {
return nil, fmt.Errorf("error parsing X509 certificate/key pair: %v", err)
}
cert.Leaf, err = x509.ParseCertificate(cert.Certificate[0])
if err != nil {
return nil, fmt.Errorf("error parsing certificate: %v", err)
}
// Create TLSConfig
// We will determine the cipher suites that we prefer.
config := tls.Config{
Certificates: []tls.Certificate{cert},
PreferServerCipherSuites: true,
MinVersion: tls.VersionTLS12,
CipherSuites: tc.Ciphers,
}
// Require client certificates as needed
if tc.Verify {
config.ClientAuth = tls.RequireAndVerifyClientCert
}
// Add in CAs if applicable.
if tc.CaFile != "" {
rootPEM, err := ioutil.ReadFile(tc.CaFile)
if err != nil || rootPEM == nil {
return nil, err
}
pool := x509.NewCertPool()
ok := pool.AppendCertsFromPEM([]byte(rootPEM))
if !ok {
return nil, fmt.Errorf("failed to parse root ca certificate")
}
config.ClientCAs = pool
}
return &config, nil
}
// MergeOptions will merge two options giving preference to the flagOpts
// if the item is present.
func MergeOptions(fileOpts, flagOpts *Options) *Options {
if fileOpts == nil {
return flagOpts
}
if flagOpts == nil {
return fileOpts
}
// Merge the two, flagOpts override
opts := *fileOpts
if flagOpts.Port != 0 {
opts.Port = flagOpts.Port
}
if flagOpts.Host != "" {
opts.Host = flagOpts.Host
}
if flagOpts.Username != "" {
opts.Username = flagOpts.Username
}
if flagOpts.Password != "" {
opts.Password = flagOpts.Password
}
if flagOpts.Authorization != "" {
opts.Authorization = flagOpts.Authorization
}
if flagOpts.HTTPPort != 0 {
opts.HTTPPort = flagOpts.HTTPPort
}
if flagOpts.Debug {
opts.Debug = true
}
if flagOpts.Trace {
opts.Trace = true
}
if flagOpts.Logtime {
opts.Logtime = true
}
if flagOpts.LogFile != "" {
opts.LogFile = flagOpts.LogFile
}
if flagOpts.PidFile != "" {
opts.PidFile = flagOpts.PidFile
}
if flagOpts.ProfPort != 0 {
opts.ProfPort = flagOpts.ProfPort
}
if flagOpts.Cluster.ListenStr != "" {
opts.Cluster.ListenStr = flagOpts.Cluster.ListenStr
}
if flagOpts.Cluster.NoAdvertise {
opts.Cluster.NoAdvertise = true
}
if flagOpts.RoutesStr != "" {
mergeRoutes(&opts, flagOpts)
}
return &opts
}
// RoutesFromStr parses route URLs from a string
func RoutesFromStr(routesStr string) []*url.URL {
routes := strings.Split(routesStr, ",")
if len(routes) == 0 {
return nil
}
routeUrls := []*url.URL{}
for _, r := range routes {
r = strings.TrimSpace(r)
u, _ := url.Parse(r)
routeUrls = append(routeUrls, u)
}
return routeUrls
}
// This will merge the flag routes and override anything that was present.
func mergeRoutes(opts, flagOpts *Options) {
routeUrls := RoutesFromStr(flagOpts.RoutesStr)
if routeUrls == nil {
return
}
opts.Routes = routeUrls
opts.RoutesStr = flagOpts.RoutesStr
}
// RemoveSelfReference removes this server from an array of routes
func RemoveSelfReference(clusterPort int, routes []*url.URL) ([]*url.URL, error) {
var cleanRoutes []*url.URL
cport := strconv.Itoa(clusterPort)
selfIPs := getInterfaceIPs()
for _, r := range routes {
host, port, err := net.SplitHostPort(r.Host)
if err != nil {
return nil, err
}
if cport == port && isIPInList(selfIPs, getURLIP(host)) {
Noticef("Self referencing IP found: ", r)
continue
}
cleanRoutes = append(cleanRoutes, r)
}
return cleanRoutes, nil
}
func isIPInList(list1 []net.IP, list2 []net.IP) bool {
for _, ip1 := range list1 {
for _, ip2 := range list2 {
if ip1.Equal(ip2) {
return true
}
}
}
return false
}
func getURLIP(ipStr string) []net.IP {
ipList := []net.IP{}
ip := net.ParseIP(ipStr)
if ip != nil {
ipList = append(ipList, ip)
return ipList
}
hostAddr, err := net.LookupHost(ipStr)
if err != nil {
Errorf("Error looking up host with route hostname: %v", err)
return ipList
}
for _, addr := range hostAddr {
ip = net.ParseIP(addr)
if ip != nil {
ipList = append(ipList, ip)
}
}
return ipList
}
func getInterfaceIPs() []net.IP {
var localIPs []net.IP
interfaceAddr, err := net.InterfaceAddrs()
if err != nil {
Errorf("Error getting self referencing address: %v", err)
return localIPs
}
for i := 0; i < len(interfaceAddr); i++ {
interfaceIP, _, _ := net.ParseCIDR(interfaceAddr[i].String())
if net.ParseIP(interfaceIP.String()) != nil {
localIPs = append(localIPs, interfaceIP)
} else {
Errorf("Error parsing self referencing address: %v", err)
}
}
return localIPs
}
func processOptions(opts *Options) {
// Setup non-standard Go defaults
if opts.Host == "" {
opts.Host = DEFAULT_HOST
}
if opts.HTTPHost == "" {
// Default to same bind from server if left undefined
opts.HTTPHost = opts.Host
}
if opts.Port == 0 {
opts.Port = DEFAULT_PORT
} else if opts.Port == RANDOM_PORT {
// Choose randomly inside of net.Listen
opts.Port = 0
}
if opts.MaxConn == 0 {
opts.MaxConn = DEFAULT_MAX_CONNECTIONS
}
if opts.PingInterval == 0 {
opts.PingInterval = DEFAULT_PING_INTERVAL
}
if opts.MaxPingsOut == 0 {
opts.MaxPingsOut = DEFAULT_PING_MAX_OUT
}
if opts.TLSTimeout == 0 {
opts.TLSTimeout = float64(TLS_TIMEOUT) / float64(time.Second)
}
if opts.AuthTimeout == 0 {
opts.AuthTimeout = float64(AUTH_TIMEOUT) / float64(time.Second)
}
if opts.Cluster.Host == "" {
opts.Cluster.Host = DEFAULT_HOST
}
if opts.Cluster.TLSTimeout == 0 {
opts.Cluster.TLSTimeout = float64(TLS_TIMEOUT) / float64(time.Second)
}
if opts.Cluster.AuthTimeout == 0 {
opts.Cluster.AuthTimeout = float64(AUTH_TIMEOUT) / float64(time.Second)
}
if opts.MaxControlLine == 0 {
opts.MaxControlLine = MAX_CONTROL_LINE_SIZE
}
if opts.MaxPayload == 0 {
opts.MaxPayload = MAX_PAYLOAD_SIZE
}
}

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

@ -0,0 +1,738 @@
// Copyright 2012-2014 Apcera Inc. All rights reserved.
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.opts != nil {
mcl = c.srv.opts.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()
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):]
}

View file

@ -0,0 +1,23 @@
// Copyright 2015-2016 Apcera Inc. All rights reserved.
package pse
import (
"errors"
"fmt"
"os"
"os/exec"
)
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 errors.New(fmt.Sprintf("ps call failed:%v", err))
}
fmt.Sscanf(string(out), "%f %d %d", pcpu, rss, vss)
*rss *= 1024 // 1k blocks, want bytes.
*vss *= 1024 // 1k blocks, want bytes.
return nil
}

View file

@ -0,0 +1,72 @@
// Copyright 2015-2016 Apcera Inc. All rights reserved.
package pse
/*
#include <sys/types.h>
#include <sys/sysctl.h>
#include <sys/user.h>
#include <stddef.h>
#include <unistd.h>
long pagetok(long size)
{
int pageshift, pagesize;
pagesize = getpagesize();
pageshift = 0;
while (pagesize > 1) {
pageshift++;
pagesize >>= 1;
}
return (size << pageshift);
}
int getusage(double *pcpu, unsigned int *rss, unsigned int *vss)
{
int mib[4], ret;
size_t len;
struct kinfo_proc kp;
len = 4;
sysctlnametomib("kern.proc.pid", mib, &len);
mib[3] = getpid();
len = sizeof(kp);
ret = sysctl(mib, 4, &kp, &len, NULL, 0);
if (ret != 0) {
return (errno);
}
*rss = pagetok(kp.ki_rssize);
*vss = kp.ki_size;
*pcpu = kp.ki_pctcpu;
return 0;
}
*/
import "C"
import (
"syscall"
)
// This is a placeholder for now.
func ProcUsage(pcpu *float64, rss, vss *int64) error {
var r, v C.uint
var c C.double
if ret := C.getusage(&c, &r, &v); ret != 0 {
return syscall.Errno(ret)
}
*pcpu = float64(c)
*rss = int64(r)
*vss = int64(v)
return nil
}

View file

@ -0,0 +1,115 @@
// Copyright 2015 Apcera Inc. All rights reserved.
package pse
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"sync/atomic"
"syscall"
"time"
)
var (
procStatFile string
ticks int64
lastTotal int64
lastSeconds int64
ipcpu int64
)
const (
utimePos = 13
stimePos = 14
startPos = 21
vssPos = 22
rssPos = 23
)
func init() {
// Avoiding to generate docker image without CGO
ticks = 100 // int64(C.sysconf(C._SC_CLK_TCK))
procStatFile = fmt.Sprintf("/proc/%d/stat", os.Getpid())
periodic()
}
// Sampling function to keep pcpu relevant.
func periodic() {
contents, err := ioutil.ReadFile(procStatFile)
if err != nil {
return
}
fields := bytes.Fields(contents)
// PCPU
pstart := parseInt64(fields[startPos])
utime := parseInt64(fields[utimePos])
stime := parseInt64(fields[stimePos])
total := utime + stime
var sysinfo syscall.Sysinfo_t
if err := syscall.Sysinfo(&sysinfo); err != nil {
return
}
seconds := int64(sysinfo.Uptime) - (pstart / ticks)
// Save off temps
lt := lastTotal
ls := lastSeconds
// Update last sample
lastTotal = total
lastSeconds = seconds
// Adjust to current time window
total -= lt
seconds -= ls
if seconds > 0 {
atomic.StoreInt64(&ipcpu, (total*1000/ticks)/seconds)
}
time.AfterFunc(1*time.Second, periodic)
}
func ProcUsage(pcpu *float64, rss, vss *int64) error {
contents, err := ioutil.ReadFile(procStatFile)
if err != nil {
return err
}
fields := bytes.Fields(contents)
// Memory
*rss = (parseInt64(fields[rssPos])) << 12
*vss = parseInt64(fields[vssPos])
// PCPU
// We track this with periodic sampling, so just load and go.
*pcpu = float64(atomic.LoadInt64(&ipcpu)) / 10.0
return nil
}
// Ascii numbers 0-9
const (
asciiZero = 48
asciiNine = 57
)
// parseInt64 expects decimal positive numbers. We
// return -1 to signal error
func parseInt64(d []byte) (n int64) {
if len(d) == 0 {
return -1
}
for _, dec := range d {
if dec < asciiZero || dec > asciiNine {
return -1
}
n = n*10 + (int64(dec) - asciiZero)
}
return n
}

View file

@ -0,0 +1,13 @@
// Copyright 2015-2016 Apcera Inc. All rights reserved.
// +build rumprun
package pse
// This is a placeholder for now.
func ProcUsage(pcpu *float64, rss, vss *int64) error {
*pcpu = 0.0
*rss = 0
*vss = 0
return nil
}

View file

@ -0,0 +1,12 @@
// Copyright 2015-2016 Apcera Inc. All rights reserved.
package pse
// This is a placeholder for now.
func ProcUsage(pcpu *float64, rss, vss *int64) error {
*pcpu = 0.0
*rss = 0
*vss = 0
return nil
}

View file

@ -0,0 +1,268 @@
// Copyright 2015-2016 Apcera Inc. All rights reserved.
// +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
}

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

@ -0,0 +1,731 @@
// Copyright 2013-2016 Apcera Inc. All rights reserved.
package server
import (
"bufio"
"bytes"
"crypto/tls"
"encoding/json"
"fmt"
"net"
"net/url"
"regexp"
"strconv"
"strings"
"sync/atomic"
"time"
"github.com/nats-io/gnatsd/util"
)
// RouteType designates the router type
type RouteType int
// Type of Route
const (
// This route we learned from speaking to other routes.
Implicit RouteType = iota
// This route was explicitly configured.
Explicit
)
type route struct {
remoteID string
didSolicit bool
retry bool
routeType RouteType
url *url.URL
authRequired bool
tlsRequired bool
}
type connectInfo struct {
Verbose bool `json:"verbose"`
Pedantic bool `json:"pedantic"`
User string `json:"user,omitempty"`
Pass string `json:"pass,omitempty"`
TLS bool `json:"tls_required"`
Name string `json:"name"`
}
// Route protocol constants
const (
ConProto = "CONNECT %s" + _CRLF_
InfoProto = "INFO %s" + _CRLF_
)
// Lock should be held entering here.
func (c *client) sendConnect(tlsRequired bool) {
var user, pass string
if userInfo := c.route.url.User; userInfo != nil {
user = userInfo.Username()
pass, _ = userInfo.Password()
}
cinfo := connectInfo{
Verbose: false,
Pedantic: false,
User: user,
Pass: pass,
TLS: tlsRequired,
Name: c.srv.info.ID,
}
b, err := json.Marshal(cinfo)
if err != nil {
c.Errorf("Error marshalling CONNECT to route: %v\n", err)
c.closeConnection()
return
}
c.sendProto([]byte(fmt.Sprintf(ConProto, b)), true)
}
// Process the info message if we are a route.
func (c *client) processRouteInfo(info *Info) {
c.mu.Lock()
// Connection can be closed at any time (by auth timeout, etc).
// Does not make sense to continue here if connection is gone.
if c.route == nil || c.nc == nil {
c.mu.Unlock()
return
}
s := c.srv
remoteID := c.route.remoteID
// We receive an INFO from a server that informs us about another server,
// so the info.ID in the INFO protocol does not match the ID of this route.
if remoteID != "" && remoteID != info.ID {
c.mu.Unlock()
// Process this implicit route. We will check that it is not an explicit
// route and/or that it has not been connected already.
s.processImplicitRoute(info)
return
}
// Need to set this for the detection of the route to self to work
// in closeConnection().
c.route.remoteID = info.ID
// Detect route to self.
if c.route.remoteID == s.info.ID {
c.mu.Unlock()
c.closeConnection()
return
}
// Copy over important information.
c.route.authRequired = info.AuthRequired
c.route.tlsRequired = info.TLSRequired
// If we do not know this route's URL, construct one on the fly
// from the information provided.
if c.route.url == nil {
// Add in the URL from host and port
hp := net.JoinHostPort(info.Host, strconv.Itoa(info.Port))
url, err := url.Parse(fmt.Sprintf("nats-route://%s/", hp))
if err != nil {
c.Errorf("Error parsing URL from INFO: %v\n", err)
c.mu.Unlock()
c.closeConnection()
return
}
c.route.url = url
}
// Check to see if we have this remote already registered.
// This can happen when both servers have routes to each other.
c.mu.Unlock()
if added, sendInfo := s.addRoute(c, info); added {
c.Debugf("Registering remote route %q", info.ID)
// Send our local subscriptions to this route.
s.sendLocalSubsToRoute(c)
if sendInfo {
// Need to get the remote IP address.
c.mu.Lock()
switch conn := c.nc.(type) {
case *net.TCPConn, *tls.Conn:
addr := conn.RemoteAddr().(*net.TCPAddr)
info.IP = fmt.Sprintf("nats-route://%s/", net.JoinHostPort(addr.IP.String(), strconv.Itoa(info.Port)))
default:
info.IP = fmt.Sprintf("%s", c.route.url)
}
c.mu.Unlock()
// Now let the known servers know about this new route
s.forwardNewRouteInfoToKnownServers(info)
}
// If the server Info did not have these URLs, update and send an INFO
// protocol to all clients that support it (unless the feature is disabled).
if s.updateServerINFO(info.ClientConnectURLs) {
s.sendAsyncInfoToClients()
}
} else {
c.Debugf("Detected duplicate remote route %q", info.ID)
c.closeConnection()
}
}
// sendAsyncInfoToClients sends an INFO protocol to all
// connected clients that accept async INFO updates.
func (s *Server) sendAsyncInfoToClients() {
s.mu.Lock()
// If there are no clients supporting async INFO protocols, we are done.
if s.cproto == 0 {
s.mu.Unlock()
return
}
// Capture under lock
proto := s.infoJSON
// Make a copy of ALL clients so we can release server lock while
// sending the protocol to clients. We could check the conditions
// (proto support, first PONG sent) here and so have potentially
// a limited number of clients, but that would mean grabbing the
// client's lock here, which we don't want since we would still
// need it in the second loop.
clients := make([]*client, 0, len(s.clients))
for _, c := range s.clients {
clients = append(clients, c)
}
s.mu.Unlock()
for _, c := range clients {
c.mu.Lock()
// If server did not yet receive the CONNECT protocol, check later
// when sending the first PONG.
if !c.flags.isSet(connectReceived) {
c.flags.set(infoUpdated)
} else if c.opts.Protocol >= ClientProtoInfo {
// Send only if first PONG was sent
if c.flags.isSet(firstPongSent) {
// sendInfo takes care of checking if the connection is still
// valid or not, so don't duplicate tests here.
c.sendInfo(proto)
} else {
// Otherwise, notify that INFO has changed and check later.
c.flags.set(infoUpdated)
}
}
c.mu.Unlock()
}
}
// This will process implicit route information received from another server.
// We will check to see if we have configured or are already connected,
// and if so we will ignore. Otherwise we will attempt to connect.
func (s *Server) processImplicitRoute(info *Info) {
remoteID := info.ID
s.mu.Lock()
defer s.mu.Unlock()
// Don't connect to ourself
if remoteID == s.info.ID {
return
}
// Check if this route already exists
if _, exists := s.remotes[remoteID]; exists {
return
}
// Check if we have this route as a configured route
if s.hasThisRouteConfigured(info) {
return
}
// Initiate the connection, using info.IP instead of info.URL here...
r, err := url.Parse(info.IP)
if err != nil {
Debugf("Error parsing URL from INFO: %v\n", err)
return
}
if info.AuthRequired {
r.User = url.UserPassword(s.opts.Cluster.Username, s.opts.Cluster.Password)
}
s.startGoRoutine(func() { s.connectToRoute(r, false) })
}
// hasThisRouteConfigured returns true if info.Host:info.Port is present
// in the server's opts.Routes, false otherwise.
// Server lock is assumed to be held by caller.
func (s *Server) hasThisRouteConfigured(info *Info) bool {
urlToCheckExplicit := strings.ToLower(net.JoinHostPort(info.Host, strconv.Itoa(info.Port)))
for _, ri := range s.opts.Routes {
if strings.ToLower(ri.Host) == urlToCheckExplicit {
return true
}
}
return false
}
// forwardNewRouteInfoToKnownServers sends the INFO protocol of the new route
// to all routes known by this server. In turn, each server will contact this
// new route.
func (s *Server) forwardNewRouteInfoToKnownServers(info *Info) {
s.mu.Lock()
defer s.mu.Unlock()
b, _ := json.Marshal(info)
infoJSON := []byte(fmt.Sprintf(InfoProto, b))
for _, r := range s.routes {
r.mu.Lock()
if r.route.remoteID != info.ID {
r.sendInfo(infoJSON)
}
r.mu.Unlock()
}
}
// This will send local subscription state to a new route connection.
// FIXME(dlc) - This could be a DOS or perf issue with many clients
// and large subscription space. Plus buffering in place not a good idea.
func (s *Server) sendLocalSubsToRoute(route *client) {
b := bytes.Buffer{}
s.mu.Lock()
for _, client := range s.clients {
client.mu.Lock()
subs := make([]*subscription, 0, len(client.subs))
for _, sub := range client.subs {
subs = append(subs, sub)
}
client.mu.Unlock()
for _, sub := range subs {
rsid := routeSid(sub)
proto := fmt.Sprintf(subProto, sub.subject, sub.queue, rsid)
b.WriteString(proto)
}
}
s.mu.Unlock()
route.mu.Lock()
defer route.mu.Unlock()
route.sendProto(b.Bytes(), true)
route.Debugf("Route sent local subscriptions")
}
func (s *Server) createRoute(conn net.Conn, rURL *url.URL) *client {
didSolicit := rURL != nil
r := &route{didSolicit: didSolicit}
for _, route := range s.opts.Routes {
if rURL != nil && (strings.ToLower(rURL.Host) == strings.ToLower(route.Host)) {
r.routeType = Explicit
}
}
c := &client{srv: s, nc: conn, opts: clientOpts{}, typ: ROUTER, route: r}
// Grab server variables
s.mu.Lock()
infoJSON := s.routeInfoJSON
authRequired := s.routeInfo.AuthRequired
tlsRequired := s.routeInfo.TLSRequired
s.mu.Unlock()
// Grab lock
c.mu.Lock()
// Initialize
c.initClient()
c.Debugf("Route connection created")
if didSolicit {
// Do this before the TLS code, otherwise, in case of failure
// and if route is explicit, it would try to reconnect to 'nil'...
r.url = rURL
}
// Check for TLS
if tlsRequired {
// Copy off the config to add in ServerName if we
tlsConfig := util.CloneTLSConfig(s.opts.Cluster.TLSConfig)
// If we solicited, we will act like the client, otherwise the server.
if didSolicit {
c.Debugf("Starting TLS route client handshake")
// Specify the ServerName we are expecting.
host, _, _ := net.SplitHostPort(rURL.Host)
tlsConfig.ServerName = host
c.nc = tls.Client(c.nc, tlsConfig)
} else {
c.Debugf("Starting TLS route server handshake")
c.nc = tls.Server(c.nc, tlsConfig)
}
conn := c.nc.(*tls.Conn)
// Setup the timeout
ttl := secondsToDuration(s.opts.Cluster.TLSTimeout)
time.AfterFunc(ttl, func() { tlsTimeout(c, conn) })
conn.SetReadDeadline(time.Now().Add(ttl))
c.mu.Unlock()
if err := conn.Handshake(); err != nil {
c.Debugf("TLS route handshake error: %v", err)
c.sendErr("Secure Connection - TLS Required")
c.closeConnection()
return nil
}
// Reset the read deadline
conn.SetReadDeadline(time.Time{})
// Re-Grab lock
c.mu.Lock()
// Verify that the connection did not go away while we released the lock.
if c.nc == nil {
c.mu.Unlock()
return nil
}
// Rewrap bw
c.bw = bufio.NewWriterSize(c.nc, startBufSize)
}
// Do final client initialization
// Set the Ping timer
c.setPingTimer()
// For routes, the "client" is added to s.routes only when processing
// the INFO protocol, that is much later.
// In the meantime, if the server shutsdown, there would be no reference
// to the client (connection) to be closed, leaving this readLoop
// uinterrupted, causing the Shutdown() to wait indefinitively.
// We need to store the client in a special map, under a special lock.
s.grMu.Lock()
s.grTmpClients[c.cid] = c
s.grMu.Unlock()
// Spin up the read loop.
s.startGoRoutine(func() { c.readLoop() })
if tlsRequired {
c.Debugf("TLS handshake complete")
cs := c.nc.(*tls.Conn).ConnectionState()
c.Debugf("TLS version %s, cipher suite %s", tlsVersion(cs.Version), tlsCipher(cs.CipherSuite))
}
// Queue Connect proto if we solicited the connection.
if didSolicit {
c.Debugf("Route connect msg sent")
c.sendConnect(tlsRequired)
}
// Send our info to the other side.
c.sendInfo(infoJSON)
// Check for Auth required state for incoming connections.
if authRequired && !didSolicit {
ttl := secondsToDuration(s.opts.Cluster.AuthTimeout)
c.setAuthTimer(ttl)
}
c.mu.Unlock()
return c
}
const (
_CRLF_ = "\r\n"
_EMPTY_ = ""
_SPC_ = " "
)
const (
subProto = "SUB %s %s %s" + _CRLF_
unsubProto = "UNSUB %s%s" + _CRLF_
)
// FIXME(dlc) - Make these reserved and reject if they come in as a sid
// from a client connection.
// Route constants
const (
RSID = "RSID"
QRSID = "QRSID"
RSID_CID_INDEX = 1
RSID_SID_INDEX = 2
EXPECTED_MATCHES = 3
)
// FIXME(dlc) - This may be too slow, check at later date.
var qrsidRe = regexp.MustCompile(`QRSID:(\d+):([^\s]+)`)
func (s *Server) routeSidQueueSubscriber(rsid []byte) (*subscription, bool) {
if !bytes.HasPrefix(rsid, []byte(QRSID)) {
return nil, false
}
matches := qrsidRe.FindSubmatch(rsid)
if matches == nil || len(matches) != EXPECTED_MATCHES {
return nil, false
}
cid := uint64(parseInt64(matches[RSID_CID_INDEX]))
s.mu.Lock()
client := s.clients[cid]
s.mu.Unlock()
if client == nil {
return nil, true
}
sid := matches[RSID_SID_INDEX]
client.mu.Lock()
sub, ok := client.subs[string(sid)]
client.mu.Unlock()
if ok {
return sub, true
}
return nil, true
}
func routeSid(sub *subscription) string {
var qi string
if len(sub.queue) > 0 {
qi = "Q"
}
return fmt.Sprintf("%s%s:%d:%s", qi, RSID, sub.client.cid, sub.sid)
}
func (s *Server) addRoute(c *client, info *Info) (bool, bool) {
id := c.route.remoteID
sendInfo := false
s.mu.Lock()
if !s.running {
s.mu.Unlock()
return false, false
}
remote, exists := s.remotes[id]
if !exists {
// Remove from the temporary map
s.grMu.Lock()
delete(s.grTmpClients, c.cid)
s.grMu.Unlock()
s.routes[c.cid] = c
s.remotes[id] = c
// If this server's ID is (alpha) less than the peer, then we will
// make sure that if we are disconnected, we will try to connect once
// more. This is to mitigate the issue where both sides add the route
// on the opposite connection, and therefore we end-up with both
// being dropped.
if s.info.ID < id {
c.mu.Lock()
// Make this as a retry (otherwise, only explicit are retried).
c.route.retry = true
c.mu.Unlock()
}
// we don't need to send if the only route is the one we just accepted.
sendInfo = len(s.routes) > 1
}
s.mu.Unlock()
if exists && c.route.didSolicit {
// upgrade to solicited?
remote.mu.Lock()
// the existing route (remote) should keep its 'retry' value, and
// not be replaced with c.route.retry.
retry := remote.route.retry
remote.route = c.route
remote.route.retry = retry
remote.mu.Unlock()
}
return !exists, sendInfo
}
func (s *Server) broadcastInterestToRoutes(proto string) {
var arg []byte
if atomic.LoadInt32(&trace) == 1 {
arg = []byte(proto[:len(proto)-LEN_CR_LF])
}
protoAsBytes := []byte(proto)
s.mu.Lock()
for _, route := range s.routes {
// FIXME(dlc) - Make same logic as deliverMsg
route.mu.Lock()
route.sendProto(protoAsBytes, true)
route.mu.Unlock()
route.traceOutOp("", arg)
}
s.mu.Unlock()
}
// broadcastSubscribe will forward a client subscription
// to all active routes.
func (s *Server) broadcastSubscribe(sub *subscription) {
if s.numRoutes() == 0 {
return
}
rsid := routeSid(sub)
proto := fmt.Sprintf(subProto, sub.subject, sub.queue, rsid)
s.broadcastInterestToRoutes(proto)
}
// broadcastUnSubscribe will forward a client unsubscribe
// action to all active routes.
func (s *Server) broadcastUnSubscribe(sub *subscription) {
if s.numRoutes() == 0 {
return
}
rsid := routeSid(sub)
maxStr := _EMPTY_
sub.client.mu.Lock()
// Set max if we have it set and have not tripped auto-unsubscribe
if sub.max > 0 && sub.nm < sub.max {
maxStr = fmt.Sprintf(" %d", sub.max)
}
sub.client.mu.Unlock()
proto := fmt.Sprintf(unsubProto, rsid, maxStr)
s.broadcastInterestToRoutes(proto)
}
func (s *Server) routeAcceptLoop(ch chan struct{}) {
hp := net.JoinHostPort(s.opts.Cluster.Host, strconv.Itoa(s.opts.Cluster.Port))
Noticef("Listening for route connections on %s", hp)
l, e := net.Listen("tcp", hp)
if e != nil {
// We need to close this channel to avoid a deadlock
close(ch)
Fatalf("Error listening on router port: %d - %v", s.opts.Cluster.Port, e)
return
}
// Setup state that can enable shutdown
s.mu.Lock()
s.routeListener = l
s.mu.Unlock()
// Let them know we are up
close(ch)
tmpDelay := ACCEPT_MIN_SLEEP
for s.isRunning() {
conn, err := l.Accept()
if err != nil {
if ne, ok := err.(net.Error); ok && ne.Temporary() {
Debugf("Temporary Route Accept Errorf(%v), sleeping %dms",
ne, tmpDelay/time.Millisecond)
time.Sleep(tmpDelay)
tmpDelay *= 2
if tmpDelay > ACCEPT_MAX_SLEEP {
tmpDelay = ACCEPT_MAX_SLEEP
}
} else if s.isRunning() {
Noticef("Accept error: %v", err)
}
continue
}
tmpDelay = ACCEPT_MIN_SLEEP
s.startGoRoutine(func() {
s.createRoute(conn, nil)
s.grWG.Done()
})
}
Debugf("Router accept loop exiting..")
s.done <- true
}
// StartRouting will start the accept loop on the cluster host:port
// and will actively try to connect to listed routes.
func (s *Server) StartRouting(clientListenReady chan struct{}) {
defer s.grWG.Done()
// Wait for the client listen port to be opened, and
// the possible ephemeral port to be selected.
<-clientListenReady
// Get all possible URLs (when server listens to 0.0.0.0).
// This is going to be sent to other Servers, so that they can let their
// clients know about us.
clientConnectURLs := s.getClientConnectURLs()
// Check for TLSConfig
tlsReq := s.opts.Cluster.TLSConfig != nil
info := Info{
ID: s.info.ID,
Version: s.info.Version,
Host: s.opts.Cluster.Host,
Port: s.opts.Cluster.Port,
AuthRequired: false,
TLSRequired: tlsReq,
SSLRequired: tlsReq,
TLSVerify: tlsReq,
MaxPayload: s.info.MaxPayload,
ClientConnectURLs: clientConnectURLs,
}
// Check for Auth items
if s.opts.Cluster.Username != "" {
info.AuthRequired = true
}
s.routeInfo = info
b, _ := json.Marshal(info)
s.routeInfoJSON = []byte(fmt.Sprintf(InfoProto, b))
// Spin up the accept loop
ch := make(chan struct{})
go s.routeAcceptLoop(ch)
<-ch
// Solicit Routes if needed.
s.solicitRoutes()
}
func (s *Server) reConnectToRoute(rURL *url.URL, rtype RouteType) {
tryForEver := rtype == Explicit
if tryForEver {
time.Sleep(DEFAULT_ROUTE_RECONNECT)
}
s.connectToRoute(rURL, tryForEver)
}
func (s *Server) connectToRoute(rURL *url.URL, tryForEver bool) {
defer s.grWG.Done()
for s.isRunning() && rURL != nil {
Debugf("Trying to connect to route on %s", rURL.Host)
conn, err := net.DialTimeout("tcp", rURL.Host, DEFAULT_ROUTE_DIAL)
if err != nil {
Debugf("Error trying to connect to route: %v", err)
select {
case <-s.rcQuit:
return
case <-time.After(DEFAULT_ROUTE_CONNECT):
if !tryForEver {
return
}
continue
}
}
// We have a route connection here.
// Go ahead and create it and exit this func.
s.createRoute(conn, rURL)
return
}
}
func (c *client) isSolicitedRoute() bool {
c.mu.Lock()
defer c.mu.Unlock()
return c.typ == ROUTER && c.route != nil && c.route.didSolicit
}
func (s *Server) solicitRoutes() {
for _, r := range s.opts.Routes {
route := r
s.startGoRoutine(func() { s.connectToRoute(route, true) })
}
}
func (s *Server) numRoutes() int {
s.mu.Lock()
defer s.mu.Unlock()
return len(s.routes)
}

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

@ -0,0 +1,923 @@
// Copyright 2012-2016 Apcera Inc. All rights reserved.
package server
import (
"bufio"
"crypto/tls"
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"net"
"net/http"
"os"
"runtime"
"strconv"
"strings"
"sync"
"time"
// Allow dynamic profiling.
_ "net/http/pprof"
"github.com/nats-io/gnatsd/util"
)
// Info is the information sent to clients to help them understand information
// about this server.
type Info struct {
ID string `json:"server_id"`
Version string `json:"version"`
GoVersion string `json:"go"`
Host string `json:"host"`
Port int `json:"port"`
AuthRequired bool `json:"auth_required"`
SSLRequired bool `json:"ssl_required"` // DEPRECATED: ssl json used for older clients
TLSRequired bool `json:"tls_required"`
TLSVerify bool `json:"tls_verify"`
MaxPayload int `json:"max_payload"`
IP string `json:"ip,omitempty"`
ClientConnectURLs []string `json:"connect_urls,omitempty"` // Contains URLs a client can connect to.
// Used internally for quick look-ups.
clientConnectURLs map[string]struct{}
}
// Server is our main struct.
type Server struct {
gcid uint64
grid uint64
stats
mu sync.Mutex
info Info
infoJSON []byte
sl *Sublist
opts *Options
cAuth Auth
rAuth Auth
trace bool
debug bool
running bool
listener net.Listener
clients map[uint64]*client
routes map[uint64]*client
remotes map[string]*client
totalClients uint64
done chan bool
start time.Time
http net.Listener
httpReqStats map[string]uint64
routeListener net.Listener
routeInfo Info
routeInfoJSON []byte
rcQuit chan bool
grMu sync.Mutex
grTmpClients map[uint64]*client
grRunning bool
grWG sync.WaitGroup // to wait on various go routines
cproto int64 // number of clients supporting async INFO
}
// Make sure all are 64bits for atomic use
type stats struct {
inMsgs int64
outMsgs int64
inBytes int64
outBytes int64
slowConsumers int64
}
// New will setup a new server struct after parsing the options.
func New(opts *Options) *Server {
processOptions(opts)
// Process TLS options, including whether we require client certificates.
tlsReq := opts.TLSConfig != nil
verify := (tlsReq && opts.TLSConfig.ClientAuth == tls.RequireAndVerifyClientCert)
info := Info{
ID: genID(),
Version: VERSION,
GoVersion: runtime.Version(),
Host: opts.Host,
Port: opts.Port,
AuthRequired: false,
TLSRequired: tlsReq,
SSLRequired: tlsReq,
TLSVerify: verify,
MaxPayload: opts.MaxPayload,
clientConnectURLs: make(map[string]struct{}),
}
s := &Server{
info: info,
sl: NewSublist(),
opts: opts,
debug: opts.Debug,
trace: opts.Trace,
done: make(chan bool, 1),
start: time.Now(),
}
s.mu.Lock()
defer s.mu.Unlock()
// For tracking clients
s.clients = make(map[uint64]*client)
// For tracking connections that are not yet registered
// in s.routes, but for which readLoop has started.
s.grTmpClients = make(map[uint64]*client)
// For tracking routes and their remote ids
s.routes = make(map[uint64]*client)
s.remotes = make(map[string]*client)
// Used to kick out all of the route
// connect Go routines.
s.rcQuit = make(chan bool)
s.generateServerInfoJSON()
s.handleSignals()
return s
}
// SetClientAuthMethod sets the authentication method for clients.
func (s *Server) SetClientAuthMethod(authMethod Auth) {
s.mu.Lock()
defer s.mu.Unlock()
s.info.AuthRequired = true
s.cAuth = authMethod
s.generateServerInfoJSON()
}
// SetRouteAuthMethod sets the authentication method for routes.
func (s *Server) SetRouteAuthMethod(authMethod Auth) {
s.mu.Lock()
defer s.mu.Unlock()
s.rAuth = authMethod
}
func (s *Server) generateServerInfoJSON() {
// Generate the info json
b, err := json.Marshal(s.info)
if err != nil {
Fatalf("Error marshalling INFO JSON: %+v\n", err)
return
}
s.infoJSON = []byte(fmt.Sprintf("INFO %s %s", b, CR_LF))
}
// PrintAndDie is exported for access in other packages.
func PrintAndDie(msg string) {
fmt.Fprintf(os.Stderr, "%s\n", msg)
os.Exit(1)
}
// PrintServerAndExit will print our version and exit.
func PrintServerAndExit() {
fmt.Printf("nats-server version %s\n", VERSION)
os.Exit(0)
}
// ProcessCommandLineArgs takes the command line arguments
// validating and setting flags for handling in case any
// sub command was present.
func ProcessCommandLineArgs(cmd *flag.FlagSet) (showVersion bool, showHelp bool, err error) {
if len(cmd.Args()) > 0 {
arg := cmd.Args()[0]
switch strings.ToLower(arg) {
case "version":
return true, false, nil
case "help":
return false, true, nil
default:
return false, false, fmt.Errorf("Unrecognized command: %q\n", arg)
}
}
return false, false, nil
}
// Protected check on running state
func (s *Server) isRunning() bool {
s.mu.Lock()
defer s.mu.Unlock()
return s.running
}
func (s *Server) logPid() {
pidStr := strconv.Itoa(os.Getpid())
err := ioutil.WriteFile(s.opts.PidFile, []byte(pidStr), 0660)
if err != nil {
PrintAndDie(fmt.Sprintf("Could not write pidfile: %v\n", err))
}
}
// Start up the server, this will block.
// Start via a Go routine if needed.
func (s *Server) Start() {
Noticef("Starting nats-server version %s", VERSION)
Debugf("Go build version %s", s.info.GoVersion)
// Avoid RACE between Start() and Shutdown()
s.mu.Lock()
s.running = true
s.mu.Unlock()
s.grMu.Lock()
s.grRunning = true
s.grMu.Unlock()
// Log the pid to a file
if s.opts.PidFile != _EMPTY_ {
s.logPid()
}
// Start up the http server if needed.
if s.opts.HTTPPort != 0 {
s.StartHTTPMonitoring()
}
// Start up the https server if needed.
if s.opts.HTTPSPort != 0 {
if s.opts.TLSConfig == nil {
Fatalf("TLS cert and key required for HTTPS")
return
}
s.StartHTTPSMonitoring()
}
// The Routing routine needs to wait for the client listen
// port to be opened and potential ephemeral port selected.
clientListenReady := make(chan struct{})
// Start up routing as well if needed.
if s.opts.Cluster.Port != 0 {
s.startGoRoutine(func() {
s.StartRouting(clientListenReady)
})
}
// Pprof http endpoint for the profiler.
if s.opts.ProfPort != 0 {
s.StartProfiler()
}
// Wait for clients.
s.AcceptLoop(clientListenReady)
}
// Shutdown will shutdown the server instance by kicking out the AcceptLoop
// and closing all associated clients.
func (s *Server) Shutdown() {
s.mu.Lock()
// Prevent issues with multiple calls.
if !s.running {
s.mu.Unlock()
return
}
s.running = false
s.grMu.Lock()
s.grRunning = false
s.grMu.Unlock()
conns := make(map[uint64]*client)
// Copy off the clients
for i, c := range s.clients {
conns[i] = c
}
// Copy off the connections that are not yet registered
// in s.routes, but for which the readLoop has started
s.grMu.Lock()
for i, c := range s.grTmpClients {
conns[i] = c
}
s.grMu.Unlock()
// Copy off the routes
for i, r := range s.routes {
conns[i] = r
}
// Number of done channel responses we expect.
doneExpected := 0
// Kick client AcceptLoop()
if s.listener != nil {
doneExpected++
s.listener.Close()
s.listener = nil
}
// Kick route AcceptLoop()
if s.routeListener != nil {
doneExpected++
s.routeListener.Close()
s.routeListener = nil
}
// Kick HTTP monitoring if its running
if s.http != nil {
doneExpected++
s.http.Close()
s.http = nil
}
// Release the solicited routes connect go routines.
close(s.rcQuit)
s.mu.Unlock()
// Close client and route connections
for _, c := range conns {
c.closeConnection()
}
// Block until the accept loops exit
for doneExpected > 0 {
<-s.done
doneExpected--
}
// Wait for go routines to be done.
s.grWG.Wait()
}
// AcceptLoop is exported for easier testing.
func (s *Server) AcceptLoop(clr chan struct{}) {
// If we were to exit before the listener is setup properly,
// make sure we close the channel.
defer func() {
if clr != nil {
close(clr)
}
}()
hp := net.JoinHostPort(s.opts.Host, strconv.Itoa(s.opts.Port))
Noticef("Listening for client connections on %s", hp)
l, e := net.Listen("tcp", hp)
if e != nil {
Fatalf("Error listening on port: %s, %q", hp, e)
return
}
// Alert of TLS enabled.
if s.opts.TLSConfig != nil {
Noticef("TLS required for client connections")
}
Debugf("Server id is %s", s.info.ID)
Noticef("Server is ready")
// Setup state that can enable shutdown
s.mu.Lock()
s.listener = l
// If server was started with RANDOM_PORT (-1), opts.Port would be equal
// to 0 at the beginning this function. So we need to get the actual port
if s.opts.Port == 0 {
// Write resolved port back to options.
_, port, err := net.SplitHostPort(l.Addr().String())
if err != nil {
Fatalf("Error parsing server address (%s): %s", l.Addr().String(), e)
s.mu.Unlock()
return
}
portNum, err := strconv.Atoi(port)
if err != nil {
Fatalf("Error parsing server address (%s): %s", l.Addr().String(), e)
s.mu.Unlock()
return
}
s.opts.Port = portNum
}
s.mu.Unlock()
// Let the caller know that we are ready
close(clr)
clr = nil
tmpDelay := ACCEPT_MIN_SLEEP
for s.isRunning() {
conn, err := l.Accept()
if err != nil {
if ne, ok := err.(net.Error); ok && ne.Temporary() {
Debugf("Temporary Client Accept Error(%v), sleeping %dms",
ne, tmpDelay/time.Millisecond)
time.Sleep(tmpDelay)
tmpDelay *= 2
if tmpDelay > ACCEPT_MAX_SLEEP {
tmpDelay = ACCEPT_MAX_SLEEP
}
} else if s.isRunning() {
Noticef("Accept error: %v", err)
}
continue
}
tmpDelay = ACCEPT_MIN_SLEEP
s.startGoRoutine(func() {
s.createClient(conn)
s.grWG.Done()
})
}
Noticef("Server Exiting..")
s.done <- true
}
// StartProfiler is called to enable dynamic profiling.
func (s *Server) StartProfiler() {
Noticef("Starting profiling on http port %d", s.opts.ProfPort)
hp := net.JoinHostPort(s.opts.Host, strconv.Itoa(s.opts.ProfPort))
go func() {
err := http.ListenAndServe(hp, nil)
if err != nil {
Fatalf("error starting monitor server: %s", err)
}
}()
}
// StartHTTPMonitoring will enable the HTTP monitoring port.
func (s *Server) StartHTTPMonitoring() {
s.startMonitoring(false)
}
// StartHTTPSMonitoring will enable the HTTPS monitoring port.
func (s *Server) StartHTTPSMonitoring() {
s.startMonitoring(true)
}
// HTTP endpoints
const (
RootPath = "/"
VarzPath = "/varz"
ConnzPath = "/connz"
RoutezPath = "/routez"
SubszPath = "/subsz"
StackszPath = "/stacksz"
)
// Start the monitoring server
func (s *Server) startMonitoring(secure bool) {
// Used to track HTTP requests
s.httpReqStats = map[string]uint64{
RootPath: 0,
VarzPath: 0,
ConnzPath: 0,
RoutezPath: 0,
SubszPath: 0,
}
var hp string
var err error
if secure {
hp = net.JoinHostPort(s.opts.HTTPHost, strconv.Itoa(s.opts.HTTPSPort))
Noticef("Starting https monitor on %s", hp)
config := util.CloneTLSConfig(s.opts.TLSConfig)
config.ClientAuth = tls.NoClientCert
s.http, err = tls.Listen("tcp", hp, config)
} else {
hp = net.JoinHostPort(s.opts.HTTPHost, strconv.Itoa(s.opts.HTTPPort))
Noticef("Starting http monitor on %s", hp)
s.http, err = net.Listen("tcp", hp)
}
if err != nil {
Fatalf("Can't listen to the monitor port: %v", err)
return
}
mux := http.NewServeMux()
// Root
mux.HandleFunc(RootPath, s.HandleRoot)
// Varz
mux.HandleFunc(VarzPath, s.HandleVarz)
// Connz
mux.HandleFunc(ConnzPath, s.HandleConnz)
// Routez
mux.HandleFunc(RoutezPath, s.HandleRoutez)
// Subz
mux.HandleFunc(SubszPath, s.HandleSubsz)
// Subz alias for backwards compatibility
mux.HandleFunc("/subscriptionsz", s.HandleSubsz)
// Stacksz
mux.HandleFunc(StackszPath, s.HandleStacksz)
srv := &http.Server{
Addr: hp,
Handler: mux,
ReadTimeout: 2 * time.Second,
WriteTimeout: 2 * time.Second,
MaxHeaderBytes: 1 << 20,
}
go func() {
srv.Serve(s.http)
srv.Handler = nil
s.done <- true
}()
}
func (s *Server) createClient(conn net.Conn) *client {
c := &client{srv: s, nc: conn, opts: defaultOpts, mpay: s.info.MaxPayload, start: time.Now()}
// Grab JSON info string
s.mu.Lock()
info := s.infoJSON
authRequired := s.info.AuthRequired
tlsRequired := s.info.TLSRequired
s.totalClients++
s.mu.Unlock()
// Grab lock
c.mu.Lock()
// Initialize
c.initClient()
c.Debugf("Client connection created")
// Check for Auth
if authRequired {
c.setAuthTimer(secondsToDuration(s.opts.AuthTimeout))
}
// Send our information.
c.sendInfo(info)
// Unlock to register
c.mu.Unlock()
// Register with the server.
s.mu.Lock()
// If server is not running, Shutdown() may have already gathered the
// list of connections to close. It won't contain this one, so we need
// to bail out now otherwise the readLoop started down there would not
// be interrupted.
if !s.running {
s.mu.Unlock()
return c
}
// If there is a max connections specified, check that adding
// this new client would not push us over the max
if s.opts.MaxConn > 0 && len(s.clients) >= s.opts.MaxConn {
s.mu.Unlock()
c.maxConnExceeded()
return nil
}
s.clients[c.cid] = c
s.mu.Unlock()
// Re-Grab lock
c.mu.Lock()
// Check for TLS
if tlsRequired {
c.Debugf("Starting TLS client connection handshake")
c.nc = tls.Server(c.nc, s.opts.TLSConfig)
conn := c.nc.(*tls.Conn)
// Setup the timeout
ttl := secondsToDuration(s.opts.TLSTimeout)
time.AfterFunc(ttl, func() { tlsTimeout(c, conn) })
conn.SetReadDeadline(time.Now().Add(ttl))
// Force handshake
c.mu.Unlock()
if err := conn.Handshake(); err != nil {
c.Debugf("TLS handshake error: %v", err)
c.sendErr("Secure Connection - TLS Required")
c.closeConnection()
return nil
}
// Reset the read deadline
conn.SetReadDeadline(time.Time{})
// Re-Grab lock
c.mu.Lock()
}
// The connection may have been closed
if c.nc == nil {
c.mu.Unlock()
return c
}
if tlsRequired {
// Rewrap bw
c.bw = bufio.NewWriterSize(c.nc, startBufSize)
}
// Do final client initialization
// Set the Ping timer
c.setPingTimer()
// Spin up the read loop.
s.startGoRoutine(func() { c.readLoop() })
if tlsRequired {
c.Debugf("TLS handshake complete")
cs := c.nc.(*tls.Conn).ConnectionState()
c.Debugf("TLS version %s, cipher suite %s", tlsVersion(cs.Version), tlsCipher(cs.CipherSuite))
}
c.mu.Unlock()
return c
}
// updateServerINFO updates the server's Info object with the given
// array of URLs and re-generate the infoJSON byte array, only if the
// given URLs were not already recorded and if the feature is not
// disabled.
// Returns a boolean indicating if server's Info was updated.
func (s *Server) updateServerINFO(urls []string) bool {
s.mu.Lock()
defer s.mu.Unlock()
// Feature disabled, do not update.
if s.opts.Cluster.NoAdvertise {
return false
}
// Will be set to true if we alter the server's Info object.
wasUpdated := false
for _, url := range urls {
if _, present := s.info.clientConnectURLs[url]; !present {
s.info.clientConnectURLs[url] = struct{}{}
s.info.ClientConnectURLs = append(s.info.ClientConnectURLs, url)
wasUpdated = true
}
}
if wasUpdated {
s.generateServerInfoJSON()
}
return wasUpdated
}
// Handle closing down a connection when the handshake has timedout.
func tlsTimeout(c *client, conn *tls.Conn) {
c.mu.Lock()
nc := c.nc
c.mu.Unlock()
// Check if already closed
if nc == nil {
return
}
cs := conn.ConnectionState()
if !cs.HandshakeComplete {
c.Debugf("TLS handshake timeout")
c.sendErr("Secure Connection - TLS Required")
c.closeConnection()
}
}
// Seems silly we have to write these
func tlsVersion(ver uint16) string {
switch ver {
case tls.VersionTLS10:
return "1.0"
case tls.VersionTLS11:
return "1.1"
case tls.VersionTLS12:
return "1.2"
}
return fmt.Sprintf("Unknown [%x]", ver)
}
// We use hex here so we don't need multiple versions
func tlsCipher(cs uint16) string {
switch cs {
case 0x0005:
return "TLS_RSA_WITH_RC4_128_SHA"
case 0x000a:
return "TLS_RSA_WITH_3DES_EDE_CBC_SHA"
case 0x002f:
return "TLS_RSA_WITH_AES_128_CBC_SHA"
case 0x0035:
return "TLS_RSA_WITH_AES_256_CBC_SHA"
case 0xc007:
return "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA"
case 0xc009:
return "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA"
case 0xc00a:
return "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA"
case 0xc011:
return "TLS_ECDHE_RSA_WITH_RC4_128_SHA"
case 0xc012:
return "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA"
case 0xc013:
return "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA"
case 0xc014:
return "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA"
case 0xc02f:
return "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
case 0xc02b:
return "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"
case 0xc030:
return "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"
case 0xc02c:
return "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"
}
return fmt.Sprintf("Unknown [%x]", cs)
}
func (s *Server) checkClientAuth(c *client) bool {
if s.cAuth == nil {
return true
}
return s.cAuth.Check(c)
}
func (s *Server) checkRouterAuth(c *client) bool {
if s.rAuth == nil {
return true
}
return s.rAuth.Check(c)
}
// Check auth and return boolean indicating if client is ok
func (s *Server) checkAuth(c *client) bool {
switch c.typ {
case CLIENT:
return s.checkClientAuth(c)
case ROUTER:
return s.checkRouterAuth(c)
default:
return false
}
}
// Remove a client or route from our internal accounting.
func (s *Server) removeClient(c *client) {
var rID string
c.mu.Lock()
cid := c.cid
typ := c.typ
r := c.route
if r != nil {
rID = r.remoteID
}
updateProtoInfoCount := false
if typ == CLIENT && c.opts.Protocol >= ClientProtoInfo {
updateProtoInfoCount = true
}
c.mu.Unlock()
s.mu.Lock()
switch typ {
case CLIENT:
delete(s.clients, cid)
if updateProtoInfoCount {
s.cproto--
}
case ROUTER:
delete(s.routes, cid)
if r != nil {
rc, ok := s.remotes[rID]
// Only delete it if it is us..
if ok && c == rc {
delete(s.remotes, rID)
}
}
}
s.mu.Unlock()
}
/////////////////////////////////////////////////////////////////
// These are some helpers for accounting in functional tests.
/////////////////////////////////////////////////////////////////
// NumRoutes will report the number of registered routes.
func (s *Server) NumRoutes() int {
s.mu.Lock()
defer s.mu.Unlock()
return len(s.routes)
}
// NumRemotes will report number of registered remotes.
func (s *Server) NumRemotes() int {
s.mu.Lock()
defer s.mu.Unlock()
return len(s.remotes)
}
// NumClients will report the number of registered clients.
func (s *Server) NumClients() int {
s.mu.Lock()
defer s.mu.Unlock()
return len(s.clients)
}
// NumSubscriptions will report how many subscriptions are active.
func (s *Server) NumSubscriptions() uint32 {
s.mu.Lock()
subs := s.sl.Count()
s.mu.Unlock()
return subs
}
// Addr will return the net.Addr object for the current listener.
func (s *Server) Addr() net.Addr {
s.mu.Lock()
defer s.mu.Unlock()
if s.listener == nil {
return nil
}
return s.listener.Addr()
}
// ReadyForConnections returns `true` if the server is ready to accept client
// and, if routing is enabled, route connections. If after the duration
// `dur` the server is still not ready, returns `false`.
func (s *Server) ReadyForConnections(dur time.Duration) bool {
end := time.Now().Add(dur)
for time.Now().Before(end) {
s.mu.Lock()
ok := s.listener != nil && (s.opts.Cluster.Port == 0 || s.routeListener != nil)
s.mu.Unlock()
if ok {
return true
}
time.Sleep(25 * time.Millisecond)
}
return false
}
// ID returns the server's ID
func (s *Server) ID() string {
s.mu.Lock()
defer s.mu.Unlock()
return s.info.ID
}
func (s *Server) startGoRoutine(f func()) {
s.grMu.Lock()
if s.grRunning {
s.grWG.Add(1)
go f()
}
s.grMu.Unlock()
}
// getClientConnectURLs returns suitable URLs for clients to connect to the listen
// port based on the server options' Host and Port. If the Host corresponds to
// "any" interfaces, this call returns the list of resolved IP addresses.
func (s *Server) getClientConnectURLs() []string {
s.mu.Lock()
defer s.mu.Unlock()
sPort := strconv.Itoa(s.opts.Port)
urls := make([]string, 0, 1)
ipAddr, err := net.ResolveIPAddr("ip", s.opts.Host)
// If the host is "any" (0.0.0.0 or ::), get specific IPs from available
// interfaces.
if err == nil && ipAddr.IP.IsUnspecified() {
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 non global unicast addresses
if !ip.IsGlobalUnicast() || ip.IsUnspecified() {
ip = nil
continue
}
urls = append(urls, net.JoinHostPort(ip.String(), sPort))
}
}
}
if err != nil || len(urls) == 0 {
// We are here if s.opts.Host is not "0.0.0.0" nor "::", or if for some
// reason we could not add any URL in the loop above.
// We had a case where a Windows VM was hosed and would have err == nil
// and not add any address in the array in the loop above, and we
// ended-up returning 0.0.0.0, which is problematic for Windows clients.
// Check for 0.0.0.0 or :: specifically, and ignore if that's the case.
if s.opts.Host == "0.0.0.0" || s.opts.Host == "::" {
Errorf("Address %q can not be resolved properly", s.opts.Host)
} else {
urls = append(urls, net.JoinHostPort(s.opts.Host, sPort))
}
}
return urls
}

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

@ -0,0 +1,34 @@
// +build !windows
// Copyright 2012-2016 Apcera Inc. All rights reserved.
package server
import (
"os"
"os/signal"
"syscall"
)
// Signal Handling
func (s *Server) handleSignals() {
if s.opts.NoSigs {
return
}
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGINT, syscall.SIGUSR1)
go func() {
for sig := range c {
Debugf("Trapped %q signal", sig)
switch sig {
case syscall.SIGINT:
Noticef("Server Exiting..")
os.Exit(0)
case syscall.SIGUSR1:
// File log re-open for rotating file logs.
s.ReOpenLogFile()
}
}
}()
}

View file

@ -0,0 +1,26 @@
// Copyright 2012-2016 Apcera Inc. All rights reserved.
package server
import (
"os"
"os/signal"
)
// Signal Handling
func (s *Server) handleSignals() {
if s.opts.NoSigs {
return
}
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
go func() {
for sig := range c {
Debugf("Trapped %q signal", sig)
Noticef("Server Exiting..")
os.Exit(0)
}
}()
}

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

@ -0,0 +1,643 @@
// Copyright 2016 Apcera Inc. All rights reserved.
// 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")
)
// cacheMax is used to bound limit the frontend cache
const slCacheMax = 1024
// 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 []*subscription
qsubs [][]*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([]*subscription, 0, 4)}
}
// Create a new default level. We use FNV1A as the hash
// algortihm for the tokens, which should be short.
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 {
if len(t) == 0 || sfwc {
s.Unlock()
return ErrInvalidSubject
}
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()
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 = append(n.psubs, sub)
} else {
// This is a queue subscription
if i := findQSliceForSub(sub, n.qsubs); i >= 0 {
n.qsubs[i] = append(n.qsubs[i], sub)
} else {
n.qsubs = append(n.qsubs, []*subscription{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) {
results.psubs = append(results.psubs, n.psubs...)
for _, qr := range n.qsubs {
if len(qr) == 0 {
continue
}
// Need to find matching list in results
if i := findQSliceForSub(qr[0], results.qsubs); i >= 0 {
results.qsubs[i] = append(results.qsubs[i], qr...)
} else {
results.qsubs = append(results.qsubs, qr)
}
}
}
// 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
}
// Remove will remove a subscription.
func (s *Sublist) Remove(sub *subscription) 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:])
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 {
if len(t) == 0 || sfwc {
return ErrInvalidSubject
}
if l == nil {
return ErrNotFound
}
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
}
// 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
}
// Removes a sub from a list.
func removeSubFromList(sub *subscription, sl []*subscription) ([]*subscription, bool) {
for i := 0; i < len(sl); i++ {
if sl[i] == sub {
last := len(sl) - 1
sl[i] = sl[last]
sl[last] = nil
sl = sl[:last]
return shrinkAsNeeded(sl), true
}
}
return sl, false
}
// 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 {
n.psubs, found = removeSubFromList(sub, n.psubs)
return found
}
// We have a queue group subscription here
if i := findQSliceForSub(sub, n.qsubs); i >= 0 {
n.qsubs[i], found = removeSubFromList(sub, n.qsubs[i])
if len(n.qsubs[i]) == 0 {
last := len(n.qsubs) - 1
n.qsubs[i] = n.qsubs[last]
n.qsubs[last] = nil
n.qsubs = n.qsubs[:last]
if len(n.qsubs) == 0 {
n.qsubs = nil
}
}
return found
}
return false
}
// Checks if we need to do a resize. This is for very large growth then
// subsequent return to a more normal size from unsubscribe.
func shrinkAsNeeded(sl []*subscription) []*subscription {
lsl := len(sl)
csl := cap(sl)
// Don't bother if list not too big
if csl <= 8 {
return sl
}
pFree := float32(csl-lsl) / float32(csl)
if pFree > 0.50 {
return append([]*subscription(nil), sl...)
}
return sl
}
// 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 = s.matches
if s.matches > 0 {
st.CacheHitRate = float64(s.cacheHits) / float64(s.matches)
}
// 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(string(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(string(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)
for i := 0; i < len(subject); i++ {
if li >= ll {
return false
}
b := subject[i]
switch b {
case pwc:
// Skip token in literal
ll := len(literal)
for {
if li >= ll || literal[li] == btsep {
li--
break
}
li++
}
case fwc:
return true
default:
if b != literal[li] {
return false
}
}
li++
}
// Make sure we have processed all of the literal's chars..
if li < ll {
return false
}
return true
}

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

@ -0,0 +1,56 @@
// Copyright 2012-2016 Apcera Inc. All rights reserved.
package server
import (
"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) {
if len(d) == 0 {
return -1
}
for _, dec := range d {
if dec < asciiZero || dec > asciiNine {
return -1
}
n = n*10 + (int(dec) - asciiZero)
}
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)
}

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

@ -0,0 +1,399 @@
// Copyright 2012-2016 Apcera Inc. All rights reserved.
package test
import (
"crypto/rand"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"net"
"os/exec"
"regexp"
"runtime"
"strings"
"time"
"github.com/nats-io/gnatsd/auth"
"github.com/nats-io/gnatsd/server"
)
const natsServerExe = "../gnatsd"
type natsServer struct {
args []string
cmd *exec.Cmd
}
// 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: "localhost",
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 {
return RunServerWithAuth(opts, nil)
}
// 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)
// Check for auth
var a server.Auth
if opts.Authorization != "" {
a = &auth.Token{Token: opts.Authorization}
}
if opts.Username != "" {
a = &auth.Plain{Username: opts.Username, Password: opts.Password}
}
if opts.Users != nil {
a = auth.NewMultiUser(opts.Users)
}
srv = RunServerWithAuth(opts, a)
return
}
// RunServerWithAuth starts a new Go routine based server with auth
func RunServerWithAuth(opts *server.Options, auth server.Auth) *server.Server {
if opts == nil {
opts = &DefaultTestOptions
}
s := server.New(opts)
if s == nil {
panic("No NATS Server object returned.")
}
if auth != nil {
s.SetClientAuthMethod(auth)
}
// 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
}
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, 1*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,\"ssl_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,\"ssl_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`)
unsubmaxRe = regexp.MustCompile(`UNSUB\s+([^\s]+)(\s+(\d+))\r\n`)
unsubnomaxRe = regexp.MustCompile(`UNSUB\s+([^\s]+)\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(2 * 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
nopts.Port++
nopts.Cluster.Port++
nopts.HTTPPort++
return &nopts
}

75
vendor/github.com/nats-io/gnatsd/util/mkpasswd.go generated vendored Normal file
View file

@ -0,0 +1,75 @@
// Copyright 2015 Apcera Inc. All rights reserved.
// +build ignore
package main
import (
"bytes"
"crypto/rand"
"flag"
"fmt"
"log"
"math/big"
"golang.org/x/crypto/bcrypt"
"golang.org/x/crypto/ssh/terminal"
)
func usage() {
log.Fatalf("Usage: mkpasswd [-p <stdin password>] [-c COST] \n")
}
const (
// Make sure the password is reasonably long to generate enough entropy.
PasswordLength = 22
// Common advice from the past couple of years suggests that 10 should be sufficient.
// Up that a little, to 11. Feel free to raise this higher if this value from 2015 is
// no longer appropriate. Min is 4, Max is 31.
DefaultCost = 11
)
func main() {
var pw = flag.Bool("p", false, "Input password via stdin")
var cost = flag.Int("c", DefaultCost, "The cost weight, range of 4-31 (11)")
log.SetFlags(0)
flag.Usage = usage
flag.Parse()
var password string
if *pw {
fmt.Printf("Enter Password: ")
bytePassword, _ := terminal.ReadPassword(0)
fmt.Printf("\nReenter Password: ")
bytePassword2, _ := terminal.ReadPassword(0)
if !bytes.Equal(bytePassword, bytePassword2) {
log.Fatalf("Error, passwords do not match\n")
}
password = string(bytePassword)
fmt.Printf("\n")
} else {
password = genPassword()
fmt.Printf("pass: %s\n", password)
}
cb, err := bcrypt.GenerateFromPassword([]byte(password), *cost)
if err != nil {
log.Fatalf("Error producing bcrypt hash: %v\n", err)
}
fmt.Printf("bcrypt hash: %s\n", cb)
}
func genPassword() string {
var ch = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@$#%^&*()")
b := make([]byte, PasswordLength)
max := big.NewInt(int64(len(ch)))
for i := range b {
ri, err := rand.Int(rand.Reader, max)
if err != nil {
log.Fatalf("Error producing random integer: %v\n", err)
}
b[i] = ch[int(ri.Int64())]
}
return string(b)
}

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

@ -0,0 +1,37 @@
// Copyright 2016 Apcera Inc. All rights reserved.
// +build 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,
DynamicRecordSizingDisabled: c.DynamicRecordSizingDisabled,
Renegotiation: c.Renegotiation,
}
}

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

@ -0,0 +1,35 @@
// Copyright 2016 Apcera Inc. All rights reserved.
// +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,
}
}

20
vendor/github.com/nats-io/go-nats-streaming/LICENSE generated vendored Normal file
View file

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2016 Apcera Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,115 @@
// Copyright 2016 Apcera Inc. All rights reserved.
//
// Uses https://github.com/gogo/protobuf
// compiled via `protoc -I=. -I=$GOPATH/src --gogofaster_out=. protocol.proto`
syntax = "proto3";
package pb;
import "github.com/gogo/protobuf/gogoproto/gogo.proto";
option (gogoproto.marshaler_all) = true;
option (gogoproto.sizer_all) = true;
option (gogoproto.unmarshaler_all) = true;
option (gogoproto.goproto_getters_all) = false;
// How messages are delivered to the STAN cluster
message PubMsg {
string clientID = 1; // ClientID
string guid = 2; // guid
string subject = 3; // subject
string reply = 4; // optional reply
bytes data = 5; // payload
bytes sha256 = 10; // optional sha256 of data
}
// Used to ACK to publishers
message PubAck {
string guid = 1; // guid
string error = 2; // err string, empty/omitted if no error
}
// Msg struct. Sequence is assigned for global ordering by
// the cluster after the publisher has been acknowledged.
message MsgProto {
uint64 sequence = 1; // globally ordered sequence number for the subject's channel
string subject = 2; // subject
string reply = 3; // optional reply
bytes data = 4; // payload
int64 timestamp = 5; // received timestamp
bool redelivered = 6; // Flag specifying if the message is being redelivered
uint32 CRC32 = 10; // optional IEEE CRC32
}
// Ack will deliver an ack for a delivered msg.
message Ack {
string subject = 1; // Subject
uint64 sequence = 2; // Sequence to acknowledge
}
// Connection Request
message ConnectRequest {
string clientID = 1; // Client name/identifier.
string heartbeatInbox = 2; // Inbox for server initiated heartbeats.
}
// Response to a client connect
message ConnectResponse {
string pubPrefix = 1; // Prefix to use when publishing to this STAN cluster
string subRequests = 2; // Subject to use for subscription requests
string unsubRequests = 3; // Subject to use for unsubscribe requests
string closeRequests = 4; // Subject for closing the stan connection
string error = 5; // err string, empty/omitted if no error
string subCloseRequests = 6; // Subject to use for subscription close requests
string publicKey = 100; // Possibly used to sign acks, etc.
}
// Enum for start position type.
enum StartPosition {
NewOnly = 0;
LastReceived = 1;
TimeDeltaStart = 2;
SequenceStart = 3;
First = 4;
}
// Protocol for a client to subscribe
message SubscriptionRequest {
string clientID = 1; // ClientID
string subject = 2; // Formal subject to subscribe to, e.g. foo.bar
string qGroup = 3; // Optional queue group
string inbox = 4; // Inbox subject to deliver messages on
int32 maxInFlight = 5; // Maximum inflight messages without an ack allowed
int32 ackWaitInSecs = 6; // Timeout for receiving an ack from the client
string durableName = 7; // Optional durable name which survives client restarts
StartPosition startPosition = 10; // Start position
uint64 startSequence = 11; // Optional start sequence number
int64 startTimeDelta = 12; // Optional start time
}
// Response for SubscriptionRequest and UnsubscribeRequests
message SubscriptionResponse {
string ackInbox = 2; // ackInbox for sending acks
string error = 3; // err string, empty/omitted if no error
}
// Protocol for a clients to unsubscribe. Will return a SubscriptionResponse
message UnsubscribeRequest {
string clientID = 1; // ClientID
string subject = 2; // subject for the subscription
string inbox = 3; // Inbox subject to identify subscription
string durableName = 4; // Optional durable name which survives client restarts
}
// Protocol for a client to close a connection
message CloseRequest {
string clientID = 1; // Client name provided to Connect() requests
}
// Response for CloseRequest
message CloseResponse {
string error = 1; // err string, empty/omitted if no error
}

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

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2012-2016 Apcera Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

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

@ -0,0 +1,249 @@
// Copyright 2012-2015 Apcera Inc. All rights reserved.
package nats
import (
"errors"
"fmt"
"reflect"
"sync"
"time"
// Default Encoders
. "github.com/nats-io/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-marshalling 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 <- 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
}

2630
vendor/github.com/nats-io/go-nats/nats.go generated vendored Normal file

File diff suppressed because it is too large Load diff

100
vendor/github.com/nats-io/go-nats/netchan.go generated vendored Normal file
View file

@ -0,0 +1,100 @@
// Copyright 2013-2014 Apcera Inc. All rights reserved.
package nats
import (
"errors"
"reflect"
)
// This allows the functionality for network channels by binding send and receive Go chans
// to subjects and optionally queue groups.
// Data will be encoded and decoded via the EncodedConn and its associated encoders.
// BindSendChan binds a channel for send operations to NATS.
func (c *EncodedConn) BindSendChan(subject string, channel interface{}) error {
chVal := reflect.ValueOf(channel)
if chVal.Kind() != reflect.Chan {
return ErrChanArg
}
go chPublish(c, chVal, subject)
return nil
}
// Publish all values that arrive on the channel until it is closed or we
// encounter an error.
func chPublish(c *EncodedConn, chVal reflect.Value, subject string) {
for {
val, ok := chVal.Recv()
if !ok {
// Channel has most likely been closed.
return
}
if e := c.Publish(subject, val.Interface()); e != nil {
// Do this under lock.
c.Conn.mu.Lock()
defer c.Conn.mu.Unlock()
if c.Conn.Opts.AsyncErrorCB != nil {
// FIXME(dlc) - Not sure this is the right thing to do.
// FIXME(ivan) - If the connection is not yet closed, try to schedule the callback
if c.Conn.isClosed() {
go c.Conn.Opts.AsyncErrorCB(c.Conn, nil, e)
} else {
c.Conn.ach <- func() { c.Conn.Opts.AsyncErrorCB(c.Conn, nil, e) }
}
}
return
}
}
}
// BindRecvChan binds a channel for receive operations from NATS.
func (c *EncodedConn) BindRecvChan(subject string, channel interface{}) (*Subscription, error) {
return c.bindRecvChan(subject, _EMPTY_, channel)
}
// BindRecvQueueChan binds a channel for queue-based receive operations from NATS.
func (c *EncodedConn) BindRecvQueueChan(subject, queue string, channel interface{}) (*Subscription, error) {
return c.bindRecvChan(subject, queue, channel)
}
// Internal function to bind receive operations for a channel.
func (c *EncodedConn) bindRecvChan(subject, queue string, channel interface{}) (*Subscription, error) {
chVal := reflect.ValueOf(channel)
if chVal.Kind() != reflect.Chan {
return nil, ErrChanArg
}
argType := chVal.Type().Elem()
cb := func(m *Msg) {
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 {
c.Conn.err = errors.New("nats: Got an error trying to unmarshal: " + err.Error())
if c.Conn.Opts.AsyncErrorCB != nil {
c.Conn.ach <- func() { c.Conn.Opts.AsyncErrorCB(c.Conn, m.Sub, c.Conn.err) }
}
return
}
if argType.Kind() != reflect.Ptr {
oPtr = reflect.Indirect(oPtr)
}
// This is a bit hacky, but in this instance we may be trying to send to a closed channel.
// and the user does not know when it is safe to close the channel.
defer func() {
// If we have panicked, recover and close the subscription.
if r := recover(); r != nil {
m.Sub.Unsubscribe()
}
}()
// Actually do the send to the channel.
chVal.Send(oPtr)
}
return c.Conn.subscribe(subject, queue, cb, nil)
}

470
vendor/github.com/nats-io/go-nats/parser.go generated vendored Normal file
View file

@ -0,0 +1,470 @@
// Copyright 2012-2014 Apcera Inc. All rights reserved.
package nats
import (
"fmt"
)
type msgArg struct {
subject []byte
reply []byte
sid int64
size int
}
const MAX_CONTROL_LINE_SIZE = 1024
type parseState struct {
state int
as int
drop int
ma msgArg
argBuf []byte
msgBuf []byte
scratch [MAX_CONTROL_LINE_SIZE]byte
}
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_M
OP_MS
OP_MSG
OP_MSG_SPC
MSG_ARG
MSG_PAYLOAD
MSG_END
OP_P
OP_PI
OP_PIN
OP_PING
OP_PO
OP_PON
OP_PONG
OP_I
OP_IN
OP_INF
OP_INFO
OP_INFO_SPC
INFO_ARG
)
// parse is the fast protocol parser engine.
func (nc *Conn) parse(buf []byte) error {
var i int
var b byte
// Move to loop instead of range syntax to allow jumping of i
for i = 0; i < len(buf); i++ {
b = buf[i]
switch nc.ps.state {
case OP_START:
switch b {
case 'M', 'm':
nc.ps.state = OP_M
case 'P', 'p':
nc.ps.state = OP_P
case '+':
nc.ps.state = OP_PLUS
case '-':
nc.ps.state = OP_MINUS
case 'I', 'i':
nc.ps.state = OP_I
default:
goto parseErr
}
case OP_M:
switch b {
case 'S', 's':
nc.ps.state = OP_MS
default:
goto parseErr
}
case OP_MS:
switch b {
case 'G', 'g':
nc.ps.state = OP_MSG
default:
goto parseErr
}
case OP_MSG:
switch b {
case ' ', '\t':
nc.ps.state = OP_MSG_SPC
default:
goto parseErr
}
case OP_MSG_SPC:
switch b {
case ' ', '\t':
continue
default:
nc.ps.state = MSG_ARG
nc.ps.as = i
}
case MSG_ARG:
switch b {
case '\r':
nc.ps.drop = 1
case '\n':
var arg []byte
if nc.ps.argBuf != nil {
arg = nc.ps.argBuf
} else {
arg = buf[nc.ps.as : i-nc.ps.drop]
}
if err := nc.processMsgArgs(arg); err != nil {
return err
}
nc.ps.drop, nc.ps.as, nc.ps.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 = nc.ps.as + nc.ps.ma.size - 1
default:
if nc.ps.argBuf != nil {
nc.ps.argBuf = append(nc.ps.argBuf, b)
}
}
case MSG_PAYLOAD:
if nc.ps.msgBuf != nil {
if len(nc.ps.msgBuf) >= nc.ps.ma.size {
nc.processMsg(nc.ps.msgBuf)
nc.ps.argBuf, nc.ps.msgBuf, nc.ps.state = nil, nil, MSG_END
} else {
// copy as much as we can to the buffer and skip ahead.
toCopy := nc.ps.ma.size - len(nc.ps.msgBuf)
avail := len(buf) - i
if avail < toCopy {
toCopy = avail
}
if toCopy > 0 {
start := len(nc.ps.msgBuf)
// This is needed for copy to work.
nc.ps.msgBuf = nc.ps.msgBuf[:start+toCopy]
copy(nc.ps.msgBuf[start:], buf[i:i+toCopy])
// Update our index
i = (i + toCopy) - 1
} else {
nc.ps.msgBuf = append(nc.ps.msgBuf, b)
}
}
} else if i-nc.ps.as >= nc.ps.ma.size {
nc.processMsg(buf[nc.ps.as:i])
nc.ps.argBuf, nc.ps.msgBuf, nc.ps.state = nil, nil, MSG_END
}
case MSG_END:
switch b {
case '\n':
nc.ps.drop, nc.ps.as, nc.ps.state = 0, i+1, OP_START
default:
continue
}
case OP_PLUS:
switch b {
case 'O', 'o':
nc.ps.state = OP_PLUS_O
default:
goto parseErr
}
case OP_PLUS_O:
switch b {
case 'K', 'k':
nc.ps.state = OP_PLUS_OK
default:
goto parseErr
}
case OP_PLUS_OK:
switch b {
case '\n':
nc.processOK()
nc.ps.drop, nc.ps.state = 0, OP_START
}
case OP_MINUS:
switch b {
case 'E', 'e':
nc.ps.state = OP_MINUS_E
default:
goto parseErr
}
case OP_MINUS_E:
switch b {
case 'R', 'r':
nc.ps.state = OP_MINUS_ER
default:
goto parseErr
}
case OP_MINUS_ER:
switch b {
case 'R', 'r':
nc.ps.state = OP_MINUS_ERR
default:
goto parseErr
}
case OP_MINUS_ERR:
switch b {
case ' ', '\t':
nc.ps.state = OP_MINUS_ERR_SPC
default:
goto parseErr
}
case OP_MINUS_ERR_SPC:
switch b {
case ' ', '\t':
continue
default:
nc.ps.state = MINUS_ERR_ARG
nc.ps.as = i
}
case MINUS_ERR_ARG:
switch b {
case '\r':
nc.ps.drop = 1
case '\n':
var arg []byte
if nc.ps.argBuf != nil {
arg = nc.ps.argBuf
nc.ps.argBuf = nil
} else {
arg = buf[nc.ps.as : i-nc.ps.drop]
}
nc.processErr(string(arg))
nc.ps.drop, nc.ps.as, nc.ps.state = 0, i+1, OP_START
default:
if nc.ps.argBuf != nil {
nc.ps.argBuf = append(nc.ps.argBuf, b)
}
}
case OP_P:
switch b {
case 'I', 'i':
nc.ps.state = OP_PI
case 'O', 'o':
nc.ps.state = OP_PO
default:
goto parseErr
}
case OP_PO:
switch b {
case 'N', 'n':
nc.ps.state = OP_PON
default:
goto parseErr
}
case OP_PON:
switch b {
case 'G', 'g':
nc.ps.state = OP_PONG
default:
goto parseErr
}
case OP_PONG:
switch b {
case '\n':
nc.processPong()
nc.ps.drop, nc.ps.state = 0, OP_START
}
case OP_PI:
switch b {
case 'N', 'n':
nc.ps.state = OP_PIN
default:
goto parseErr
}
case OP_PIN:
switch b {
case 'G', 'g':
nc.ps.state = OP_PING
default:
goto parseErr
}
case OP_PING:
switch b {
case '\n':
nc.processPing()
nc.ps.drop, nc.ps.state = 0, OP_START
}
case OP_I:
switch b {
case 'N', 'n':
nc.ps.state = OP_IN
default:
goto parseErr
}
case OP_IN:
switch b {
case 'F', 'f':
nc.ps.state = OP_INF
default:
goto parseErr
}
case OP_INF:
switch b {
case 'O', 'o':
nc.ps.state = OP_INFO
default:
goto parseErr
}
case OP_INFO:
switch b {
case ' ', '\t':
nc.ps.state = OP_INFO_SPC
default:
goto parseErr
}
case OP_INFO_SPC:
switch b {
case ' ', '\t':
continue
default:
nc.ps.state = INFO_ARG
nc.ps.as = i
}
case INFO_ARG:
switch b {
case '\r':
nc.ps.drop = 1
case '\n':
var arg []byte
if nc.ps.argBuf != nil {
arg = nc.ps.argBuf
nc.ps.argBuf = nil
} else {
arg = buf[nc.ps.as : i-nc.ps.drop]
}
nc.processAsyncInfo(arg)
nc.ps.drop, nc.ps.as, nc.ps.state = 0, i+1, OP_START
default:
if nc.ps.argBuf != nil {
nc.ps.argBuf = append(nc.ps.argBuf, b)
}
}
default:
goto parseErr
}
}
// Check for split buffer scenarios
if (nc.ps.state == MSG_ARG || nc.ps.state == MINUS_ERR_ARG || nc.ps.state == INFO_ARG) && nc.ps.argBuf == nil {
nc.ps.argBuf = nc.ps.scratch[:0]
nc.ps.argBuf = append(nc.ps.argBuf, buf[nc.ps.as:i-nc.ps.drop]...)
// FIXME, check max len
}
// Check for split msg
if nc.ps.state == MSG_PAYLOAD && nc.ps.msgBuf == nil {
// We need to clone the msgArg if it is still referencing the
// read buffer and we are not able to process the msg.
if nc.ps.argBuf == nil {
nc.cloneMsgArg()
}
// If we will overflow the scratch buffer, just create a
// new buffer to hold the split message.
if nc.ps.ma.size > cap(nc.ps.scratch)-len(nc.ps.argBuf) {
lrem := len(buf[nc.ps.as:])
nc.ps.msgBuf = make([]byte, lrem, nc.ps.ma.size)
copy(nc.ps.msgBuf, buf[nc.ps.as:])
} else {
nc.ps.msgBuf = nc.ps.scratch[len(nc.ps.argBuf):len(nc.ps.argBuf)]
nc.ps.msgBuf = append(nc.ps.msgBuf, (buf[nc.ps.as:])...)
}
}
return nil
parseErr:
return fmt.Errorf("nats: Parse Error [%d]: '%s'", nc.ps.state, buf[i:])
}
// cloneMsgArg 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 (nc *Conn) cloneMsgArg() {
nc.ps.argBuf = nc.ps.scratch[:0]
nc.ps.argBuf = append(nc.ps.argBuf, nc.ps.ma.subject...)
nc.ps.argBuf = append(nc.ps.argBuf, nc.ps.ma.reply...)
nc.ps.ma.subject = nc.ps.argBuf[:len(nc.ps.ma.subject)]
if nc.ps.ma.reply != nil {
nc.ps.ma.reply = nc.ps.argBuf[len(nc.ps.ma.subject):]
}
}
const argsLenMax = 4
func (nc *Conn) processMsgArgs(arg []byte) error {
// Unroll splitArgs to avoid runtime/heap issues
a := [argsLenMax][]byte{}
args := a[:0]
start := -1
for i, b := range arg {
switch b {
case ' ', '\t', '\r', '\n':
if start >= 0 {
args = append(args, arg[start:i])
start = -1
}
default:
if start < 0 {
start = i
}
}
}
if start >= 0 {
args = append(args, arg[start:])
}
switch len(args) {
case 3:
nc.ps.ma.subject = args[0]
nc.ps.ma.sid = parseInt64(args[1])
nc.ps.ma.reply = nil
nc.ps.ma.size = int(parseInt64(args[2]))
case 4:
nc.ps.ma.subject = args[0]
nc.ps.ma.sid = parseInt64(args[1])
nc.ps.ma.reply = args[2]
nc.ps.ma.size = int(parseInt64(args[3]))
default:
return fmt.Errorf("nats: processMsgArgs Parse Error: '%s'", arg)
}
if nc.ps.ma.sid < 0 {
return fmt.Errorf("nats: processMsgArgs Bad or Missing Sid: '%s'", arg)
}
if nc.ps.ma.size < 0 {
return fmt.Errorf("nats: processMsgArgs Bad or Missing Size: '%s'", arg)
}
return nil
}
// Ascii numbers 0-9
const (
ascii_0 = 48
ascii_9 = 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 < ascii_0 || dec > ascii_9 {
return -1
}
n = n*10 + (int64(dec) - ascii_0)
}
return n
}

View file

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2016 Apcera Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -0,0 +1,115 @@
// Copyright 2016 Apcera Inc. All rights reserved.
package server
import (
"github.com/nats-io/nats-streaming-server/stores"
"sync"
"time"
)
// This is a proxy to the store interface.
type clientStore struct {
store stores.Store
}
// client has information needed by the server. A client is also
// stored in a stores.Client object (which contains ID and HbInbox).
type client struct {
sync.RWMutex
unregistered bool
hbt *time.Timer
fhb int
subs []*subState
}
// Register a client if new, otherwise returns the client already registered
// and `false` to indicate that the client is not new.
func (cs *clientStore) Register(ID, hbInbox string) (*stores.Client, bool, error) {
// Will be gc'ed if we fail to register, that's ok.
c := &client{subs: make([]*subState, 0, 4)}
sc, isNew, err := cs.store.AddClient(ID, hbInbox, c)
if err != nil {
return nil, false, err
}
return sc, isNew, nil
}
// Unregister a client.
func (cs *clientStore) Unregister(ID string) *stores.Client {
sc := cs.store.DeleteClient(ID)
if sc != nil {
c := sc.UserData.(*client)
c.Lock()
c.unregistered = true
c.Unlock()
}
return sc
}
// IsValid returns true if the client is registered, false otherwise.
func (cs *clientStore) IsValid(ID string) bool {
return cs.store.GetClient(ID) != nil
}
// Lookup a client
func (cs *clientStore) Lookup(ID string) *client {
sc := cs.store.GetClient(ID)
if sc != nil {
return sc.UserData.(*client)
}
return nil
}
// GetSubs returns the list of subscriptions for the client identified by ID,
// or nil if such client is not found.
func (cs *clientStore) GetSubs(ID string) []*subState {
c := cs.Lookup(ID)
if c == nil {
return nil
}
c.RLock()
subs := make([]*subState, len(c.subs))
copy(subs, c.subs)
c.RUnlock()
return subs
}
// AddSub adds the subscription to the client identified by clientID
// and returns true only if the client has not been unregistered,
// otherwise returns false.
func (cs *clientStore) AddSub(ID string, sub *subState) bool {
sc := cs.store.GetClient(ID)
if sc == nil {
return false
}
c := sc.UserData.(*client)
c.Lock()
if c.unregistered {
c.Unlock()
return false
}
c.subs = append(c.subs, sub)
c.Unlock()
return true
}
// RemoveSub removes the subscription from the client identified by clientID
// and returns true only if the client has not been unregistered and that
// the subscription was found, otherwise returns false.
func (cs *clientStore) RemoveSub(ID string, sub *subState) bool {
sc := cs.store.GetClient(ID)
if sc == nil {
return false
}
c := sc.UserData.(*client)
c.Lock()
if c.unregistered {
c.Unlock()
return false
}
removed := false
c.subs, removed = sub.deleteFromList(c.subs)
c.Unlock()
return removed
}

View file

@ -0,0 +1,291 @@
// Copyright 2016 Apcera Inc. All rights reserved.
package server
import (
"fmt"
"io/ioutil"
"reflect"
"strings"
"time"
"github.com/nats-io/gnatsd/conf"
"github.com/nats-io/nats-streaming-server/stores"
)
// ProcessConfigFile parses the configuration file `configFile` and updates
// the given Streaming options `opts`.
func ProcessConfigFile(configFile string, opts *Options) error {
data, err := ioutil.ReadFile(configFile)
if err != nil {
return err
}
m, err := conf.Parse(string(data))
if err != nil {
return err
}
for k, v := range m {
name := strings.ToLower(k)
switch name {
case "id", "cid", "cluster_id":
if err := checkType(k, reflect.String, v); err != nil {
return err
}
opts.ID = v.(string)
case "discover_prefix":
if err := checkType(k, reflect.String, v); err != nil {
return err
}
opts.DiscoverPrefix = v.(string)
case "st", "store_type", "store", "storetype":
if err := checkType(k, reflect.String, v); err != nil {
return err
}
switch strings.ToUpper(v.(string)) {
case stores.TypeFile:
opts.StoreType = stores.TypeFile
case stores.TypeMemory:
opts.StoreType = stores.TypeMemory
default:
return fmt.Errorf("Unknown store type: %v", v.(string))
}
case "dir", "datastore":
if err := checkType(k, reflect.String, v); err != nil {
return err
}
opts.FilestoreDir = v.(string)
case "sd", "stan_debug":
if err := checkType(k, reflect.Bool, v); err != nil {
return err
}
opts.Debug = v.(bool)
case "sv", "stan_trace":
if err := checkType(k, reflect.Bool, v); err != nil {
return err
}
opts.Trace = v.(bool)
case "ns", "nats_server", "nats_server_url":
if err := checkType(k, reflect.String, v); err != nil {
return err
}
opts.NATSServerURL = v.(string)
case "secure":
if err := checkType(k, reflect.Bool, v); err != nil {
return err
}
opts.Secure = v.(bool)
case "tls":
if err := parseTLS(v, opts); err != nil {
return err
}
case "limits", "store_limits", "storelimits":
if err := parseStoreLimits(v, opts); err != nil {
return err
}
case "file", "file_options":
if err := parseFileOptions(v, opts); err != nil {
return err
}
}
}
return nil
}
// checkType returns a formatted error if `v` is not of the expected kind.
func checkType(name string, kind reflect.Kind, v interface{}) error {
actualKind := reflect.TypeOf(v).Kind()
if actualKind != kind {
return fmt.Errorf("Parameter %q value is expected to be %v, got %v",
name, kind.String(), actualKind.String())
}
return nil
}
// parseTLS updates `opts` with TLS config
func parseTLS(itf interface{}, opts *Options) error {
m, ok := itf.(map[string]interface{})
if !ok {
return fmt.Errorf("Expected TLS to be a map/struct, got %v", itf)
}
for k, v := range m {
name := strings.ToLower(k)
switch name {
case "client_cert":
if err := checkType(k, reflect.String, v); err != nil {
return err
}
opts.ClientCert = v.(string)
case "client_key":
if err := checkType(k, reflect.String, v); err != nil {
return err
}
opts.ClientKey = v.(string)
case "client_ca", "client_cacert":
if err := checkType(k, reflect.String, v); err != nil {
return err
}
opts.ClientCA = v.(string)
}
}
return nil
}
// parseStoreLimits updates `opts` with store limits
func parseStoreLimits(itf interface{}, opts *Options) error {
m, ok := itf.(map[string]interface{})
if !ok {
return fmt.Errorf("Expected store limits to be a map/struct, got %v", itf)
}
for k, v := range m {
name := strings.ToLower(k)
switch name {
case "mc", "max_channels", "maxchannels":
if err := checkType(k, reflect.Int64, v); err != nil {
return err
}
opts.MaxChannels = int(v.(int64))
case "channels", "channels_limits", "channelslimits", "per_channel", "per_channel_limits":
if err := parsePerChannelLimits(v, opts); err != nil {
return err
}
default:
// Check for the global limits (MaxMsgs, MaxBytes, etc..)
if err := parseChannelLimits(&opts.ChannelLimits, k, name, v); err != nil {
return err
}
}
}
return nil
}
// parseChannelLimits updates `cl` with channel limits.
func parseChannelLimits(cl *stores.ChannelLimits, k, name string, v interface{}) error {
switch name {
case "msu", "max_subs", "max_subscriptions", "maxsubscriptions":
if err := checkType(k, reflect.Int64, v); err != nil {
return err
}
cl.MaxSubscriptions = int(v.(int64))
case "mm", "max_msgs", "maxmsgs", "max_count", "maxcount":
if err := checkType(k, reflect.Int64, v); err != nil {
return err
}
cl.MaxMsgs = int(v.(int64))
case "mb", "max_bytes", "maxbytes":
if err := checkType(k, reflect.Int64, v); err != nil {
return err
}
cl.MaxBytes = v.(int64)
case "ma", "max_age", "maxage":
if err := checkType(k, reflect.String, v); err != nil {
return err
}
dur, err := time.ParseDuration(v.(string))
if err != nil {
return err
}
cl.MaxAge = dur
}
return nil
}
// parsePerChannelLimits updates `opts` with per channel limits.
func parsePerChannelLimits(itf interface{}, opts *Options) error {
m, ok := itf.(map[string]interface{})
if !ok {
return fmt.Errorf("Expected per channel limits to be a map/struct, got %v", itf)
}
for channelName, limits := range m {
limitsMap, ok := limits.(map[string]interface{})
if !ok {
return fmt.Errorf("Expected channel limits to be a map/struct, got %v", limits)
}
cl := &stores.ChannelLimits{}
for k, v := range limitsMap {
name := strings.ToLower(k)
if err := parseChannelLimits(cl, k, name, v); err != nil {
return err
}
}
sl := &opts.StoreLimits
sl.AddPerChannel(channelName, cl)
}
return nil
}
func parseFileOptions(itf interface{}, opts *Options) error {
m, ok := itf.(map[string]interface{})
if !ok {
return fmt.Errorf("Expected file options to be a map/struct, got %v", itf)
}
for k, v := range m {
name := strings.ToLower(k)
switch name {
case "compact", "compact_enabled":
if err := checkType(k, reflect.Bool, v); err != nil {
return err
}
opts.FileStoreOpts.CompactEnabled = v.(bool)
case "compact_frag", "compact_fragmentation":
if err := checkType(k, reflect.Int64, v); err != nil {
return err
}
opts.FileStoreOpts.CompactFragmentation = int(v.(int64))
case "compact_interval":
if err := checkType(k, reflect.Int64, v); err != nil {
return err
}
opts.FileStoreOpts.CompactInterval = int(v.(int64))
case "compact_min_size":
if err := checkType(k, reflect.Int64, v); err != nil {
return err
}
opts.FileStoreOpts.CompactMinFileSize = v.(int64)
case "buffer_size":
if err := checkType(k, reflect.Int64, v); err != nil {
return err
}
opts.FileStoreOpts.BufferSize = int(v.(int64))
case "crc", "do_crc":
if err := checkType(k, reflect.Bool, v); err != nil {
return err
}
opts.FileStoreOpts.DoCRC = v.(bool)
case "crc_poly":
if err := checkType(k, reflect.Int64, v); err != nil {
return err
}
opts.FileStoreOpts.CRCPolynomial = v.(int64)
case "sync", "do_sync", "sync_on_flush":
if err := checkType(k, reflect.Bool, v); err != nil {
return err
}
opts.FileStoreOpts.DoSync = v.(bool)
case "slice_max_msgs", "slice_max_count", "slice_msgs", "slice_count":
if err := checkType(k, reflect.Int64, v); err != nil {
return err
}
opts.FileStoreOpts.SliceMaxMsgs = int(v.(int64))
case "slice_max_bytes", "slice_max_size", "slice_bytes", "slice_size":
if err := checkType(k, reflect.Int64, v); err != nil {
return err
}
opts.FileStoreOpts.SliceMaxBytes = v.(int64)
case "slice_max_age", "slice_age", "slice_max_time", "slice_time_limit":
if err := checkType(k, reflect.String, v); err != nil {
return err
}
dur, err := time.ParseDuration(v.(string))
if err != nil {
return err
}
opts.FileStoreOpts.SliceMaxAge = dur
case "slice_archive_script", "slice_archive", "slice_script":
if err := checkType(k, reflect.String, v); err != nil {
return err
}
opts.FileStoreOpts.SliceArchiveScript = v.(string)
}
}
return nil
}

View file

@ -0,0 +1,149 @@
// Copyright 2016 Apcera Inc. All rights reserved.
package server
import (
"github.com/nats-io/gnatsd/logger"
natsd "github.com/nats-io/gnatsd/server"
"os"
"sync"
"sync/atomic"
)
// Logging in STAN
//
// The STAN logger is an instance of a NATS logger, (basically duplicated
// from the NATS server code), and is passed into the NATS server.
//
// A note on Debugf and Tracef: These will be enabled within the log if
// either STAN or the NATS server enables them. However, STAN will only
// trace/debug if the local STAN debug/trace flags are set. NATS will do
// the same with it's logger flags. This enables us to use the same logger,
// but differentiate between STAN and NATS debug/trace.
//
// All logging functions are fully implemented (versus calling into the NATS
// server) in case STAN is decoupled from the NATS server.
// Package globals for performance checks
var trace int32
var debug int32
// The STAN logger, encapsulates a NATS logger
var stanLog = struct {
sync.Mutex
logger natsd.Logger
}{}
// ConfigureLogger configures logging for STAN and the embedded NATS server
// based on options passed.
func ConfigureLogger(stanOpts *Options, natsOpts *natsd.Options) {
var s *natsd.Server
var newLogger natsd.Logger
sOpts := stanOpts
nOpts := natsOpts
if sOpts == nil {
sOpts = GetDefaultOptions()
}
if nOpts == nil {
nOpts = &natsd.Options{}
}
enableDebug := nOpts.Debug || sOpts.Debug
enableTrace := nOpts.Trace || sOpts.Trace
if nOpts.LogFile != "" {
newLogger = logger.NewFileLogger(nOpts.LogFile, nOpts.Logtime, enableDebug, sOpts.Trace, true)
} else if nOpts.RemoteSyslog != "" {
newLogger = logger.NewRemoteSysLogger(nOpts.RemoteSyslog, sOpts.Debug, sOpts.Trace)
} else if nOpts.Syslog {
newLogger = logger.NewSysLogger(sOpts.Debug, sOpts.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
}
newLogger = logger.NewStdLogger(nOpts.Logtime, enableDebug, enableTrace, colors, true)
}
if sOpts.Debug {
atomic.StoreInt32(&debug, 1)
}
if sOpts.Trace {
atomic.StoreInt32(&trace, 1)
}
// The NATS server will use the STAN logger
s.SetLogger(newLogger, nOpts.Debug, nOpts.Trace)
stanLog.Lock()
stanLog.logger = newLogger
stanLog.Unlock()
}
// RemoveLogger clears the logger instance and debug/trace flags.
// Used for testing.
func RemoveLogger() {
var s *natsd.Server
atomic.StoreInt32(&trace, 0)
atomic.StoreInt32(&debug, 0)
stanLog.Lock()
stanLog.logger = nil
stanLog.Unlock()
s.SetLogger(nil, false, false)
}
// Noticef logs a notice statement
func Noticef(format string, v ...interface{}) {
executeLogCall(func(log natsd.Logger, format string, v ...interface{}) {
log.Noticef(format, v...)
}, format, v...)
}
// Errorf logs an error
func Errorf(format string, v ...interface{}) {
executeLogCall(func(log natsd.Logger, format string, v ...interface{}) {
log.Errorf(format, v...)
}, format, v...)
}
// Fatalf logs a fatal error
func Fatalf(format string, v ...interface{}) {
executeLogCall(func(log natsd.Logger, format string, v ...interface{}) {
log.Fatalf(format, v...)
}, format, v...)
}
// Debugf logs a debug statement
func Debugf(format string, v ...interface{}) {
if atomic.LoadInt32(&debug) != 0 {
executeLogCall(func(log natsd.Logger, format string, v ...interface{}) {
log.Debugf(format, v...)
}, format, v...)
}
}
// Tracef logs a trace statement
func Tracef(format string, v ...interface{}) {
if atomic.LoadInt32(&trace) != 0 {
executeLogCall(func(logger natsd.Logger, format string, v ...interface{}) {
logger.Tracef(format, v...)
}, format, v...)
}
}
func executeLogCall(f func(logger natsd.Logger, format string, v ...interface{}), format string, args ...interface{}) {
stanLog.Lock()
defer stanLog.Unlock()
if stanLog.logger == nil {
return
}
f(stanLog.logger, format, args...)
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,71 @@
// Copyright 2016 Apcera Inc. All rights reserved.
//
// Uses https://github.com/gogo/protobuf
// compiled via `protoc -I=. -I=$GOPATH/src --gogofaster_out=. protocol.proto`
syntax = "proto3";
package spb;
import "github.com/gogo/protobuf/gogoproto/gogo.proto";
option (gogoproto.marshaler_all) = true;
option (gogoproto.sizer_all) = true;
option (gogoproto.unmarshaler_all) = true;
option (gogoproto.goproto_getters_all) = false;
// SubState represents the state of a Subscription
message SubState {
uint64 ID = 1; // Subscription ID assigned by the SubStore interface
string clientID = 2; // ClientID
string qGroup = 3; // Optional queue group
string inbox = 4; // Inbox subject to deliver messages on
string ackInbox = 5; // Inbox for acks
int32 maxInFlight = 6; // Maximum inflight messages without an ack allowed
int32 ackWaitInSecs = 7; // Timeout for receiving an ack from the client
string durableName = 8; // Optional durable name which survives client restarts
uint64 lastSent = 9; // Start position
bool isDurable =10; // Indicate durability for this subscriber
}
// SubStateDelete marks a Subscription as deleted
message SubStateDelete {
uint64 ID = 1; // Subscription ID being deleted
}
// SubStateUpdate represents a subscription update (either Msg or Ack)
message SubStateUpdate {
uint64 ID = 1; // Subscription ID
uint64 seqno = 2; // Sequence of the message (pending or ack'ed)
}
// ServerInfo contains basic information regarding the Server
message ServerInfo {
string ClusterID = 1; // Cluster ID
string Discovery = 2; // Subject server receives connect requests on.
string Publish = 3; // Subject prefix server receives published messages on.
string Subscribe = 4; // Subject server receives subscription requests on.
string Unsubscribe = 5; // Subject server receives unsubscribe requests on.
string Close = 6; // Subject server receives close requests on.
string SubClose = 7; // Subject server receives subscription close requests on.
}
// ClientInfo contains information related to a Client
message ClientInfo {
string ID = 1; // Client ID
string HbInbox = 2; // The inbox heartbeats are sent to
}
message ClientDelete {
string ID = 1; // ID of the client being unregistered
}
message CtrlMsg {
enum Type {
SubUnsubscribe = 0; // Subscription Unsubscribe request.
SubClose = 1; // Subscription Close request.
ConnClose = 2; // Connection Close request.
}
Type MsgType = 1; // Type of the control message.
string ServerID = 2; // Allows a server to detect if it is the intended receipient.
bytes Data = 3; // Optional bytes that carries context information.
}

View file

@ -0,0 +1,400 @@
// Copyright 2016 Apcera Inc. All rights reserved.
package stores
import (
"fmt"
"sync"
"github.com/nats-io/go-nats-streaming/pb"
"github.com/nats-io/nats-streaming-server/spb"
)
// format string used to report that limit is reached when storing
// messages.
var droppingMsgsFmt = "WARNING: Reached limits for store %q (msgs=%v/%v bytes=%v/%v), " +
"dropping old messages to make room for new ones."
// commonStore contains everything that is common to any type of store
type commonStore struct {
sync.RWMutex
closed bool
}
// genericStore is the generic store implementation with a map of channels.
type genericStore struct {
commonStore
limits StoreLimits
name string
channels map[string]*ChannelStore
clients map[string]*Client
}
// genericSubStore is the generic store implementation that manages subscriptions
// for a given channel.
type genericSubStore struct {
commonStore
limits SubStoreLimits
subject string // Can't be wildcard
subsCount int
maxSubID uint64
}
// genericMsgStore is the generic store implementation that manages messages
// for a given channel.
type genericMsgStore struct {
commonStore
limits MsgStoreLimits
subject string // Can't be wildcard
first uint64
last uint64
totalCount int
totalBytes uint64
hitLimit bool // indicates if store had to drop messages due to limit
}
////////////////////////////////////////////////////////////////////////////
// genericStore methods
////////////////////////////////////////////////////////////////////////////
// init initializes the structure of a generic store
func (gs *genericStore) init(name string, limits *StoreLimits) {
gs.name = name
if limits == nil {
limits = &DefaultStoreLimits
}
gs.setLimits(limits)
// Do not use limits values to create the map.
gs.channels = make(map[string]*ChannelStore)
gs.clients = make(map[string]*Client)
}
// Init can be used to initialize the store with server's information.
func (gs *genericStore) Init(info *spb.ServerInfo) error {
return nil
}
// Name returns the type name of this store
func (gs *genericStore) Name() string {
return gs.name
}
// setLimits makes a copy of the given StoreLimits,
// validates the limits and if ok, applies the inheritance.
func (gs *genericStore) setLimits(limits *StoreLimits) error {
// Make a copy
gs.limits = *limits
// of the map too
if len(limits.PerChannel) > 0 {
gs.limits.PerChannel = make(map[string]*ChannelLimits, len(limits.PerChannel))
for key, val := range limits.PerChannel {
// Make a copy of the values. We want ownership
// of those structures
gs.limits.PerChannel[key] = &(*val)
}
}
// Build will validate and apply inheritance if no error.
sl := &gs.limits
return sl.Build()
}
// SetLimits sets limits for this store
func (gs *genericStore) SetLimits(limits *StoreLimits) error {
gs.Lock()
err := gs.setLimits(limits)
gs.Unlock()
return err
}
// CreateChannel creates a ChannelStore for the given channel, and returns
// `true` to indicate that the channel is new, false if it already exists.
func (gs *genericStore) CreateChannel(channel string, userData interface{}) (*ChannelStore, bool, error) {
// no-op
return nil, false, fmt.Errorf("Generic store, feature not implemented")
}
// LookupChannel returns a ChannelStore for the given channel.
func (gs *genericStore) LookupChannel(channel string) *ChannelStore {
gs.RLock()
cs := gs.channels[channel]
gs.RUnlock()
return cs
}
// HasChannel returns true if this store has any channel
func (gs *genericStore) HasChannel() bool {
gs.RLock()
l := len(gs.channels)
gs.RUnlock()
return l > 0
}
// State returns message store statistics for a given channel ('*' for all)
func (gs *genericStore) MsgsState(channel string) (numMessages int, byteSize uint64, err error) {
numMessages = 0
byteSize = 0
err = nil
if channel == AllChannels {
gs.RLock()
cs := gs.channels
gs.RUnlock()
for _, c := range cs {
n, b, lerr := c.Msgs.State()
if lerr != nil {
err = lerr
return
}
numMessages += n
byteSize += b
}
} else {
cs := gs.LookupChannel(channel)
if cs != nil {
numMessages, byteSize, err = cs.Msgs.State()
}
}
return
}
// canAddChannel returns true if the current number of channels is below the limit.
// Store lock is assumed to be locked.
func (gs *genericStore) canAddChannel() error {
if gs.limits.MaxChannels > 0 && len(gs.channels) >= gs.limits.MaxChannels {
return ErrTooManyChannels
}
return nil
}
// AddClient stores information about the client identified by `clientID`.
func (gs *genericStore) AddClient(clientID, hbInbox string, userData interface{}) (*Client, bool, error) {
c := &Client{spb.ClientInfo{ID: clientID, HbInbox: hbInbox}, userData}
gs.Lock()
oldClient := gs.clients[clientID]
if oldClient != nil {
gs.Unlock()
return oldClient, false, nil
}
gs.clients[c.ID] = c
gs.Unlock()
return c, true, nil
}
// GetClient returns the stored Client, or nil if it does not exist.
func (gs *genericStore) GetClient(clientID string) *Client {
gs.RLock()
c := gs.clients[clientID]
gs.RUnlock()
return c
}
// GetClients returns all stored Client objects, as a map keyed by client IDs.
func (gs *genericStore) GetClients() map[string]*Client {
gs.RLock()
clients := make(map[string]*Client, len(gs.clients))
for k, v := range gs.clients {
clients[k] = v
}
gs.RUnlock()
return clients
}
// GetClientsCount returns the number of registered clients
func (gs *genericStore) GetClientsCount() int {
gs.RLock()
count := len(gs.clients)
gs.RUnlock()
return count
}
// DeleteClient deletes the client identified by `clientID`.
func (gs *genericStore) DeleteClient(clientID string) *Client {
gs.Lock()
c := gs.clients[clientID]
if c != nil {
delete(gs.clients, clientID)
}
gs.Unlock()
return c
}
// Close closes all stores
func (gs *genericStore) Close() error {
gs.Lock()
defer gs.Unlock()
if gs.closed {
return nil
}
gs.closed = true
return gs.close()
}
// close closes all stores. Store lock is assumed held on entry
func (gs *genericStore) close() error {
var err error
var lerr error
for _, cs := range gs.channels {
lerr = cs.Subs.Close()
if lerr != nil && err == nil {
err = lerr
}
lerr = cs.Msgs.Close()
if lerr != nil && err == nil {
err = lerr
}
}
return err
}
////////////////////////////////////////////////////////////////////////////
// genericMsgStore methods
////////////////////////////////////////////////////////////////////////////
// init initializes this generic message store
func (gms *genericMsgStore) init(subject string, limits *MsgStoreLimits) {
gms.subject = subject
gms.limits = *limits
}
// State returns some statistics related to this store
func (gms *genericMsgStore) State() (numMessages int, byteSize uint64, err error) {
gms.RLock()
c, b := gms.totalCount, gms.totalBytes
gms.RUnlock()
return c, b, nil
}
// FirstSequence returns sequence for first message stored.
func (gms *genericMsgStore) FirstSequence() uint64 {
gms.RLock()
first := gms.first
gms.RUnlock()
return first
}
// LastSequence returns sequence for last message stored.
func (gms *genericMsgStore) LastSequence() uint64 {
gms.RLock()
last := gms.last
gms.RUnlock()
return last
}
// FirstAndLastSequence returns sequences for the first and last messages stored.
func (gms *genericMsgStore) FirstAndLastSequence() (uint64, uint64) {
gms.RLock()
first, last := gms.first, gms.last
gms.RUnlock()
return first, last
}
// Lookup returns the stored message with given sequence number.
func (gms *genericMsgStore) Lookup(seq uint64) *pb.MsgProto {
// no-op
return nil
}
// FirstMsg returns the first message stored.
func (gms *genericMsgStore) FirstMsg() *pb.MsgProto {
// no-op
return nil
}
// LastMsg returns the last message stored.
func (gms *genericMsgStore) LastMsg() *pb.MsgProto {
// no-op
return nil
}
func (gms *genericMsgStore) Flush() error {
// no-op
return nil
}
// GetSequenceFromTimestamp returns the sequence of the first message whose
// timestamp is greater or equal to given timestamp.
func (gms *genericMsgStore) GetSequenceFromTimestamp(timestamp int64) uint64 {
// no-op
return 0
}
// Close closes this store.
func (gms *genericMsgStore) Close() error {
return nil
}
////////////////////////////////////////////////////////////////////////////
// genericSubStore methods
////////////////////////////////////////////////////////////////////////////
// init initializes the structure of a generic sub store
func (gss *genericSubStore) init(channel string, limits *SubStoreLimits) {
gss.subject = channel
gss.limits = *limits
}
// CreateSub records a new subscription represented by SubState. On success,
// it records the subscription's ID in SubState.ID. This ID is to be used
// by the other SubStore methods.
func (gss *genericSubStore) CreateSub(sub *spb.SubState) error {
gss.Lock()
err := gss.createSub(sub)
gss.Unlock()
return err
}
// UpdateSub updates a given subscription represented by SubState.
func (gss *genericSubStore) UpdateSub(sub *spb.SubState) error {
return nil
}
// createSub is the unlocked version of CreateSub that can be used by
// non-generic implementations.
func (gss *genericSubStore) createSub(sub *spb.SubState) error {
if gss.limits.MaxSubscriptions > 0 && gss.subsCount >= gss.limits.MaxSubscriptions {
return ErrTooManySubs
}
// Bump the max value before assigning it to the new subscription.
gss.maxSubID++
gss.subsCount++
// This new subscription has the max value.
sub.ID = gss.maxSubID
return nil
}
// DeleteSub invalidates this subscription.
func (gss *genericSubStore) DeleteSub(subid uint64) {
gss.Lock()
gss.subsCount--
gss.Unlock()
}
// AddSeqPending adds the given message seqno to the given subscription.
func (gss *genericSubStore) AddSeqPending(subid, seqno uint64) error {
// no-op
return nil
}
// AckSeqPending records that the given message seqno has been acknowledged
// by the given subscription.
func (gss *genericSubStore) AckSeqPending(subid, seqno uint64) error {
// no-op
return nil
}
// Flush is for stores that may buffer operations and need them to be persisted.
func (gss *genericSubStore) Flush() error {
// no-op
return nil
}
// Close closes this store
func (gss *genericSubStore) Close() error {
// no-op
return nil
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,109 @@
// Copyright 2016 Apcera Inc. All rights reserved.
package stores
import (
"fmt"
)
// AddPerChannel stores limits for the given channel `name` in the StoreLimits.
// Inheritance (that is, specifying 0 for a limit means that the global limit
// should be used) is not applied in this call. This is done in StoreLimits.Build
// along with some validation.
func (sl *StoreLimits) AddPerChannel(name string, cl *ChannelLimits) {
if sl.PerChannel == nil {
sl.PerChannel = make(map[string]*ChannelLimits)
}
sl.PerChannel[name] = cl
}
// Build sets the global limits into per-channel limits that are set
// to zero. This call also validates the limits. An error is returned if:
// * any limit is set to a negative value.
// * the number of per-channel is higher than StoreLimits.MaxChannels.
// * any per-channel limit is higher than the corresponding global limit.
func (sl *StoreLimits) Build() error {
// Check that there is no negative value
if sl.MaxChannels < 0 {
return fmt.Errorf("Max channels limit cannot be negative")
}
if err := sl.checkChannelLimits(&sl.ChannelLimits, ""); err != nil {
return err
}
// If there is no per-channel, we are done.
if len(sl.PerChannel) == 0 {
return nil
}
if len(sl.PerChannel) > sl.MaxChannels {
return fmt.Errorf("Too many channels defined (%v). The max channels limit is set to %v",
len(sl.PerChannel), sl.MaxChannels)
}
for cn, cl := range sl.PerChannel {
if err := sl.checkChannelLimits(cl, cn); err != nil {
return err
}
}
// If we are here, it means that there was no error,
// so we now apply inheritance.
for _, cl := range sl.PerChannel {
if cl.MaxSubscriptions == 0 {
cl.MaxSubscriptions = sl.MaxSubscriptions
}
if cl.MaxMsgs == 0 {
cl.MaxMsgs = sl.MaxMsgs
}
if cl.MaxBytes == 0 {
cl.MaxBytes = sl.MaxBytes
}
if cl.MaxAge == 0 {
cl.MaxAge = sl.MaxAge
}
}
return nil
}
func (sl *StoreLimits) checkChannelLimits(cl *ChannelLimits, channelName string) error {
// Check that there is no per-channel unlimited limit if corresponding
// limit is not.
if err := verifyLimit("subscriptions", channelName,
int64(cl.MaxSubscriptions), int64(sl.MaxSubscriptions)); err != nil {
return err
}
if err := verifyLimit("messages", channelName,
int64(cl.MaxMsgs), int64(sl.MaxMsgs)); err != nil {
return err
}
if err := verifyLimit("bytes", channelName,
cl.MaxBytes, sl.MaxBytes); err != nil {
return err
}
if err := verifyLimit("age", channelName,
int64(cl.MaxAge), int64(sl.MaxAge)); err != nil {
return err
}
return nil
}
func verifyLimit(errText, channelName string, limit, globalLimit int64) error {
// No limit can be negative. If channelName is "" we are
// verifying the global limit (in this case limit == globalLimit).
// Otherwise, we verify a given per-channel limit. Make
// sure that the value is not greater than the corresponding
// global limit.
if channelName == "" {
if limit < 0 {
return fmt.Errorf("Max %s for global limit cannot be negative", errText)
}
return nil
}
// Per-channel limit specific here.
if limit < 0 {
return fmt.Errorf("Max %s for channel %q cannot be negative. "+
"Set it to 0 to be equal to the global limit of %v", errText, channelName, globalLimit)
}
if limit > globalLimit {
return fmt.Errorf("Max %s for channel %q cannot be higher than global limit "+
"of %v", errText, channelName, globalLimit)
}
return nil
}

View file

@ -0,0 +1,248 @@
// Copyright 2016 Apcera Inc. All rights reserved.
package stores
import (
"sort"
"sync"
"time"
"github.com/nats-io/go-nats-streaming/pb"
)
// MemoryStore is a factory for message and subscription stores.
type MemoryStore struct {
genericStore
}
// MemorySubStore is a subscription store in memory
type MemorySubStore struct {
genericSubStore
}
// MemoryMsgStore is a per channel message store in memory
type MemoryMsgStore struct {
genericMsgStore
msgs map[uint64]*pb.MsgProto
ageTimer *time.Timer
wg sync.WaitGroup
}
////////////////////////////////////////////////////////////////////////////
// MemoryStore methods
////////////////////////////////////////////////////////////////////////////
// NewMemoryStore returns a factory for stores held in memory.
// If not limits are provided, the store will be created with
// DefaultStoreLimits.
func NewMemoryStore(limits *StoreLimits) (*MemoryStore, error) {
ms := &MemoryStore{}
ms.init(TypeMemory, limits)
return ms, nil
}
// CreateChannel creates a ChannelStore for the given channel, and returns
// `true` to indicate that the channel is new, false if it already exists.
func (ms *MemoryStore) CreateChannel(channel string, userData interface{}) (*ChannelStore, bool, error) {
ms.Lock()
defer ms.Unlock()
channelStore := ms.channels[channel]
if channelStore != nil {
return channelStore, false, nil
}
if err := ms.canAddChannel(); err != nil {
return nil, false, err
}
// Defaults to the global limits
msgStoreLimits := ms.limits.MsgStoreLimits
subStoreLimits := ms.limits.SubStoreLimits
// See if there is an override
thisChannelLimits, exists := ms.limits.PerChannel[channel]
if exists {
// Use this channel specific limits
msgStoreLimits = thisChannelLimits.MsgStoreLimits
subStoreLimits = thisChannelLimits.SubStoreLimits
}
msgStore := &MemoryMsgStore{msgs: make(map[uint64]*pb.MsgProto, 64)}
msgStore.init(channel, &msgStoreLimits)
subStore := &MemorySubStore{}
subStore.init(channel, &subStoreLimits)
channelStore = &ChannelStore{
Subs: subStore,
Msgs: msgStore,
UserData: userData,
}
ms.channels[channel] = channelStore
return channelStore, true, nil
}
////////////////////////////////////////////////////////////////////////////
// MemoryMsgStore methods
////////////////////////////////////////////////////////////////////////////
// Store a given message.
func (ms *MemoryMsgStore) Store(data []byte) (uint64, error) {
ms.Lock()
defer ms.Unlock()
if ms.first == 0 {
ms.first = 1
}
ms.last++
m := &pb.MsgProto{
Sequence: ms.last,
Subject: ms.subject,
Data: data,
Timestamp: time.Now().UnixNano(),
}
ms.msgs[ms.last] = m
ms.totalCount++
ms.totalBytes += uint64(m.Size())
// If there is an age limit and no timer yet created, do so now
if ms.limits.MaxAge > time.Duration(0) && ms.ageTimer == nil {
ms.wg.Add(1)
ms.ageTimer = time.AfterFunc(ms.limits.MaxAge, ms.expireMsgs)
}
// Check if we need to remove any (but leave at least the last added)
maxMsgs := ms.limits.MaxMsgs
maxBytes := ms.limits.MaxBytes
if maxMsgs > 0 || maxBytes > 0 {
for ms.totalCount > 1 &&
((maxMsgs > 0 && ms.totalCount > maxMsgs) ||
(maxBytes > 0 && (ms.totalBytes > uint64(maxBytes)))) {
ms.removeFirstMsg()
if !ms.hitLimit {
ms.hitLimit = true
Noticef(droppingMsgsFmt, ms.subject, ms.totalCount, ms.limits.MaxMsgs, ms.totalBytes, ms.limits.MaxBytes)
}
}
}
return ms.last, nil
}
// Lookup returns the stored message with given sequence number.
func (ms *MemoryMsgStore) Lookup(seq uint64) *pb.MsgProto {
ms.RLock()
m := ms.msgs[seq]
ms.RUnlock()
return m
}
// FirstMsg returns the first message stored.
func (ms *MemoryMsgStore) FirstMsg() *pb.MsgProto {
ms.RLock()
m := ms.msgs[ms.first]
ms.RUnlock()
return m
}
// LastMsg returns the last message stored.
func (ms *MemoryMsgStore) LastMsg() *pb.MsgProto {
ms.RLock()
m := ms.msgs[ms.last]
ms.RUnlock()
return m
}
// GetSequenceFromTimestamp returns the sequence of the first message whose
// timestamp is greater or equal to given timestamp.
func (ms *MemoryMsgStore) GetSequenceFromTimestamp(timestamp int64) uint64 {
ms.RLock()
defer ms.RUnlock()
index := sort.Search(len(ms.msgs), func(i int) bool {
m := ms.msgs[uint64(i)+ms.first]
if m.Timestamp >= timestamp {
return true
}
return false
})
return uint64(index) + ms.first
}
// expireMsgs ensures that messages don't stay in the log longer than the
// limit's MaxAge.
func (ms *MemoryMsgStore) expireMsgs() {
ms.Lock()
if ms.closed {
ms.Unlock()
ms.wg.Done()
return
}
defer ms.Unlock()
now := time.Now().UnixNano()
maxAge := int64(ms.limits.MaxAge)
for {
m, ok := ms.msgs[ms.first]
if !ok {
ms.ageTimer = nil
ms.wg.Done()
return
}
elapsed := now - m.Timestamp
if elapsed >= maxAge {
ms.removeFirstMsg()
} else {
ms.ageTimer.Reset(time.Duration(maxAge - elapsed))
return
}
}
}
// removeFirstMsg removes the first message and updates totals.
func (ms *MemoryMsgStore) removeFirstMsg() {
firstMsg := ms.msgs[ms.first]
ms.totalBytes -= uint64(firstMsg.Size())
ms.totalCount--
delete(ms.msgs, ms.first)
ms.first++
}
// Close implements the MsgStore interface
func (ms *MemoryMsgStore) Close() error {
ms.Lock()
if ms.closed {
ms.Unlock()
return nil
}
ms.closed = true
if ms.ageTimer != nil {
if ms.ageTimer.Stop() {
ms.wg.Done()
}
}
ms.Unlock()
ms.wg.Wait()
return nil
}
////////////////////////////////////////////////////////////////////////////
// MemorySubStore methods
////////////////////////////////////////////////////////////////////////////
// AddSeqPending adds the given message seqno to the given subscription.
func (*MemorySubStore) AddSeqPending(subid, seqno uint64) error {
// Overrides in case genericSubStore does something. For the memory
// based store, we want to minimize the cost of this to a minimum.
return nil
}
// AckSeqPending records that the given message seqno has been acknowledged
// by the given subscription.
func (*MemorySubStore) AckSeqPending(subid, seqno uint64) error {
// Overrides in case genericSubStore does something. For the memory
// based store, we want to minimize the cost of this to a minimum.
return nil
}

View file

@ -0,0 +1,261 @@
// Copyright 2016 Apcera Inc. All rights reserved.
package stores
import (
"errors"
"time"
"github.com/nats-io/gnatsd/server"
"github.com/nats-io/go-nats-streaming/pb"
"github.com/nats-io/nats-streaming-server/spb"
)
const (
// TypeMemory is the store type name for memory based stores
TypeMemory = "MEMORY"
// TypeFile is the store type name for file based stores
TypeFile = "FILE"
)
const (
// AllChannels allows to get state for all channels.
AllChannels = "*"
)
// Errors.
var (
ErrTooManyChannels = errors.New("too many channels")
ErrTooManySubs = errors.New("too many subscriptions per channel")
)
// Noticef logs a notice statement
func Noticef(format string, v ...interface{}) {
server.Noticef(format, v...)
}
// StoreLimits define limits for a store.
type StoreLimits struct {
// How many channels are allowed.
MaxChannels int
// Global limits. Any 0 value means that the limit is ignored (unlimited).
ChannelLimits
// Per-channel limits. If a limit for a channel in this map is 0,
// the corresponding global limit (specified above) is used.
PerChannel map[string]*ChannelLimits
}
// ChannelLimits defines limits for a given channel
type ChannelLimits struct {
// Limits for message stores
MsgStoreLimits
// Limits for subscriptions stores
SubStoreLimits
}
// MsgStoreLimits defines limits for a MsgStore.
// For global limits, a value of 0 means "unlimited".
// For per-channel limits, it means that the corresponding global
// limit is used.
type MsgStoreLimits struct {
// How many messages are allowed.
MaxMsgs int
// How many bytes are allowed.
MaxBytes int64
// How long messages are kept in the log (unit is seconds)
MaxAge time.Duration
}
// SubStoreLimits defines limits for a SubStore
type SubStoreLimits struct {
// How many subscriptions are allowed.
MaxSubscriptions int
}
// DefaultStoreLimits are the limits that a Store must
// use when none are specified to the Store constructor.
// Store limits can be changed with the Store.SetLimits() method.
var DefaultStoreLimits = StoreLimits{
100,
ChannelLimits{
MsgStoreLimits{
MaxMsgs: 1000000,
MaxBytes: 1000000 * 1024,
},
SubStoreLimits{
MaxSubscriptions: 1000,
},
},
nil,
}
// RecoveredState allows the server to reconstruct its state after a restart.
type RecoveredState struct {
Info *spb.ServerInfo
Clients []*Client
Subs RecoveredSubscriptions
}
// Client represents a client with ID, Heartbeat Inbox and user data sets
// when adding it to the store.
type Client struct {
spb.ClientInfo
UserData interface{}
}
// RecoveredSubscriptions is a map of recovered subscriptions, keyed by channel name.
type RecoveredSubscriptions map[string][]*RecoveredSubState
// PendingAcks is a set of message sequences waiting to be acknowledged.
type PendingAcks map[uint64]struct{}
// RecoveredSubState represents a recovered Subscription with a map
// of pending messages.
type RecoveredSubState struct {
Sub *spb.SubState
Pending PendingAcks
}
// ChannelStore contains a reference to both Subscription and Message stores.
type ChannelStore struct {
// UserData is set when the channel is created.
UserData interface{}
// Subs is the Subscriptions Store.
Subs SubStore
// Msgs is the Messages Store.
Msgs MsgStore
}
// Store is the storage interface for NATS Streaming servers.
//
// If an implementation has a Store constructor with StoreLimits, it should be
// noted that the limits don't apply to any state being recovered, for Store
// implementations supporting recovery.
//
type Store interface {
// Init can be used to initialize the store with server's information.
Init(info *spb.ServerInfo) error
// Name returns the name type of this store (e.g: MEMORY, FILESTORE, etc...).
Name() string
// SetLimits sets limits for this store. The action is not expected
// to be retroactive.
// The store implementation should make a deep copy as to not change
// the content of the structure passed by the caller.
// This call may return an error due to limits validation errors.
SetLimits(limits *StoreLimits) error
// CreateChannel creates a ChannelStore for the given channel, and returns
// `true` to indicate that the channel is new, false if it already exists.
// Limits defined for this channel in StoreLimits.PeChannel map, if present,
// will apply. Otherwise, the global limits in StoreLimits will apply.
CreateChannel(channel string, userData interface{}) (*ChannelStore, bool, error)
// LookupChannel returns a ChannelStore for the given channel, nil if channel
// does not exist.
LookupChannel(channel string) *ChannelStore
// HasChannel returns true if this store has any channel.
HasChannel() bool
// MsgsState returns message store statistics for a given channel, or all
// if 'channel' is AllChannels.
MsgsState(channel string) (numMessages int, byteSize uint64, err error)
// AddClient stores information about the client identified by `clientID`.
// If a Client is already registered, this call returns the currently
// registered Client object, and the boolean set to false to indicate
// that the client is not new.
AddClient(clientID, hbInbox string, userData interface{}) (*Client, bool, error)
// GetClient returns the stored Client, or nil if it does not exist.
GetClient(clientID string) *Client
// GetClients returns a map of all stored Client objects, keyed by client IDs.
// The returned map is a copy of the state maintained by the store so that
// it is safe for the caller to walk through the map while clients may be
// added/deleted from the store.
GetClients() map[string]*Client
// GetClientsCount returns the number of registered clients.
GetClientsCount() int
// DeleteClient removes the client identified by `clientID` from the store
// and returns it to the caller.
DeleteClient(clientID string) *Client
// Close closes all stores.
Close() error
}
// SubStore is the interface for storage of Subscriptions on a given channel.
//
// Implementations of this interface should not attempt to validate that
// a subscription is valid (that is, has not been deleted) when processing
// updates.
type SubStore interface {
// CreateSub records a new subscription represented by SubState. On success,
// it records the subscription's ID in SubState.ID. This ID is to be used
// by the other SubStore methods.
CreateSub(*spb.SubState) error
// UpdateSub updates a given subscription represented by SubState.
UpdateSub(*spb.SubState) error
// DeleteSub invalidates the subscription 'subid'.
DeleteSub(subid uint64)
// AddSeqPending adds the given message 'seqno' to the subscription 'subid'.
AddSeqPending(subid, seqno uint64) error
// AckSeqPending records that the given message 'seqno' has been acknowledged
// by the subscription 'subid'.
AckSeqPending(subid, seqno uint64) error
// Flush is for stores that may buffer operations and need them to be persisted.
Flush() error
// Close closes the subscriptions store.
Close() error
}
// MsgStore is the interface for storage of Messages on a given channel.
type MsgStore interface {
// State returns some statistics related to this store.
State() (numMessages int, byteSize uint64, err error)
// Store stores a message and returns the message sequence.
Store(data []byte) (uint64, error)
// Lookup returns the stored message with given sequence number.
Lookup(seq uint64) *pb.MsgProto
// FirstSequence returns sequence for first message stored, 0 if no
// message is stored.
FirstSequence() uint64
// LastSequence returns sequence for last message stored, 0 if no
// message is stored.
LastSequence() uint64
// FirstAndLastSequence returns sequences for the first and last messages stored,
// 0 if no message is stored.
FirstAndLastSequence() (uint64, uint64)
// GetSequenceFromTimestamp returns the sequence of the first message whose
// timestamp is greater or equal to given timestamp.
GetSequenceFromTimestamp(timestamp int64) uint64
// FirstMsg returns the first message stored.
FirstMsg() *pb.MsgProto
// LastMsg returns the last message stored.
LastMsg() *pb.MsgProto
// Flush is for stores that may buffer operations and need them to be persisted.
Flush() error
// Close closes the store.
Close() error
}

View file

@ -0,0 +1,53 @@
// Copyright 2016 Apcera Inc. All rights reserved.
package util
import (
"encoding/binary"
"io"
)
// ByteOrder specifies how to convert byte sequences into 16-, 32-, or 64-bit
// unsigned integers.
var ByteOrder binary.ByteOrder
func init() {
ByteOrder = binary.LittleEndian
}
// EnsureBufBigEnough checks that given buffer is big enough to hold 'needed'
// bytes, otherwise returns a buffer of a size of at least 'needed' bytes.
func EnsureBufBigEnough(buf []byte, needed int) []byte {
if buf == nil {
return make([]byte, needed)
} else if needed > len(buf) {
return make([]byte, int(float32(needed)*1.1))
}
return buf
}
// WriteInt writes an int (4 bytes) to the given writer using ByteOrder.
func WriteInt(w io.Writer, v int) error {
var b [4]byte
var bs []byte
bs = b[:4]
ByteOrder.PutUint32(bs, uint32(v))
_, err := w.Write(bs)
return err
}
// ReadInt reads an int (4 bytes) from the reader using ByteOrder.
func ReadInt(r io.Reader) (int, error) {
var b [4]byte
var bs []byte
bs = b[:4]
_, err := io.ReadFull(r, bs)
if err != nil {
return 0, err
}
return int(ByteOrder.Uint32(bs)), nil
}

20
vendor/github.com/nats-io/nats/LICENSE generated vendored Normal file
View file

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2012-2016 Apcera Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -0,0 +1,106 @@
// Copyright 2012-2015 Apcera Inc. All rights reserved.
package builtin
import (
"bytes"
"fmt"
"reflect"
"strconv"
"unsafe"
)
// DefaultEncoder implementation for EncodedConn.
// This encoder will leave []byte and string untouched, but will attempt to
// turn numbers into appropriate strings that can be decoded. It will also
// propely encoded and decode bools. If will encode a struct, but if you want
// to properly handle structures you should use JsonEncoder.
type DefaultEncoder struct {
// Empty
}
var trueB = []byte("true")
var falseB = []byte("false")
var nilB = []byte("")
// Encode
func (je *DefaultEncoder) Encode(subject string, v interface{}) ([]byte, error) {
switch arg := v.(type) {
case string:
bytes := *(*[]byte)(unsafe.Pointer(&arg))
return bytes, nil
case []byte:
return arg, nil
case bool:
if arg {
return trueB, nil
} else {
return falseB, nil
}
case nil:
return nilB, nil
default:
var buf bytes.Buffer
fmt.Fprintf(&buf, "%+v", arg)
return buf.Bytes(), nil
}
}
// Decode
func (je *DefaultEncoder) Decode(subject string, data []byte, vPtr interface{}) error {
// Figure out what it's pointing to...
sData := *(*string)(unsafe.Pointer(&data))
switch arg := vPtr.(type) {
case *string:
*arg = sData
return nil
case *[]byte:
*arg = data
return nil
case *int:
n, err := strconv.ParseInt(sData, 10, 64)
if err != nil {
return err
}
*arg = int(n)
return nil
case *int32:
n, err := strconv.ParseInt(sData, 10, 64)
if err != nil {
return err
}
*arg = int32(n)
return nil
case *int64:
n, err := strconv.ParseInt(sData, 10, 64)
if err != nil {
return err
}
*arg = int64(n)
return nil
case *float32:
n, err := strconv.ParseFloat(sData, 32)
if err != nil {
return err
}
*arg = float32(n)
return nil
case *float64:
n, err := strconv.ParseFloat(sData, 64)
if err != nil {
return err
}
*arg = float64(n)
return nil
case *bool:
b, err := strconv.ParseBool(sData)
if err != nil {
return err
}
*arg = b
return nil
default:
vt := reflect.TypeOf(arg).Elem()
return fmt.Errorf("nats: Default Encoder can't decode to type %s", vt)
}
}

View file

@ -0,0 +1,34 @@
// Copyright 2013-2015 Apcera Inc. All rights reserved.
package builtin
import (
"bytes"
"encoding/gob"
)
// GobEncoder is a Go specific GOB Encoder implementation for EncodedConn.
// This encoder will use the builtin encoding/gob to Marshal
// and Unmarshal most types, including structs.
type GobEncoder struct {
// Empty
}
// FIXME(dlc) - This could probably be more efficient.
// Encode
func (ge *GobEncoder) Encode(subject string, v interface{}) ([]byte, error) {
b := new(bytes.Buffer)
enc := gob.NewEncoder(b)
if err := enc.Encode(v); err != nil {
return nil, err
}
return b.Bytes(), nil
}
// Decode
func (ge *GobEncoder) Decode(subject string, data []byte, vPtr interface{}) (err error) {
dec := gob.NewDecoder(bytes.NewBuffer(data))
err = dec.Decode(vPtr)
return
}

View file

@ -0,0 +1,45 @@
// Copyright 2012-2015 Apcera Inc. All rights reserved.
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
}

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

@ -0,0 +1,37 @@
// Copyright 2016 Apcera Inc. All rights reserved.
// +build 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,
DynamicRecordSizingDisabled: c.DynamicRecordSizingDisabled,
Renegotiation: c.Renegotiation,
}
}

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

@ -0,0 +1,35 @@
// Copyright 2016 Apcera Inc. All rights reserved.
// +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,
}
}

21
vendor/github.com/nats-io/nuid/LICENSE generated vendored Normal file
View file

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2012-2016 Apcera Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

124
vendor/github.com/nats-io/nuid/nuid.go generated vendored Normal file
View file

@ -0,0 +1,124 @@
// Copyright 2016 Apcera Inc. All rights reserved.
// A unique identifier generator that is high performance, very fast, and tries to be entropy pool friendly.
package nuid
import (
"crypto/rand"
"fmt"
"math"
"math/big"
"sync"
"time"
prand "math/rand"
)
// NUID needs to be very fast to generate and truly unique, all while being entropy pool friendly.
// We will use 12 bytes of crypto generated data (entropy draining), and 10 bytes of sequential data
// that is started at a pseudo random number and increments with a pseudo-random increment.
// Total is 22 bytes of base 62 ascii text :)
// Version of the library
const Version = "1.0.0"
const (
digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
base = 62
preLen = 12
seqLen = 10
maxSeq = int64(839299365868340224) // base^seqLen == 62^10
minInc = int64(33)
maxInc = int64(333)
totalLen = preLen + seqLen
)
type NUID struct {
pre []byte
seq int64
inc int64
}
type lockedNUID struct {
sync.Mutex
*NUID
}
// Global NUID
var globalNUID *lockedNUID
// Seed sequential random with crypto or math/random and current time
// and generate crypto prefix.
func init() {
r, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt64))
if err != nil {
prand.Seed(time.Now().UnixNano())
} else {
prand.Seed(r.Int64())
}
globalNUID = &lockedNUID{NUID: New()}
globalNUID.RandomizePrefix()
}
// New will generate a new NUID and properly initialize the prefix, sequential start, and sequential increment.
func New() *NUID {
n := &NUID{
seq: prand.Int63n(maxSeq),
inc: minInc + prand.Int63n(maxInc-minInc),
pre: make([]byte, preLen),
}
n.RandomizePrefix()
return n
}
// Generate the next NUID string from the global locked NUID instance.
func Next() string {
globalNUID.Lock()
nuid := globalNUID.Next()
globalNUID.Unlock()
return nuid
}
// Generate the next NUID string.
func (n *NUID) Next() string {
// Increment and capture.
n.seq += n.inc
if n.seq >= maxSeq {
n.RandomizePrefix()
n.resetSequential()
}
seq := n.seq
// Copy prefix
var b [totalLen]byte
bs := b[:preLen]
copy(bs, n.pre)
// copy in the seq in base36.
for i, l := len(b), seq; i > preLen; l /= base {
i -= 1
b[i] = digits[l%base]
}
return string(b[:])
}
// Resets the sequential portion of the NUID.
func (n *NUID) resetSequential() {
n.seq = prand.Int63n(maxSeq)
n.inc = minInc + prand.Int63n(maxInc-minInc)
}
// Generate a new prefix from crypto/rand.
// This call *can* drain entropy and will be called automatically when we exhaust the sequential range.
// Will panic if it gets an error from rand.Int()
func (n *NUID) RandomizePrefix() {
var cb [preLen]byte
cbs := cb[:]
if nb, err := rand.Read(cbs); nb != preLen || err != nil {
panic(fmt.Sprintf("nuid: failed generating crypto random number: %v\n", err))
}
for i := 0; i < preLen; i++ {
n.pre[i] = digits[int(cbs[i])%base]
}
}