<commit_before>package zk

import (
	"errors"
	"fmt"
	"log"
	"path"
	"strings"
	"sync"
	"time"

	"github.com/samuel/go-zookeeper/zk"
)

const (
	coordinationVersion = "v1"
	defaultZKPort       = 2181
	maxCreateRetries    = 5
)

var defaultZkACL = zk.WorldACL(zk.PermAll)

// A Watcher manages a single connection to zookeeper, watching for changes to
// directories and managing ephemeral nodes. It lazily connects and reconnects
// to zookeeper, and tries its best to be resilient to failures, but defaults to
// silently not providing updates.
type Watcher struct {
	lock           sync.RWMutex
	zkServers      []string
	connectTimeout time.Duration
	sessionTimeout time.Duration
	prefix         string
	conn           *zk.Conn
	errs           chan error
	shutdown       chan bool

	hooksLock      sync.Mutex
	ephemeralNodes map[string]bool
	watchedNodes   map[string]watchedNode
}

type watchedNode struct {
	updates      chan []string
	disconnected chan bool
	cancel       chan bool
}

// Connect connects to the zookeeper cluster specified by zkServers, and returns
// a Watcher. All future operations on the Watcher are rooted at the given
// prefix.
func Connect(zkServers []string, prefix string, connectTimeout, sessionTimeout time.Duration) (*Watcher, error) {
	w := &Watcher{
		zkServers:      zkServers,
		connectTimeout: connectTimeout,
		sessionTimeout: sessionTimeout,
		prefix:         path.Join(prefix, coordinationVersion),
		errs:           make(chan error, 1),
		shutdown:       make(chan bool),
		ephemeralNodes: make(map[string]bool),
		watchedNodes:   make(map[string]watchedNode),
	}

	err := w.reconnect()
	if err != nil {
		return nil, fmt.Errorf("Zookeeper error: %s", err)
	}

	go w.run()
	return w, nil
}

func (w *Watcher) reconnect() error {
	var conn *zk.Conn
	var events <-chan zk.Event
	var err error

	for i, s := range w.zkServers {
		if strings.Index(s, ":") < 0 {
			w.zkServers[i] = fmt.Sprintf("%s:%d", s, defaultZKPort)
		}
	}

	w.lock.Lock()
	defer w.lock.Unlock()

	servers := strings.Join(w.zkServers, ",")
	log.Println("Connecting to zookeeper at", servers)
	conn, events, err = zk.Connect(w.zkServers, w.sessionTimeout)
	if err != nil {
		return err
	}

	if w.conn != nil {
		w.conn.Close()
	}
	w.conn = conn

	connectTimeout := time.NewTimer(w.connectTimeout)
	select {
	case <-connectTimeout.C:
		return errors.New("connection timeout")
	case event := <-events:
		if event.State != zk.StateConnected && event.State != zk.StateConnecting {
			return fmt.Errorf("connection error: %s", event)
		}
	}

	err = w.createAll(w.prefix)
	if err != nil {
		return fmt.Errorf("creating base path: %s", err)
	}

	go func() {
		for ev := range events {
			if ev.State != zk.StateConnected && ev.State != zk.StateConnecting {
				if ev.Err != nil {
					sendErr(w.errs, ev.Err)
					return
				}
			}
		}
	}()

	return nil
}

func (w *Watcher) runHooks() error {
	w.hooksLock.Lock()
	defer w.hooksLock.Unlock()

	for node := range w.ephemeralNodes {
		err := w.createEphemeral(node)
		if err != nil {
			return err
		}
	}

	for node, wn := range w.watchedNodes {
		err := w.watchChildren(node, wn)
		if err != nil {
			return err
		}
	}

	return nil
}

func (w *Watcher) notifyDisconnected() {
	for _, wn := range w.watchedNodes {
		select {
		case wn.disconnected <- true:
		default:
		}
	}
}

func (w *Watcher) cancelWatches() {
	w.hooksLock.Lock()
	defer w.hooksLock.Unlock()

	w.notifyDisconnected()

	for _, wn := range w.watchedNodes {
		wn.cancel <- true
	}
}

// run runs the main loop. On any errors, it resets the connection.
func (w *Watcher) run() {
	first := true

Reconnect:
	for {
		if !first {
			// Wait before trying to reconnect again.
			wait := time.NewTimer(w.sessionTimeout)
			select {
			case <-w.shutdown:
				break Reconnect
			case <-wait.C:
			}

			err := w.reconnect()
			if err != nil {
				log.Println("Error reconnecting to zookeeper:", err)
				continue Reconnect
			}

			// Every time we connect, reset watches and recreate ephemeral nodes.
			err = w.runHooks()
			if err != nil {
				log.Println("Error running zookeeper hooks:", err)
				continue Reconnect
			}
		} else {
			first = false
		}

		select {
		case <-w.shutdown:
			break Reconnect
		case err := <-w.errs:
			log.Println("Disconnecting from zookeeper because of error:", err)
			w.cancelWatches()
			continue Reconnect
		}
	}

	w.cancelWatches()
}

// CreateEphemeral creates an ephemeral node on the zookeeper cluster. Any
// needed parent nodes are also created, as permanent nodes. The ephemeral is
// then recreated any time the Watcher reconnects.
func (w *Watcher) CreateEphemeral(node string) {
	w.hooksLock.Lock()
	defer w.hooksLock.Unlock()

	node = path.Join(w.prefix, node)
	w.ephemeralNodes[node] = true
	err := w.createEphemeral(node)
	if err != nil {
		sendErr(w.errs, err)
	}
}

func (w *Watcher) createEphemeral(node string) error {
	w.lock.RLock()
	defer w.lock.RUnlock()

	// Retry a few times, in case the node is removed in between the two following
	// steps.
	for i := 0; i < maxCreateRetries; i++ {
		_, err := w.conn.Create(node, []byte{}, zk.FlagEphemeral, defaultZkACL)
		if err == nil {
			break
		} else if err != nil && !isNoNode(err) {
			return err
		}

		// Create the parent nodes.
		parent, _ := path.Split(node)
		err = w.createAll(parent)
		if err != nil {
			return fmt.Errorf("create %s: %s", node, err)
		}
	}

	return nil
}

// RemoveEphemeral removes the given ephemeral node from the zookeeper cluster.
// The node is then no longer recreated when the Watcher reconnects.
func (w *Watcher) RemoveEphemeral(node string) {
	w.hooksLock.Lock()
	defer w.hooksLock.Unlock()

	w.lock.RLock()
	defer w.lock.RUnlock()

	node = path.Join(w.prefix, node)
	w.conn.Delete(node, -1)
	delete(w.ephemeralNodes, node)
}

// WatchChildren sets up a watch on the given node, and returns two channels.
// The first is updated whenever the list of children for the node changes; the
// second, whenever the cluster is disconnected. The watch persists through
// reconnects. Before the watch is set, WatchChildren creates the node and any
// parents.
func (w *Watcher) WatchChildren(node string) (chan []string, chan bool) {
	w.hooksLock.Lock()
	defer w.hooksLock.Unlock()

	node = path.Join(w.prefix, node)
	updates := make(chan []string)
	disconnected := make(chan bool)
	cancel := make(chan bool)

	wn := watchedNode{updates: updates, disconnected: disconnected, cancel: cancel}
	w.watchedNodes[node] = wn
	err := w.watchChildren(node, wn)
	if err != nil {
		sendErr(w.errs, err)
		go func() {
			<-cancel
		}()
	}

	return updates, disconnected
}

func (w *Watcher) watchChildren(node string, wn watchedNode) error {
	w.lock.RLock()
	defer w.lock.RUnlock()

	children, _, events, err := w.childrenW(node)
	if err != nil {
		return err
	}

	go func() {
		// Normally, a watchChildren loop closes just so it can be reestablished
		// once we're reconnected to zookeeper. In that case wn.cancel just gets an
		// update, rather than being closed. If wn.cancel is closed, then
		// reconnecting gets set to false below, and we also close wn.updates and
		// wn.disconnected on our way out.
		reconnecting := true
		defer func() {
			if !reconnecting {
				close(wn.updates)
				close(wn.disconnected)
			}
		}()

		for {
			select {
			case reconnecting = <-wn.cancel:
				return
			case wn.updates <- children:
			}

			select {
			case reconnecting = <-wn.cancel:
				return
			case ev := <-events:

				if ev.Err != nil {
					sendErr(w.errs, ev.Err)
					<-wn.cancel
					return
				}
			}

			w.lock.RLock()
			children, _, events, err = w.childrenW(node)
			w.lock.RUnlock()

			if err != nil {
				sendErr(w.errs, err)
				reconnecting = <-wn.cancel
				return
			}
		}
	}()

	return nil
}

func (w *Watcher) childrenW(node string) (children []string, stat *zk.Stat, events <-chan zk.Event, err error) {
	// Retry a few times, in case the node is removed in between the two following
	// steps.
	for i := 0; i < maxCreateRetries; i++ {
		children, stat, events, err = w.conn.ChildrenW(node)
		if !isNoNode(err) {
			return
		}

		// Create the node so we can watch it.
		err = w.createAll(node)
		if err != nil {
			err = fmt.Errorf("create %s: %s", node, err)
			return
		}
	}

	return
}

// RemoveWatch removes a watch previously set by WatchChildren.
func (w *Watcher) RemoveWatch(node string) {
	w.hooksLock.Lock()
	defer w.hooksLock.Unlock()

	node = path.Join(w.prefix, node)
	if wn, ok := w.watchedNodes[node]; ok {
		delete(w.watchedNodes, node)
		close(wn.cancel)
	}
}

func (w *Watcher) createAll(node string) error {
	base, _ := path.Split(path.Clean(node))
	if base != "" && base != "/" {
		err := w.createAll(base)
		if err != nil {
			return err
		}
	}

	_, err := w.conn.Create(path.Clean(node), []byte{}, 0, defaultZkACL)
	if err != nil && !isNodeExists(err) {
		return err
	}

	return nil
}

// TriggerCleanup walks the prefix and deletes any non-ephemeral, empty
// nodes under it. It ignores any errors encountered.
func (w *Watcher) TriggerCleanup() {
	w.lock.RLock()
	defer w.lock.RUnlock()

	w.cleanupTree(w.prefix)
}

func (w *Watcher) cleanupTree(node string) {
	children, stat, err := w.conn.Children(node)
	if err != nil {
		return
	} else if stat.EphemeralOwner != 0 {
		return
	}

	for _, child := range children {
		w.cleanupTree(path.Join(node, child))
	}

	w.conn.Delete(node, -1)
}

func (w *Watcher) Close() {
	w.lock.Lock()
	defer w.lock.Unlock()

	w.shutdown <- true
	w.conn.Close()
}

// sendErr sends the error over the channel, or discards it if the error is full.
func sendErr(errs chan error, err error) {
	log.Println("Zookeeper error:", err)

	select {
	case errs <- err:
	default:
	}
}

func isNodeExists(err error) bool {
	if err == zk.ErrNodeExists {

		return true
	}

	return false
}

func isNoNode(err error) bool {

	if err == zk.ErrNoNode {
		return true
	}

	return false
}
<commit_msg>Improve zookeeper error logging.<commit_after>package zk

import (
	"errors"
	"fmt"
	"log"
	"path"
	"strings"
	"sync"
	"time"

	"github.com/samuel/go-zookeeper/zk"
)

const (
	coordinationVersion = "v1"
	defaultZKPort       = 2181
	maxCreateRetries    = 5
)

var defaultZkACL = zk.WorldACL(zk.PermAll)

// A Watcher manages a single connection to zookeeper, watching for changes to
// directories and managing ephemeral nodes. It lazily connects and reconnects
// to zookeeper, and tries its best to be resilient to failures, but defaults to
// silently not providing updates.
type Watcher struct {
	lock           sync.RWMutex
	zkServers      []string
	connectTimeout time.Duration
	sessionTimeout time.Duration
	prefix         string
	conn           *zk.Conn
	errs           chan error
	shutdown       chan bool

	hooksLock      sync.Mutex
	ephemeralNodes map[string]bool
	watchedNodes   map[string]watchedNode
}

type watchedNode struct {
	updates      chan []string
	disconnected chan bool
	cancel       chan bool
}

// Connect connects to the zookeeper cluster specified by zkServers, and returns
// a Watcher. All future operations on the Watcher are rooted at the given
// prefix.
func Connect(zkServers []string, prefix string, connectTimeout, sessionTimeout time.Duration) (*Watcher, error) {
	w := &Watcher{
		zkServers:      zkServers,
		connectTimeout: connectTimeout,
		sessionTimeout: sessionTimeout,
		prefix:         path.Join(prefix, coordinationVersion),
		errs:           make(chan error, 1),
		shutdown:       make(chan bool),
		ephemeralNodes: make(map[string]bool),
		watchedNodes:   make(map[string]watchedNode),
	}

	err := w.reconnect()
	if err != nil {
		return nil, fmt.Errorf("Zookeeper error: %s", err)
	}

	go w.run()
	return w, nil
}

func (w *Watcher) reconnect() error {
	var conn *zk.Conn
	var events <-chan zk.Event
	var err error

	for i, s := range w.zkServers {
		if strings.Index(s, ":") < 0 {
			w.zkServers[i] = fmt.Sprintf("%s:%d", s, defaultZKPort)
		}
	}

	w.lock.Lock()
	defer w.lock.Unlock()

	servers := strings.Join(w.zkServers, ",")
	log.Println("Connecting to zookeeper at", servers)
	conn, events, err = zk.Connect(w.zkServers, w.sessionTimeout)
	if err != nil {
		return err
	}

	if w.conn != nil {
		w.conn.Close()
	}
	w.conn = conn

	connectTimeout := time.NewTimer(w.connectTimeout)
	select {
	case <-connectTimeout.C:
		return errors.New("connection timeout")
	case event := <-events:
		if event.State != zk.StateConnected && event.State != zk.StateConnecting {
			return fmt.Errorf("connection error: %s", event)
		}
	}

	err = w.createAll(w.prefix)
	if err != nil {
		return fmt.Errorf("creating base path: %s", err)
	}

	go func() {
		for ev := range events {
			if ev.State != zk.StateConnected && ev.State != zk.StateConnecting {
				if ev.Err != nil {
					sendErr(w.errs, ev.Err, ev.Path, ev.Server)
					return
				}
			}
		}
	}()

	return nil
}

func (w *Watcher) runHooks() error {
	w.hooksLock.Lock()
	defer w.hooksLock.Unlock()

	for node := range w.ephemeralNodes {
		err := w.createEphemeral(node)
		if err != nil {
			return err
		}
	}

	for node, wn := range w.watchedNodes {
		err := w.watchChildren(node, wn)
		if err != nil {
			return err
		}
	}

	return nil
}

func (w *Watcher) notifyDisconnected() {
	for _, wn := range w.watchedNodes {
		select {
		case wn.disconnected <- true:
		default:
		}
	}
}

func (w *Watcher) cancelWatches() {
	w.hooksLock.Lock()
	defer w.hooksLock.Unlock()

	w.notifyDisconnected()

	for _, wn := range w.watchedNodes {
		wn.cancel <- true
	}
}

// run runs the main loop. On any errors, it resets the connection.
func (w *Watcher) run() {
	first := true

Reconnect:
	for {
		if !first {
			// Wait before trying to reconnect again.
			wait := time.NewTimer(w.sessionTimeout)
			select {
			case <-w.shutdown:
				break Reconnect
			case <-wait.C:
			}

			err := w.reconnect()
			if err != nil {
				log.Println("Error reconnecting to zookeeper:", err)
				continue Reconnect
			}

			// Every time we connect, reset watches and recreate ephemeral nodes.
			err = w.runHooks()
			if err != nil {
				log.Println("Error running zookeeper hooks:", err)
				continue Reconnect
			}
		} else {
			first = false
		}

		select {
		case <-w.shutdown:
			break Reconnect
		case err := <-w.errs:
			log.Println("Disconnecting from zookeeper because of error:", err)
			w.cancelWatches()
			continue Reconnect
		}
	}

	w.cancelWatches()
}

// CreateEphemeral creates an ephemeral node on the zookeeper cluster. Any
// needed parent nodes are also created, as permanent nodes. The ephemeral is
// then recreated any time the Watcher reconnects.
func (w *Watcher) CreateEphemeral(node string) {
	w.hooksLock.Lock()
	defer w.hooksLock.Unlock()

	node = path.Join(w.prefix, node)
	w.ephemeralNodes[node] = true
	err := w.createEphemeral(node)
	if err != nil {
		sendErr(w.errs, err, node, "")
	}
}

func (w *Watcher) createEphemeral(node string) error {
	w.lock.RLock()
	defer w.lock.RUnlock()

	// Retry a few times, in case the node is removed in between the two following
	// steps.
	for i := 0; i < maxCreateRetries; i++ {
		_, err := w.conn.Create(node, []byte{}, zk.FlagEphemeral, defaultZkACL)
		if err == nil {
			break
		} else if err != nil && !isNoNode(err) {
			return err
		}

		// Create the parent nodes.
		parent, _ := path.Split(node)
		err = w.createAll(parent)
		if err != nil {
			return fmt.Errorf("create %s: %s", node, err)
		}
	}

	return nil
}

// RemoveEphemeral removes the given ephemeral node from the zookeeper cluster.
// The node is then no longer recreated when the Watcher reconnects.
func (w *Watcher) RemoveEphemeral(node string) {
	w.hooksLock.Lock()
	defer w.hooksLock.Unlock()

	w.lock.RLock()
	defer w.lock.RUnlock()

	node = path.Join(w.prefix, node)
	w.conn.Delete(node, -1)
	delete(w.ephemeralNodes, node)
}

// WatchChildren sets up a watch on the given node, and returns two channels.
// The first is updated whenever the list of children for the node changes; the
// second, whenever the cluster is disconnected. The watch persists through
// reconnects. Before the watch is set, WatchChildren creates the node and any
// parents.
func (w *Watcher) WatchChildren(node string) (chan []string, chan bool) {
	w.hooksLock.Lock()
	defer w.hooksLock.Unlock()

	node = path.Join(w.prefix, node)
	updates := make(chan []string)
	disconnected := make(chan bool)
	cancel := make(chan bool)

	wn := watchedNode{updates: updates, disconnected: disconnected, cancel: cancel}
	w.watchedNodes[node] = wn
	err := w.watchChildren(node, wn)
	if err != nil {
		sendErr(w.errs, err, node, "")
		go func() {
			<-cancel
		}()
	}

	return updates, disconnected
}

func (w *Watcher) watchChildren(node string, wn watchedNode) error {
	w.lock.RLock()
	defer w.lock.RUnlock()

	children, _, events, err := w.childrenW(node)
	if err != nil {
		return err
	}

	go func() {
		// Normally, a watchChildren loop closes just so it can be reestablished
		// once we're reconnected to zookeeper. In that case wn.cancel just gets an
		// update, rather than being closed. If wn.cancel is closed, then
		// reconnecting gets set to false below, and we also close wn.updates and
		// wn.disconnected on our way out.
		reconnecting := true
		defer func() {
			if !reconnecting {
				close(wn.updates)
				close(wn.disconnected)
			}
		}()

		for {
			select {
			case reconnecting = <-wn.cancel:
				return
			case wn.updates <- children:
			}

			select {
			case reconnecting = <-wn.cancel:
				return
			case ev := <-events:

				if ev.Err != nil {
					sendErr(w.errs, ev.Err, ev.Path, ev.Server)
					<-wn.cancel
					return
				}
			}

			w.lock.RLock()
			children, _, events, err = w.childrenW(node)
			w.lock.RUnlock()

			if err != nil {
				sendErr(w.errs, err, node, "")
				reconnecting = <-wn.cancel
				return
			}
		}
	}()

	return nil
}

func (w *Watcher) childrenW(node string) (children []string, stat *zk.Stat, events <-chan zk.Event, err error) {
	// Retry a few times, in case the node is removed in between the two following
	// steps.
	for i := 0; i < maxCreateRetries; i++ {
		children, stat, events, err = w.conn.ChildrenW(node)
		if !isNoNode(err) {
			return
		}

		// Create the node so we can watch it.
		err = w.createAll(node)
		if err != nil {
			err = fmt.Errorf("create %s: %s", node, err)
			return
		}
	}

	return
}

// RemoveWatch removes a watch previously set by WatchChildren.
func (w *Watcher) RemoveWatch(node string) {
	w.hooksLock.Lock()
	defer w.hooksLock.Unlock()

	node = path.Join(w.prefix, node)
	if wn, ok := w.watchedNodes[node]; ok {
		delete(w.watchedNodes, node)
		close(wn.cancel)
	}
}

func (w *Watcher) createAll(node string) error {
	base, _ := path.Split(path.Clean(node))
	if base != "" && base != "/" {
		err := w.createAll(base)
		if err != nil {
			return err
		}
	}

	_, err := w.conn.Create(path.Clean(node), []byte{}, 0, defaultZkACL)
	if err != nil && !isNodeExists(err) {
		return err
	}

	return nil
}

// TriggerCleanup walks the prefix and deletes any non-ephemeral, empty
// nodes under it. It ignores any errors encountered.
func (w *Watcher) TriggerCleanup() {
	w.lock.RLock()
	defer w.lock.RUnlock()

	w.cleanupTree(w.prefix)
}

func (w *Watcher) cleanupTree(node string) {
	children, stat, err := w.conn.Children(node)
	if err != nil {
		return
	} else if stat.EphemeralOwner != 0 {
		return
	}

	for _, child := range children {
		w.cleanupTree(path.Join(node, child))
	}

	w.conn.Delete(node, -1)
}

func (w *Watcher) Close() {
	w.lock.Lock()
	defer w.lock.Unlock()

	w.shutdown <- true
	w.conn.Close()
}

// sendErr sends the error over the channel, or discards it if the error is full.
func sendErr(errs chan error, err error, path string, server string) {
	log.Println("Zookeeper error: err=%q, path=%q, server=%q", err, path, server)

	select {
	case errs <- err:
	default:
	}
}

func isNodeExists(err error) bool {
	if err == zk.ErrNodeExists {

		return true
	}

	return false
}

func isNoNode(err error) bool {

	if err == zk.ErrNoNode {
		return true
	}

	return false
}
<|endoftext|>


<commit_before>package gophercloud

import (
	"github.com/racker/perigee"
)

// See the CloudImagesProvider interface for details.
func (gsp *genericServersProvider) ListKeyPairs() ([]KeyPair, error) {
	type KeyPairs struct {
		KeyPairs []struct {
			KeyPair KeyPair `json:"keypair"`
		} `json:"keypairs"`
	}

	var kp KeyPairs

	err := gsp.context.WithReauth(gsp.access, func() error {
		url := gsp.endpoint + "/os-keypairs"
		return perigee.Get(url, perigee.Options{
			CustomClient: gsp.context.httpClient,
			Results:      &kp,
			MoreHeaders: map[string]string{
				"X-Auth-Token": gsp.access.AuthToken(),
			},
		})
	})

	// Flatten out the list of keypairs
	var keypairs []KeyPair
	for _, k := range kp.KeyPairs {
		keypairs = append(keypairs, k.KeyPair)
	}
	return keypairs, err
}

func (gsp *genericServersProvider) CreateKeyPair(nkp NewKeyPair) (KeyPair, error) {
	var kp KeyPair

	err := gsp.context.WithReauth(gsp.access, func() error {
		url := gsp.endpoint + "/os-keypairs"
		return perigee.Post(url, perigee.Options{
			ReqBody: &struct {
				KeyPair *NewKeyPair `json:"keypair"`
			}{&nkp},
			CustomClient: gsp.context.httpClient,
			Results:      &struct{ KeyPair *KeyPair }{&kp},
			MoreHeaders: map[string]string{
				"X-Auth-Token": gsp.access.AuthToken(),
			},
		})
	})
	return kp, err
}

// See the CloudImagesProvider interface for details.
func (gsp *genericServersProvider) DeleteKeyPair(name string) error {
	err := gsp.context.WithReauth(gsp.access, func() error {
		url := gsp.endpoint + "/os-keypairs/" + name
		return perigee.Delete(url, perigee.Options{
			CustomClient: gsp.context.httpClient,
			MoreHeaders: map[string]string{
				"X-Auth-Token": gsp.access.AuthToken(),
			},
			OkCodes: []int{202},
		})
	})
	return err
}

func (gsp *genericServersProvider) ShowKeyPair(name string) (KeyPair, error) {
	var kp KeyPair

	err := gsp.context.WithReauth(gsp.access, func() error {
		url := gsp.endpoint + "/os-keypairs/" + name
		return perigee.Get(url, perigee.Options{
			CustomClient: gsp.context.httpClient,
			Results:      &struct{ KeyPair *KeyPair }{&kp},
			MoreHeaders: map[string]string{
				"X-Auth-Token": gsp.access.AuthToken(),
			},
		})
	})
	return kp, err
}

type KeyPair struct {
	FingerPrint string `json:"fingerprint"`
	Name        string `json:"name"`
	PublicKey   string `json:"public_key"`
	UserID      string `json:"user_id,omitempty"`
}

type NewKeyPair struct {
	Name      string `json:"name"`
	PublicKey string `json:"public_key,omitempty"`
}
<commit_msg>Add PrivateKey field to KeyPair for returning from CreateKeyPair<commit_after>package gophercloud

import (
	"github.com/racker/perigee"
)

// See the CloudImagesProvider interface for details.
func (gsp *genericServersProvider) ListKeyPairs() ([]KeyPair, error) {
	type KeyPairs struct {
		KeyPairs []struct {
			KeyPair KeyPair `json:"keypair"`
		} `json:"keypairs"`
	}

	var kp KeyPairs

	err := gsp.context.WithReauth(gsp.access, func() error {
		url := gsp.endpoint + "/os-keypairs"
		return perigee.Get(url, perigee.Options{
			CustomClient: gsp.context.httpClient,
			Results:      &kp,
			MoreHeaders: map[string]string{
				"X-Auth-Token": gsp.access.AuthToken(),
			},
		})
	})

	// Flatten out the list of keypairs
	var keypairs []KeyPair
	for _, k := range kp.KeyPairs {
		keypairs = append(keypairs, k.KeyPair)
	}
	return keypairs, err
}

func (gsp *genericServersProvider) CreateKeyPair(nkp NewKeyPair) (KeyPair, error) {
	var kp KeyPair

	err := gsp.context.WithReauth(gsp.access, func() error {
		url := gsp.endpoint + "/os-keypairs"
		return perigee.Post(url, perigee.Options{
			ReqBody: &struct {
				KeyPair *NewKeyPair `json:"keypair"`
			}{&nkp},
			CustomClient: gsp.context.httpClient,
			Results:      &struct{ KeyPair *KeyPair }{&kp},
			MoreHeaders: map[string]string{
				"X-Auth-Token": gsp.access.AuthToken(),
			},
		})
	})
	return kp, err
}

// See the CloudImagesProvider interface for details.
func (gsp *genericServersProvider) DeleteKeyPair(name string) error {
	err := gsp.context.WithReauth(gsp.access, func() error {
		url := gsp.endpoint + "/os-keypairs/" + name
		return perigee.Delete(url, perigee.Options{
			CustomClient: gsp.context.httpClient,
			MoreHeaders: map[string]string{
				"X-Auth-Token": gsp.access.AuthToken(),
			},
			OkCodes: []int{202},
		})
	})
	return err
}

func (gsp *genericServersProvider) ShowKeyPair(name string) (KeyPair, error) {
	var kp KeyPair

	err := gsp.context.WithReauth(gsp.access, func() error {
		url := gsp.endpoint + "/os-keypairs/" + name
		return perigee.Get(url, perigee.Options{
			CustomClient: gsp.context.httpClient,
			Results:      &struct{ KeyPair *KeyPair }{&kp},
			MoreHeaders: map[string]string{
				"X-Auth-Token": gsp.access.AuthToken(),
			},
		})
	})
	return kp, err
}

type KeyPair struct {
	FingerPrint string `json:"fingerprint"`
	Name        string `json:"name"`
	PrivateKey  string `json:"private_key,omitempty"`
	PublicKey   string `json:"public_key"`
	UserID      string `json:"user_id,omitempty"`
}

type NewKeyPair struct {
	Name      string `json:"name"`
	PublicKey string `json:"public_key,omitempty"`
}
<|endoftext|>


<commit_before>package mtree

import (
	"crypto/md5"
	"crypto/sha1"
	"crypto/sha256"
	"crypto/sha512"
	"fmt"
	"hash"
	"io"
	"os"
	"strings"

	"golang.org/x/crypto/ripemd160"
)

// KeywordFunc is the type of a function called on each file to be included in
// a DirectoryHierarchy, that will produce the string output of the keyword to
// be included for the file entry. Otherwise, empty string.
type KeywordFunc func(path string, info os.FileInfo) (string, error)

// KeyVal is a "keyword=value"
type KeyVal string

// Keyword is the mapping to the available keywords
func (kv KeyVal) Keyword() string {
	if !strings.Contains(string(kv), "=") {
		return ""
	}
	chunks := strings.SplitN(strings.TrimSpace(string(kv)), "=", 2)[0]
	if !strings.Contains(chunks, ".") {
		return chunks
	}
	return strings.SplitN(chunks, ".", 2)[0]
}

// KeywordSuffix is really only used for xattr, as the keyword is a prefix to
// the xattr "namespace.key"
func (kv KeyVal) KeywordSuffix() string {
	if !strings.Contains(string(kv), "=") {
		return ""
	}
	chunks := strings.SplitN(strings.TrimSpace(string(kv)), "=", 2)[0]
	if !strings.Contains(chunks, ".") {
		return ""
	}
	return strings.SplitN(chunks, ".", 2)[1]
}

// Value is the data/value portion of "keyword=value"
func (kv KeyVal) Value() string {
	if !strings.Contains(string(kv), "=") {
		return ""
	}
	return strings.SplitN(strings.TrimSpace(string(kv)), "=", 2)[1]
}

// keywordSelector takes an array of "keyword=value" and filters out that only the set of words
func keywordSelector(keyval, words []string) []string {
	retList := []string{}
	for _, kv := range keyval {
		if inSlice(KeyVal(kv).Keyword(), words) {
			retList = append(retList, kv)
		}
	}
	return retList
}

// NewKeyVals constructs a list of KeyVal from the list of strings, like "keyword=value"
func NewKeyVals(keyvals []string) KeyVals {
	kvs := make(KeyVals, len(keyvals))
	for i := range keyvals {
		kvs[i] = KeyVal(keyvals[i])
	}
	return kvs
}

// KeyVals is a list of KeyVal
type KeyVals []KeyVal

// Has the "keyword" present in the list of KeyVal, and returns the
// corresponding KeyVal, else an empty string.
func (kvs KeyVals) Has(keyword string) KeyVal {
	for i := range kvs {
		if kvs[i].Keyword() == keyword {
			return kvs[i]
		}
	}
	return emptyKV
}

var emptyKV = KeyVal("")

// MergeSet takes the current setKeyVals, and then applies the entryKeyVals
// such that the entry's values win. The union is returned.
func MergeSet(setKeyVals, entryKeyVals []string) KeyVals {
	retList := NewKeyVals(append([]string{}, setKeyVals...))
	eKVs := NewKeyVals(entryKeyVals)
	seenKeywords := []string{}
	for i := range retList {
		word := retList[i].Keyword()
		if ekv := eKVs.Has(word); ekv != emptyKV {
			retList[i] = ekv
		}
		seenKeywords = append(seenKeywords, word)
	}
	for i := range eKVs {
		if !inSlice(eKVs[i].Keyword(), seenKeywords) {
			retList = append(retList, eKVs[i])
		}
	}
	return retList
}

var (
	// DefaultKeywords has the several default keyword producers (uid, gid,
	// mode, nlink, type, size, mtime)
	DefaultKeywords = []string{
		"size",
		"type",
		"uid",
		"gid",
		"mode",
		"link",
		"nlink",
		"time",
	}
	// SetKeywords is the default set of keywords calculated for a `/set` SpecialType
	SetKeywords = []string{
		"uid",
		"gid",
	}
	// KeywordFuncs is the map of all keywords (and the functions to produce them)
	KeywordFuncs = map[string]KeywordFunc{
		"size":            sizeKeywordFunc,                                     // The size, in bytes, of the file
		"type":            typeKeywordFunc,                                     // The type of the file
		"time":            timeKeywordFunc,                                     // The last modification time of the file
		"link":            linkKeywordFunc,                                     // The target of the symbolic link when type=link
		"uid":             uidKeywordFunc,                                      // The file owner as a numeric value
		"gid":             gidKeywordFunc,                                      // The file group as a numeric value
		"nlink":           nlinkKeywordFunc,                                    // The number of hard links the file is expected to have
		"uname":           unameKeywordFunc,                                    // The file owner as a symbolic name
		"mode":            modeKeywordFunc,                                     // The current file's permissions as a numeric (octal) or symbolic value
		"cksum":           cksumKeywordFunc,                                    // The checksum of the file using the default algorithm specified by the cksum(1) utility
		"md5":             hasherKeywordFunc("md5", md5.New),                   // The MD5 message digest of the file
		"md5digest":       hasherKeywordFunc("md5digest", md5.New),             // A synonym for `md5`
		"rmd160":          hasherKeywordFunc("rmd160", ripemd160.New),          // The RIPEMD160 message digest of the file
		"rmd160digest":    hasherKeywordFunc("rmd160digest", ripemd160.New),    // A synonym for `rmd160`
		"ripemd160digest": hasherKeywordFunc("ripemd160digest", ripemd160.New), // A synonym for `rmd160`
		"sha1":            hasherKeywordFunc("sha1", sha1.New),                 // The SHA1 message digest of the file
		"sha1digest":      hasherKeywordFunc("sha1digest", sha1.New),           // A synonym for `sha1`
		"sha256":          hasherKeywordFunc("sha256", sha256.New),             // The SHA256 message digest of the file
		"sha256digest":    hasherKeywordFunc("sha256digest", sha256.New),       // A synonym for `sha256`
		"sha384":          hasherKeywordFunc("sha384", sha512.New384),          // The SHA384 message digest of the file
		"sha384digest":    hasherKeywordFunc("sha384digest", sha512.New384),    // A synonym for `sha384`
		"sha512":          hasherKeywordFunc("sha512", sha512.New),             // The SHA512 message digest of the file
		"sha512digest":    hasherKeywordFunc("sha512digest", sha512.New),       // A synonym for `sha512`

		// This is not an upstreamed keyword, but a needed attribute for file validation.
		// The pattern for this keyword key is prefixed by "xattr." followed by the extended attribute "namespace.key".
		// The keyword value is the SHA1 digest of the extended attribute's value.
		// In this way, the order of the keys does not matter, and the contents of the value is not revealed.
		"xattr": xattrKeywordFunc,
	}
)

var (
	modeKeywordFunc = func(path string, info os.FileInfo) (string, error) {
		return fmt.Sprintf("mode=%#o", info.Mode().Perm()), nil
	}
	sizeKeywordFunc = func(path string, info os.FileInfo) (string, error) {
		return fmt.Sprintf("size=%d", info.Size()), nil
	}
	cksumKeywordFunc = func(path string, info os.FileInfo) (string, error) {
		if !info.Mode().IsRegular() {
			return "", nil
		}

		fh, err := os.Open(path)
		if err != nil {
			return "", err
		}
		defer fh.Close()
		sum, _, err := cksum(fh)
		if err != nil {
			return "", err
		}
		return fmt.Sprintf("cksum=%d", sum), nil
	}
	hasherKeywordFunc = func(name string, newHash func() hash.Hash) KeywordFunc {
		return func(path string, info os.FileInfo) (string, error) {
			if !info.Mode().IsRegular() {
				return "", nil
			}

			fh, err := os.Open(path)
			if err != nil {
				return "", err
			}
			defer fh.Close()

			h := newHash()
			if _, err := io.Copy(h, fh); err != nil {
				return "", err
			}
			return fmt.Sprintf("%s=%x", name, h.Sum(nil)), nil
		}
	}
	timeKeywordFunc = func(path string, info os.FileInfo) (string, error) {
		t := info.ModTime().UnixNano()
		return fmt.Sprintf("time=%d.%d", (t / 1e9), (t % (t / 1e9))), nil
	}
	linkKeywordFunc = func(path string, info os.FileInfo) (string, error) {
		if info.Mode()&os.ModeSymlink != 0 {
			str, err := os.Readlink(path)
			if err != nil {
				return "", err
			}
			return fmt.Sprintf("link=%s", str), nil
		}
		return "", nil
	}
	typeKeywordFunc = func(path string, info os.FileInfo) (string, error) {
		if info.Mode().IsDir() {
			return "type=dir", nil
		}
		if info.Mode().IsRegular() {
			return "type=file", nil
		}
		if info.Mode()&os.ModeSocket != 0 {
			return "type=socket", nil
		}
		if info.Mode()&os.ModeSymlink != 0 {
			return "type=link", nil
		}
		if info.Mode()&os.ModeNamedPipe != 0 {
			return "type=fifo", nil
		}
		if info.Mode()&os.ModeDevice != 0 {
			if info.Mode()&os.ModeCharDevice != 0 {
				return "type=char", nil
			}
			return "type=device", nil
		}
		return "", nil
	}
)
<commit_msg>keywords: time can be 0<commit_after>package mtree

import (
	"crypto/md5"
	"crypto/sha1"
	"crypto/sha256"
	"crypto/sha512"
	"fmt"
	"hash"
	"io"
	"os"
	"strings"

	"golang.org/x/crypto/ripemd160"
)

// KeywordFunc is the type of a function called on each file to be included in
// a DirectoryHierarchy, that will produce the string output of the keyword to
// be included for the file entry. Otherwise, empty string.
type KeywordFunc func(path string, info os.FileInfo) (string, error)

// KeyVal is a "keyword=value"
type KeyVal string

// Keyword is the mapping to the available keywords
func (kv KeyVal) Keyword() string {
	if !strings.Contains(string(kv), "=") {
		return ""
	}
	chunks := strings.SplitN(strings.TrimSpace(string(kv)), "=", 2)[0]
	if !strings.Contains(chunks, ".") {
		return chunks
	}
	return strings.SplitN(chunks, ".", 2)[0]
}

// KeywordSuffix is really only used for xattr, as the keyword is a prefix to
// the xattr "namespace.key"
func (kv KeyVal) KeywordSuffix() string {
	if !strings.Contains(string(kv), "=") {
		return ""
	}
	chunks := strings.SplitN(strings.TrimSpace(string(kv)), "=", 2)[0]
	if !strings.Contains(chunks, ".") {
		return ""
	}
	return strings.SplitN(chunks, ".", 2)[1]
}

// Value is the data/value portion of "keyword=value"
func (kv KeyVal) Value() string {
	if !strings.Contains(string(kv), "=") {
		return ""
	}
	return strings.SplitN(strings.TrimSpace(string(kv)), "=", 2)[1]
}

// keywordSelector takes an array of "keyword=value" and filters out that only the set of words
func keywordSelector(keyval, words []string) []string {
	retList := []string{}
	for _, kv := range keyval {
		if inSlice(KeyVal(kv).Keyword(), words) {
			retList = append(retList, kv)
		}
	}
	return retList
}

// NewKeyVals constructs a list of KeyVal from the list of strings, like "keyword=value"
func NewKeyVals(keyvals []string) KeyVals {
	kvs := make(KeyVals, len(keyvals))
	for i := range keyvals {
		kvs[i] = KeyVal(keyvals[i])
	}
	return kvs
}

// KeyVals is a list of KeyVal
type KeyVals []KeyVal

// Has the "keyword" present in the list of KeyVal, and returns the
// corresponding KeyVal, else an empty string.
func (kvs KeyVals) Has(keyword string) KeyVal {
	for i := range kvs {
		if kvs[i].Keyword() == keyword {
			return kvs[i]
		}
	}
	return emptyKV
}

var emptyKV = KeyVal("")

// MergeSet takes the current setKeyVals, and then applies the entryKeyVals
// such that the entry's values win. The union is returned.
func MergeSet(setKeyVals, entryKeyVals []string) KeyVals {
	retList := NewKeyVals(append([]string{}, setKeyVals...))
	eKVs := NewKeyVals(entryKeyVals)
	seenKeywords := []string{}
	for i := range retList {
		word := retList[i].Keyword()
		if ekv := eKVs.Has(word); ekv != emptyKV {
			retList[i] = ekv
		}
		seenKeywords = append(seenKeywords, word)
	}
	for i := range eKVs {
		if !inSlice(eKVs[i].Keyword(), seenKeywords) {
			retList = append(retList, eKVs[i])
		}
	}
	return retList
}

var (
	// DefaultKeywords has the several default keyword producers (uid, gid,
	// mode, nlink, type, size, mtime)
	DefaultKeywords = []string{
		"size",
		"type",
		"uid",
		"gid",
		"mode",
		"link",
		"nlink",
		"time",
	}
	// SetKeywords is the default set of keywords calculated for a `/set` SpecialType
	SetKeywords = []string{
		"uid",
		"gid",
	}
	// KeywordFuncs is the map of all keywords (and the functions to produce them)
	KeywordFuncs = map[string]KeywordFunc{
		"size":            sizeKeywordFunc,                                     // The size, in bytes, of the file
		"type":            typeKeywordFunc,                                     // The type of the file
		"time":            timeKeywordFunc,                                     // The last modification time of the file
		"link":            linkKeywordFunc,                                     // The target of the symbolic link when type=link
		"uid":             uidKeywordFunc,                                      // The file owner as a numeric value
		"gid":             gidKeywordFunc,                                      // The file group as a numeric value
		"nlink":           nlinkKeywordFunc,                                    // The number of hard links the file is expected to have
		"uname":           unameKeywordFunc,                                    // The file owner as a symbolic name
		"mode":            modeKeywordFunc,                                     // The current file's permissions as a numeric (octal) or symbolic value
		"cksum":           cksumKeywordFunc,                                    // The checksum of the file using the default algorithm specified by the cksum(1) utility
		"md5":             hasherKeywordFunc("md5", md5.New),                   // The MD5 message digest of the file
		"md5digest":       hasherKeywordFunc("md5digest", md5.New),             // A synonym for `md5`
		"rmd160":          hasherKeywordFunc("rmd160", ripemd160.New),          // The RIPEMD160 message digest of the file
		"rmd160digest":    hasherKeywordFunc("rmd160digest", ripemd160.New),    // A synonym for `rmd160`
		"ripemd160digest": hasherKeywordFunc("ripemd160digest", ripemd160.New), // A synonym for `rmd160`
		"sha1":            hasherKeywordFunc("sha1", sha1.New),                 // The SHA1 message digest of the file
		"sha1digest":      hasherKeywordFunc("sha1digest", sha1.New),           // A synonym for `sha1`
		"sha256":          hasherKeywordFunc("sha256", sha256.New),             // The SHA256 message digest of the file
		"sha256digest":    hasherKeywordFunc("sha256digest", sha256.New),       // A synonym for `sha256`
		"sha384":          hasherKeywordFunc("sha384", sha512.New384),          // The SHA384 message digest of the file
		"sha384digest":    hasherKeywordFunc("sha384digest", sha512.New384),    // A synonym for `sha384`
		"sha512":          hasherKeywordFunc("sha512", sha512.New),             // The SHA512 message digest of the file
		"sha512digest":    hasherKeywordFunc("sha512digest", sha512.New),       // A synonym for `sha512`

		// This is not an upstreamed keyword, but a needed attribute for file validation.
		// The pattern for this keyword key is prefixed by "xattr." followed by the extended attribute "namespace.key".
		// The keyword value is the SHA1 digest of the extended attribute's value.
		// In this way, the order of the keys does not matter, and the contents of the value is not revealed.
		"xattr": xattrKeywordFunc,
	}
)

var (
	modeKeywordFunc = func(path string, info os.FileInfo) (string, error) {
		return fmt.Sprintf("mode=%#o", info.Mode().Perm()), nil
	}
	sizeKeywordFunc = func(path string, info os.FileInfo) (string, error) {
		return fmt.Sprintf("size=%d", info.Size()), nil
	}
	cksumKeywordFunc = func(path string, info os.FileInfo) (string, error) {
		if !info.Mode().IsRegular() {
			return "", nil
		}

		fh, err := os.Open(path)
		if err != nil {
			return "", err
		}
		defer fh.Close()
		sum, _, err := cksum(fh)
		if err != nil {
			return "", err
		}
		return fmt.Sprintf("cksum=%d", sum), nil
	}
	hasherKeywordFunc = func(name string, newHash func() hash.Hash) KeywordFunc {
		return func(path string, info os.FileInfo) (string, error) {
			if !info.Mode().IsRegular() {
				return "", nil
			}

			fh, err := os.Open(path)
			if err != nil {
				return "", err
			}
			defer fh.Close()

			h := newHash()
			if _, err := io.Copy(h, fh); err != nil {
				return "", err
			}
			return fmt.Sprintf("%s=%x", name, h.Sum(nil)), nil
		}
	}
	timeKeywordFunc = func(path string, info os.FileInfo) (string, error) {
		t := info.ModTime().UnixNano()
		if t == 0 {
			return "time=0.0", nil
		}
		return fmt.Sprintf("time=%d.%d", (t / 1e9), (t % (t / 1e9))), nil
	}
	linkKeywordFunc = func(path string, info os.FileInfo) (string, error) {
		if info.Mode()&os.ModeSymlink != 0 {
			str, err := os.Readlink(path)
			if err != nil {
				return "", err
			}
			return fmt.Sprintf("link=%s", str), nil
		}
		return "", nil
	}
	typeKeywordFunc = func(path string, info os.FileInfo) (string, error) {
		if info.Mode().IsDir() {
			return "type=dir", nil
		}
		if info.Mode().IsRegular() {
			return "type=file", nil
		}
		if info.Mode()&os.ModeSocket != 0 {
			return "type=socket", nil
		}
		if info.Mode()&os.ModeSymlink != 0 {
			return "type=link", nil
		}
		if info.Mode()&os.ModeNamedPipe != 0 {
			return "type=fifo", nil
		}
		if info.Mode()&os.ModeDevice != 0 {
			if info.Mode()&os.ModeCharDevice != 0 {
				return "type=char", nil
			}
			return "type=device", nil
		}
		return "", nil
	}
)
<|endoftext|>


<commit_before>package sofa

import (
	"fmt"
	"strconv"
	"testing"
	"time"

	"github.com/nbio/st"
	"gopkg.in/h2non/gock.v1"
)

func getFeedTestOtherDoc() Document {
	return &struct {
		DocumentMetadata
		Name string `json:"name"`
		Type string `json:"type"`
	}{
		DocumentMetadata: DocumentMetadata{
			ID: "fruit2",
		},
		Name: "papaya",
		Type: "fruit",
	}
}

func TestPollingFeed(t *testing.T) {
	defer gock.Off()

	gock.New(fmt.Sprintf("https://%s", globalTestConnections.Version1MockHost)).
		Post("/test_db/_changes")
}

func TestFeedPollingReal(t *testing.T) {
	con := globalTestConnections.Version1(t, false)

	defer func() {
		cleanTestDB(t, con.Connection, "feed_test_db", false)
	}()

	for _, lp := range []bool{true, false} {
		// Delete the DB if it currently exists
		cleanTestDB(t, con.Connection, "feed_test_db", true)

		db, err := con.CreateDatabase("feed_test_db")
		st.Assert(t, err, nil)

		feed := db.PollingChangesFeed(lp)

		// This is not true if we are doing long polling - it would just block
		// until a change is made (which would be never).
		if !lp {
			emptyUpdate, err := feed.Next(&ChangesFeedParams1{})
			st.Assert(t, err, nil)

			st.Expect(t, emptyUpdate.LastSeq, AlwaysString("0"))
			st.Expect(t, emptyUpdate.Pending, int64(0))
			st.Expect(t, len(emptyUpdate.Results), 0)
		}

		doc := getDefaultTestDoc()

		_, err = db.Put(doc)
		st.Assert(t, err, nil)

		middleUpdate, err := feed.Next(&ChangesFeedParams1{})
		st.Assert(t, err, nil)

		st.Expect(t, middleUpdate.LastSeq, AlwaysString("1"))
		st.Assert(t, middleUpdate.Pending, int64(0))

		st.Assert(t, middleUpdate.Results[0].Deleted, false)
		st.Assert(t, middleUpdate.Results[0].ID, "fruit1")
		st.Assert(t, middleUpdate.Results[0].Seq, AlwaysString("1"))

		_, err = db.Get(doc, doc.Metadata().ID, "")
		st.Assert(t, err, nil)

		_, err = db.Delete(doc)
		st.Assert(t, err, nil)

		lastUpdate, err := feed.Next(&ChangesFeedParams1{})
		st.Assert(t, err, nil)

		st.Assert(t, lastUpdate.LastSeq, AlwaysString("2"))
		st.Assert(t, lastUpdate.Pending, int64(0))

		st.Assert(t, lastUpdate.Results[0].Deleted, true)
		st.Assert(t, lastUpdate.Results[0].ID, "fruit1")
		st.Assert(t, lastUpdate.Results[0].Seq, AlwaysString("2"))

		since, err := strconv.Atoi(string(middleUpdate.LastSeq))
		st.Assert(t, err, nil)

		updateSince, err := feed.Next(&ChangesFeedParams1{
			Since: int64(since),
		})
		st.Assert(t, err, nil)

		st.Assert(t, lastUpdate.LastSeq, updateSince.LastSeq)
	}
}

func TestFeedContinuousReal(t *testing.T) {
	con := globalTestConnections.Version1(t, false)

	// Delete the DB if it currently exists
	cleanTestDB(t, con.Connection, "feed_test_db", true)

	db, err := con.CreateDatabase("feed_test_db")
	st.Assert(t, err, nil)

	defer func() {
		cleanTestDB(t, con.Connection, "feed_test_db", false)
	}()

	feed := db.ContinuousChangesFeed(&ChangesFeedParams1{})

	doc := getDefaultTestDoc()

	_, err = db.Put(doc)
	st.Assert(t, err, nil)

	middleUpdate, err := feed.Next()
	st.Assert(t, err, nil)

	st.Assert(t, middleUpdate.Deleted, false)
	st.Assert(t, middleUpdate.ID, "fruit1")
	st.Assert(t, middleUpdate.Seq, AlwaysString("1"))

	_, err = db.Get(doc, doc.Metadata().ID, "")
	st.Assert(t, err, nil)

	_, err = db.Delete(doc)
	st.Assert(t, err, nil)

	lastUpdate, err := feed.Next()
	st.Assert(t, err, nil)

	st.Assert(t, lastUpdate.Deleted, true)
	st.Assert(t, lastUpdate.ID, "fruit1")
	st.Assert(t, lastUpdate.Seq, AlwaysString("2"))

	otherDoc := getFeedTestOtherDoc()

	go func() {
		time.Sleep(time.Millisecond * 500)
		_, err := db.Put(otherDoc)
		st.Assert(t, err, nil)
	}()

	updateAsync, err := feed.Next()
	st.Assert(t, err, nil)

	st.Assert(t, updateAsync.Deleted, false)
	st.Assert(t, updateAsync.ID, "fruit2")
	st.Assert(t, updateAsync.Seq, AlwaysString("3"))
}

func TestFeedPolling2Real(t *testing.T) {
	con := globalTestConnections.Version2(t, false)

	defer func() {
		cleanTestDB(t, con.Connection, "feed_test_db", false)
	}()

	for _, lp := range []bool{true, false} {
		// Delete the DB if it currently exists
		cleanTestDB(t, con.Connection, "feed_test_db", true)

		db, err := con.CreateDatabase("feed_test_db")
		st.Assert(t, err, nil)

		feed := db.PollingChangesFeed(lp)

		// This is not true if we are doing long polling - it would just block
		// until a change is made (which would be never).
		if !lp {
			emptyUpdate, err := feed.Next(&ChangesFeedParams2{})
			st.Assert(t, err, nil)

			assertPrefix(t, string(emptyUpdate.LastSeq), "0-")
			st.Expect(t, emptyUpdate.Pending, int64(0))
			st.Expect(t, len(emptyUpdate.Results), 0)
		}

		doc := getDefaultTestDoc()

		_, err = db.Put(doc)
		st.Assert(t, err, nil)

		middleUpdate, err := feed.Next(&ChangesFeedParams2{})
		st.Assert(t, err, nil)

		assertPrefix(t, string(middleUpdate.LastSeq), "1-")
		st.Assert(t, middleUpdate.Pending, int64(0))

		st.Assert(t, middleUpdate.Results[0].Deleted, false)
		st.Assert(t, middleUpdate.Results[0].ID, "fruit1")
		assertPrefix(t, string(middleUpdate.Results[0].Seq), "1-")

		_, err = db.Get(doc, doc.Metadata().ID, "")
		st.Assert(t, err, nil)

		_, err = db.Delete(doc)
		st.Assert(t, err, nil)

		lastUpdate, err := feed.Next(&ChangesFeedParams2{})
		st.Assert(t, err, nil)

		assertPrefix(t, string(lastUpdate.LastSeq), "2-")
		st.Assert(t, lastUpdate.Pending, int64(0))

		st.Assert(t, lastUpdate.Results[0].Deleted, true)
		st.Assert(t, lastUpdate.Results[0].ID, "fruit1")
		assertPrefix(t, string(lastUpdate.Results[0].Seq), "2-")

		updateSince, err := feed.Next(&ChangesFeedParams2{
			Since: string(middleUpdate.LastSeq),
		})
		st.Assert(t, err, nil)

		st.Assert(t, lastUpdate.LastSeq, updateSince.LastSeq)
	}
}

func TestFeedContinuous2Real(t *testing.T) {
	con := globalTestConnections.Version2(t, false)

	// Delete the DB if it currently exists
	cleanTestDB(t, con.Connection, "feed_test_db", true)

	db, err := con.CreateDatabase("feed_test_db")
	st.Assert(t, err, nil)

	defer func() {
		cleanTestDB(t, con.Connection, "feed_test_db", false)
	}()

	feed := db.ContinuousChangesFeed(&ChangesFeedParams2{})

	doc := getDefaultTestDoc()

	_, err = db.Put(doc)
	st.Assert(t, err, nil)

	middleUpdate, err := feed.Next()
	st.Assert(t, err, nil)

	st.Assert(t, middleUpdate.Deleted, false)
	st.Assert(t, middleUpdate.ID, "fruit1")
	assertPrefix(t, string(middleUpdate.Seq), "1-")

	_, err = db.Get(doc, doc.Metadata().ID, "")
	st.Assert(t, err, nil)

	_, err = db.Delete(doc)
	st.Assert(t, err, nil)

	lastUpdate, err := feed.Next()
	st.Assert(t, err, nil)

	st.Assert(t, lastUpdate.Deleted, true)
	st.Assert(t, lastUpdate.ID, "fruit1")
	assertPrefix(t, string(lastUpdate.Seq), "2-")

	otherDoc := getFeedTestOtherDoc()

	go func() {
		time.Sleep(time.Millisecond * 500)
		_, err := db.Put(otherDoc)
		st.Assert(t, err, nil)
	}()

	updateAsync, err := feed.Next()
	st.Assert(t, err, nil)

	st.Assert(t, updateAsync.Deleted, false)
	st.Assert(t, updateAsync.ID, "fruit2")
	assertPrefix(t, string(updateAsync.Seq), "3-")
}

func TestFeedPolling3Real(t *testing.T) {
	con := globalTestConnections.Version3(t, false)

	defer func() {
		cleanTestDB(t, con.Connection, "feed_test_db", false)
	}()

	for _, lp := range []bool{true, false} {
		// Delete the DB if it currently exists
		cleanTestDB(t, con.Connection, "feed_test_db", true)

		db, err := con.CreateDatabase("feed_test_db")
		st.Assert(t, err, nil)

		feed := db.PollingChangesFeed(lp)

		// This is not true if we are doing long polling - it would just block
		// until a change is made (which would be never).
		if !lp {
			emptyUpdate, err := feed.Next(&ChangesFeedParams2{})
			st.Assert(t, err, nil)

			assertPrefix(t, string(emptyUpdate.LastSeq), "0-")
			st.Expect(t, emptyUpdate.Pending, int64(0))
			st.Expect(t, len(emptyUpdate.Results), 0)
		}

		doc := getDefaultTestDoc()

		_, err = db.Put(doc)
		st.Assert(t, err, nil)

		middleUpdate, err := feed.Next(&ChangesFeedParams2{})
		st.Assert(t, err, nil)

		assertPrefix(t, string(middleUpdate.LastSeq), "1-")
		st.Assert(t, middleUpdate.Pending, int64(0))

		st.Assert(t, middleUpdate.Results[0].Deleted, false)
		st.Assert(t, middleUpdate.Results[0].ID, "fruit1")
		assertPrefix(t, string(middleUpdate.Results[0].Seq), "1-")

		_, err = db.Get(doc, doc.Metadata().ID, "")
		st.Assert(t, err, nil)

		_, err = db.Delete(doc)
		st.Assert(t, err, nil)

		lastUpdate, err := feed.Next(&ChangesFeedParams2{})
		st.Assert(t, err, nil)

		assertPrefix(t, string(lastUpdate.LastSeq), "2-")
		st.Assert(t, lastUpdate.Pending, int64(0))

		st.Assert(t, lastUpdate.Results[0].Deleted, true)
		st.Assert(t, lastUpdate.Results[0].ID, "fruit1")
		assertPrefix(t, string(lastUpdate.Results[0].Seq), "2-")

		updateSince, err := feed.Next(&ChangesFeedParams2{
			Since: string(middleUpdate.LastSeq),
		})
		st.Assert(t, err, nil)

		st.Assert(t, lastUpdate.LastSeq, updateSince.LastSeq)
	}
}

func TestFeedContinuous3Real(t *testing.T) {
	con := globalTestConnections.Version3(t, false)

	// Delete the DB if it currently exists
	cleanTestDB(t, con.Connection, "feed_test_db", true)

	db, err := con.CreateDatabase("feed_test_db")
	st.Assert(t, err, nil)

	defer func() {
		cleanTestDB(t, con.Connection, "feed_test_db", false)
	}()

	feed := db.ContinuousChangesFeed(&ChangesFeedParams3{})

	doc := getDefaultTestDoc()

	_, err = db.Put(doc)
	st.Assert(t, err, nil)

	middleUpdate, err := feed.Next()
	st.Assert(t, err, nil)

	st.Assert(t, middleUpdate.Deleted, false)
	st.Assert(t, middleUpdate.ID, "fruit1")
	assertPrefix(t, string(middleUpdate.Seq), "1-")

	_, err = db.Get(doc, doc.Metadata().ID, "")
	st.Assert(t, err, nil)

	_, err = db.Delete(doc)
	st.Assert(t, err, nil)

	lastUpdate, err := feed.Next()
	st.Assert(t, err, nil)

	st.Assert(t, lastUpdate.Deleted, true)
	st.Assert(t, lastUpdate.ID, "fruit1")
	assertPrefix(t, string(lastUpdate.Seq), "2-")

	otherDoc := getFeedTestOtherDoc()

	go func() {
		time.Sleep(time.Millisecond * 500)
		_, err := db.Put(otherDoc)
		st.Assert(t, err, nil)
	}()

	updateAsync, err := feed.Next()
	st.Assert(t, err, nil)

	st.Assert(t, updateAsync.Deleted, false)
	st.Assert(t, updateAsync.ID, "fruit2")
	assertPrefix(t, string(updateAsync.Seq), "3-")
}
<commit_msg>Make test naming consistent<commit_after>package sofa

import (
	"fmt"
	"strconv"
	"testing"
	"time"

	"github.com/nbio/st"
	"gopkg.in/h2non/gock.v1"
)

func getFeedTestOtherDoc() Document {
	return &struct {
		DocumentMetadata
		Name string `json:"name"`
		Type string `json:"type"`
	}{
		DocumentMetadata: DocumentMetadata{
			ID: "fruit2",
		},
		Name: "papaya",
		Type: "fruit",
	}
}

func TestPollingFeed(t *testing.T) {
	defer gock.Off()

	gock.New(fmt.Sprintf("https://%s", globalTestConnections.Version1MockHost)).
		Post("/test_db/_changes")
}

func TestFeedPollingReal1(t *testing.T) {
	con := globalTestConnections.Version1(t, false)

	defer func() {
		cleanTestDB(t, con.Connection, "feed_test_db", false)
	}()

	for _, lp := range []bool{true, false} {
		// Delete the DB if it currently exists
		cleanTestDB(t, con.Connection, "feed_test_db", true)

		db, err := con.CreateDatabase("feed_test_db")
		st.Assert(t, err, nil)

		feed := db.PollingChangesFeed(lp)

		// This is not true if we are doing long polling - it would just block
		// until a change is made (which would be never).
		if !lp {
			emptyUpdate, err := feed.Next(&ChangesFeedParams1{})
			st.Assert(t, err, nil)

			st.Expect(t, emptyUpdate.LastSeq, AlwaysString("0"))
			st.Expect(t, emptyUpdate.Pending, int64(0))
			st.Expect(t, len(emptyUpdate.Results), 0)
		}

		doc := getDefaultTestDoc()

		_, err = db.Put(doc)
		st.Assert(t, err, nil)

		middleUpdate, err := feed.Next(&ChangesFeedParams1{})
		st.Assert(t, err, nil)

		st.Expect(t, middleUpdate.LastSeq, AlwaysString("1"))
		st.Assert(t, middleUpdate.Pending, int64(0))

		st.Assert(t, middleUpdate.Results[0].Deleted, false)
		st.Assert(t, middleUpdate.Results[0].ID, "fruit1")
		st.Assert(t, middleUpdate.Results[0].Seq, AlwaysString("1"))

		_, err = db.Get(doc, doc.Metadata().ID, "")
		st.Assert(t, err, nil)

		_, err = db.Delete(doc)
		st.Assert(t, err, nil)

		lastUpdate, err := feed.Next(&ChangesFeedParams1{})
		st.Assert(t, err, nil)

		st.Assert(t, lastUpdate.LastSeq, AlwaysString("2"))
		st.Assert(t, lastUpdate.Pending, int64(0))

		st.Assert(t, lastUpdate.Results[0].Deleted, true)
		st.Assert(t, lastUpdate.Results[0].ID, "fruit1")
		st.Assert(t, lastUpdate.Results[0].Seq, AlwaysString("2"))

		since, err := strconv.Atoi(string(middleUpdate.LastSeq))
		st.Assert(t, err, nil)

		updateSince, err := feed.Next(&ChangesFeedParams1{
			Since: int64(since),
		})
		st.Assert(t, err, nil)

		st.Assert(t, lastUpdate.LastSeq, updateSince.LastSeq)
	}
}

func TestFeedContinuousReal1(t *testing.T) {
	con := globalTestConnections.Version1(t, false)

	// Delete the DB if it currently exists
	cleanTestDB(t, con.Connection, "feed_test_db", true)

	db, err := con.CreateDatabase("feed_test_db")
	st.Assert(t, err, nil)

	defer func() {
		cleanTestDB(t, con.Connection, "feed_test_db", false)
	}()

	feed := db.ContinuousChangesFeed(&ChangesFeedParams1{})

	doc := getDefaultTestDoc()

	_, err = db.Put(doc)
	st.Assert(t, err, nil)

	middleUpdate, err := feed.Next()
	st.Assert(t, err, nil)

	st.Assert(t, middleUpdate.Deleted, false)
	st.Assert(t, middleUpdate.ID, "fruit1")
	st.Assert(t, middleUpdate.Seq, AlwaysString("1"))

	_, err = db.Get(doc, doc.Metadata().ID, "")
	st.Assert(t, err, nil)

	_, err = db.Delete(doc)
	st.Assert(t, err, nil)

	lastUpdate, err := feed.Next()
	st.Assert(t, err, nil)

	st.Assert(t, lastUpdate.Deleted, true)
	st.Assert(t, lastUpdate.ID, "fruit1")
	st.Assert(t, lastUpdate.Seq, AlwaysString("2"))

	otherDoc := getFeedTestOtherDoc()

	go func() {
		time.Sleep(time.Millisecond * 500)
		_, err := db.Put(otherDoc)
		st.Assert(t, err, nil)
	}()

	updateAsync, err := feed.Next()
	st.Assert(t, err, nil)

	st.Assert(t, updateAsync.Deleted, false)
	st.Assert(t, updateAsync.ID, "fruit2")
	st.Assert(t, updateAsync.Seq, AlwaysString("3"))
}

func TestFeedPollingReal2(t *testing.T) {
	con := globalTestConnections.Version2(t, false)

	defer func() {
		cleanTestDB(t, con.Connection, "feed_test_db", false)
	}()

	for _, lp := range []bool{true, false} {
		// Delete the DB if it currently exists
		cleanTestDB(t, con.Connection, "feed_test_db", true)

		db, err := con.CreateDatabase("feed_test_db")
		st.Assert(t, err, nil)

		feed := db.PollingChangesFeed(lp)

		// This is not true if we are doing long polling - it would just block
		// until a change is made (which would be never).
		if !lp {
			emptyUpdate, err := feed.Next(&ChangesFeedParams2{})
			st.Assert(t, err, nil)

			assertPrefix(t, string(emptyUpdate.LastSeq), "0-")
			st.Expect(t, emptyUpdate.Pending, int64(0))
			st.Expect(t, len(emptyUpdate.Results), 0)
		}

		doc := getDefaultTestDoc()

		_, err = db.Put(doc)
		st.Assert(t, err, nil)

		middleUpdate, err := feed.Next(&ChangesFeedParams2{})
		st.Assert(t, err, nil)

		assertPrefix(t, string(middleUpdate.LastSeq), "1-")
		st.Assert(t, middleUpdate.Pending, int64(0))

		st.Assert(t, middleUpdate.Results[0].Deleted, false)
		st.Assert(t, middleUpdate.Results[0].ID, "fruit1")
		assertPrefix(t, string(middleUpdate.Results[0].Seq), "1-")

		_, err = db.Get(doc, doc.Metadata().ID, "")
		st.Assert(t, err, nil)

		_, err = db.Delete(doc)
		st.Assert(t, err, nil)

		lastUpdate, err := feed.Next(&ChangesFeedParams2{})
		st.Assert(t, err, nil)

		assertPrefix(t, string(lastUpdate.LastSeq), "2-")
		st.Assert(t, lastUpdate.Pending, int64(0))

		st.Assert(t, lastUpdate.Results[0].Deleted, true)
		st.Assert(t, lastUpdate.Results[0].ID, "fruit1")
		assertPrefix(t, string(lastUpdate.Results[0].Seq), "2-")

		updateSince, err := feed.Next(&ChangesFeedParams2{
			Since: string(middleUpdate.LastSeq),
		})
		st.Assert(t, err, nil)

		st.Assert(t, lastUpdate.LastSeq, updateSince.LastSeq)
	}
}

func TestFeedContinuousReal2(t *testing.T) {
	con := globalTestConnections.Version2(t, false)

	// Delete the DB if it currently exists
	cleanTestDB(t, con.Connection, "feed_test_db", true)

	db, err := con.CreateDatabase("feed_test_db")
	st.Assert(t, err, nil)

	defer func() {
		cleanTestDB(t, con.Connection, "feed_test_db", false)
	}()

	feed := db.ContinuousChangesFeed(&ChangesFeedParams2{})

	doc := getDefaultTestDoc()

	_, err = db.Put(doc)
	st.Assert(t, err, nil)

	middleUpdate, err := feed.Next()
	st.Assert(t, err, nil)

	st.Assert(t, middleUpdate.Deleted, false)
	st.Assert(t, middleUpdate.ID, "fruit1")
	assertPrefix(t, string(middleUpdate.Seq), "1-")

	_, err = db.Get(doc, doc.Metadata().ID, "")
	st.Assert(t, err, nil)

	_, err = db.Delete(doc)
	st.Assert(t, err, nil)

	lastUpdate, err := feed.Next()
	st.Assert(t, err, nil)

	st.Assert(t, lastUpdate.Deleted, true)
	st.Assert(t, lastUpdate.ID, "fruit1")
	assertPrefix(t, string(lastUpdate.Seq), "2-")

	otherDoc := getFeedTestOtherDoc()

	go func() {
		time.Sleep(time.Millisecond * 500)
		_, err := db.Put(otherDoc)
		st.Assert(t, err, nil)
	}()

	updateAsync, err := feed.Next()
	st.Assert(t, err, nil)

	st.Assert(t, updateAsync.Deleted, false)
	st.Assert(t, updateAsync.ID, "fruit2")
	assertPrefix(t, string(updateAsync.Seq), "3-")
}

func TestFeedPollingReal3(t *testing.T) {
	con := globalTestConnections.Version3(t, false)

	defer func() {
		cleanTestDB(t, con.Connection, "feed_test_db", false)
	}()

	for _, lp := range []bool{true, false} {
		// Delete the DB if it currently exists
		cleanTestDB(t, con.Connection, "feed_test_db", true)

		db, err := con.CreateDatabase("feed_test_db")
		st.Assert(t, err, nil)

		feed := db.PollingChangesFeed(lp)

		// This is not true if we are doing long polling - it would just block
		// until a change is made (which would be never).
		if !lp {
			emptyUpdate, err := feed.Next(&ChangesFeedParams2{})
			st.Assert(t, err, nil)

			assertPrefix(t, string(emptyUpdate.LastSeq), "0-")
			st.Expect(t, emptyUpdate.Pending, int64(0))
			st.Expect(t, len(emptyUpdate.Results), 0)
		}

		doc := getDefaultTestDoc()

		_, err = db.Put(doc)
		st.Assert(t, err, nil)

		middleUpdate, err := feed.Next(&ChangesFeedParams2{})
		st.Assert(t, err, nil)

		assertPrefix(t, string(middleUpdate.LastSeq), "1-")
		st.Assert(t, middleUpdate.Pending, int64(0))

		st.Assert(t, middleUpdate.Results[0].Deleted, false)
		st.Assert(t, middleUpdate.Results[0].ID, "fruit1")
		assertPrefix(t, string(middleUpdate.Results[0].Seq), "1-")

		_, err = db.Get(doc, doc.Metadata().ID, "")
		st.Assert(t, err, nil)

		_, err = db.Delete(doc)
		st.Assert(t, err, nil)

		lastUpdate, err := feed.Next(&ChangesFeedParams2{})
		st.Assert(t, err, nil)

		assertPrefix(t, string(lastUpdate.LastSeq), "2-")
		st.Assert(t, lastUpdate.Pending, int64(0))

		st.Assert(t, lastUpdate.Results[0].Deleted, true)
		st.Assert(t, lastUpdate.Results[0].ID, "fruit1")
		assertPrefix(t, string(lastUpdate.Results[0].Seq), "2-")

		updateSince, err := feed.Next(&ChangesFeedParams2{
			Since: string(middleUpdate.LastSeq),
		})
		st.Assert(t, err, nil)

		st.Assert(t, lastUpdate.LastSeq, updateSince.LastSeq)
	}
}

func TestFeedContinuousReal3(t *testing.T) {
	con := globalTestConnections.Version3(t, false)

	// Delete the DB if it currently exists
	cleanTestDB(t, con.Connection, "feed_test_db", true)

	db, err := con.CreateDatabase("feed_test_db")
	st.Assert(t, err, nil)

	defer func() {
		cleanTestDB(t, con.Connection, "feed_test_db", false)
	}()

	feed := db.ContinuousChangesFeed(&ChangesFeedParams3{})

	doc := getDefaultTestDoc()

	_, err = db.Put(doc)
	st.Assert(t, err, nil)

	middleUpdate, err := feed.Next()
	st.Assert(t, err, nil)

	st.Assert(t, middleUpdate.Deleted, false)
	st.Assert(t, middleUpdate.ID, "fruit1")
	assertPrefix(t, string(middleUpdate.Seq), "1-")

	_, err = db.Get(doc, doc.Metadata().ID, "")
	st.Assert(t, err, nil)

	_, err = db.Delete(doc)
	st.Assert(t, err, nil)

	lastUpdate, err := feed.Next()
	st.Assert(t, err, nil)

	st.Assert(t, lastUpdate.Deleted, true)
	st.Assert(t, lastUpdate.ID, "fruit1")
	assertPrefix(t, string(lastUpdate.Seq), "2-")

	otherDoc := getFeedTestOtherDoc()

	go func() {
		time.Sleep(time.Millisecond * 500)
		_, err := db.Put(otherDoc)
		st.Assert(t, err, nil)
	}()

	updateAsync, err := feed.Next()
	st.Assert(t, err, nil)

	st.Assert(t, updateAsync.Deleted, false)
	st.Assert(t, updateAsync.ID, "fruit2")
	assertPrefix(t, string(updateAsync.Seq), "3-")
}
<|endoftext|>


<commit_before>package reporting

import (
	"bytes"
	"fmt"
	"log"
	"net/smtp"
	"strings"
	"text/template"
)

type Mail struct {
	Host       string
	Port       string
	Username   string
	Password   string
	Sender     string
	Recipients []string
}

func (m *Mail) AddRecipient(recipient string) {
	m.Recipients = append(m.Recipients, recipient)
}

func (m *Mail) getSMTPHostAndPort() string {
	return m.Host + ":" + m.Port
}

func (m *Mail) recipientHeader() string {
	var recipients string
	for _, recipient := range m.Recipients {
		recipients += recipient + ","
	}
	return strings.TrimRight(recipients, ",")
}

func (m *Mail) useAuthentication() bool {
	return m.Username != ""
}

func (m *Mail) SendFromTemplate(tmpl *template.Template, templateData EmailSummaryTemplateData) {
	//// Connect to the remote SMTP server.
	//c, err := smtp.Dial(m.GetSMTPHostAndPort())
	//if err != nil {
	//	log.Fatal(err)
	//}
	//defer c.Close()
	//
	//// Set the sender and recipient.
	//c.Mail(m.Sender)
	//c.Rcpt(m.Recipients[0])
	//
	////c.Auth()
	//
	//// Send the email body.
	//wc, err := c.Data()
	//if err != nil {
	//	log.Fatal(err)
	//}
	//defer wc.Close()
	//
	var subject string
	if templateData.IsSuccessful() {
		subject = "[SUCCESS] Frosty Backup Report"
	} else {
		subject = "[FAILURE] Frosty Backup Report"
	}

	var wc bytes.Buffer

	headers := make(map[string]string)
	headers["From"] = m.Sender
	headers["To"] = m.recipientHeader()
	headers["Subject"] = subject
	headers["Content-Type"] = "text/html; charset=utf-8"

	for k, v := range headers {
		h := fmt.Sprintf("%s: %s\r\n", k, v)
		wc.Write([]byte(h))
	}

	wc.Write([]byte("\r\n"))

	tmpl.Execute(&wc, templateData)

	if m.useAuthentication() {
		fmt.Println("Sending email with auth")
		a := smtp.PlainAuth("", m.Username, m.Password, m.Host)
		err := smtp.SendMail(m.getSMTPHostAndPort(), a, m.Sender, m.Recipients, wc.Bytes())
		if err != nil {
			log.Printf("Error sending email with auth: %s\n", err)
		}
	} else {
		fmt.Println("Sending email without auth")
		err := smtp.SendMail(m.getSMTPHostAndPort(), nil, m.Sender, m.Recipients, wc.Bytes())
		if err != nil {
			log.Printf("Error sending email without auth: %s\n", err)
		}
	}
}
<commit_msg>Handle the sending of both authenticated and unauthenticed email.<commit_after>package reporting

import (
	"bytes"
	"fmt"
	"log"
	"net/smtp"
	"strings"
	"text/template"
)

type Mail struct {
	Host       string
	Port       string
	Username   string
	Password   string
	Sender     string
	Recipients []string
}

func (m *Mail) AddRecipient(recipient string) {
	m.Recipients = append(m.Recipients, recipient)
}

func (m *Mail) getSMTPHostAndPort() string {
	return m.Host + ":" + m.Port
}

func (m *Mail) recipientHeader() string {
	var recipients string
	for _, recipient := range m.Recipients {
		recipients += recipient + ","
	}
	return strings.TrimRight(recipients, ",")
}

func (m *Mail) useAuthentication() bool {
	return m.Username != ""
}

func (m *Mail) SendFromTemplate(tmpl *template.Template, templateData EmailSummaryTemplateData) {
	var subject string
	if templateData.IsSuccessful() {
		subject = "[SUCCESS] Frosty Backup Report"
	} else {
		subject = "[FAILURE] Frosty Backup Report"
	}

	var wc bytes.Buffer

	headers := make(map[string]string)
	headers["From"] = m.Sender
	headers["To"] = m.recipientHeader()
	headers["Subject"] = subject
	headers["Content-Type"] = "text/html; charset=utf-8"

	for k, v := range headers {
		h := fmt.Sprintf("%s: %s\r\n", k, v)
		wc.Write([]byte(h))
	}

	wc.Write([]byte("\r\n"))

	tmpl.Execute(&wc, templateData)

	if m.useAuthentication() {
		a := smtp.PlainAuth("", m.Username, m.Password, m.Host)
		err := smtp.SendMail(m.getSMTPHostAndPort(), a, m.Sender, m.Recipients, wc.Bytes())
		if err != nil {
			log.Fatalf("Error sending email with auth: %s\n", err)
		}
	} else {
		// Connect to the remote SMTP server.
		c, err := smtp.Dial(m.getSMTPHostAndPort())
		if err != nil {
			log.Fatal(err)
		}
		defer c.Close()

		// Set the sender and recipient.
		c.Mail(m.Sender)
		c.Rcpt(m.Recipients[0])

		// Send the email body.
		cwc, err := c.Data()
		if err != nil {
			log.Fatal(err)
		}
		defer cwc.Close()

		cwc.Write(wc.Bytes())
	}
}
<|endoftext|>


<commit_before>// Code generated by go generate. DO NOT EDIT.
// Source: vendor/istio.io/api/mixer/v1/global_dictionary.yaml

package attribute

func GlobalList() ([]string) {
    tmp := make([]string, len(globalList))
    copy(tmp, globalList)
    return tmp
}

var (
    globalList = []string{
		"source.ip",
		"source.port",
		"source.name",
		"source.uid",
		"source.namespace",
		"source.labels",
		"source.user",
		"target.ip",
		"target.port",
		"target.service",
		"target.name",
		"target.uid",
		"target.namespace",
		"target.labels",
		"target.user",
		"request.headers",
		"request.id",
		"request.path",
		"request.host",
		"request.method",
		"request.reason",
		"request.referer",
		"request.scheme",
		"request.size",
		"request.time",
		"request.useragent",
		"response.headers",
		"response.size",
		"response.time",
		"response.duration",
		"response.code",
		":authority",
		":method",
		":path",
		":scheme",
		":status",
		"access-control-allow-origin",
		"access-control-allow-methods",
		"access-control-allow-headers",
		"access-control-max-age",
		"access-control-request-method",
		"access-control-request-headers",
		"accept-charset",
		"accept-encoding",
		"accept-language",
		"accept-ranges",
		"accept",
		"access-control-allow",
		"age",
		"allow",
		"authorization",
		"cache-control",
		"content-disposition",
		"content-encoding",
		"content-language",
		"content-length",
		"content-location",
		"content-range",
		"content-type",
		"cookie",
		"date",
		"etag",
		"expect",
		"expires",
		"from",
		"host",
		"if-match",
		"if-modified-since",
		"if-none-match",
		"if-range",
		"if-unmodified-since",
		"keep-alive",
		"last-modified",
		"link",
		"location",
		"max-forwards",
		"proxy-authenticate",
		"proxy-authorization",
		"range",
		"referer",
		"refresh",
		"retry-after",
		"server",
		"set-cookie",
		"strict-transport-sec",
		"transfer-encoding",
		"user-agent",
		"vary",
		"via",
		"www-authenticate",
		"GET",
		"POST",
		"http",
		"envoy",
		"'200'",
		"Keep-Alive",
		"chunked",
		"x-envoy-service-time",
		"x-forwarded-for",
		"x-forwarded-host",
		"x-forwarded-proto",
		"x-http-method-override",
		"x-request-id",
		"x-requested-with",
		"application/json",
		"application/xml",
		"gzip",
		"text/html",
		"text/html; charset=utf-8",
		"text/plain",
		"text/plain; charset=utf-8",
		"'0'",
		"'1'",
		"true",
		"false",
		"gzip, deflate",
		"max-age=0",
		"x-envoy-upstream-service-time",
		"x-envoy-internal",
		"x-envoy-expected-rq-timeout-ms",
		"x-ot-span-context",
		"x-b3-traceid",
		"x-b3-sampled",
		"x-b3-spanid",
		"tcp",
		"connection.id",
		"connection.received.bytes",
		"connection.received.bytes_total",
		"connection.sent.bytes",
		"connection.sent.bytes_total",
		"connection.duration",
		"context.protocol",
		"context.timestamp",
		"context.time",
		"0",
		"1",
		"200",
		"302",
		"400",
		"401",
		"403",
		"404",
		"409",
		"429",
		"499",
		"500",
		"501",
		"502",
		"503",
		"504",
		"destination.ip",
		"destination.port",
		"destination.service",
		"destination.name",
		"destination.uid",
		"destination.namespace",
		"destination.labels",
		"destination.user",
		"source.service",
		"api.service",
		"api.version",
		"api.operation",
		"api.protocol",
		"request.auth.principal",
		"request.auth.audiences",
		"request.auth.presenter",
		"request.api_key",
		"check.error_code",
		"check.error_message",
    }
)
<commit_msg>Introduce runtime2/config. (#2447)<commit_after>// Code generated by go generate. DO NOT EDIT.
// Source: vendor/istio.io/api/mixer/v1/global_dictionary.yaml

package attribute

func GlobalList() ([]string) {
    tmp := make([]string, len(globalList))
    copy(tmp, globalList)
    return tmp
}

var (
    globalList = []string{
		"source.ip",
		"source.port",
		"source.name",
		"source.uid",
		"source.namespace",
		"source.labels",
		"source.user",
		"target.ip",
		"target.port",
		"target.service",
		"target.name",
		"target.uid",
		"target.namespace",
		"target.labels",
		"target.user",
		"request.headers",
		"request.id",
		"request.path",
		"request.host",
		"request.method",
		"request.reason",
		"request.referer",
		"request.scheme",
		"request.size",
		"request.time",
		"request.useragent",
		"response.headers",
		"response.size",
		"response.time",
		"response.duration",
		"response.code",
		":authority",
		":method",
		":path",
		":scheme",
		":status",
		"access-control-allow-origin",
		"access-control-allow-methods",
		"access-control-allow-headers",
		"access-control-max-age",
		"access-control-request-method",
		"access-control-request-headers",
		"accept-charset",
		"accept-encoding",
		"accept-language",
		"accept-ranges",
		"accept",
		"access-control-allow",
		"age",
		"allow",
		"authorization",
		"cache-control",
		"content-disposition",
		"content-encoding",
		"content-language",
		"content-length",
		"content-location",
		"content-range",
		"content-type",
		"cookie",
		"date",
		"etag",
		"expect",
		"expires",
		"from",
		"host",
		"if-match",
		"if-modified-since",
		"if-none-match",
		"if-range",
		"if-unmodified-since",
		"keep-alive",
		"last-modified",
		"link",
		"location",
		"max-forwards",
		"proxy-authenticate",
		"proxy-authorization",
		"range",
		"referer",
		"refresh",
		"retry-after",
		"server",
		"set-cookie",
		"strict-transport-sec",
		"transfer-encoding",
		"user-agent",
		"vary",
		"via",
		"www-authenticate",
		"GET",
		"POST",
		"http",
		"envoy",
		"'200'",
		"Keep-Alive",
		"chunked",
		"x-envoy-service-time",
		"x-forwarded-for",
		"x-forwarded-host",
		"x-forwarded-proto",
		"x-http-method-override",
		"x-request-id",
		"x-requested-with",
		"application/json",
		"application/xml",
		"gzip",
		"text/html",
		"text/html; charset=utf-8",
		"text/plain",
		"text/plain; charset=utf-8",
		"'0'",
		"'1'",
		"true",
		"false",
		"gzip, deflate",
		"max-age=0",
		"x-envoy-upstream-service-time",
		"x-envoy-internal",
		"x-envoy-expected-rq-timeout-ms",
		"x-ot-span-context",
		"x-b3-traceid",
		"x-b3-sampled",
		"x-b3-spanid",
		"tcp",
		"connection.id",
		"connection.received.bytes",
		"connection.received.bytes_total",
		"connection.sent.bytes",
		"connection.sent.bytes_total",
		"connection.duration",
		"context.protocol",
		"context.timestamp",
		"context.time",
		"0",
		"1",
		"200",
		"302",
		"400",
		"401",
		"403",
		"404",
		"409",
		"429",
		"499",
		"500",
		"501",
		"502",
		"503",
		"504",
		"destination.ip",
		"destination.port",
		"destination.service",
		"destination.name",
		"destination.uid",
		"destination.namespace",
		"destination.labels",
		"destination.user",
		"source.service",
		"api.service",
		"api.version",
		"api.operation",
		"api.protocol",
		"request.auth.principal",
		"request.auth.audiences",
		"request.auth.presenter",
		"request.api_key",
		"check.error_code",
		"check.error_message",
    }
)
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"bytes"
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"log"
	"net"
	"strconv"
	"time"
)

var (
	ping   = []byte("ping")
	closed = []byte("close")
)

// Listener accepts connections from devices.
type Listener struct {
	debug    bool
	listener net.Listener
}

// Listen creates a TCP listener with the given PEM encoded X.509 certificate and the private key on the local network address laddr.
// Debug mode logs all server activity.
func Listen(cert, privKey []byte, laddr string, debug bool) (*Listener, error) {
	tlsCert, err := tls.X509KeyPair(cert, privKey)
	pool := x509.NewCertPool()
	ok := pool.AppendCertsFromPEM(cert)
	if err != nil || !ok {
		return nil, fmt.Errorf("failed to parse the certificate or the private key with error: %v", err)
	}

	config := tls.Config{
		Certificates: []tls.Certificate{tlsCert},
		ClientCAs:    pool,
		ClientAuth:   tls.VerifyClientCertIfGiven,
	}

	listener, err := tls.Listen("tcp", laddr, &config)
	if err != nil {
		return nil, err
	}
	if debug {
		log.Printf("Listener created with local network address: %v\n", laddr)
	}

	return &Listener{
		debug:    debug,
		listener: listener,
	}, nil
}

// Session is a generic session data store for client handlers.
type Session struct {
	id   int
	data interface{}
}

// Accept waits for incoming connections and forwards the client connect/message/disconnect
// events to provided handlers in a new goroutine.
// This function never returns, unless there is an error while accepting a new connection.
func (l *Listener) Accept(handleMsg func(conn *tls.Conn, session *Session, msg []byte), handleDisconn func(conn *tls.Conn, session *Session)) error {
	for {
		conn, err := l.listener.Accept()
		if err != nil {
			return fmt.Errorf("error while accepting a new connection from a client: %v", err)
			// todo: it might not be appropriate to break the loop on recoverable errors (like client disconnect during handshake)
			// the underlying fd.accept() does some basic recovery though we might need more: http://golang.org/src/net/fd_unix.go
		}
		tlsconn, _ := conn.(*tls.Conn) // todo: check ok
		if l.debug {
			log.Println("Client connected: listening for messages from client IP:", conn.RemoteAddr())
		}
		go handleClient(tlsconn, l.debug, handleMsg, handleDisconn)
	}
}

// handleClient waits for messages from the connected client and forwards the client message/disconnect
// events to provided handlers in a new goroutine.
// This function never returns, unless there is an error while reading from the channel or the client disconnects.
func handleClient(conn *tls.Conn, debug bool, handleMsg func(conn *tls.Conn, session *Session, msg []byte), handleDisconn func(conn *tls.Conn, session *Session)) {
	defer conn.Close()
	if debug {
		defer log.Println("Closed connection to client with IP:", conn.RemoteAddr())
	}

	session := &Session{id: 1}
	reader := bufio.NewReader(conn)

	for {
		err := conn.SetReadDeadline(time.Now().Add(time.Minute * 5))

		// read the content length header
		line, err := reader.ReadSlice('|') // todo: remove this and use the readslice source code directly not to have another wrapper
		if err != nil {
			log.Fatalln("Client read error: ", err)
			break
		}

		// calculate the content length
		n, err := strconv.Atoi(string(line[:len(line)-1]))
		if err != nil || n == 0 {
			log.Fatalln("Client read error: invalid content lenght header sent or content lenght mismatch: ", err)
			break
		}

		// read the message content
		if debug {
			log.Println("Starting to read message content of bytes: ", n)
		}
		msg := make([]byte, n)
		total := 0
		for total < n {
			i, err := reader.Read(msg)
			if err != nil {
				log.Fatalln("Error while reading incoming message: ", err)
				break
			}
			total += i
		}
		if err != nil {
			log.Fatalln("Error while reading incoming message: ", err)
			break
		}
		if debug {
			log.Printf("Read %v bytes message '%v' from client with IP: %v\n", n, string(msg), conn.RemoteAddr())
		}

		if n == 4 && bytes.Equal(msg, ping) {
			continue
		}

		if n == 5 && bytes.Equal(msg, closed) {
			go handleDisconn(conn, session)
			return
		}

		go handleMsg(conn, session, msg)
	}
}

// Close closes the listener.
func (l *Listener) Close() error {
	if l.debug {
		defer log.Println("Listener was closed on local network address:", l.listener.Addr())
	}
	return l.listener.Close()
}
<commit_msg>add note<commit_after>package main

import (
	"bufio"
	"bytes"
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"log"
	"net"
	"strconv"
	"time"
)

var (
	ping   = []byte("ping")
	closed = []byte("close")
)

// Listener accepts connections from devices.
type Listener struct {
	debug    bool
	listener net.Listener
}

// Listen creates a TCP listener with the given PEM encoded X.509 certificate and the private key on the local network address laddr.
// Debug mode logs all server activity.
func Listen(cert, privKey []byte, laddr string, debug bool) (*Listener, error) {
	tlsCert, err := tls.X509KeyPair(cert, privKey)
	pool := x509.NewCertPool()
	ok := pool.AppendCertsFromPEM(cert)
	if err != nil || !ok {
		return nil, fmt.Errorf("failed to parse the certificate or the private key with error: %v", err)
	}

	config := tls.Config{
		Certificates: []tls.Certificate{tlsCert},
		ClientCAs:    pool,
		ClientAuth:   tls.VerifyClientCertIfGiven,
	}

	listener, err := tls.Listen("tcp", laddr, &config)
	if err != nil {
		return nil, err
	}
	if debug {
		log.Printf("Listener created with local network address: %v\n", laddr)
	}

	return &Listener{
		debug:    debug,
		listener: listener,
	}, nil
}

// Session is a generic session data store for client handlers.
type Session struct {
	id   int
	data interface{}
}

// Accept waits for incoming connections and forwards the client connect/message/disconnect
// events to provided handlers in a new goroutine.
// This function never returns, unless there is an error while accepting a new connection.
func (l *Listener) Accept(handleMsg func(conn *tls.Conn, session *Session, msg []byte), handleDisconn func(conn *tls.Conn, session *Session)) error {
	for {
		conn, err := l.listener.Accept()
		if err != nil {
			return fmt.Errorf("error while accepting a new connection from a client: %v", err)
			// todo: it might not be appropriate to break the loop on recoverable errors (like client disconnect during handshake)
			// the underlying fd.accept() does some basic recovery though we might need more: http://golang.org/src/net/fd_unix.go
		}
		tlsconn, _ := conn.(*tls.Conn) // todo: check ok
		if l.debug {
			log.Println("Client connected: listening for messages from client IP:", conn.RemoteAddr())
		}
		go handleClient(tlsconn, l.debug, handleMsg, handleDisconn)
	}
}

// handleClient waits for messages from the connected client and forwards the client message/disconnect
// events to provided handlers in a new goroutine.
// This function never returns, unless there is an error while reading from the channel or the client disconnects.
func handleClient(conn *tls.Conn, debug bool, handleMsg func(conn *tls.Conn, session *Session, msg []byte), handleDisconn func(conn *tls.Conn, session *Session)) {
	defer conn.Close()
	if debug {
		defer log.Println("Closed connection to client with IP:", conn.RemoteAddr())
	}

	session := &Session{id: 1}
	reader := bufio.NewReader(conn)

	for {
		err := conn.SetReadDeadline(time.Now().Add(time.Minute * 5))

		// read the content length header
		line, err := reader.ReadSlice('|') // todo: remove this and use the readslice source code directly not to have another wrapper
		if err != nil {
			log.Fatalln("Client read error: ", err)
			break
		}

		// calculate the content length
		n, err := strconv.Atoi(string(line[:len(line)-1]))
		if err != nil || n == 0 {
			log.Fatalln("Client read error: invalid content lenght header sent or content lenght mismatch: ", err)
			break
		}

		// read the message content
		if debug {
			log.Println("Starting to read message content of bytes: ", n)
		}
		msg := make([]byte, n)
		total := 0
		for total < n {
			// todo: log here in case it gets stuck, pumping up cpu usage!
			i, err := reader.Read(msg)
			if err != nil {
				log.Fatalln("Error while reading incoming message: ", err)
				break
			}
			total += i
		}
		if err != nil {
			log.Fatalln("Error while reading incoming message: ", err)
			break
		}
		if debug {
			log.Printf("Read %v bytes message '%v' from client with IP: %v\n", n, string(msg), conn.RemoteAddr())
		}

		if n == 4 && bytes.Equal(msg, ping) {
			continue
		}

		if n == 5 && bytes.Equal(msg, closed) {
			go handleDisconn(conn, session)
			return
		}

		go handleMsg(conn, session, msg)
	}
}

// Close closes the listener.
func (l *Listener) Close() error {
	if l.debug {
		defer log.Println("Listener was closed on local network address:", l.listener.Addr())
	}
	return l.listener.Close()
}
<|endoftext|>


<commit_before>package gramework

import (
	"github.com/apex/log"
	"testing"
)

func TestLogErrorfShouldNotPanic(t *testing.T) {
	defer func() {
		e := recover()
		if e != nil {
			t.Logf("panic handled while testing log.errorf: %+#v", e)
			t.FailNow()
		}
	}()
	Errorf("test: %v", []string{"test"})
}

func TestFastHTTPLoggerAdapter(t *testing.T) {
	var apexLogger log.Interface
	apexLogger = Logger
	logger := NewFastHTTPLoggerAdapter(&apexLogger)
	logger.Printf("printed")
}
<commit_msg>Fix by the goimports.<commit_after>package gramework

import (
	"testing"

	"github.com/apex/log"
)

func TestLogErrorfShouldNotPanic(t *testing.T) {
	defer func() {
		e := recover()
		if e != nil {
			t.Logf("panic handled while testing log.errorf: %+#v", e)
			t.FailNow()
		}
	}()
	Errorf("test: %v", []string{"test"})
}

func TestFastHTTPLoggerAdapter(t *testing.T) {
	var apexLogger log.Interface
	apexLogger = Logger
	logger := NewFastHTTPLoggerAdapter(&apexLogger)
	logger.Printf("printed")
}
<|endoftext|>


<commit_before>package ltsvlog

import (
	"bytes"
	"log"
	"math"
	"testing"
)

func TestAppendValueNil(t *testing.T) {
	buf := appendValue(nil, nil)
	want := []byte("<nil>")
	if !bytes.Equal(buf, want) {
		t.Errorf("nil value mismatch. got=%s, want=%s", string(buf), string(want))
	}
}

func TestAppendValueString(t *testing.T) {
	val := "string"
	buf := appendValue(nil, val)
	want := []byte("string")
	if !bytes.Equal(buf, want) {
		t.Errorf("string value mismatch. got=%s, want=%s", string(buf), string(want))
	}
}

func TestAppendValueUint8(t *testing.T) {
	var val uint8 = math.MaxUint8
	buf := appendValue(nil, val)
	want := []byte("255")
	if !bytes.Equal(buf, want) {
		t.Errorf("uint8 value mismatch. got=%s, want=%s", string(buf), string(want))
	}
}

func TestAppendValueUint16(t *testing.T) {
	var val uint16 = math.MaxUint16
	buf := appendValue(nil, val)
	want := []byte("65535")
	if !bytes.Equal(buf, want) {
		t.Errorf("uint16 value mismatch. got=%s, want=%s", string(buf), string(want))
	}
}

func TestAppendValueUint32(t *testing.T) {
	var val uint32 = math.MaxUint32
	buf := appendValue(nil, val)
	want := []byte("4294967295")
	if !bytes.Equal(buf, want) {
		t.Errorf("uint32 value mismatch. got=%s, want=%s", string(buf), string(want))
	}
}

func TestAppendValueUint64(t *testing.T) {
	var val uint64 = math.MaxUint64
	buf := appendValue(nil, val)
	want := []byte("18446744073709551615")
	if !bytes.Equal(buf, want) {
		t.Errorf("uint64 value mismatch. got=%s, want=%s", string(buf), string(want))
	}
}

func TestAppendValueInt8(t *testing.T) {
	var val int8 = math.MaxInt8
	buf := appendValue(nil, val)
	want := []byte("127")
	if !bytes.Equal(buf, want) {
		t.Errorf("int8 value mismatch. got=%s, want=%s", string(buf), string(want))
	}
}

func TestAppendValueInt16(t *testing.T) {
	var val int16 = math.MaxInt16
	buf := appendValue(nil, val)
	want := []byte("32767")
	if !bytes.Equal(buf, want) {
		t.Errorf("int16 value mismatch. got=%s, want=%s", string(buf), string(want))
	}
}

func TestAppendValueInt32(t *testing.T) {
	var val int32 = math.MaxInt32
	buf := appendValue(nil, val)
	want := []byte("2147483647")
	if !bytes.Equal(buf, want) {
		t.Errorf("int32 value mismatch. got=%s, want=%s", string(buf), string(want))
	}
}

func TestAppendValueInt64(t *testing.T) {
	var val int64 = math.MaxInt64
	buf := appendValue(nil, val)
	want := []byte("9223372036854775807")
	if !bytes.Equal(buf, want) {
		t.Errorf("int64 value mismatch. got=%s, want=%s", string(buf), string(want))
	}
}

func TestAppendValueBool(t *testing.T) {
	testCases := []struct {
		val  bool
		want string
	}{
		{val: true, want: "true"},
		{val: false, want: "false"},
	}
	for _, c := range testCases {
		buf := appendValue(nil, c.val)
		want := []byte(c.want)
		if !bytes.Equal(buf, want) {
			t.Errorf("bool value mismatch. got=%s, want=%s", string(buf), string(want))
		}
	}
}

func TestAppendValueFloat32(t *testing.T) {
	testCases := []struct {
		val  float32
		want string
	}{
		{val: 0, want: "0"},
		{val: math.SmallestNonzeroFloat32, want: "1e-45"},
		{val: math.MaxFloat32, want: "3.4028235e+38"},
		{val: -math.SmallestNonzeroFloat32, want: "-1e-45"},
		{val: -math.MaxFloat32, want: "-3.4028235e+38"},
		{val: float32(math.NaN()), want: "NaN"},
	}
	for _, c := range testCases {
		buf := appendValue(nil, c.val)
		want := []byte(c.want)
		if !bytes.Equal(buf, want) {
			t.Errorf("float32 value mismatch. got=%s, want=%s", string(buf), string(want))
		}
	}
}

func TestAppendValueFloat64(t *testing.T) {
	testCases := []struct {
		val  float64
		want string
	}{
		{val: 0, want: "0"},
		{val: math.SmallestNonzeroFloat64, want: "5e-324"},
		{val: math.MaxFloat64, want: "1.7976931348623157e+308"},
		{val: -math.SmallestNonzeroFloat64, want: "-5e-324"},
		{val: -math.MaxFloat64, want: "-1.7976931348623157e+308"},
		{val: float64(math.NaN()), want: "NaN"},
	}
	for _, c := range testCases {
		buf := appendValue(nil, c.val)
		want := []byte(c.want)
		if !bytes.Equal(buf, want) {
			t.Errorf("float64 value mismatch. got=%s, want=%s", string(buf), string(want))
		}
	}
}

func BenchmarkLTSVLog(b *testing.B) {
	w := new(bytes.Buffer)
	logger := NewLTSVLogger(w, false)
	for i := 0; i < b.N; i++ {
		logger.Info(LV{"msg", "sample log message"}, LV{"key1", "value1"}, LV{"key2", "value2"})
	}
}

// NOTE: This does not produce a proper LTSV log since a log record does not have the "time: label.
// This is used just for benchmark comparison.
func BenchmarkStandardLog(b *testing.B) {
	w := new(bytes.Buffer)
	logger := log.New(w, "", log.LstdFlags|log.Lmicroseconds)
	for i := 0; i < b.N; i++ {
		logger.Printf("level:Info\tmsg:sample log message\tkey1:%s\tkey2:%s", "value1", "value2")
	}
}
<commit_msg>Change benchmarks to write to a temporary file<commit_after>package ltsvlog

import (
	"bytes"
	"io/ioutil"
	"log"
	"math"
	"os"
	"testing"
)

func TestAppendValueNil(t *testing.T) {
	buf := appendValue(nil, nil)
	want := []byte("<nil>")
	if !bytes.Equal(buf, want) {
		t.Errorf("nil value mismatch. got=%s, want=%s", string(buf), string(want))
	}
}

func TestAppendValueString(t *testing.T) {
	val := "string"
	buf := appendValue(nil, val)
	want := []byte("string")
	if !bytes.Equal(buf, want) {
		t.Errorf("string value mismatch. got=%s, want=%s", string(buf), string(want))
	}
}

func TestAppendValueUint8(t *testing.T) {
	var val uint8 = math.MaxUint8
	buf := appendValue(nil, val)
	want := []byte("255")
	if !bytes.Equal(buf, want) {
		t.Errorf("uint8 value mismatch. got=%s, want=%s", string(buf), string(want))
	}
}

func TestAppendValueUint16(t *testing.T) {
	var val uint16 = math.MaxUint16
	buf := appendValue(nil, val)
	want := []byte("65535")
	if !bytes.Equal(buf, want) {
		t.Errorf("uint16 value mismatch. got=%s, want=%s", string(buf), string(want))
	}
}

func TestAppendValueUint32(t *testing.T) {
	var val uint32 = math.MaxUint32
	buf := appendValue(nil, val)
	want := []byte("4294967295")
	if !bytes.Equal(buf, want) {
		t.Errorf("uint32 value mismatch. got=%s, want=%s", string(buf), string(want))
	}
}

func TestAppendValueUint64(t *testing.T) {
	var val uint64 = math.MaxUint64
	buf := appendValue(nil, val)
	want := []byte("18446744073709551615")
	if !bytes.Equal(buf, want) {
		t.Errorf("uint64 value mismatch. got=%s, want=%s", string(buf), string(want))
	}
}

func TestAppendValueInt8(t *testing.T) {
	var val int8 = math.MaxInt8
	buf := appendValue(nil, val)
	want := []byte("127")
	if !bytes.Equal(buf, want) {
		t.Errorf("int8 value mismatch. got=%s, want=%s", string(buf), string(want))
	}
}

func TestAppendValueInt16(t *testing.T) {
	var val int16 = math.MaxInt16
	buf := appendValue(nil, val)
	want := []byte("32767")
	if !bytes.Equal(buf, want) {
		t.Errorf("int16 value mismatch. got=%s, want=%s", string(buf), string(want))
	}
}

func TestAppendValueInt32(t *testing.T) {
	var val int32 = math.MaxInt32
	buf := appendValue(nil, val)
	want := []byte("2147483647")
	if !bytes.Equal(buf, want) {
		t.Errorf("int32 value mismatch. got=%s, want=%s", string(buf), string(want))
	}
}

func TestAppendValueInt64(t *testing.T) {
	var val int64 = math.MaxInt64
	buf := appendValue(nil, val)
	want := []byte("9223372036854775807")
	if !bytes.Equal(buf, want) {
		t.Errorf("int64 value mismatch. got=%s, want=%s", string(buf), string(want))
	}
}

func TestAppendValueBool(t *testing.T) {
	testCases := []struct {
		val  bool
		want string
	}{
		{val: true, want: "true"},
		{val: false, want: "false"},
	}
	for _, c := range testCases {
		buf := appendValue(nil, c.val)
		want := []byte(c.want)
		if !bytes.Equal(buf, want) {
			t.Errorf("bool value mismatch. got=%s, want=%s", string(buf), string(want))
		}
	}
}

func TestAppendValueFloat32(t *testing.T) {
	testCases := []struct {
		val  float32
		want string
	}{
		{val: 0, want: "0"},
		{val: math.SmallestNonzeroFloat32, want: "1e-45"},
		{val: math.MaxFloat32, want: "3.4028235e+38"},
		{val: -math.SmallestNonzeroFloat32, want: "-1e-45"},
		{val: -math.MaxFloat32, want: "-3.4028235e+38"},
		{val: float32(math.NaN()), want: "NaN"},
	}
	for _, c := range testCases {
		buf := appendValue(nil, c.val)
		want := []byte(c.want)
		if !bytes.Equal(buf, want) {
			t.Errorf("float32 value mismatch. got=%s, want=%s", string(buf), string(want))
		}
	}
}

func TestAppendValueFloat64(t *testing.T) {
	testCases := []struct {
		val  float64
		want string
	}{
		{val: 0, want: "0"},
		{val: math.SmallestNonzeroFloat64, want: "5e-324"},
		{val: math.MaxFloat64, want: "1.7976931348623157e+308"},
		{val: -math.SmallestNonzeroFloat64, want: "-5e-324"},
		{val: -math.MaxFloat64, want: "-1.7976931348623157e+308"},
		{val: float64(math.NaN()), want: "NaN"},
	}
	for _, c := range testCases {
		buf := appendValue(nil, c.val)
		want := []byte(c.want)
		if !bytes.Equal(buf, want) {
			t.Errorf("float64 value mismatch. got=%s, want=%s", string(buf), string(want))
		}
	}
}

func BenchmarkLTSVLog(b *testing.B) {
	tmpfile, err := ioutil.TempFile("", "benchmark")
	if err != nil {
		b.Fatal(err)
	}
	defer os.Remove(tmpfile.Name())

	logger := NewLTSVLogger(tmpfile, false)
	for i := 0; i < b.N; i++ {
		logger.Info(LV{"msg", "sample log message"}, LV{"key1", "value1"}, LV{"key2", "value2"})
	}
}

// NOTE: This does not produce a proper LTSV log since a log record does not have the "time: label.
// This is used just for benchmark comparison.
func BenchmarkStandardLog(b *testing.B) {
	tmpfile, err := ioutil.TempFile("", "benchmark")
	if err != nil {
		b.Fatal(err)
	}
	defer os.Remove(tmpfile.Name())

	logger := log.New(tmpfile, "", log.LstdFlags|log.Lmicroseconds)
	for i := 0; i < b.N; i++ {
		logger.Printf("level:Info\tmsg:sample log message\tkey1:%s\tkey2:%s", "value1", "value2")
	}
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"github.com/boivie/lovebeat/alert"
	"github.com/boivie/lovebeat/backend"
	"github.com/boivie/lovebeat/config"
	"github.com/boivie/lovebeat/dashboard"
	"github.com/boivie/lovebeat/httpapi"
	"github.com/boivie/lovebeat/metrics"
	"github.com/boivie/lovebeat/service"
	"github.com/boivie/lovebeat/tcpapi"
	"github.com/boivie/lovebeat/udpapi"
	"github.com/gorilla/mux"
	"github.com/op/go-logging"
	"log/syslog"
	"net/http"
	"os"
	"os/signal"
	"runtime"
	"strings"
	"syscall"
)

var log = logging.MustGetLogger("lovebeat")

const (
	VERSION = "0.8.0"
)

var (
	debug       = flag.Bool("debug", false, "Enable debug logs")
	showVersion = flag.Bool("version", false, "Print version string")
	cfgFile     = flag.String("config", "/etc/lovebeat.cfg", "Configuration file")
	useSyslog   = flag.Bool("syslog", false, "Log to syslog instead of stderr")
)

var (
	signalchan = make(chan os.Signal, 1)
)

func signalHandler(be backend.Backend) {
	for {
		select {
		case sig := <-signalchan:
			fmt.Printf("!! Caught signal %d... shutting down\n", sig)
			be.Sync()
			return
		}
	}
}

func httpServer(cfg *config.ConfigBind, svcs *service.Services) {
	rtr := mux.NewRouter()
	httpapi.Register(rtr, svcs.GetClient())
	dashboard.Register(rtr, svcs.GetClient())
	http.Handle("/", rtr)
	log.Info("HTTP listening on %s\n", cfg.Listen)
	http.ListenAndServe(cfg.Listen, nil)
}

func getHostname() string {
	var hostname, err = os.Hostname()
	if err != nil {
		return fmt.Sprintf("unknown_%d", os.Getpid())
	}
	return strings.Split(hostname, ".")[0]
}

func main() {
	flag.Parse()

	if *debug {
		logging.SetLevel(logging.DEBUG, "lovebeat")
	} else {
		logging.SetLevel(logging.INFO, "lovebeat")
	}
	if *useSyslog {
		var backend, err = logging.NewSyslogBackendPriority("lovebeat", syslog.LOG_DAEMON)
		if err != nil {
			panic(err)
		}
		logging.SetBackend(logging.AddModuleLevel(backend))
	} else {
		var format = logging.MustStringFormatter("%{level} %{message}")
		logging.SetFormatter(format)
	}
	log.Debug("Debug logs enabled")

	if *showVersion {
		fmt.Printf("lovebeats v%s (built w/%s)\n", VERSION, runtime.Version())
		return
	}

	var cfg = config.ReadConfig(*cfgFile)

	var hostname = getHostname()
	log.Info("Lovebeat v%s started as host %s, PID %d", VERSION, hostname, os.Getpid())

	m := metrics.New(&cfg.Metrics)

	var be = backend.NewFileBackend(&cfg.Database, m)
	var alerters = []alert.Alerter{alert.NewMailAlerter(&cfg.Mail),
		alert.NewWebhooksAlerter()}
	var svcs = service.NewServices(be, alerters, m)

	signal.Notify(signalchan, syscall.SIGTERM)
	signal.Notify(signalchan, os.Interrupt)

	go svcs.Monitor()
	go httpServer(&cfg.Http, svcs)
	go udpapi.Listener(&cfg.Udp, svcs.GetClient())
	go tcpapi.Listener(&cfg.Tcp, svcs.GetClient())

	// Ensure that the 'all' view exists
	svcs.GetClient().CreateOrUpdateView("all", "", "", "")

	m.IncCounter("started.count")

	signalHandler(be)
}
<commit_msg>Printing current working directory<commit_after>package main

import (
	"flag"
	"fmt"
	"github.com/boivie/lovebeat/alert"
	"github.com/boivie/lovebeat/backend"
	"github.com/boivie/lovebeat/config"
	"github.com/boivie/lovebeat/dashboard"
	"github.com/boivie/lovebeat/httpapi"
	"github.com/boivie/lovebeat/metrics"
	"github.com/boivie/lovebeat/service"
	"github.com/boivie/lovebeat/tcpapi"
	"github.com/boivie/lovebeat/udpapi"
	"github.com/gorilla/mux"
	"github.com/op/go-logging"
	"log/syslog"
	"net/http"
	"os"
	"os/signal"
	"runtime"
	"strings"
	"syscall"
)

var log = logging.MustGetLogger("lovebeat")

const (
	VERSION = "0.8.0"
)

var (
	debug       = flag.Bool("debug", false, "Enable debug logs")
	showVersion = flag.Bool("version", false, "Print version string")
	cfgFile     = flag.String("config", "/etc/lovebeat.cfg", "Configuration file")
	useSyslog   = flag.Bool("syslog", false, "Log to syslog instead of stderr")
)

var (
	signalchan = make(chan os.Signal, 1)
)

func signalHandler(be backend.Backend) {
	for {
		select {
		case sig := <-signalchan:
			fmt.Printf("!! Caught signal %d... shutting down\n", sig)
			be.Sync()
			return
		}
	}
}

func httpServer(cfg *config.ConfigBind, svcs *service.Services) {
	rtr := mux.NewRouter()
	httpapi.Register(rtr, svcs.GetClient())
	dashboard.Register(rtr, svcs.GetClient())
	http.Handle("/", rtr)
	log.Info("HTTP listening on %s\n", cfg.Listen)
	http.ListenAndServe(cfg.Listen, nil)
}

func getHostname() string {
	var hostname, err = os.Hostname()
	if err != nil {
		return fmt.Sprintf("unknown_%d", os.Getpid())
	}
	return strings.Split(hostname, ".")[0]
}

func main() {
	flag.Parse()

	if *debug {
		logging.SetLevel(logging.DEBUG, "lovebeat")
	} else {
		logging.SetLevel(logging.INFO, "lovebeat")
	}
	if *useSyslog {
		var backend, err = logging.NewSyslogBackendPriority("lovebeat", syslog.LOG_DAEMON)
		if err != nil {
			panic(err)
		}
		logging.SetBackend(logging.AddModuleLevel(backend))
	} else {
		var format = logging.MustStringFormatter("%{level} %{message}")
		logging.SetFormatter(format)
	}
	log.Debug("Debug logs enabled")

	if *showVersion {
		fmt.Printf("lovebeats v%s (built w/%s)\n", VERSION, runtime.Version())
		return
	}

	var cfg = config.ReadConfig(*cfgFile)

	var hostname = getHostname()
	log.Info("Lovebeat v%s started as host %s, PID %d", VERSION, hostname, os.Getpid())
	wd, _ := os.Getwd()
	log.Info("Running from %s", wd)

	m := metrics.New(&cfg.Metrics)

	var be = backend.NewFileBackend(&cfg.Database, m)
	var alerters = []alert.Alerter{alert.NewMailAlerter(&cfg.Mail),
		alert.NewWebhooksAlerter()}
	var svcs = service.NewServices(be, alerters, m)

	signal.Notify(signalchan, syscall.SIGTERM)
	signal.Notify(signalchan, os.Interrupt)

	go svcs.Monitor()
	go httpServer(&cfg.Http, svcs)
	go udpapi.Listener(&cfg.Udp, svcs.GetClient())
	go tcpapi.Listener(&cfg.Tcp, svcs.GetClient())

	// Ensure that the 'all' view exists
	svcs.GetClient().CreateOrUpdateView("all", "", "", "")

	m.IncCounter("started.count")

	signalHandler(be)
}
<|endoftext|>