<commit_before>// Copyright 2012 Apcera Inc. All rights reserved.

package gnatsd

import (
	"sync"

	"github.com/apcera/gnatsd/hashmap"
)

type node struct {
	next *level
	subs []interface{}
}

type level struct {
	nodes    *hashmap.HashMap
	pwc, fwc *node
}

type Sublist struct {
	lck   sync.RWMutex
	root  *level
	count uint32
	cache *hashmap.HashMap
}

func newNode() *node {
	return &node{subs: make([]interface{}, 0, 4)}
}

func newLevel() *level {
	return &level{nodes: hashmap.New()}
}

func New() *Sublist {
	return &Sublist{
		root:  newLevel(),
		cache: hashmap.New(),
	}
}

var (
	_PWC = byte('*')
	_FWC = byte('>')
	_SEP = byte('.')
)

func split(subject []byte, tokens [][]byte) [][]byte {
	start := 0
	for i, b := range subject {
		if b == _SEP {
			tokens = append(tokens, subject[start:i])
			start = i + 1
		}
	}
	return append(tokens, subject[start:])
}

func (s *Sublist) Insert(subject []byte, sub interface{}) {
	tsa := [16][]byte{}
	toks := split(subject, tsa[:0])

	s.lck.Lock()
	l := s.root
	var n *node

	for _, t := range toks {
		switch t[0] {
		case _PWC:
			n = l.pwc
		case _FWC:
			n = l.fwc
		default:
			if v := l.nodes.Get(t); v == nil {
				n = nil
			} else {
				n = v.(*node)
			}
		}
		if n == nil {
			n = newNode()
			switch t[0] {
			case _PWC:
				l.pwc = n
			case _FWC:
				l.fwc = n
			default:
				l.nodes.Set(t, n)
			}
		}
		if n.next == nil {
			n.next = newLevel()
		}
		l = n.next
	}
	n.subs = append(n.subs, sub)
	s.count++
	// FIXME: Do something more intelligent here
	s.cache = hashmap.New()
	s.lck.Unlock()
}

func (s *Sublist) Match(subject []byte) []interface{} {

	s.lck.RLock()
	r := s.cache.Get(subject)
	s.lck.RUnlock()

	if r != nil {
		return r.([]interface{})
	}

	tsa := [32][]byte{}
	toks := tsa[:0]

	start := 0
	for i, b := range subject {
		if b == _SEP {
			toks = append(toks, subject[start:i])
			start = i + 1
		}
	}
	toks = append(toks, subject[start:])
	results := make([]interface{}, 0, 4)

	s.lck.Lock()
	matchLevel(s.root, toks, &results)
	s.cache.Set(subject, results)
	s.lck.Unlock()
	return results
}

func matchLevel(l *level, toks [][]byte, results *[]interface{}) {
	var pwc, n *node
	for i, t := range toks {
		if l == nil {
			return
		}
		if l.fwc != nil {
			*results = append(*results, l.fwc.subs...)
		}
		if pwc = l.pwc; pwc != nil {
			matchLevel(pwc.next, toks[i+1:], results)
		}
		if v := l.nodes.Get(t); v == nil {
			n = nil
		} else {
			n = v.(*node)
		}
		if n != nil {
			l = n.next
		} else {
			l = nil
		}
	}
	if n != nil {
		*results = append(*results, n.subs...)
	}
	if pwc != nil {
		*results = append(*results, pwc.subs...)
	}
	return
}

type lnt struct {
	l *level
	n *node
	t []byte
}

func (s *Sublist) Remove(subject []byte, sub interface{}) {
	tsa := [16][]byte{}
	toks := split(subject, tsa[:0])

	s.lck.Lock()
	l := s.root
	var n *node

	var lnts [32]lnt
	levels := lnts[:0]

	for _, t := range toks {
		if l == nil {
			s.lck.Unlock()
			return
		}
		switch t[0] {
		case _PWC:
			n = l.pwc
		case _FWC:
			n = l.fwc
		default:
			if v := l.nodes.Get(t); v == nil {
				n = nil
			} else {
				n = v.(*node)
			}
		}
		if n != nil {
			levels = append(levels, lnt{l, n, t})
			l = n.next
		} else {
			l = nil
		}
	}
	if !s.removeFromNode(n, sub) {
		s.lck.Unlock()
		return
	}
	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)
		}
	}
	// FIXME: Do something more intelligent here
	s.cache = hashmap.New()
	s.lck.Unlock()
}

func (l *level) pruneNode(n *node, t []byte) {
	if n == nil {
		return
	}
	if n == l.fwc {
		l.fwc = nil
	} else if n == l.pwc {
		l.pwc = nil
	} else {
		l.nodes.Remove(t)
	}
}

func (n *node) isEmpty() bool {
	if len(n.subs) == 0 {
		if n.next == nil || n.next.numNodes() == 0 {
			return true
		}
	}
	return false
}

func (l *level) numNodes() uint32 {
	num := l.nodes.Count()
	if l.pwc != nil {
		num += 1
	}
	if l.fwc != nil {
		num += 1
	}
	return num
}

func (s *Sublist) removeFromNode(n *node, sub interface{}) bool {
	if n == nil {
		return false
	}
	for i, v := range n.subs {
		if v == sub {
			s.count--
			num := len(n.subs)
			a := n.subs
			copy(a[i:num-1], a[i+1:num])
			n.subs = a[0 : num-1]
			return true
		}
	}
	return false
}

func (s *Sublist) Count() uint32 { return s.count }

func (s *Sublist) DebugNumLevels() int {
	return visitLevel(s.root, 0)
}

func visitLevel(l *level, depth int) int {
	if l == nil || l.numNodes() == 0 {
		return depth
	}

	depth += 1
	maxDepth := depth

	all := l.nodes.All()
	for _, a := range all {
		if a == nil {
			continue
		}
		n := a.(*node)
		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
}
<commit_msg>Added code to handle cache changes more efficiently<commit_after>// Copyright 2012 Apcera Inc. All rights reserved.

package gnatsd

import (
	"sync"

	"github.com/apcera/gnatsd/hash"
	"github.com/apcera/gnatsd/hashmap"
)

// A Sublist stores and efficiently retrieves subscriptions. It uses a
// trie structure and an efficient LRU cache to achieve quick lookups.
type Sublist struct {
	mu    sync.RWMutex
	root  *level
	count uint32
	cache *hashmap.HashMap
}

type node struct {
	next *level
	subs []interface{}
}

type level struct {
	nodes    *hashmap.HashMap
	pwc, fwc *node
}

func newNode() *node {
	return &node{subs: make([]interface{}, 0, 4)}
}

func newLevel() *level {
	h := hashmap.New()
	h.Hash = hash.FNV1A
	return &level{nodes: h}
}

func New() *Sublist {
	return &Sublist{
		root:  newLevel(),
		cache: hashmap.New(),
	}
}

var (
	_PWC = byte('*')
	_FWC = byte('>')
	_SEP = byte('.')
)

// split will split a subject into tokens
func split(subject []byte, tokens [][]byte) [][]byte {
	start := 0
	for i, b := range subject {
		if b == _SEP {
			tokens = append(tokens, subject[start:i])
			start = i + 1
		}
	}
	return append(tokens, subject[start:])
}

func (s *Sublist) Insert(subject []byte, sub interface{}) {
	tsa := [16][]byte{}
	toks := split(subject, tsa[:0])

	s.mu.Lock()
	l := s.root
	var n *node

	for _, t := range toks {
		switch t[0] {
		case _PWC:
			n = l.pwc
		case _FWC:
			n = l.fwc
		default:
			if v := l.nodes.Get(t); v == nil {
				n = nil
			} else {
				n = v.(*node)
			}
		}
		if n == nil {
			n = newNode()
			switch t[0] {
			case _PWC:
				l.pwc = n
			case _FWC:
				l.fwc = n
			default:
				l.nodes.Set(t, n)
			}
		}
		if n.next == nil {
			n.next = newLevel()
		}
		l = n.next
	}
	n.subs = append(n.subs, sub)
	s.count++
	s.addToCache(subject, sub)
	s.mu.Unlock()
}

// addToCache will add the new entry to existing cache
// entries if needed.
func (s *Sublist) addToCache(subject []byte, sub interface{}) {
	if s.cache.Count() == 0 {
		return
	}
	all := s.cache.AllKeys()
	for _, k := range all {
		if !matchLiteral(k, subject) {
			continue
		}
		r := s.cache.Get(k)
		if r == nil {
			continue
		}
		res := r.([]interface{})
		res = append(res, sub)
		s.cache.Set(k, res)
	}
}

// removeFromCache will remove the sub from any active cache entries
func (s *Sublist) removeFromCache(subject []byte, sub interface{}) {
	if s.cache.Count() == 0 {
		return
	}
	all := s.cache.AllKeys()
	for _, k := range all {
		if !matchLiteral(k, subject) {
			continue
		}
		r := s.cache.Get(k)
		if r == nil {
			continue
		}
		s.cache.Remove(k)
	}
}

// Match will match all entries to the literal subject. It will return a
// slice of results.
func (s *Sublist) Match(subject []byte) []interface{} {

	s.mu.RLock()
	r := s.cache.Get(subject)
	s.mu.RUnlock()

	if r != nil {
		return r.([] interface{})
	}

	// Cache miss
	// Process subject into tokens, this is performed
	// unlocked, so can be parallel.
	tsa := [32][]byte{}
	toks := tsa[:0]

	start := 0
	for i, b := range subject {
		if b == _SEP {
			toks = append(toks, subject[start:i])
			start = i + 1
		}
	}
	toks = append(toks, subject[start:])
	results := make([]interface{}, 0, 4)

	// Lookup and add entry to hash.
	s.mu.Lock()
	matchLevel(s.root, toks, &results)

	// FIXME: This can overwhelm memory, but can't find a fast enough solution.
	// LRU is too slow, LFU and time.Now() is also too slow. Can try PLRU or
	// possible 2-way, although 2-way also uses time. We could have a go routine
	// for getting time, then on a fetch we just &timeNow in an atomic way.
	s.cache.Set(subject, results)
	s.mu.Unlock()

	return results
}

// matchLevel is used to recursively descend into the trie when there
// is a cache miss.
func matchLevel(l *level, toks [][]byte, results *[]interface{}) {
	var pwc, n *node
	for i, t := range toks {
		if l == nil {
			return
		}
		if l.fwc != nil {
			*results = append(*results, l.fwc.subs...)
		}
		if pwc = l.pwc; pwc != nil {
			matchLevel(pwc.next, toks[i+1:], results)
		}
		if v := l.nodes.Get(t); v == nil {
			n = nil
		} else {
			n = v.(*node)
		}
		if n != nil {
			l = n.next
		} else {
			l = nil
		}
	}
	if n != nil {
		*results = append(*results, n.subs...)
	}
	if pwc != nil {
		*results = append(*results, pwc.subs...)
	}
	return
}

// lnt is used to track descent into a removal for pruning.
type lnt struct {
	l *level
	n *node
	t []byte
}

// Remove will remove any item associated with key. It will track descent
// into the trie and prune upon successful removal.
func (s *Sublist) Remove(subject []byte, sub interface{}) {
	tsa := [16][]byte{}
	toks := split(subject, tsa[:0])

	s.mu.Lock()
	l := s.root
	var n *node

	var lnts [32]lnt
	levels := lnts[:0]

	for _, t := range toks {
		if l == nil {
			s.mu.Unlock()
			return
		}
		switch t[0] {
		case _PWC:
			n = l.pwc
		case _FWC:
			n = l.fwc
		default:
			if v := l.nodes.Get(t); v == nil {
				n = nil
			} else {
				n = v.(*node)
			}
		}
		if n != nil {
			levels = append(levels, lnt{l, n, t})
			l = n.next
		} else {
			l = nil
		}
	}
	if !s.removeFromNode(n, sub) {
		s.mu.Unlock()
		return
	}
	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)
	s.mu.Unlock()
}

func (l *level) pruneNode(n *node, t []byte) {
	if n == nil {
		return
	}
	if n == l.fwc {
		l.fwc = nil
	} else if n == l.pwc {
		l.pwc = nil
	} else {
		l.nodes.Remove(t)
	}
}

func (n *node) isEmpty() bool {
	if len(n.subs) == 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() uint32 {
	num := l.nodes.Count()
	if l.pwc != nil {
		num += 1
	}
	if l.fwc != nil {
		num += 1
	}
	return num
}

// Remove the sub for the given node.
func (s *Sublist) removeFromNode(n *node, sub interface{}) bool {
	if n == nil {
		return false
	}
	for i, v := range n.subs {
		if v == sub {
			s.count--
			num := len(n.subs)
			a := n.subs
			copy(a[i:num-1], a[i+1:num])
			n.subs = a[0 : num-1]
			return true
		}
	}
	return false
}

// 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 []byte) bool {
	li := 0
	for _, b := range(subject) {
		if li >= len(literal) {
			return false
		}
		switch b {
		case _PWC:
			// Skip token
			for {
				if li >= len(literal) || literal[li] == _SEP {
					break;
				}
				li += 1
			}
		case _FWC:
			return true
		default:
			if b != literal[li] {
				return false
			}
		}
		li += 1
	}
	return true
}

// Count return the number of stored items in the HashMap.
func (s *Sublist) Count() uint32 { return s.count }

// DebugNumLevels will return the number of levels contained in
// the HashMap.
func (s *Sublist) DebugNumLevels() int {
	return visitLevel(s.root, 0)
}

func visitLevel(l *level, depth int) int {
	if l == nil || l.numNodes() == 0 {
		return depth
	}

	depth += 1
	maxDepth := depth

	all := l.nodes.All()
	for _, a := range all {
		if a == nil {
			continue
		}
		n := a.(*node)
		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
}
<|endoftext|>


<commit_before>// Package svc provides some tooling to make building services with remind101/pkg
// easier.
//
// Recommend Usage:
//
//	func main() {
//		env := svc.InitAll()
//		defer env.Close()
//
//		r := httpx.NewRouter()
//		// ... add routes
//
//		h := svc.NewStandardHandler(svc.HandlerOpts{
//			Router:   r,
//			Reporter: env.Reporter,
//	})
//
// 	svc.RunServer(h, "80", 5*time.Second)
// }
package svc

import (
	"context"
	"fmt"
	"log"
	"net/http"
	"os"
	"os/signal"
	"strings"
	"syscall"
	"time"

	ddtrace "github.com/DataDog/dd-trace-go/opentracing"
	opentracing "github.com/opentracing/opentracing-go"
	"github.com/pkg/errors"
	"github.com/remind101/pkg/httpx"
	"github.com/remind101/pkg/httpx/middleware"
	"github.com/remind101/pkg/logger"
	"github.com/remind101/pkg/metrics"
	"github.com/remind101/pkg/reporter"
	"github.com/remind101/pkg/reporter/rollbar"
)

type HandlerOpts struct {
	Router            *httpx.Router
	Reporter          reporter.Reporter
	ForwardingHeaders []string
	BasicAuth         string
}

// NewStandardHandler returns an http.Handler with a standard middleware stack.
// The last middleware added is the first middleware to handle the request.
// Order is pretty important as some middleware depends on others having run
// already.
func NewStandardHandler(opts HandlerOpts) http.Handler {
	var h httpx.Handler

	// Recover from panics. A panic is converted to an error. This should be first,
	// even though it means panics in middleware will not be recovered, because
	// later middleware expects endpoint panics to be returned as an error.
	h = middleware.BasicRecover(opts.Router)

	// Add request tracing. Must go before the HandleError middleware in order
	// to capture any errors from the endpoint handler.
	h = middleware.OpentracingTracing(h, opts.Router)

	// Handler errors returned by endpoint handler or recovery middleware.
	// Errors will no longer be returned after this middeware.
	h = middleware.HandleError(h, middleware.ReportingErrorHandler)

	// Insert logger into context and log requests at INFO level.
	h = middleware.LogTo(h, middleware.LoggerWithRequestID)

	// Add reporter to context and request to reporter context.
	h = middleware.WithReporter(h, opts.Reporter)

	// Add the request id to the context.
	h = middleware.ExtractRequestID(h)

	// Add basic auth
	if opts.BasicAuth != "" {
		user := strings.Split(opts.BasicAuth, ":")[0]
		pass := strings.Split(opts.BasicAuth, ":")[1]
		h = middleware.BasicAuth(h, user, pass, "")
	}

	// Adds forwarding headers from request to the context. This allows http clients
	// to get those headers from the context and add them to upstream requests.
	if len(opts.ForwardingHeaders) > 0 {
		for _, header := range opts.ForwardingHeaders {
			h = middleware.ExtractHeader(h, header)
		}
	}

	// Wrap the route in middleware to add a context.Context. This middleware must be
	// last as it acts as the adaptor between http.Handler and httpx.Handler.
	return middleware.BackgroundContext(h)
}

// RunServer handles the biolerplate of starting an http server and handling
// signals gracefully.
func RunServer(h http.Handler, port string, writeTimeout time.Duration) {
	errCh := make(chan error)

	// Handle SIGINT and SIGTERM.
	sigCh := make(chan os.Signal)
	signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)

	fmt.Printf("Listening on port %s\n", port)

	// Add timeouts to the server
	srv := &http.Server{
		WriteTimeout: writeTimeout * time.Second,
		Addr:         ":" + port,
		Handler:      h,
	}

	go func() {
		defer reporter.Monitor(context.Background())
		err := srv.ListenAndServe()
		if err != nil {
			errCh <- errors.Wrapf(err, "unable to start server")
		}
	}()

	select {
	case sig := <-sigCh:
		fmt.Println("Received signal, stopping.", "signal", sig)
	// Cleanup
	case err := <-errCh:
		fmt.Println(err)
		os.Exit(1)
	}
}

// Env holds global dependencies that need to be initialized in main() and
// injected as dependencies into an application.
type Env struct {
	Reporter reporter.Reporter
	Logger   logger.Logger
	Close    func() // Should be called in a defer in main().
}

// InitAll will initialize all the common dependencies such as metrics, reporting,
// tracing, and logging.
func InitAll() Env {
	traceCloser := InitTracer()
	metricsCloser := InitMetrics()

	l := InitLogger()
	logger.DefaultLogger = l

	return Env{
		Logger:   l,
		Reporter: InitReporter(),
		Close: func() {
			traceCloser()
			metricsCloser()
		},
	}
}

// InitTracer configures a global datadog tracer.
//
// Env Vars:
// * DDTRACE_ADDR - The host:port of the local trace agent server.
// * EMPIRE_APPNAME - App name, used to construct the service name.
// * EMPIRE_PROCESS - Process name, used to construct the service name.
func InitTracer() func() {
	// create a Tracer configuration
	config := ddtrace.NewConfiguration()
	config.ServiceName = fmt.Sprintf("%s.%s", os.Getenv("EMPIRE_APPNAME"), os.Getenv("EMPIRE_PROCESS"))
	if addr := os.Getenv("DDTRACE_ADDR"); addr != "" {
		config.AgentHostname = addr
	}

	// Initialize a Tracer and ensure a graceful shutdown
	// using the `closer.Close()`
	tracer, closer, err := ddtrace.NewTracer(config)
	if err != nil {
		fmt.Println(err)
	}

	// set the Datadog tracer as a GlobalTracer
	opentracing.SetGlobalTracer(tracer)

	return func() {
		closer.Close()
	}
}

// InitMetrics configures pkg/metrics
//
// Env Vars:
// * STATSD_ADDR - The host:port of the statsd server.
func InitMetrics() func() {
	if addr := os.Getenv("STATSD_ADDR"); addr != "" {
		metrics.SetEmpireDefaultTags()
		metrics.Reporter, _ = metrics.NewDataDogMetricsReporter(addr)
	}

	return func() {
		metrics.Close()
	}
}

// InitLogger configures a leveled logger.
//
// Env Vars:
// * LOG_LEVEL - The log level
//
// If you want to replace the global default logger:
//	logger.DefaultLogger = InitLogger()
func InitLogger() logger.Logger {
	lvl := logger.ERROR
	if ll := os.Getenv("LOG_LEVEL"); ll != "" {
		lvl = logger.ParseLevel(ll)
	}

	return logger.New(log.New(os.Stdout, "", 0), lvl)
}

// InitReporter configures and returns a reporter.Reporter instance.
//
// Env Vars:
// * ROLLBAR_ACCESS_TOKEN - The Rollbar access token
// * ROLLBAR_ENVIRONMENT  - The Rollbar environment (staging, production)
func InitReporter() reporter.Reporter {
	rbToken := os.Getenv("ROLLBAR_ACCESS_TOKEN")
	rbEnv := os.Getenv("ROLLBAR_ENVIRONMENT")

	rep := reporter.MultiReporter{}

	// Log Reporter, uses package level logger.
	rep = append(rep, reporter.NewLogReporter())

	// Rollbar reporter
	if rbToken != "" && rbEnv != "" {
		rollbar.ConfigureReporter(rbToken, rbEnv)
		rep = append(rep, rollbar.Reporter)
	} else {
		fmt.Println("Rollbar is not configured, skipping Rollbar reporter")
	}

	return rep
}
<commit_msg>Update svc.go<commit_after>// Package svc provides some tooling to make building services with remind101/pkg
// easier.
//
// Recommend Usage:
//
//	func main() {
//		env := svc.InitAll()
//		defer env.Close()
//
//		r := httpx.NewRouter()
//		// ... add routes
//
//		h := svc.NewStandardHandler(svc.HandlerOpts{
//			Router:   r,
//			Reporter: env.Reporter,
//	})
//
// 	svc.RunServer(h, "80", 5*time.Second)
// }
package svc

import (
	"context"
	"fmt"
	"log"
	"net/http"
	"os"
	"os/signal"
	"strings"
	"syscall"
	"time"

	ddtrace "github.com/DataDog/dd-trace-go/opentracing"
	opentracing "github.com/opentracing/opentracing-go"
	"github.com/pkg/errors"
	"github.com/remind101/pkg/httpx"
	"github.com/remind101/pkg/httpx/middleware"
	"github.com/remind101/pkg/logger"
	"github.com/remind101/pkg/metrics"
	"github.com/remind101/pkg/reporter"
	"github.com/remind101/pkg/reporter/rollbar"
)

type HandlerOpts struct {
	Router            *httpx.Router
	Reporter          reporter.Reporter
	ForwardingHeaders []string
	BasicAuth         string
	ErrorHandler      middleware.ErrorHandlerFunc
}

// NewStandardHandler returns an http.Handler with a standard middleware stack.
// The last middleware added is the first middleware to handle the request.
// Order is pretty important as some middleware depends on others having run
// already.
func NewStandardHandler(opts HandlerOpts) http.Handler {
	var h httpx.Handler

	// Recover from panics. A panic is converted to an error. This should be first,
	// even though it means panics in middleware will not be recovered, because
	// later middleware expects endpoint panics to be returned as an error.
	h = middleware.BasicRecover(opts.Router)

	// Add request tracing. Must go before the HandleError middleware in order
	// to capture any errors from the endpoint handler.
	h = middleware.OpentracingTracing(h, opts.Router)

	// Handler errors returned by endpoint handler or recovery middleware.
	// Errors will no longer be returned after this middeware.
	errorHandler := opts.ErrorHandler
	if errorHandler == nil {
                 errorHandler = middleware.ReportingErrorHandler
	}
	h = middleware.HandleError(h, errorHandler)

	// Insert logger into context and log requests at INFO level.
	h = middleware.LogTo(h, middleware.LoggerWithRequestID)

	// Add reporter to context and request to reporter context.
	h = middleware.WithReporter(h, opts.Reporter)

	// Add the request id to the context.
	h = middleware.ExtractRequestID(h)

	// Add basic auth
	if opts.BasicAuth != "" {
		user := strings.Split(opts.BasicAuth, ":")[0]
		pass := strings.Split(opts.BasicAuth, ":")[1]
		h = middleware.BasicAuth(h, user, pass, "")
	}

	// Adds forwarding headers from request to the context. This allows http clients
	// to get those headers from the context and add them to upstream requests.
	if len(opts.ForwardingHeaders) > 0 {
		for _, header := range opts.ForwardingHeaders {
			h = middleware.ExtractHeader(h, header)
		}
	}

	// Wrap the route in middleware to add a context.Context. This middleware must be
	// last as it acts as the adaptor between http.Handler and httpx.Handler.
	return middleware.BackgroundContext(h)
}

// RunServer handles the biolerplate of starting an http server and handling
// signals gracefully.
func RunServer(h http.Handler, port string, writeTimeout time.Duration) {
	errCh := make(chan error)

	// Handle SIGINT and SIGTERM.
	sigCh := make(chan os.Signal)
	signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)

	fmt.Printf("Listening on port %s\n", port)

	// Add timeouts to the server
	srv := &http.Server{
		WriteTimeout: writeTimeout * time.Second,
		Addr:         ":" + port,
		Handler:      h,
	}

	go func() {
		defer reporter.Monitor(context.Background())
		err := srv.ListenAndServe()
		if err != nil {
			errCh <- errors.Wrapf(err, "unable to start server")
		}
	}()

	select {
	case sig := <-sigCh:
		fmt.Println("Received signal, stopping.", "signal", sig)
	// Cleanup
	case err := <-errCh:
		fmt.Println(err)
		os.Exit(1)
	}
}

// Env holds global dependencies that need to be initialized in main() and
// injected as dependencies into an application.
type Env struct {
	Reporter reporter.Reporter
	Logger   logger.Logger
	Close    func() // Should be called in a defer in main().
}

// InitAll will initialize all the common dependencies such as metrics, reporting,
// tracing, and logging.
func InitAll() Env {
	traceCloser := InitTracer()
	metricsCloser := InitMetrics()

	l := InitLogger()
	logger.DefaultLogger = l

	return Env{
		Logger:   l,
		Reporter: InitReporter(),
		Close: func() {
			traceCloser()
			metricsCloser()
		},
	}
}

// InitTracer configures a global datadog tracer.
//
// Env Vars:
// * DDTRACE_ADDR - The host:port of the local trace agent server.
// * EMPIRE_APPNAME - App name, used to construct the service name.
// * EMPIRE_PROCESS - Process name, used to construct the service name.
func InitTracer() func() {
	// create a Tracer configuration
	config := ddtrace.NewConfiguration()
	config.ServiceName = fmt.Sprintf("%s.%s", os.Getenv("EMPIRE_APPNAME"), os.Getenv("EMPIRE_PROCESS"))
	if addr := os.Getenv("DDTRACE_ADDR"); addr != "" {
		config.AgentHostname = addr
	}

	// Initialize a Tracer and ensure a graceful shutdown
	// using the `closer.Close()`
	tracer, closer, err := ddtrace.NewTracer(config)
	if err != nil {
		fmt.Println(err)
	}

	// set the Datadog tracer as a GlobalTracer
	opentracing.SetGlobalTracer(tracer)

	return func() {
		closer.Close()
	}
}

// InitMetrics configures pkg/metrics
//
// Env Vars:
// * STATSD_ADDR - The host:port of the statsd server.
func InitMetrics() func() {
	if addr := os.Getenv("STATSD_ADDR"); addr != "" {
		metrics.SetEmpireDefaultTags()
		metrics.Reporter, _ = metrics.NewDataDogMetricsReporter(addr)
	}

	return func() {
		metrics.Close()
	}
}

// InitLogger configures a leveled logger.
//
// Env Vars:
// * LOG_LEVEL - The log level
//
// If you want to replace the global default logger:
//	logger.DefaultLogger = InitLogger()
func InitLogger() logger.Logger {
	lvl := logger.ERROR
	if ll := os.Getenv("LOG_LEVEL"); ll != "" {
		lvl = logger.ParseLevel(ll)
	}

	return logger.New(log.New(os.Stdout, "", 0), lvl)
}

// InitReporter configures and returns a reporter.Reporter instance.
//
// Env Vars:
// * ROLLBAR_ACCESS_TOKEN - The Rollbar access token
// * ROLLBAR_ENVIRONMENT  - The Rollbar environment (staging, production)
func InitReporter() reporter.Reporter {
	rbToken := os.Getenv("ROLLBAR_ACCESS_TOKEN")
	rbEnv := os.Getenv("ROLLBAR_ENVIRONMENT")

	rep := reporter.MultiReporter{}

	// Log Reporter, uses package level logger.
	rep = append(rep, reporter.NewLogReporter())

	// Rollbar reporter
	if rbToken != "" && rbEnv != "" {
		rollbar.ConfigureReporter(rbToken, rbEnv)
		rep = append(rep, rollbar.Reporter)
	} else {
		fmt.Println("Rollbar is not configured, skipping Rollbar reporter")
	}

	return rep
}
<|endoftext|>


<commit_before>package svn

import (
	"encoding/xml"

	"github.com/pkg/errors"
)

type Log struct {
	XMLName xml.Name   `xml:"log"`
	Entries []LogEntry `xml:"logentry"`
}

type LogEntry struct {
	*Commit
	XMLName xml.Name `xml:"logentry"`
	Paths   []Path   `xml:"paths>path"`
	Message string   `xml:"msg"`
}

type Path struct {
	XMLName               xml.Name `xml:"path"`
	TextModifications     bool     `xml:"text-mods,attr"`
	Kind                  string   `xml:"kind,attr"`
	CopyFromPath          *string  `xml:"copyfrom-path,attr,omitempty"`
	CopyFromRevision      *int     `xml:"copyfrom-rev,attr,omitempty"`
	Action                string   `xml:"action,attr"`
	PropertyModifications bool     `xml:"prop-mods,attr"`
	Name                  string   `xml:",chardata"`
}

func GetLog(address string) (*Log, error) {
	log := Log{}

	if err := Execute(&log, "log", "--xml", "--verbose", address); err != nil {
		return nil, errors.Wrapf(err, "failed to get log for %s", address)
	}

	return &log, nil
}
<commit_msg>Add function for retrieving a specific revision<commit_after>package svn

import (
	"encoding/xml"
	"fmt"
	"strconv"

	"github.com/pkg/errors"
)

type Log struct {
	XMLName xml.Name   `xml:"log"`
	Entries []LogEntry `xml:"logentry"`
}

type LogEntry struct {
	*Commit
	XMLName xml.Name `xml:"logentry"`
	Paths   []Path   `xml:"paths>path"`
	Message string   `xml:"msg"`
}

type Path struct {
	XMLName               xml.Name `xml:"path"`
	TextModifications     bool     `xml:"text-mods,attr"`
	Kind                  string   `xml:"kind,attr"`
	CopyFromPath          *string  `xml:"copyfrom-path,attr,omitempty"`
	CopyFromRevision      *int     `xml:"copyfrom-rev,attr,omitempty"`
	Action                string   `xml:"action,attr"`
	PropertyModifications bool     `xml:"prop-mods,attr"`
	Name                  string   `xml:",chardata"`
}

func GetRevision(address string, revision int) (*LogEntry, error) {
	log := Log{}

	if err := Execute(&log, "log", "--xml", "--verbose", "--revision", strconv.Itoa(revision), address); err != nil {
		return nil, errors.Wrapf(err, "failed to get log for %s (revision %d)", address, revision)
	}

	if len(log.Entries) != 1 {
		return nil, fmt.Errorf("found %d log entries but expected 1 for %s (revison %d)", len(log.Entries), address, revision)
	}

	return &log.Entries[0], nil
}

func GetLog(address string) (*Log, error) {
	log := Log{}

	if err := Execute(&log, "log", "--xml", "--verbose", address); err != nil {
		return nil, errors.Wrapf(err, "failed to get log for %s", address)
	}

	return &log, nil
}
<|endoftext|>


<commit_before>package main

import (
	"context"
	"flag"
	"io/ioutil"
	"log"
	"time"

	"github.com/brotherlogic/goserver"
	"github.com/brotherlogic/keystore/client"
	"google.golang.org/grpc"

	pb "github.com/brotherlogic/discogssyncer/server"
	pbdi "github.com/brotherlogic/discovery/proto"
	pbd "github.com/brotherlogic/godiscogs"
)

// Syncer the configuration for the syncer
type Syncer struct {
	*goserver.GoServer
	token      string
	retr       saver
	collection *pb.RecordCollection
	rMap       map[int]*pbd.Release
}

var (
	syncTime int64
)

const (
	//KEY under which we store the collection
	KEY = "/github.com/brotherlogic/discogssyncer/collection"

	//TOKEN for discogs
	TOKEN = "/github.com/brotherlogic/discogssyncer/token"
)

// This is the only method that interacts with disk
func (s *Syncer) readRecordCollection() error {
	collection := &pb.RecordCollection{}
	data, err := s.KSclient.Read(KEY, collection)

	if err != nil {
		return err
	}

	s.collection = data.(*pb.RecordCollection)

	//Ensure we don't keep metadata for release id = 0
	removed := 0
	for i, m := range s.collection.GetMetadata() {
		if m.Id == 0 {
			s.collection.Metadata = append(s.collection.Metadata[:(i-removed)], s.collection.Metadata[(i-removed)+1:]...)
		}
	}

	// Build out the release map
	for _, f := range s.collection.Folders {
		for _, r := range f.Releases.Releases {
			s.rMap[int(r.Id)] = r
		}
	}

	return nil
}

func (s *Syncer) saveCollection() {
	t := time.Now()
	s.KSclient.Save(KEY, s.collection)
	s.LogFunction("saveCollection", t)
}

func (s *Syncer) deleteRelease(rel *pbd.Release, folder int32) {
	index := -1
	for _, f := range s.collection.Folders {
		if f.Folder.Id == folder {
			for i, r := range f.Releases.Releases {
				if r.Id == rel.Id {
					index = i
				}

			}
			if index >= 0 {
				f.Releases.Releases = append(f.Releases.Releases[:index], f.Releases.Releases[index+1:]...)
			}
		}
	}

}

// DoRegister does RPC registration
func (s Syncer) DoRegister(server *grpc.Server) {
	pb.RegisterDiscogsServiceServer(server, &s)
}

func findServer(name string) (string, int) {
	conn, err := grpc.Dial("192.168.86.64:50055", grpc.WithInsecure())
	if err != nil {
		log.Fatalf("Cannot reach discover server: %v (trying to discover %v)", err, name)
	}
	defer conn.Close()

	registry := pbdi.NewDiscoveryServiceClient(conn)
	rs, err := registry.ListAllServices(context.Background(), &pbdi.Empty{})

	if err != nil {
		log.Fatalf("Failure to list: %v", err)
	}

	for _, r := range rs.Services {
		if r.Name == name {
			return r.Ip, int(r.Port)
		}
	}

	return "", -1
}

// InitServer builds an initial server
func InitServer() Syncer {
	syncer := Syncer{GoServer: &goserver.GoServer{}, collection: &pb.RecordCollection{Wantlist: &pb.Wantlist{}}, rMap: make(map[int]*pbd.Release)}
	syncer.GoServer.KSclient = *keystoreclient.GetClient(findServer)
	err := syncer.readRecordCollection()
	if err != nil {
		log.Fatalf("Unable to read record collection")
	}

	return syncer
}

// Mote promotes/demotes this server
func (s Syncer) Mote(master bool) error {
	return nil
}

// ReportHealth alerts if we're not healthy
func (s Syncer) ReportHealth() bool {
	return true
}

func main() {
	var quiet = flag.Bool("quiet", true, "Show all output")
	var token = flag.String("token", "", "Discogs token")
	flag.Parse()

	//Turn off logging
	if *quiet {
		log.SetFlags(0)
		log.SetOutput(ioutil.Discard)
	}

	syncer := InitServer()

	if len(*token) > 0 {
		syncer.KSclient.Save(TOKEN, &pb.Token{Token: *token})
	}

	tType := &pb.Token{}
	tResp, err := syncer.KSclient.Read(TOKEN, tType)

	if err != nil {
		log.Fatalf("Unable to read token: %v", err)
	}

	sToken := tResp.(*pb.Token).Token
	syncer.retr = pbd.NewDiscogsRetriever(sToken)
	syncer.token = sToken

	syncer.Register = syncer
	syncer.PrepServer()
	syncer.RegisterServer("discogssyncer", false)

	syncer.Serve()
}
<commit_msg>Fix for the build<commit_after>package main

import (
	"flag"
	"io/ioutil"
	"log"
	"time"

	"github.com/brotherlogic/goserver"
	"github.com/brotherlogic/keystore/client"
	"google.golang.org/grpc"

	pb "github.com/brotherlogic/discogssyncer/server"
	pbd "github.com/brotherlogic/godiscogs"
)

// Syncer the configuration for the syncer
type Syncer struct {
	*goserver.GoServer
	token      string
	retr       saver
	collection *pb.RecordCollection
	rMap       map[int]*pbd.Release
}

var (
	syncTime int64
)

const (
	//KEY under which we store the collection
	KEY = "/github.com/brotherlogic/discogssyncer/collection"

	//TOKEN for discogs
	TOKEN = "/github.com/brotherlogic/discogssyncer/token"
)

// This is the only method that interacts with disk
func (s *Syncer) readRecordCollection() error {
	collection := &pb.RecordCollection{}
	data, err := s.KSclient.Read(KEY, collection)

	if err != nil {
		return err
	}

	s.collection = data.(*pb.RecordCollection)

	//Ensure we don't keep metadata for release id = 0
	removed := 0
	for i, m := range s.collection.GetMetadata() {
		if m.Id == 0 {
			s.collection.Metadata = append(s.collection.Metadata[:(i-removed)], s.collection.Metadata[(i-removed)+1:]...)
		}
	}

	// Build out the release map
	for _, f := range s.collection.Folders {
		for _, r := range f.Releases.Releases {
			s.rMap[int(r.Id)] = r
		}
	}

	return nil
}

func (s *Syncer) saveCollection() {
	t := time.Now()
	s.KSclient.Save(KEY, s.collection)
	s.LogFunction("saveCollection", t)
}

func (s *Syncer) deleteRelease(rel *pbd.Release, folder int32) {
	index := -1
	for _, f := range s.collection.Folders {
		if f.Folder.Id == folder {
			for i, r := range f.Releases.Releases {
				if r.Id == rel.Id {
					index = i
				}

			}
			if index >= 0 {
				f.Releases.Releases = append(f.Releases.Releases[:index], f.Releases.Releases[index+1:]...)
			}
		}
	}

}

// DoRegister does RPC registration
func (s Syncer) DoRegister(server *grpc.Server) {
	pb.RegisterDiscogsServiceServer(server, &s)
}

// InitServer builds an initial server
func InitServer() Syncer {
	syncer := Syncer{GoServer: &goserver.GoServer{}, collection: &pb.RecordCollection{Wantlist: &pb.Wantlist{}}, rMap: make(map[int]*pbd.Release)}
	syncer.GoServer.KSclient = *keystoreclient.GetClient(syncer.GetIP)
	err := syncer.readRecordCollection()
	if err != nil {
		log.Fatalf("Unable to read record collection")
	}

	return syncer
}

// Mote promotes/demotes this server
func (s Syncer) Mote(master bool) error {
	return nil
}

// ReportHealth alerts if we're not healthy
func (s Syncer) ReportHealth() bool {
	return true
}

func main() {
	var quiet = flag.Bool("quiet", true, "Show all output")
	var token = flag.String("token", "", "Discogs token")
	flag.Parse()

	//Turn off logging
	if *quiet {
		log.SetFlags(0)
		log.SetOutput(ioutil.Discard)
	}

	syncer := InitServer()

	if len(*token) > 0 {
		syncer.KSclient.Save(TOKEN, &pb.Token{Token: *token})
	}

	tType := &pb.Token{}
	tResp, err := syncer.KSclient.Read(TOKEN, tType)

	if err != nil {
		log.Fatalf("Unable to read token: %v", err)
	}

	sToken := tResp.(*pb.Token).Token
	syncer.retr = pbd.NewDiscogsRetriever(sToken)
	syncer.token = sToken

	syncer.Register = syncer
	syncer.PrepServer()
	syncer.RegisterServer("discogssyncer", false)

	syncer.Serve()
}
<|endoftext|>


<commit_before>package classfile

type ConstantPool struct {
	cf      *ClassFile
	cpInfos []ConstantInfo
}

func (self *ConstantPool) read(reader *ClassReader) {
	cpCount := int(reader.readUint16())
	self.cpInfos = make([]ConstantInfo, cpCount)

	// The constant_pool table is indexed from 1 to constant_pool_count - 1.
	for i := 1; i < cpCount; i++ {
		self.cpInfos[i] = readConstantInfo(reader, self)
		// http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.4.5
		// All 8-byte constants take up two entries in the constant_pool table of the class file.
		// If a CONSTANT_Long_info or CONSTANT_Double_info structure is the item in the constant_pool
		// table at index n, then the next usable item in the pool is located at index n+2.
		// The constant_pool index n+1 must be valid but is considered unusable.
		switch self.cpInfos[i].(type) {
		case *ConstantLongInfo, *ConstantDoubleInfo:
			i++
		}
	}
}

func (self *ConstantPool) Infos() []ConstantInfo {
	return self.cpInfos
}

func (self *ConstantPool) getConstantInfo(index uint16) ConstantInfo {
	// todo check index
	return self.cpInfos[index]
}

func (self *ConstantPool) getNameAndType(index uint16) (name, _type string) {
	ntInfo := self.getConstantInfo(index).(*ConstantNameAndTypeInfo)
	name = self.getUtf8(ntInfo.nameIndex)
	_type = self.getUtf8(ntInfo.descriptorIndex)
	return
}

func (self *ConstantPool) getClassName(index uint16) string {
	classInfo := self.getConstantInfo(index).(*ConstantClassInfo)
	return self.getUtf8(classInfo.nameIndex)
}

func (self *ConstantPool) getUtf8(index uint16) string {
	utf8Info := self.getConstantInfo(index).(*ConstantUtf8Info)
	return utf8Info.str
}
<commit_msg>check constant pool index<commit_after>package classfile

import (
	"fmt"
)

type ConstantPool struct {
	cf      *ClassFile
	cpInfos []ConstantInfo
}

func (self *ConstantPool) read(reader *ClassReader) {
	cpCount := int(reader.readUint16())
	self.cpInfos = make([]ConstantInfo, cpCount)

	// The constant_pool table is indexed from 1 to constant_pool_count - 1.
	for i := 1; i < cpCount; i++ {
		self.cpInfos[i] = readConstantInfo(reader, self)
		// http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.4.5
		// All 8-byte constants take up two entries in the constant_pool table of the class file.
		// If a CONSTANT_Long_info or CONSTANT_Double_info structure is the item in the constant_pool
		// table at index n, then the next usable item in the pool is located at index n+2.
		// The constant_pool index n+1 must be valid but is considered unusable.
		switch self.cpInfos[i].(type) {
		case *ConstantLongInfo, *ConstantDoubleInfo:
			i++
		}
	}
}

func (self *ConstantPool) Infos() []ConstantInfo {
	return self.cpInfos
}

func (self *ConstantPool) getConstantInfo(index uint16) ConstantInfo {
	cpInfo := self.cpInfos[index]
	if cpInfo == nil {
		panic(fmt.Errorf("Bad constant pool index: %v!", index))
	}

	return cpInfo
}

func (self *ConstantPool) getNameAndType(index uint16) (name, _type string) {
	ntInfo := self.getConstantInfo(index).(*ConstantNameAndTypeInfo)
	name = self.getUtf8(ntInfo.nameIndex)
	_type = self.getUtf8(ntInfo.descriptorIndex)
	return
}

func (self *ConstantPool) getClassName(index uint16) string {
	classInfo := self.getConstantInfo(index).(*ConstantClassInfo)
	return self.getUtf8(classInfo.nameIndex)
}

func (self *ConstantPool) getUtf8(index uint16) string {
	utf8Info := self.getConstantInfo(index).(*ConstantUtf8Info)
	return utf8Info.str
}
<|endoftext|>


<commit_before>package main

import (
	"strings"
	"sync"
	"time"

	"github.com/goamz/goamz/aws"
	"github.com/goamz/goamz/cloudwatch"
	"github.com/goamz/goamz/rds"
	"github.com/litl/galaxy/log"
	"github.com/litl/galaxy/stack"
)

type CloudwatchStat struct {
	Namespace  string
	Dimensions map[string]string
	MetricName string
	Statistic  string
	Component  string
}

var ELBMetrics = []string{
	"RequestCount", "HTTPCode_Backend_2XX", "HTTPCode_Backend_3XX",
	"HTTPCode_Backend_4XX", "HTTPCode_Backend_5XX", "HTTPCode_ELB_4XX",
	"HTTPCode_ELB_5XX", "Latency", "HealthyHostCount", "UnHealthyHostCount",
	"BackendConnectionErrors", "SurgeQueueLength", "SpilloverCount",
}

var RDSMetrics = []string{
	"BinLogDiskUsage", "CPUUtilization", "DatabaseConnections",
	"DiskQueueDepth", "FreeableMemory", "FreeStorageSpace", "ReplicaLag",
	"SwapUsage", "ReadIOPS", "WriteIOPS", "ReadLatency", "WriteLatency",
	"ReadThroughput", "WriteThroughput", "NetworkReceiveThroughput",
	"NetworkTransmitThroughput",
}

func loadELBStats(auth aws.Auth, statChan chan []Stat, done *sync.WaitGroup) {
	log.Debugf("Checking ELB...")
	defer func() {
		log.Debugf("Done checking ELB")
		done.Done()
	}()

	names, err := stack.ListActive()
	if err != nil {
		log.Errorf("ERROR: %s\n", err)
		return
	}

	for _, i := range names {
		rs, err := stack.ListStackResources(i)
		if err != nil {
			log.Errorf("ERROR: %s\n", err)
			continue
		}

		var elbName string
		for _, i := range rs.Resources {
			if i.Type == "AWS::ElasticLoadBalancing::LoadBalancer" {
				elbName = i.PhysicalId
			}
		}

		if elbName == "" {
			log.Debugf("Could not lookup ELB name for %s. Skipping.", i)
			continue
		}

		parts := strings.Split(i, "-")
		if len(parts) != 3 {
			log.Debugf("ELB %s. Does not appear to be related to galaxy. Skipping.", elbName)
			continue
		}
		resourceEnv := parts[1]

		for _, metric := range ELBMetrics {
			cwStat := CloudwatchStat{
				Namespace: "AWS/ELB",
				Dimensions: map[string]string{
					"LoadBalancerName": elbName,
				},
				MetricName: metric,
				Statistic:  "Sum",
				Component:  "elb",
			}

			if metric == "SurgeQueueLength" {
				cwStat.Statistic = "Maximum"
			}
			if metric == "Latency" || metric == "HealthyHostCount" || metric == "UnHealthyHostCount" {
				cwStat.Statistic = "Average"
			}

			datapoints, err := cwStat.Get()
			if err != nil {
				log.Errorf("ERROR: %s\n", err)
				continue
			}

			var stats []Stat
			prefix := strings.Join([]string{resourceEnv, "aws", "elb", elbName, metric}, ".")
			for _, metric := range datapoints {
				stats = append(stats, Stat{Path: prefix, Value: metric.Average, TS: metric.Timestamp})
			}

			statChan <- stats
		}
	}
}

func loadRDSStats(auth aws.Auth, statChan chan []Stat, done *sync.WaitGroup) {

	log.Debugf("Checking RDS...")
	defer func() {
		log.Debugf("Done checking RDS")
		done.Done()
	}()

	reg, err := stack.GetAWSRegion("")
	if err != nil {
		log.Errorf("ERROR: %s", err)
		return
	}

	rdsClient, err := rds.New(auth, *reg)
	if err != nil {
		log.Errorf("ERROR: %s", err)
		return
	}

	resp, err := rdsClient.DescribeDBInstances("", 0, "")
	if err != nil {
		log.Errorf("ERROR: %s", err)
		return
	}

	instanceIds := []string{}
	for _, inst := range resp.DBInstances {
		instanceIds = append(instanceIds, inst.DBInstanceIdentifier)
	}

	for _, dbInstance := range instanceIds {
		log.Debugf("fetching stats for RDS: %s", dbInstance)

		for _, metric := range RDSMetrics {
			cwStat := CloudwatchStat{
				Namespace: "AWS/RDS",
				Dimensions: map[string]string{
					"DBInstanceIdentifier": dbInstance,
				},
				MetricName: metric,
				Statistic:  "Average",
				Component:  "rds",
			}

			datapoints, err := cwStat.Get()
			if err != nil {
				log.Errorf("ERROR: %s\n", err)
				continue
			}

			var stats []Stat
			prefix := strings.Join([]string{dbInstance, "aws", "rds", dbInstance, metric}, ".")
			for _, metric := range datapoints {
				stats = append(stats, Stat{Path: prefix, Value: metric.Average, TS: metric.Timestamp})
			}

			statChan <- stats
		}
	}
}

func loadCloudwatchStats(statChan chan []Stat) {
	pollWg := sync.WaitGroup{}

	ticker := time.NewTicker(5 * time.Minute)
	for {
		auth, err := aws.GetAuth("", "", "", time.Now().UTC())
		if err != nil {
			log.Debugf("%s. Skipping collection.", err)
			time.Sleep(60 * time.Second)
			continue
		}

		log.Debugf("Checking cloudwatch...")
		pollWg.Add(1)
		go loadELBStats(auth, statChan, &pollWg)

		pollWg.Add(1)
		go loadRDSStats(auth, statChan, &pollWg)

		pollWg.Wait()
		<-ticker.C
	}
}

// This fetches the actual stats from Cloudwatch and return a []Stat for feeding into graphite
func (c *CloudwatchStat) Get() ([]cloudwatch.Datapoint, error) {
	auth, err := aws.GetAuth("", "", "", time.Now())
	if err != nil {
		return nil, err
	}

	cw, err := cloudwatch.NewCloudWatch(auth, aws.USEast.CloudWatchServicepoint)
	if err != nil {
		return nil, err
	}

	dimensions := []cloudwatch.Dimension{}
	for k, v := range c.Dimensions {
		dimensions = append(dimensions, cloudwatch.Dimension{
			Name:  k,
			Value: v,
		})
	}

	s, err := cw.GetMetricStatistics(&cloudwatch.GetMetricStatisticsRequest{
		Namespace:  c.Namespace,
		Dimensions: dimensions,
		MetricName: c.MetricName,
		StartTime:  time.Now().UTC().Add(-5 * time.Minute),
		EndTime:    time.Now().UTC(),
		Period:     60,
		Statistics: []string{c.Statistic},
	})
	if err != nil {
		return nil, err
	}

	return s.GetMetricStatisticsResult.Datapoints, nil
}
<commit_msg>remove extra dbInstance from RDS stats<commit_after>package main

import (
	"strings"
	"sync"
	"time"

	"github.com/goamz/goamz/aws"
	"github.com/goamz/goamz/cloudwatch"
	"github.com/goamz/goamz/rds"
	"github.com/litl/galaxy/log"
	"github.com/litl/galaxy/stack"
)

type CloudwatchStat struct {
	Namespace  string
	Dimensions map[string]string
	MetricName string
	Statistic  string
	Component  string
}

var ELBMetrics = []string{
	"RequestCount", "HTTPCode_Backend_2XX", "HTTPCode_Backend_3XX",
	"HTTPCode_Backend_4XX", "HTTPCode_Backend_5XX", "HTTPCode_ELB_4XX",
	"HTTPCode_ELB_5XX", "Latency", "HealthyHostCount", "UnHealthyHostCount",
	"BackendConnectionErrors", "SurgeQueueLength", "SpilloverCount",
}

var RDSMetrics = []string{
	"BinLogDiskUsage", "CPUUtilization", "DatabaseConnections",
	"DiskQueueDepth", "FreeableMemory", "FreeStorageSpace", "ReplicaLag",
	"SwapUsage", "ReadIOPS", "WriteIOPS", "ReadLatency", "WriteLatency",
	"ReadThroughput", "WriteThroughput", "NetworkReceiveThroughput",
	"NetworkTransmitThroughput",
}

func loadELBStats(auth aws.Auth, statChan chan []Stat, done *sync.WaitGroup) {
	log.Debugf("Checking ELB...")
	defer func() {
		log.Debugf("Done checking ELB")
		done.Done()
	}()

	names, err := stack.ListActive()
	if err != nil {
		log.Errorf("ERROR: %s\n", err)
		return
	}

	for _, i := range names {
		rs, err := stack.ListStackResources(i)
		if err != nil {
			log.Errorf("ERROR: %s\n", err)
			continue
		}

		var elbName string
		for _, i := range rs.Resources {
			if i.Type == "AWS::ElasticLoadBalancing::LoadBalancer" {
				elbName = i.PhysicalId
			}
		}

		if elbName == "" {
			log.Debugf("Could not lookup ELB name for %s. Skipping.", i)
			continue
		}

		parts := strings.Split(i, "-")
		if len(parts) != 3 {
			log.Debugf("ELB %s. Does not appear to be related to galaxy. Skipping.", elbName)
			continue
		}
		resourceEnv := parts[1]

		for _, metric := range ELBMetrics {
			cwStat := CloudwatchStat{
				Namespace: "AWS/ELB",
				Dimensions: map[string]string{
					"LoadBalancerName": elbName,
				},
				MetricName: metric,
				Statistic:  "Sum",
				Component:  "elb",
			}

			if metric == "SurgeQueueLength" {
				cwStat.Statistic = "Maximum"
			}
			if metric == "Latency" || metric == "HealthyHostCount" || metric == "UnHealthyHostCount" {
				cwStat.Statistic = "Average"
			}

			datapoints, err := cwStat.Get()
			if err != nil {
				log.Errorf("ERROR: %s\n", err)
				continue
			}

			var stats []Stat
			prefix := strings.Join([]string{resourceEnv, "aws", "elb", elbName, metric}, ".")
			for _, metric := range datapoints {
				stats = append(stats, Stat{Path: prefix, Value: metric.Average, TS: metric.Timestamp})
			}

			statChan <- stats
		}
	}
}

func loadRDSStats(auth aws.Auth, statChan chan []Stat, done *sync.WaitGroup) {

	log.Debugf("Checking RDS...")
	defer func() {
		log.Debugf("Done checking RDS")
		done.Done()
	}()

	reg, err := stack.GetAWSRegion("")
	if err != nil {
		log.Errorf("ERROR: %s", err)
		return
	}

	rdsClient, err := rds.New(auth, *reg)
	if err != nil {
		log.Errorf("ERROR: %s", err)
		return
	}

	resp, err := rdsClient.DescribeDBInstances("", 0, "")
	if err != nil {
		log.Errorf("ERROR: %s", err)
		return
	}

	instanceIds := []string{}
	for _, inst := range resp.DBInstances {
		instanceIds = append(instanceIds, inst.DBInstanceIdentifier)
	}

	for _, dbInstance := range instanceIds {
		log.Debugf("fetching stats for RDS: %s", dbInstance)

		for _, metric := range RDSMetrics {
			cwStat := CloudwatchStat{
				Namespace: "AWS/RDS",
				Dimensions: map[string]string{
					"DBInstanceIdentifier": dbInstance,
				},
				MetricName: metric,
				Statistic:  "Average",
				Component:  "rds",
			}

			datapoints, err := cwStat.Get()
			if err != nil {
				log.Errorf("ERROR: %s\n", err)
				continue
			}

			var stats []Stat
			prefix := strings.Join([]string{dbInstance, "aws", "rds", metric}, ".")
			for _, metric := range datapoints {
				stats = append(stats, Stat{Path: prefix, Value: metric.Average, TS: metric.Timestamp})
			}

			statChan <- stats
		}
	}
}

func loadCloudwatchStats(statChan chan []Stat) {
	pollWg := sync.WaitGroup{}

	ticker := time.NewTicker(5 * time.Minute)
	for {
		auth, err := aws.GetAuth("", "", "", time.Now().UTC())
		if err != nil {
			log.Debugf("%s. Skipping collection.", err)
			time.Sleep(60 * time.Second)
			continue
		}

		log.Debugf("Checking cloudwatch...")
		pollWg.Add(1)
		go loadELBStats(auth, statChan, &pollWg)

		pollWg.Add(1)
		go loadRDSStats(auth, statChan, &pollWg)

		pollWg.Wait()
		<-ticker.C
	}
}

// This fetches the actual stats from Cloudwatch and return a []Stat for feeding into graphite
func (c *CloudwatchStat) Get() ([]cloudwatch.Datapoint, error) {
	auth, err := aws.GetAuth("", "", "", time.Now())
	if err != nil {
		return nil, err
	}

	cw, err := cloudwatch.NewCloudWatch(auth, aws.USEast.CloudWatchServicepoint)
	if err != nil {
		return nil, err
	}

	dimensions := []cloudwatch.Dimension{}
	for k, v := range c.Dimensions {
		dimensions = append(dimensions, cloudwatch.Dimension{
			Name:  k,
			Value: v,
		})
	}

	s, err := cw.GetMetricStatistics(&cloudwatch.GetMetricStatisticsRequest{
		Namespace:  c.Namespace,
		Dimensions: dimensions,
		MetricName: c.MetricName,
		StartTime:  time.Now().UTC().Add(-5 * time.Minute),
		EndTime:    time.Now().UTC(),
		Period:     60,
		Statistics: []string{c.Statistic},
	})
	if err != nil {
		return nil, err
	}

	return s.GetMetricStatisticsResult.Datapoints, nil
}
<|endoftext|>


<commit_before>package configupgrade

import (
	"fmt"
	"log"

	hcl1 "github.com/hashicorp/hcl"
	hcl1ast "github.com/hashicorp/hcl/hcl/ast"
	hcl1parser "github.com/hashicorp/hcl/hcl/parser"

	"github.com/hashicorp/terraform/addrs"
	"github.com/hashicorp/terraform/configs/configschema"
	"github.com/hashicorp/terraform/moduledeps"
	"github.com/hashicorp/terraform/plugin/discovery"
	"github.com/hashicorp/terraform/terraform"
)

// analysis is a container for the various different information gathered
// by Upgrader.analyze.
type analysis struct {
	ProviderSchemas      map[string]*terraform.ProviderSchema
	ProvisionerSchemas   map[string]*configschema.Block
	ResourceProviderType map[addrs.Resource]string
	ResourceHasCount     map[addrs.Resource]bool
}

// analyze processes the configuration files included inside the receiver
// and returns an assortment of information required to make decisions during
// a configuration upgrade.
func (u *Upgrader) analyze(ms ModuleSources) (*analysis, error) {
	ret := &analysis{
		ProviderSchemas:      make(map[string]*terraform.ProviderSchema),
		ProvisionerSchemas:   make(map[string]*configschema.Block),
		ResourceProviderType: make(map[addrs.Resource]string),
		ResourceHasCount:     make(map[addrs.Resource]bool),
	}

	m := &moduledeps.Module{
		Providers: make(moduledeps.Providers),
	}

	// This is heavily based on terraform.ModuleTreeDependencies but
	// differs in that it works directly with the HCL1 AST rather than
	// the legacy config structs (and can thus outlive those) and that
	// it only works on one module at a time, and so doesn't need to
	// recurse into child calls.
	for name, src := range ms {
		if ext := fileExt(name); ext != ".tf" {
			continue
		}

		log.Printf("[TRACE] configupgrade: Analyzing %q", name)

		f, err := hcl1parser.Parse(src)
		if err != nil {
			// If we encounter a syntax error then we'll just skip for now
			// and assume that we'll catch this again when we do the upgrade.
			// If not, we'll break the upgrade step of renaming .tf files to
			// .tf.json if they seem to be JSON syntax.
			log.Printf("[ERROR] Failed to parse %q: %s", name, err)
			continue
		}

		list, ok := f.Node.(*hcl1ast.ObjectList)
		if !ok {
			return nil, fmt.Errorf("error parsing: file doesn't contain a root object")
		}

		if providersList := list.Filter("provider"); len(providersList.Items) > 0 {
			providerObjs := providersList.Children()
			for _, providerObj := range providerObjs.Items {
				if len(providerObj.Keys) != 1 {
					return nil, fmt.Errorf("provider block has wrong number of labels")
				}
				name := providerObj.Keys[0].Token.Value().(string)

				var listVal *hcl1ast.ObjectList
				if ot, ok := providerObj.Val.(*hcl1ast.ObjectType); ok {
					listVal = ot.List
				} else {
					return nil, fmt.Errorf("provider %q: must be a block", name)
				}

				var versionStr string
				if a := listVal.Filter("version"); len(a.Items) > 0 {
					err := hcl1.DecodeObject(&versionStr, a.Items[0].Val)
					if err != nil {
						return nil, fmt.Errorf("Error reading version for provider %q: %s", name, err)
					}
				}
				var constraints discovery.Constraints
				if versionStr != "" {
					constraints, err = discovery.ConstraintStr(versionStr).Parse()
					if err != nil {
						return nil, fmt.Errorf("Error parsing version for provider %q: %s", name, err)
					}
				}

				var alias string
				if a := listVal.Filter("alias"); len(a.Items) > 0 {
					err := hcl1.DecodeObject(&alias, a.Items[0].Val)
					if err != nil {
						return nil, fmt.Errorf("Error reading alias for provider %q: %s", name, err)
					}
				}

				inst := moduledeps.ProviderInstance(name)
				if alias != "" {
					inst = moduledeps.ProviderInstance(name + "." + alias)
				}
				log.Printf("[TRACE] Provider block requires provider %q", inst)
				m.Providers[inst] = moduledeps.ProviderDependency{
					Constraints: constraints,
					Reason:      moduledeps.ProviderDependencyExplicit,
				}
			}
		}

		{
			// For our purposes here we don't need to distinguish "resource"
			// and "data" blocks -- provider references are the same for
			// both of them -- so we'll just merge them together into a
			// single list and iterate it.
			resourceConfigsList := list.Filter("resource")
			dataResourceConfigsList := list.Filter("data")
			resourceConfigsList.Items = append(resourceConfigsList.Items, dataResourceConfigsList.Items...)

			resourceObjs := resourceConfigsList.Children()
			for _, resourceObj := range resourceObjs.Items {
				if len(resourceObj.Keys) != 2 {
					return nil, fmt.Errorf("resource or data block has wrong number of labels")
				}
				typeName := resourceObj.Keys[0].Token.Value().(string)
				name := resourceObj.Keys[1].Token.Value().(string)
				rAddr := addrs.Resource{
					Mode: addrs.ManagedResourceMode, // not necessarily true, but good enough for our purposes here
					Type: typeName,
					Name: name,
				}

				var listVal *hcl1ast.ObjectList
				if ot, ok := resourceObj.Val.(*hcl1ast.ObjectType); ok {
					listVal = ot.List
				} else {
					return nil, fmt.Errorf("resource %q %q must be a block", typeName, name)
				}

				if o := listVal.Filter("count"); len(o.Items) > 0 {
					ret.ResourceHasCount[rAddr] = true
				}

				var providerKey string
				if o := listVal.Filter("provider"); len(o.Items) > 0 {
					err := hcl1.DecodeObject(&providerKey, o.Items[0].Val)
					if err != nil {
						return nil, fmt.Errorf("Error reading provider for resource %q %q: %s", typeName, name, err)
					}
				}

				if providerKey == "" {
					providerKey = rAddr.DefaultProviderConfig().StringCompact()
				}

				inst := moduledeps.ProviderInstance(providerKey)
				log.Printf("[TRACE] Resource block for %q %q requires provider %q", typeName, name, inst)
				if _, exists := m.Providers[inst]; !exists {
					m.Providers[inst] = moduledeps.ProviderDependency{
						Reason: moduledeps.ProviderDependencyImplicit,
					}
				}
				ret.ResourceProviderType[rAddr] = inst.Type()
			}
		}
	}

	providerFactories, err := u.Providers.ResolveProviders(m.PluginRequirements())
	if err != nil {
		return nil, fmt.Errorf("error resolving providers: %s", err)
	}

	for name, fn := range providerFactories {
		log.Printf("[TRACE] Fetching schema from provider %q", name)
		provider, err := fn()
		if err != nil {
			return nil, fmt.Errorf("failed to load provider %q: %s", name, err)
		}

		resp := provider.GetSchema()
		if resp.Diagnostics.HasErrors() {
			return nil, resp.Diagnostics.Err()
		}

		schema := &terraform.ProviderSchema{
			Provider:      resp.Provider.Block,
			ResourceTypes: map[string]*configschema.Block{},
			DataSources:   map[string]*configschema.Block{},
		}
		for t, s := range resp.ResourceTypes {
			schema.ResourceTypes[t] = s.Block
		}
		for t, s := range resp.DataSources {
			schema.DataSources[t] = s.Block
		}
		ret.ProviderSchemas[name] = schema
	}

	// TODO: Also ProvisionerSchemas

	return ret, nil
}
<commit_msg>configs/configupgrade: Distinguish data resources in analysis<commit_after>package configupgrade

import (
	"fmt"
	"log"

	hcl1 "github.com/hashicorp/hcl"
	hcl1ast "github.com/hashicorp/hcl/hcl/ast"
	hcl1parser "github.com/hashicorp/hcl/hcl/parser"
	hcl1token "github.com/hashicorp/hcl/hcl/token"

	"github.com/hashicorp/terraform/addrs"
	"github.com/hashicorp/terraform/configs/configschema"
	"github.com/hashicorp/terraform/moduledeps"
	"github.com/hashicorp/terraform/plugin/discovery"
	"github.com/hashicorp/terraform/terraform"
)

// analysis is a container for the various different information gathered
// by Upgrader.analyze.
type analysis struct {
	ProviderSchemas      map[string]*terraform.ProviderSchema
	ProvisionerSchemas   map[string]*configschema.Block
	ResourceProviderType map[addrs.Resource]string
	ResourceHasCount     map[addrs.Resource]bool
}

// analyze processes the configuration files included inside the receiver
// and returns an assortment of information required to make decisions during
// a configuration upgrade.
func (u *Upgrader) analyze(ms ModuleSources) (*analysis, error) {
	ret := &analysis{
		ProviderSchemas:      make(map[string]*terraform.ProviderSchema),
		ProvisionerSchemas:   make(map[string]*configschema.Block),
		ResourceProviderType: make(map[addrs.Resource]string),
		ResourceHasCount:     make(map[addrs.Resource]bool),
	}

	m := &moduledeps.Module{
		Providers: make(moduledeps.Providers),
	}

	// This is heavily based on terraform.ModuleTreeDependencies but
	// differs in that it works directly with the HCL1 AST rather than
	// the legacy config structs (and can thus outlive those) and that
	// it only works on one module at a time, and so doesn't need to
	// recurse into child calls.
	for name, src := range ms {
		if ext := fileExt(name); ext != ".tf" {
			continue
		}

		log.Printf("[TRACE] configupgrade: Analyzing %q", name)

		f, err := hcl1parser.Parse(src)
		if err != nil {
			// If we encounter a syntax error then we'll just skip for now
			// and assume that we'll catch this again when we do the upgrade.
			// If not, we'll break the upgrade step of renaming .tf files to
			// .tf.json if they seem to be JSON syntax.
			log.Printf("[ERROR] Failed to parse %q: %s", name, err)
			continue
		}

		list, ok := f.Node.(*hcl1ast.ObjectList)
		if !ok {
			return nil, fmt.Errorf("error parsing: file doesn't contain a root object")
		}

		if providersList := list.Filter("provider"); len(providersList.Items) > 0 {
			providerObjs := providersList.Children()
			for _, providerObj := range providerObjs.Items {
				if len(providerObj.Keys) != 1 {
					return nil, fmt.Errorf("provider block has wrong number of labels")
				}
				name := providerObj.Keys[0].Token.Value().(string)

				var listVal *hcl1ast.ObjectList
				if ot, ok := providerObj.Val.(*hcl1ast.ObjectType); ok {
					listVal = ot.List
				} else {
					return nil, fmt.Errorf("provider %q: must be a block", name)
				}

				var versionStr string
				if a := listVal.Filter("version"); len(a.Items) > 0 {
					err := hcl1.DecodeObject(&versionStr, a.Items[0].Val)
					if err != nil {
						return nil, fmt.Errorf("Error reading version for provider %q: %s", name, err)
					}
				}
				var constraints discovery.Constraints
				if versionStr != "" {
					constraints, err = discovery.ConstraintStr(versionStr).Parse()
					if err != nil {
						return nil, fmt.Errorf("Error parsing version for provider %q: %s", name, err)
					}
				}

				var alias string
				if a := listVal.Filter("alias"); len(a.Items) > 0 {
					err := hcl1.DecodeObject(&alias, a.Items[0].Val)
					if err != nil {
						return nil, fmt.Errorf("Error reading alias for provider %q: %s", name, err)
					}
				}

				inst := moduledeps.ProviderInstance(name)
				if alias != "" {
					inst = moduledeps.ProviderInstance(name + "." + alias)
				}
				log.Printf("[TRACE] Provider block requires provider %q", inst)
				m.Providers[inst] = moduledeps.ProviderDependency{
					Constraints: constraints,
					Reason:      moduledeps.ProviderDependencyExplicit,
				}
			}
		}

		{
			resourceConfigsList := list.Filter("resource")
			dataResourceConfigsList := list.Filter("data")
			// list.Filter annoyingly strips off the key used for matching,
			// so we'll put it back here so we can distinguish our two types
			// of blocks below.
			for _, obj := range resourceConfigsList.Items {
				obj.Keys = append([]*hcl1ast.ObjectKey{
					{Token: hcl1token.Token{Type: hcl1token.IDENT, Text: "resource"}},
				}, obj.Keys...)
			}
			for _, obj := range dataResourceConfigsList.Items {
				obj.Keys = append([]*hcl1ast.ObjectKey{
					{Token: hcl1token.Token{Type: hcl1token.IDENT, Text: "data"}},
				}, obj.Keys...)
			}
			// Now we can merge the two lists together, since we can distinguish
			// them just by their keys[0].
			resourceConfigsList.Items = append(resourceConfigsList.Items, dataResourceConfigsList.Items...)

			resourceObjs := resourceConfigsList.Children()
			for _, resourceObj := range resourceObjs.Items {
				if len(resourceObj.Keys) != 3 {
					return nil, fmt.Errorf("resource or data block has wrong number of labels")
				}
				typeName := resourceObj.Keys[1].Token.Value().(string)
				name := resourceObj.Keys[2].Token.Value().(string)
				rAddr := addrs.Resource{
					Mode: addrs.ManagedResourceMode,
					Type: typeName,
					Name: name,
				}
				if resourceObj.Keys[0].Token.Value() == "data" {
					rAddr.Mode = addrs.DataResourceMode
				}

				var listVal *hcl1ast.ObjectList
				if ot, ok := resourceObj.Val.(*hcl1ast.ObjectType); ok {
					listVal = ot.List
				} else {
					return nil, fmt.Errorf("config for %q must be a block", rAddr)
				}

				if o := listVal.Filter("count"); len(o.Items) > 0 {
					ret.ResourceHasCount[rAddr] = true
				}

				var providerKey string
				if o := listVal.Filter("provider"); len(o.Items) > 0 {
					err := hcl1.DecodeObject(&providerKey, o.Items[0].Val)
					if err != nil {
						return nil, fmt.Errorf("Error reading provider for resource %s: %s", rAddr, err)
					}
				}

				if providerKey == "" {
					providerKey = rAddr.DefaultProviderConfig().StringCompact()
				}

				inst := moduledeps.ProviderInstance(providerKey)
				log.Printf("[TRACE] Resource block for %s requires provider %q", rAddr, inst)
				if _, exists := m.Providers[inst]; !exists {
					m.Providers[inst] = moduledeps.ProviderDependency{
						Reason: moduledeps.ProviderDependencyImplicit,
					}
				}
				ret.ResourceProviderType[rAddr] = inst.Type()
			}
		}
	}

	providerFactories, err := u.Providers.ResolveProviders(m.PluginRequirements())
	if err != nil {
		return nil, fmt.Errorf("error resolving providers: %s", err)
	}

	for name, fn := range providerFactories {
		log.Printf("[TRACE] Fetching schema from provider %q", name)
		provider, err := fn()
		if err != nil {
			return nil, fmt.Errorf("failed to load provider %q: %s", name, err)
		}

		resp := provider.GetSchema()
		if resp.Diagnostics.HasErrors() {
			return nil, resp.Diagnostics.Err()
		}

		schema := &terraform.ProviderSchema{
			Provider:      resp.Provider.Block,
			ResourceTypes: map[string]*configschema.Block{},
			DataSources:   map[string]*configschema.Block{},
		}
		for t, s := range resp.ResourceTypes {
			schema.ResourceTypes[t] = s.Block
		}
		for t, s := range resp.DataSources {
			schema.DataSources[t] = s.Block
		}
		ret.ProviderSchemas[name] = schema
	}

	// TODO: Also ProvisionerSchemas

	return ret, nil
}
<|endoftext|>


<commit_before>// Copyright (c) 2015 Uber Technologies, 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.

package tchannel_test

// This file contains test setup logic, and is named with a _test.go suffix to
// ensure it's only compiled with tests.

import (
	"fmt"
	"reflect"
	"sort"
	"strings"
	"sync"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
)

type statsValue struct {
	// count is the counter value if this metric is a counter.
	count int64

	// timers is the list of timer values if this metrics is a timer.
	timers []time.Duration
}

type recordingStatsReporter struct {
	sync.Mutex

	// Values is a map from the metricName -> map[tagMapAsString]*statsValue
	Values map[string]map[string]*statsValue

	// Expected stores expected counter values.
	Expected *recordingStatsReporter
}

func newRecordingStatsReporter() *recordingStatsReporter {
	return &recordingStatsReporter{
		Values: make(map[string]map[string]*statsValue),
		Expected: &recordingStatsReporter{
			Values: make(map[string]map[string]*statsValue),
		},
	}
}

// keysMap returns the keys of the given map as a sorted list of strings.
// If the map is not of the type map[string]* then the function will panic.
func keysMap(m interface{}) []string {
	var keys []string
	mapKeys := reflect.ValueOf(m).MapKeys()
	for _, v := range mapKeys {
		keys = append(keys, v.Interface().(string))
	}
	sort.Strings(keys)
	return keys
}

// tagsToString converts a map of tags to a string that can be used as a map key.
func tagsToString(tags map[string]string) string {
	var vals []string
	for _, k := range keysMap(tags) {
		vals = append(vals, fmt.Sprintf("%v = %v", k, tags[k]))
	}
	return strings.Join(vals, ", ")
}

func (r *recordingStatsReporter) getStat(name string, tags map[string]string) *statsValue {
	r.Lock()
	defer r.Unlock()

	tagMap, ok := r.Values[name]
	if !ok {
		tagMap = make(map[string]*statsValue)
		r.Values[name] = tagMap
	}

	tagStr := tagsToString(tags)
	statVal, ok := tagMap[tagStr]
	if !ok {
		statVal = &statsValue{}
		tagMap[tagStr] = statVal
	}

	return statVal
}

func (r *recordingStatsReporter) IncCounter(name string, tags map[string]string, value int64) {
	statVal := r.getStat(name, tags)
	statVal.count += value
}

func (r *recordingStatsReporter) RecordTimer(name string, tags map[string]string, d time.Duration) {
	statVal := r.getStat(name, tags)
	statVal.timers = append(statVal.timers, d)
}

func (r *recordingStatsReporter) Reset() {
	*r = *newRecordingStatsReporter()
}

func (r *recordingStatsReporter) Validate(t *testing.T) {
	r.Lock()
	defer r.Unlock()

	assert.Equal(t, keysMap(r.Expected.Values), keysMap(r.Values),
		"Metric keys are different")
	for counterKey, counter := range r.Values {
		expectedCounter, ok := r.Expected.Values[counterKey]
		if !ok {
			continue
		}

		assert.Equal(t, keysMap(expectedCounter), keysMap(counter),
			"Metric %v has different reported tags", counterKey)
		for tags, stat := range counter {
			expectedStat, ok := expectedCounter[tags]
			if !ok {
				continue
			}

			assert.Equal(t, expectedStat, stat,
				"Metric %v with tags %v has mismatched value", counterKey, tags)
		}
	}
}

func (r *recordingStatsReporter) UpdateGauge(name string, tags map[string]string, value int64) {}
<commit_msg>Fix "copying lock" go vet<commit_after>// Copyright (c) 2015 Uber Technologies, 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.

package tchannel_test

// This file contains test setup logic, and is named with a _test.go suffix to
// ensure it's only compiled with tests.

import (
	"fmt"
	"reflect"
	"sort"
	"strings"
	"sync"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
)

type statsValue struct {
	// count is the counter value if this metric is a counter.
	count int64

	// timers is the list of timer values if this metrics is a timer.
	timers []time.Duration
}

type recordingStatsReporter struct {
	sync.Mutex

	// Values is a map from the metricName -> map[tagMapAsString]*statsValue
	Values map[string]map[string]*statsValue

	// Expected stores expected counter values.
	Expected *recordingStatsReporter
}

func newRecordingStatsReporter() *recordingStatsReporter {
	return &recordingStatsReporter{
		Values: make(map[string]map[string]*statsValue),
		Expected: &recordingStatsReporter{
			Values: make(map[string]map[string]*statsValue),
		},
	}
}

// keysMap returns the keys of the given map as a sorted list of strings.
// If the map is not of the type map[string]* then the function will panic.
func keysMap(m interface{}) []string {
	var keys []string
	mapKeys := reflect.ValueOf(m).MapKeys()
	for _, v := range mapKeys {
		keys = append(keys, v.Interface().(string))
	}
	sort.Strings(keys)
	return keys
}

// tagsToString converts a map of tags to a string that can be used as a map key.
func tagsToString(tags map[string]string) string {
	var vals []string
	for _, k := range keysMap(tags) {
		vals = append(vals, fmt.Sprintf("%v = %v", k, tags[k]))
	}
	return strings.Join(vals, ", ")
}

func (r *recordingStatsReporter) getStat(name string, tags map[string]string) *statsValue {
	r.Lock()
	defer r.Unlock()

	tagMap, ok := r.Values[name]
	if !ok {
		tagMap = make(map[string]*statsValue)
		r.Values[name] = tagMap
	}

	tagStr := tagsToString(tags)
	statVal, ok := tagMap[tagStr]
	if !ok {
		statVal = &statsValue{}
		tagMap[tagStr] = statVal
	}

	return statVal
}

func (r *recordingStatsReporter) IncCounter(name string, tags map[string]string, value int64) {
	statVal := r.getStat(name, tags)
	statVal.count += value
}

func (r *recordingStatsReporter) RecordTimer(name string, tags map[string]string, d time.Duration) {
	statVal := r.getStat(name, tags)
	statVal.timers = append(statVal.timers, d)
}

func (r *recordingStatsReporter) Reset() {
	newReporter := newRecordingStatsReporter()
	r.Values = newReporter.Values
	r.Expected = newReporter.Expected
}

func (r *recordingStatsReporter) Validate(t *testing.T) {
	r.Lock()
	defer r.Unlock()

	assert.Equal(t, keysMap(r.Expected.Values), keysMap(r.Values),
		"Metric keys are different")
	for counterKey, counter := range r.Values {
		expectedCounter, ok := r.Expected.Values[counterKey]
		if !ok {
			continue
		}

		assert.Equal(t, keysMap(expectedCounter), keysMap(counter),
			"Metric %v has different reported tags", counterKey)
		for tags, stat := range counter {
			expectedStat, ok := expectedCounter[tags]
			if !ok {
				continue
			}

			assert.Equal(t, expectedStat, stat,
				"Metric %v with tags %v has mismatched value", counterKey, tags)
		}
	}
}

func (r *recordingStatsReporter) UpdateGauge(name string, tags map[string]string, value int64) {}
<|endoftext|>


<commit_before>package storage

import (
	"os"
	"path/filepath"
	"sync"
	"syscall"
	"time"

	"github.com/containers/storage/pkg/stringid"
)

// A Locker represents a file lock where the file is used to cache an
// identifier of the last party that made changes to whatever's being protected
// by the lock.
type Locker interface {
	sync.Locker

	// Touch records, for others sharing the lock, that it was updated by the
	// caller.  It should only be called with the lock held.
	Touch() error

	// Modified() checks if the most recent writer was a party other than the
	// caller.  It should only be called with the lock held.
	Modified() (bool, error)

	// TouchedSince() checks if the most recent writer modified the file (likely using Touch()) after the specified time.
	TouchedSince(when time.Time) bool
}

type lockfile struct {
	mu   sync.Mutex
	file string
	fd   uintptr
	me   string
}

var (
	lockfiles     map[string]*lockfile
	lockfilesLock sync.Mutex
)

// GetLockfile opens a lock file, creating it if necessary.  The Locker object
// return will be returned unlocked.
func GetLockfile(path string) (Locker, error) {
	lockfilesLock.Lock()
	defer lockfilesLock.Unlock()
	if lockfiles == nil {
		lockfiles = make(map[string]*lockfile)
	}
	if locker, ok := lockfiles[filepath.Clean(path)]; ok {
		return locker, nil
	}
	fd, err := syscall.Open(filepath.Clean(path), os.O_RDWR|os.O_CREATE, syscall.S_IRUSR|syscall.S_IWUSR)
	if err != nil {
		return nil, err
	}
	locker := &lockfile{file: path, fd: uintptr(fd)}
	lockfiles[filepath.Clean(path)] = locker
	return locker, nil
}

func (l *lockfile) Lock() {
	lk := syscall.Flock_t{
		Type:   syscall.F_WRLCK,
		Whence: int16(os.SEEK_SET),
		Start:  0,
		Len:    0,
		Pid:    int32(os.Getpid()),
	}
	l.mu.Lock()
	for syscall.FcntlFlock(l.fd, syscall.F_SETLKW, &lk) != nil {
		time.Sleep(10 * time.Millisecond)
	}
}

func (l *lockfile) Unlock() {
	lk := syscall.Flock_t{
		Type:   syscall.F_UNLCK,
		Whence: int16(os.SEEK_SET),
		Start:  0,
		Len:    0,
		Pid:    int32(os.Getpid()),
	}
	for syscall.FcntlFlock(l.fd, syscall.F_SETLKW, &lk) != nil {
		time.Sleep(10 * time.Millisecond)
	}
	l.mu.Unlock()
}

func (l *lockfile) Touch() error {
	l.me = stringid.GenerateRandomID()
	id := []byte(l.me)
	_, err := syscall.Seek(int(l.fd), 0, os.SEEK_SET)
	if err != nil {
		return err
	}
	n, err := syscall.Write(int(l.fd), id)
	if err != nil {
		return err
	}
	if n != len(id) {
		return syscall.ENOSPC
	}
	err = syscall.Fsync(int(l.fd))
	if err != nil {
		return err
	}
	return nil
}

func (l *lockfile) Modified() (bool, error) {
	id := []byte(l.me)
	_, err := syscall.Seek(int(l.fd), 0, os.SEEK_SET)
	if err != nil {
		return true, err
	}
	n, err := syscall.Read(int(l.fd), id)
	if err != nil {
		return true, err
	}
	if n != len(id) {
		return true, syscall.ENOSPC
	}
	return string(id) != l.me, nil
}

func (l *lockfile) TouchedSince(when time.Time) bool {
	st := syscall.Stat_t{}
	err := syscall.Fstat(int(l.fd), &st)
	if err != nil {
		return true
	}
	touched := time.Unix(st.Mtim.Unix())
	return when.Before(touched)
}
<commit_msg>Make lockfile.Modified() true at initialization<commit_after>package storage

import (
	"os"
	"path/filepath"
	"sync"
	"syscall"
	"time"

	"github.com/containers/storage/pkg/stringid"
)

// A Locker represents a file lock where the file is used to cache an
// identifier of the last party that made changes to whatever's being protected
// by the lock.
type Locker interface {
	sync.Locker

	// Touch records, for others sharing the lock, that it was updated by the
	// caller.  It should only be called with the lock held.
	Touch() error

	// Modified() checks if the most recent writer was a party other than the
	// caller.  It should only be called with the lock held.
	Modified() (bool, error)

	// TouchedSince() checks if the most recent writer modified the file (likely using Touch()) after the specified time.
	TouchedSince(when time.Time) bool
}

type lockfile struct {
	mu   sync.Mutex
	file string
	fd   uintptr
	me   string
}

var (
	lockfiles     map[string]*lockfile
	lockfilesLock sync.Mutex
)

// GetLockfile opens a lock file, creating it if necessary.  The Locker object
// return will be returned unlocked.
func GetLockfile(path string) (Locker, error) {
	lockfilesLock.Lock()
	defer lockfilesLock.Unlock()
	if lockfiles == nil {
		lockfiles = make(map[string]*lockfile)
	}
	if locker, ok := lockfiles[filepath.Clean(path)]; ok {
		return locker, nil
	}
	fd, err := syscall.Open(filepath.Clean(path), os.O_RDWR|os.O_CREATE, syscall.S_IRUSR|syscall.S_IWUSR)
	if err != nil {
		return nil, err
	}
	locker := &lockfile{file: path, fd: uintptr(fd), me: stringid.GenerateRandomID()}
	lockfiles[filepath.Clean(path)] = locker
	return locker, nil
}

func (l *lockfile) Lock() {
	lk := syscall.Flock_t{
		Type:   syscall.F_WRLCK,
		Whence: int16(os.SEEK_SET),
		Start:  0,
		Len:    0,
		Pid:    int32(os.Getpid()),
	}
	l.mu.Lock()
	for syscall.FcntlFlock(l.fd, syscall.F_SETLKW, &lk) != nil {
		time.Sleep(10 * time.Millisecond)
	}
}

func (l *lockfile) Unlock() {
	lk := syscall.Flock_t{
		Type:   syscall.F_UNLCK,
		Whence: int16(os.SEEK_SET),
		Start:  0,
		Len:    0,
		Pid:    int32(os.Getpid()),
	}
	for syscall.FcntlFlock(l.fd, syscall.F_SETLKW, &lk) != nil {
		time.Sleep(10 * time.Millisecond)
	}
	l.mu.Unlock()
}

func (l *lockfile) Touch() error {
	l.me = stringid.GenerateRandomID()
	id := []byte(l.me)
	_, err := syscall.Seek(int(l.fd), 0, os.SEEK_SET)
	if err != nil {
		return err
	}
	n, err := syscall.Write(int(l.fd), id)
	if err != nil {
		return err
	}
	if n != len(id) {
		return syscall.ENOSPC
	}
	err = syscall.Fsync(int(l.fd))
	if err != nil {
		return err
	}
	return nil
}

func (l *lockfile) Modified() (bool, error) {
	id := []byte(l.me)
	_, err := syscall.Seek(int(l.fd), 0, os.SEEK_SET)
	if err != nil {
		return true, err
	}
	n, err := syscall.Read(int(l.fd), id)
	if err != nil {
		return true, err
	}
	if n != len(id) {
		return true, syscall.ENOSPC
	}
	return string(id) != l.me, nil
}

func (l *lockfile) TouchedSince(when time.Time) bool {
	st := syscall.Stat_t{}
	err := syscall.Fstat(int(l.fd), &st)
	if err != nil {
		return true
	}
	touched := time.Unix(st.Mtim.Unix())
	return when.Before(touched)
}
<|endoftext|>


<commit_before>package store

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

	"github.com/boltdb/bolt"
	"github.com/gorilla/securecookie"
	"github.com/gorilla/sessions"
)

func TestStore_Get(t *testing.T) {
	db, err := bolt.Open("./sessions.db", 0666)
	if err != nil {
		t.Error(err.Error())
	}
	defer db.Close()

	req, err := http.NewRequest("GET", "http://localhost:3000/", nil)
	if err != nil {
		t.Error(err.Error())
	}

	str, err := New(
		db,
		Config{},
		[]byte("secret-key"),
	)
	if err != nil {
		t.Error(err.Error())
	}

	session, err := str.Get(req, "test")
	if err != nil {
		t.Error(err.Error())
	}

	if session.IsNew != true {
		t.Errorf("session.IsNew should be true (actual: %+v)", session.IsNew)
	}
}

func TestStore_New(t *testing.T) {
	db, err := bolt.Open("./sessions.db", 0666)
	if err != nil {
		t.Error(err.Error())
	}
	defer db.Close()

	str, err := New(
		db,
		Config{},
		[]byte("secret-key"),
	)
	if err != nil {
		t.Error(err.Error())
	}

	req, err := http.NewRequest("GET", "http://localhost:3000/", nil)
	if err != nil {
		t.Error(err.Error())
	}

	encoded, err := securecookie.EncodeMulti("test", "1", str.codecs...)

	req.AddCookie(sessions.NewCookie("test", encoded, &sessions.Options{
		MaxAge: 1024,
	}))

	session, err := str.New(req, "test")
	if err != nil {
		t.Error(err.Error())
	}

	if session.IsNew != true {
		t.Errorf("session.IsNew should be true (actual: %+v)", session.IsNew)
	}
}

func TestStore_Save(t *testing.T) {
	// When session.Options.MaxAge < 0
	db, err := bolt.Open("./sessions.db", 0666)
	if err != nil {
		t.Error(err.Error())
	}
	defer db.Close()

	str, err := New(
		db,
		Config{},
		[]byte("secret-key"),
	)
	if err != nil {
		t.Error(err.Error())
	}

	req, err := http.NewRequest("GET", "http://localhost:3000/", nil)
	if err != nil {
		t.Error(err.Error())
	}

	session, err := str.Get(req, "test")
	if err != nil {
		t.Error(err.Error())
	}

	session.Options.MaxAge = -1

	w := httptest.NewRecorder()

	str.Save(req, w, session)

	// When session.Options.MaxAge >= 0
	session, err = str.Get(req, "test")
	if err != nil {
		t.Error(err.Error())
	}

	session.Options.MaxAge = 1

	w = httptest.NewRecorder()

	str.Save(req, w, session)

	// When session.Options.MaxAge >= 0 and
	// s.save returns an error
	session.Values = make(map[interface{}]interface{})
	session.Values[make(chan int)] = make(chan int)
	str.Save(req, w, session)

	// When session.Options.MaxAge >= 0 and
	// securecookie.EncodeMulti  returns an error
	session.Values = make(map[interface{}]interface{})
	str.codecs = nil
	str.Save(req, w, session)
}

func TestStore_load(t *testing.T) {
	db, err := bolt.Open("./sessions.db", 0666)
	if err != nil {
		t.Error(err.Error())
	}
	defer db.Close()

	str, err := New(
		db,
		Config{},
		[]byte("secret-key"),
	)
	if err != nil {
		t.Error(err.Error())
	}

	req, err := http.NewRequest("GET", "http://localhost:3000/", nil)
	if err != nil {
		t.Error(err.Error())
	}

	session, err := str.Get(req, "test")
	if err != nil {
		t.Error(err.Error())
	}

	w := httptest.NewRecorder()

	str.Save(req, w, session)

	exists, err := str.load(session)
	if err != nil {
		t.Error(err.Error())
	}

	if exists != true {
		t.Error("Store.load should return true (actual: false)")
	}

	// When the target session data is nil
	session.ID = "x"
	exists, err = str.load(session)
	if err != nil {
		t.Error(err.Error())
	}

	if exists != false {
		t.Error("Store.load should return false (actual: true)")
	}

	// When shared.Session returns an error
	err = db.Update(func(tx *bolt.Tx) error {
		return tx.Bucket(str.config.DBOptions.BucketName).Put([]byte("x"), []byte("test"))
	})
	if err != nil {
		t.Error(err.Error())
	}
	_, err = str.load(session)
	if err == nil || err.Error() != "proto: field/encoding mismatch: wrong type for field" {
		t.Error(`str.load should return an error "%s" (actual: %s)`, "proto: field/encoding mismatch: wrong type for field", err)
	}

	// When the target session data is expired
	session, err = str.Get(req, "test")
	if err != nil {
		t.Error(err.Error())
	}
	session.Options.MaxAge = 0
	str.Save(req, w, session)
	time.Sleep(time.Second)
	_, err = str.load(session)
	if err != nil {
		t.Error(err.Error())
	}
}

func TestSession_delete(t *testing.T) {
	db, err := bolt.Open("./sessions.db", 0666)
	if err != nil {
		t.Error(err.Error())
	}
	defer db.Close()

	str, err := New(
		db,
		Config{},
		[]byte("secret-key"),
	)
	if err != nil {
		t.Error(err.Error())
	}

	req, err := http.NewRequest("GET", "http://localhost:3000/", nil)
	if err != nil {
		t.Error(err.Error())
	}

	session, err := str.Get(req, "test")
	if err != nil {
		t.Error(err.Error())
	}

	db.Close()

	err = str.delete(session)

	if err.Error() != "database not open" {
		t.Error(`str.delete should return an error "%s" (actual: %s)`, "database not open", err)
	}
}

func TestNew(t *testing.T) {
	// When db.Update returns an error
	db, err := bolt.Open("./sessions.db", 0666)
	if err != nil {
		t.Error(err.Error())
	}
	db.Close()

	_, err = New(
		db,
		Config{},
		[]byte("secret-key"),
	)
	if err.Error() != "database not open" {
		t.Error(`str.delete  should return an error "%s" (actual: %s)`, "database not open", err)
	}
}

func ExampleNew() {
	fmt.Println("This is an example.")
	// Output:
	// aaa
}
<commit_msg>Update store/store_test.go<commit_after>package store

import (
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

	"github.com/boltdb/bolt"
	"github.com/gorilla/securecookie"
	"github.com/gorilla/sessions"
)

func TestStore_Get(t *testing.T) {
	db, err := bolt.Open("./sessions.db", 0666)
	if err != nil {
		t.Error(err.Error())
	}
	defer db.Close()

	req, err := http.NewRequest("GET", "http://localhost:3000/", nil)
	if err != nil {
		t.Error(err.Error())
	}

	str, err := New(
		db,
		Config{},
		[]byte("secret-key"),
	)
	if err != nil {
		t.Error(err.Error())
	}

	session, err := str.Get(req, "test")
	if err != nil {
		t.Error(err.Error())
	}

	if session.IsNew != true {
		t.Errorf("session.IsNew should be true (actual: %+v)", session.IsNew)
	}
}

func TestStore_New(t *testing.T) {
	db, err := bolt.Open("./sessions.db", 0666)
	if err != nil {
		t.Error(err.Error())
	}
	defer db.Close()

	str, err := New(
		db,
		Config{},
		[]byte("secret-key"),
	)
	if err != nil {
		t.Error(err.Error())
	}

	req, err := http.NewRequest("GET", "http://localhost:3000/", nil)
	if err != nil {
		t.Error(err.Error())
	}

	encoded, err := securecookie.EncodeMulti("test", "1", str.codecs...)

	req.AddCookie(sessions.NewCookie("test", encoded, &sessions.Options{
		MaxAge: 1024,
	}))

	session, err := str.New(req, "test")
	if err != nil {
		t.Error(err.Error())
	}

	if session.IsNew != true {
		t.Errorf("session.IsNew should be true (actual: %+v)", session.IsNew)
	}
}

func TestStore_Save(t *testing.T) {
	// When session.Options.MaxAge < 0
	db, err := bolt.Open("./sessions.db", 0666)
	if err != nil {
		t.Error(err.Error())
	}
	defer db.Close()

	str, err := New(
		db,
		Config{},
		[]byte("secret-key"),
	)
	if err != nil {
		t.Error(err.Error())
	}

	req, err := http.NewRequest("GET", "http://localhost:3000/", nil)
	if err != nil {
		t.Error(err.Error())
	}

	session, err := str.Get(req, "test")
	if err != nil {
		t.Error(err.Error())
	}

	session.Options.MaxAge = -1

	w := httptest.NewRecorder()

	str.Save(req, w, session)

	// When session.Options.MaxAge >= 0
	session, err = str.Get(req, "test")
	if err != nil {
		t.Error(err.Error())
	}

	session.Options.MaxAge = 1

	w = httptest.NewRecorder()

	str.Save(req, w, session)

	// When session.Options.MaxAge >= 0 and
	// s.save returns an error
	session.Values = make(map[interface{}]interface{})
	session.Values[make(chan int)] = make(chan int)
	str.Save(req, w, session)

	// When session.Options.MaxAge >= 0 and
	// securecookie.EncodeMulti  returns an error
	session.Values = make(map[interface{}]interface{})
	str.codecs = nil
	str.Save(req, w, session)
}

func TestStore_load(t *testing.T) {
	db, err := bolt.Open("./sessions.db", 0666)
	if err != nil {
		t.Error(err.Error())
	}
	defer db.Close()

	str, err := New(
		db,
		Config{},
		[]byte("secret-key"),
	)
	if err != nil {
		t.Error(err.Error())
	}

	req, err := http.NewRequest("GET", "http://localhost:3000/", nil)
	if err != nil {
		t.Error(err.Error())
	}

	session, err := str.Get(req, "test")
	if err != nil {
		t.Error(err.Error())
	}

	w := httptest.NewRecorder()

	str.Save(req, w, session)

	exists, err := str.load(session)
	if err != nil {
		t.Error(err.Error())
	}

	if exists != true {
		t.Error("Store.load should return true (actual: false)")
	}

	// When the target session data is nil
	session.ID = "x"
	exists, err = str.load(session)
	if err != nil {
		t.Error(err.Error())
	}

	if exists != false {
		t.Error("Store.load should return false (actual: true)")
	}

	// When shared.Session returns an error
	err = db.Update(func(tx *bolt.Tx) error {
		return tx.Bucket(str.config.DBOptions.BucketName).Put([]byte("x"), []byte("test"))
	})
	if err != nil {
		t.Error(err.Error())
	}
	_, err = str.load(session)
	if err == nil || err.Error() != "proto: field/encoding mismatch: wrong type for field" {
		t.Error(`str.load should return an error "%s" (actual: %s)`, "proto: field/encoding mismatch: wrong type for field", err)
	}

	// When the target session data is expired
	session, err = str.Get(req, "test")
	if err != nil {
		t.Error(err.Error())
	}
	session.Options.MaxAge = 0
	str.Save(req, w, session)
	time.Sleep(time.Second)
	_, err = str.load(session)
	if err != nil {
		t.Error(err.Error())
	}
}

func TestSession_delete(t *testing.T) {
	db, err := bolt.Open("./sessions.db", 0666)
	if err != nil {
		t.Error(err.Error())
	}
	defer db.Close()

	str, err := New(
		db,
		Config{},
		[]byte("secret-key"),
	)
	if err != nil {
		t.Error(err.Error())
	}

	req, err := http.NewRequest("GET", "http://localhost:3000/", nil)
	if err != nil {
		t.Error(err.Error())
	}

	session, err := str.Get(req, "test")
	if err != nil {
		t.Error(err.Error())
	}

	db.Close()

	err = str.delete(session)

	if err.Error() != "database not open" {
		t.Error(`str.delete should return an error "%s" (actual: %s)`, "database not open", err)
	}
}

func TestNew(t *testing.T) {
	// When db.Update returns an error
	db, err := bolt.Open("./sessions.db", 0666)
	if err != nil {
		t.Error(err.Error())
	}
	db.Close()

	_, err = New(
		db,
		Config{},
		[]byte("secret-key"),
	)
	if err.Error() != "database not open" {
		t.Error(`str.delete  should return an error "%s" (actual: %s)`, "database not open", err)
	}
}

func ExampleStore_Get() {
	// db(*bolt.DB) should be opened beforehand and passed by the other function.
	var db *bolt.DB

	// r(*http.Request) should be passed by the other function.
	var r *http.Request

	// Create a store.
	str, err := New(db, Config{}, []byte("secret-key"))
	if err != nil {
		panic(err)
	}

	// Get a session.
	session, err := str.Get(r, "session-key")
	if err != nil {
		panic(err)
	}

	// Add a value on the session.
	session.Values["foo"] = "bar"
}

func ExampleStore_New() {
	// db(*bolt.DB) should be opened beforehand and passed by the other function.
	var db *bolt.DB

	// r(*http.Request) should be passed by the other function.
	var r *http.Request

	// Create a store.
	str, err := New(db, Config{}, []byte("secret-key"))
	if err != nil {
		panic(err)
	}

	// Create a session.
	session, err := str.New(r, "session-key")
	if err != nil {
		panic(err)
	}

	// Add a value on the session.
	session.Values["foo"] = "bar"
}

func ExampleStore_Save() {
	// db(*bolt.DB) should be opened beforehand and passed by the other function.
	var db *bolt.DB

	// w(http.ResponseWriter) should be passed by the other function.
	var w http.ResponseWriter

	// r(*http.Request) should be passed by the other function.
	var r *http.Request

	// Create a store.
	str, err := New(db, Config{}, []byte("secret-key"))
	if err != nil {
		panic(err)
	}

	// Create a session.
	session, err := str.New(r, "session-key")
	if err != nil {
		panic(err)
	}

	// Add a value on the session.
	session.Values["foo"] = "bar"

	// Save the session.
	if err := sessions.Save(r, w); err != nil {
		panic(err)
	}

	// You can delete the session by setting the session options's MaxAge
	// to a minus value
	session.Options.MaxAge = -1
	if err := sessions.Save(r, w); err != nil {
		panic(err)
	}
}

func ExampleNew() {
	// db should be opened beforehand and passed by the other function.
	var db *bolt.DB

	// r(*http.Request) should be passed by the other function.
	var r *http.Request

	// Create a store.
	str, err := New(db, Config{}, []byte("secret-key"))
	if err != nil {
		panic(err)
	}

	// Get a session.
	session, err := str.Get(r, "session-key")
	if err != nil {
		panic(err)
	}

	// Add a value on the session.
	session.Values["foo"] = "bar"
}
<|endoftext|>