<commit_before>package main

import (
	"bytes"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"strconv"
)

type Request struct {
	hash string
	size int
}

func readFromFile(filename string, size int) *Avatar {
	file, err := os.Open(filename)
	if err != nil {
		log.Printf("Error reading file: %v", err)
		return nil
	}
	defer file.Close()
	avatar := readImage(file)
	err = scale(avatar, size)
	if err != nil {
		log.Printf("Could not scale image: %v", err)
		return nil // don't return the image, if we can't scale it it is probably corrupt
	}

	avatar.cacheControl = "max-age=300"
	if info, e := os.Stat(filename); e == nil {
		timestamp := info.ModTime().UTC()
		avatar.lastModified = timestamp.Format(http.TimeFormat)
	} else {
		avatar.lastModified = "Sat, 1 Jan 2000 12:00:00 GMT"
	}
	return avatar
}

func retrieveFromLocal(request Request) *Avatar {
	return readFromFile(createAvatarPath(request.hash), request.size)
}

// Retrieves the avatar from the remote service, returning nil if there is no avatar or it could not be retrieved
func retrieveFromRemote(request Request) *Avatar {
	if remoteUrl == "" {
		return nil
	}
	options := fmt.Sprintf("s=%d", request.size)
	if remoteDefault != "" {
		options += "&d=" + remoteDefault
	}
	remote := remoteUrl + "/" + request.hash + "?" + options
	log.Printf("Retrieving from: %s", remote)
	resp, err2 := http.Get(remote)
	if err2 != nil {
		log.Printf("Remote lookup of %s failed with error: %s", remote, err2)
		return nil
	}
	if resp.StatusCode == 404 {
		log.Printf("Avatar not found on remote")
		return nil
	}
	avatar := readImage(resp.Body)
	avatar.size = request.size // assume image is scaled by remote service
	avatar.cacheControl = resp.Header.Get("Cache-Control")
	avatar.lastModified = resp.Header.Get("Last-Modified")
	return avatar
}

func writeAvatarResult(w http.ResponseWriter, avatar *Avatar) {
	setHeaderField(w, "Last-Modified", avatar.lastModified)
	setHeaderField(w, "Cache-Control", avatar.cacheControl)
	b := bytes.NewBuffer(avatar.data)
	_, err := io.Copy(w, b)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

func retrieveImage(request Request, w http.ResponseWriter, r *http.Request) *Avatar {
	avatar := retrieveFromLocal(request)
	if avatar == nil {
		avatar = retrieveFromRemote(request)
	}
	if avatar == nil {
		avatar = readFromFile(defaultImage, request.size)
	}
	if avatar == nil {
		avatar = readFromFile("resources/mm", request.size)
	}
	return avatar
}

func loadImage(request Request, w http.ResponseWriter, r *http.Request) {
	log.Printf("Loading image: %v", request)
	avatar := retrieveImage(request, w, r)
	if avatar == nil {
		http.NotFound(w, r)
	} else {
		writeAvatarResult(w, avatar)
	}
}

func avatarHandler(w http.ResponseWriter, r *http.Request, title string) {
	r.ParseForm()
	sizeParam := r.FormValue("s")
	size := 80
	if sizeParam != "" {
		if s, err := strconv.Atoi(sizeParam); err == nil {
			size = max(min(s, maxSize), minSize)
		}
	}
//	defaultParam := r.FormValue("d")

	loadImage(Request{hash: title, size: size}, w, r)
}
<commit_msg>Added support for requests with ?d=404. With this we can run tests with another intravatar instance as remote.<commit_after>package main

import (
	"bytes"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"strconv"
)

type Request struct {
	hash string
	size int
	dflt string
}

const (
	d_404 = "404"
)

func readFromFile(filename string, size int) *Avatar {
	file, err := os.Open(filename)
	if err != nil {
		log.Printf("Error reading file: %v", err)
		return nil
	}
	defer file.Close()
	avatar := readImage(file)
	err = scale(avatar, size)
	if err != nil {
		log.Printf("Could not scale image: %v", err)
		return nil // don't return the image, if we can't scale it it is probably corrupt
	}

	avatar.cacheControl = "max-age=300"
	if info, e := os.Stat(filename); e == nil {
		timestamp := info.ModTime().UTC()
		avatar.lastModified = timestamp.Format(http.TimeFormat)
	} else {
		avatar.lastModified = "Sat, 1 Jan 2000 12:00:00 GMT"
	}
	return avatar
}

func retrieveFromLocal(request Request) *Avatar {
	return readFromFile(createAvatarPath(request.hash), request.size)
}

// Retrieves the avatar from the remote service, returning nil if there is no avatar or it could not be retrieved
func retrieveFromRemote(request Request) *Avatar {
	if remoteUrl == "" {
		return nil
	}
	options := fmt.Sprintf("s=%d", request.size)
	dflt := remoteDefault
	if request.dflt != "" {
		dflt = request.dflt
	}
	if dflt != "" {
		options += "&d=" + dflt
	}
	remote := remoteUrl + "/" + request.hash + "?" + options
	log.Printf("Retrieving from: %s", remote)
	resp, err2 := http.Get(remote)
	if err2 != nil {
		log.Printf("Remote lookup of %s failed with error: %s", remote, err2)
		return nil
	}
	if resp.StatusCode == 404 {
		log.Printf("Avatar not found on remote")
		return nil
	}
	avatar := readImage(resp.Body)
	avatar.size = request.size // assume image is scaled by remote service
	avatar.cacheControl = resp.Header.Get("Cache-Control")
	avatar.lastModified = resp.Header.Get("Last-Modified")
	return avatar
}

func writeAvatarResult(w http.ResponseWriter, avatar *Avatar) {
	setHeaderField(w, "Last-Modified", avatar.lastModified)
	setHeaderField(w, "Cache-Control", avatar.cacheControl)
	b := bytes.NewBuffer(avatar.data)
	_, err := io.Copy(w, b)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

func retrieveImage(request Request, w http.ResponseWriter, r *http.Request) *Avatar {
	avatar := retrieveFromLocal(request)
	if avatar == nil {
		avatar = retrieveFromRemote(request)
	}
	if avatar == nil && request.dflt != d_404 {
		avatar = readFromFile(defaultImage, request.size)
	}
	if avatar == nil && request.dflt != d_404 {
		avatar = readFromFile("resources/mm", request.size)
	}
	return avatar
}

func loadImage(request Request, w http.ResponseWriter, r *http.Request) {
	log.Printf("Loading image: %v", request)
	avatar := retrieveImage(request, w, r)
	if avatar == nil {
		http.NotFound(w, r)
	} else {
		writeAvatarResult(w, avatar)
	}
}

// checks if dflt is a valid default image and only then returns it
// otherwise an empty string is returned
func validDefault(dflt string) string {
	if dflt == d_404 {
		return dflt
	}
	return ""
}

func avatarHandler(w http.ResponseWriter, r *http.Request, hash string) {
	r.ParseForm()
	sizeParam := r.FormValue("s")
	size := 80
	if sizeParam != "" {
		if s, err := strconv.Atoi(sizeParam); err == nil {
			size = max(min(s, maxSize), minSize)
		}
	}
	dflt := validDefault(r.FormValue("d"))

	loadImage(Request{hash: hash, size: size, dflt: dflt}, w, r)
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"os"
	"os/user"
	"path"
	"strings"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/route53"
)

type recordSet struct {
	name         string
	value        string // ip
	rsType       string
	ttl          int64
	hostedZoneId string
}

const progName = "dyndns53"

func main() {
	log.SetPrefix(progName + ": ")
	log.SetFlags(0)

	var recSet recordSet
	var logFn string
	flag.StringVar(&recSet.name, "name", "", "record set name (domain)")
	flag.StringVar(&recSet.rsType, "type", "A", `record set type; "A" or "AAAA"`)
	flag.Int64Var(&recSet.ttl, "ttl", 300, "TTL (time to live) in seconds")
	flag.StringVar(&recSet.hostedZoneId, "zone", "", "hosted zone id")
	flag.StringVar(&logFn, "log", "", "file name to log to (default is stdout)")
	if len(os.Args) == 1 {
		flag.Usage()
		os.Exit(1)
	}
	flag.Parse()

	recSet.name = strings.TrimSuffix(recSet.name, ".") + "." // append . if missing
	if err := recSet.validate(); err != nil {
		log.Fatal(err)
	}

	if logFn != "" {
		f, err := os.OpenFile(logFn, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
		if err != nil {
			log.Fatalf("log file: %v", err)
		}
		defer f.Close()

		log.SetFlags(log.LstdFlags) // restore standard flags
		log.SetOutput(f)            // log to file
	}

	var err error
	recSet.value, err = currentIPAddress()
	if err != nil {
		log.Fatal(err)
	}
	log.Println("current IP address is", recSet.value)

	domain := strings.TrimSuffix(recSet.name, ".")
	if domainResolvesToIP(domain, recSet.value) {
		log.Println("nothing to do")
		os.Exit(0)
	}

	_, err = recSet.upsert()
	if err != nil {
		log.Fatal(err)
	}
	log.Println("upsert request sent")
}

func currentIPAddress() (string, error) {
	resp, err := http.Get("http://checkip.amazonaws.com/")
	if err != nil {
		return "", fmt.Errorf("currentIPAddress: %v", err)
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", fmt.Errorf("currentIPAddress: %v", err)
	}
	ip := strings.TrimSpace(string(body))
	return ip, nil
}

func domainResolvesToIP(domain, checkIP string) bool {
	ips, err := net.LookupIP(domain)
	if err != nil {
		return false
	}
	for _, ip := range ips {
		if ip.String() == checkIP {
			return true
		}
	}
	return false
}

func (rs *recordSet) upsert() (*route53.ChangeResourceRecordSetsOutput, error) {
	usr, err := user.Current()
	if err != nil {
		return nil, fmt.Errorf("(*recordSet).upsert: %v", err)
	}
	credentialsPath := path.Join(usr.HomeDir, ".aws", "credentials")
	credentials := credentials.NewSharedCredentials(credentialsPath, progName)

	sess, err := session.NewSession()
	if err != nil {
		return nil, fmt.Errorf("(*recordSet).upsert: %v", err)
	}

	svc := route53.New(sess, &aws.Config{Credentials: credentials})
	params := &route53.ChangeResourceRecordSetsInput{
		ChangeBatch: &route53.ChangeBatch{
			Changes: []*route53.Change{
				{
					Action: aws.String("UPSERT"),
					ResourceRecordSet: &route53.ResourceRecordSet{
						Name: aws.String(rs.name),
						Type: aws.String(rs.rsType),
						TTL:  aws.Int64(rs.ttl),
						ResourceRecords: []*route53.ResourceRecord{
							{
								Value: aws.String(rs.value),
							},
						},
					},
				},
			},
		},
		HostedZoneId: aws.String(rs.hostedZoneId),
	}
	resp, err := svc.ChangeResourceRecordSets(params)
	if err != nil {
		return nil, fmt.Errorf("(*recordSet).upsert: %v", err)
	}
	return resp, nil
}

func (rs *recordSet) validate() error {
	if rs.name == "" {
		return fmt.Errorf("missing record set name")
	}
	if !strings.HasSuffix(rs.name, ".") {
		return fmt.Errorf(`record set name must end with a "."`)
	}
	if rs.rsType == "" {
		return fmt.Errorf("missing record set type")
	}
	if rs.rsType != "A" && rs.rsType != "AAAA" {
		return fmt.Errorf("invalid record set type: %s", rs.rsType)
	}
	if rs.ttl < 1 {
		return fmt.Errorf("invalid record set TTL: %d", rs.ttl)
	}
	if rs.hostedZoneId == "" {
		return fmt.Errorf("missing hosted zone id")
	}
	return nil
}
<commit_msg>Rename hostedZoneId to hostedZoneID<commit_after>package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"os"
	"os/user"
	"path"
	"strings"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/route53"
)

type recordSet struct {
	name         string
	value        string // ip
	rsType       string
	ttl          int64
	hostedZoneID string
}

const progName = "dyndns53"

func main() {
	log.SetPrefix(progName + ": ")
	log.SetFlags(0)

	var recSet recordSet
	var logFn string
	flag.StringVar(&recSet.name, "name", "", "record set name (domain)")
	flag.StringVar(&recSet.rsType, "type", "A", `record set type; "A" or "AAAA"`)
	flag.Int64Var(&recSet.ttl, "ttl", 300, "TTL (time to live) in seconds")
	flag.StringVar(&recSet.hostedZoneID, "zone", "", "hosted zone id")
	flag.StringVar(&logFn, "log", "", "file name to log to (default is stdout)")
	if len(os.Args) == 1 {
		flag.Usage()
		os.Exit(1)
	}
	flag.Parse()

	recSet.name = strings.TrimSuffix(recSet.name, ".") + "." // append . if missing
	if err := recSet.validate(); err != nil {
		log.Fatal(err)
	}

	if logFn != "" {
		f, err := os.OpenFile(logFn, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
		if err != nil {
			log.Fatalf("log file: %v", err)
		}
		defer f.Close()

		log.SetFlags(log.LstdFlags) // restore standard flags
		log.SetOutput(f)            // log to file
	}

	var err error
	recSet.value, err = currentIPAddress()
	if err != nil {
		log.Fatal(err)
	}
	log.Println("current IP address is", recSet.value)

	domain := strings.TrimSuffix(recSet.name, ".")
	if domainResolvesToIP(domain, recSet.value) {
		log.Println("nothing to do")
		os.Exit(0)
	}

	_, err = recSet.upsert()
	if err != nil {
		log.Fatal(err)
	}
	log.Println("upsert request sent")
}

func currentIPAddress() (string, error) {
	resp, err := http.Get("http://checkip.amazonaws.com/")
	if err != nil {
		return "", fmt.Errorf("currentIPAddress: %v", err)
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", fmt.Errorf("currentIPAddress: %v", err)
	}
	ip := strings.TrimSpace(string(body))
	return ip, nil
}

func domainResolvesToIP(domain, checkIP string) bool {
	ips, err := net.LookupIP(domain)
	if err != nil {
		return false
	}
	for _, ip := range ips {
		if ip.String() == checkIP {
			return true
		}
	}
	return false
}

func (rs *recordSet) upsert() (*route53.ChangeResourceRecordSetsOutput, error) {
	usr, err := user.Current()
	if err != nil {
		return nil, fmt.Errorf("(*recordSet).upsert: %v", err)
	}
	credentialsPath := path.Join(usr.HomeDir, ".aws", "credentials")
	credentials := credentials.NewSharedCredentials(credentialsPath, progName)

	sess, err := session.NewSession()
	if err != nil {
		return nil, fmt.Errorf("(*recordSet).upsert: %v", err)
	}

	svc := route53.New(sess, &aws.Config{Credentials: credentials})
	params := &route53.ChangeResourceRecordSetsInput{
		ChangeBatch: &route53.ChangeBatch{
			Changes: []*route53.Change{
				{
					Action: aws.String("UPSERT"),
					ResourceRecordSet: &route53.ResourceRecordSet{
						Name: aws.String(rs.name),
						Type: aws.String(rs.rsType),
						TTL:  aws.Int64(rs.ttl),
						ResourceRecords: []*route53.ResourceRecord{
							{
								Value: aws.String(rs.value),
							},
						},
					},
				},
			},
		},
		HostedZoneId: aws.String(rs.hostedZoneID),
	}
	resp, err := svc.ChangeResourceRecordSets(params)
	if err != nil {
		return nil, fmt.Errorf("(*recordSet).upsert: %v", err)
	}
	return resp, nil
}

func (rs *recordSet) validate() error {
	if rs.name == "" {
		return fmt.Errorf("missing record set name")
	}
	if !strings.HasSuffix(rs.name, ".") {
		return fmt.Errorf(`record set name must end with a "."`)
	}
	if rs.rsType == "" {
		return fmt.Errorf("missing record set type")
	}
	if rs.rsType != "A" && rs.rsType != "AAAA" {
		return fmt.Errorf("invalid record set type: %s", rs.rsType)
	}
	if rs.ttl < 1 {
		return fmt.Errorf("invalid record set TTL: %d", rs.ttl)
	}
	if rs.hostedZoneID == "" {
		return fmt.Errorf("missing hosted zone id")
	}
	return nil
}
<|endoftext|>


<commit_before>package redis2

import (
	"fmt"
	"io"
	"log"
	"net"
	"sync"
	"sync/atomic"
	"time"

	"github.com/gallir/radix.improved/redis"
	"github.com/gallir/smart-relayer/lib"
	"github.com/gallir/smart-relayer/redis"
)

// Client is the thread that connect to the remote redis server
type Client struct {
	sync.Mutex
	config             lib.RelayerConfig
	mode               int
	ready              int32
	connected          int32
	conn               net.Conn
	buf                *lib.NetBuffedReadWriter
	requestChan        chan *lib.Request // The relayer sends the requests via this channel
	database           int               // The current selected database
	queueChan          chan *lib.Request // Requests sent to the Redis server, some pending of responses
	lastConnectFailure time.Time
	connectedAt        time.Time
	failures           int64
	pipelined          int
}

// NewClient creates a new client that connect to a Redis server
func NewClient(c *lib.RelayerConfig) lib.RelayerClient {
	clt := &Client{}
	clt.Reload(c)

	clt.requestChan = make(chan *lib.Request, requestBufferSize)
	clt.setReady(true)
	go clt.listen(clt.requestChan)
	clt.connect()
	lib.Debugf("Client %s for target %s ready", clt.config.Listen, clt.config.Host())

	return clt
}

func (clt *Client) Reload(c *lib.RelayerConfig) {
	clt.Lock()
	defer clt.Unlock()

	if clt.isReady() {
		clt.flush(true)
	}
	clt.config = *c
	clt.mode = clt.config.Type()
}

func (clt *Client) setReady(s bool) {
	if s {
		atomic.StoreInt32(&clt.ready, 1)
	} else {
		atomic.StoreInt32(&clt.ready, 0)
	}
}

func (clt *Client) isReady() bool {
	if atomic.LoadInt32(&clt.ready) == 1 {
		return true
	}
	return false
}

func (clt *Client) setConnected(s bool) {
	if s {
		atomic.StoreInt32(&clt.connected, 1)
	} else {
		atomic.StoreInt32(&clt.connected, 0)
	}
}

func (clt *Client) isConnected() bool {
	if atomic.LoadInt32(&clt.connected) == 1 {
		return true
	}
	return false
}

func (clt *Client) connect() bool {
	clt.Lock()
	defer clt.Unlock()

	if clt.failures > 10 {
		// The pool manager will see we are invalid and kill us
		clt.setReady(false)
		return false
	}
	if clt.lastConnectFailure.Add(200 * time.Millisecond).After(time.Now()) {
		// It failed too recently
		return false
	}

	conn, err := net.DialTimeout(clt.config.Scheme(), clt.config.Host(), connectTimeout)
	if err != nil {
		lib.Debugf("Failed to connect to %s", clt.config.Host())
		clt.conn = nil
		clt.lastConnectFailure = time.Now()
		clt.failures++
		return false
	}
	clt.failures = 0
	clt.connectedAt = time.Now()
	lib.Debugf("Connected to %s", conn.RemoteAddr())
	clt.conn = conn
	clt.queueChan = make(chan *lib.Request, requestBufferSize)
	clt.buf = lib.NewNetReadWriter(conn, time.Duration(clt.config.Timeout)*time.Second, 0)

	go clt.netListener(clt.buf, clt.queueChan)
	clt.setConnected(true)

	return true
}

// Listen for clients' messages from the requestChan
func (clt *Client) listen(ch chan *lib.Request) {
	defer lib.Debugf("Finished Redis client")
	defer func() {
		clt.setReady(false)
	}()

	timer := time.NewTimer(maxIdle)
	defer func() {
		// Clean timer gracefully
		if !timer.Stop() {
			select {
			case <-timer.C:
			default:
			}
		}
	}()

	for {
		select {
		case req, more := <-ch:
			if !more { // exit from the program because the channel was closed
				clt.disconnect()
				return
			}
			_, err := clt.write(req)
			if err != nil {
				log.Println("Error writing:", clt.config.Host(), err)
				sendAsyncResponse(req.ResponseChannel, respKO)
				clt.disconnect()
			}
			timer.Stop()
			timer.Reset(maxIdle)
		case <-timer.C:
			if clt.conn != nil {
				lib.Debugf("Closing by idle %s", clt.config.Host())
				clt.disconnect()
			}
		}
	}
}

// This goroutine listens for incoming answers from the Redis server
func (clt *Client) netListener(buf io.ReadWriter, queue chan *lib.Request) {
	lib.Debugf("Net listener started")
	reader := redis.NewRespReader(buf)
	for req := range queue {
		r := reader.Read()
		if req == nil {
			continue
		}

		if r.Err != nil && r.IsType(redis.IOErr) {
			if redis.IsTimeout(r) {
				log.Printf("Timeout (%d secs) at %s", clt.config.Timeout, clt.config.Host())
			} else {
				log.Printf("Error with server %s connection: %s", clt.config.Host(), r.Err)
			}
			sendAsyncResponse(req.ResponseChannel, respKO) // Send back and error immediately
			clt.disconnect()
			return
		}

		if clt.config.Compress || clt.config.Uncompress {
			r = compress.UResp(r)
		}
		sendAsyncResponse(req.ResponseChannel, r)
	}
	lib.Debugf("Net listener exiting")
}

func (clt *Client) purgeRequests() {
	for {
		select {
		case req := <-clt.queueChan:
			sendAsyncResponse(req.ResponseChannel, respKO)
		default:
			return
		}
	}
}

func (clt *Client) write(r *lib.Request) (int64, error) {
	if !clt.isConnected() && !clt.connect() {
		return 0, fmt.Errorf("Connection failed")
	}

	if r.Command == selectCommand {
		if clt.mode == lib.ModeSmart && clt.database == r.Database { // There is no need to select again
			return 0, nil
		}
		clt.database = r.Database
	} else if clt.database != r.Database {
		changer := redis.NewResp([]interface{}{
			selectCommand,
			fmt.Sprintf("%d", r.Database),
		})
		_, err := changer.WriteTo(clt.buf)
		if err != nil {
			log.Println("Error changing database", err)
			clt.disconnect()
			return 0, fmt.Errorf("Error in select")
		}
		err = clt.flush(false)
		if err != nil {
			return 0, err
		}
		clt.database = r.Database
		clt.queueChan <- nil
	}

	resp := r.Resp
	if clt.config.Compress {
		items, ok := compress.Items(r.Items)
		if ok {
			resp = redis.NewResp(items)
		}
	}

	c, err := resp.WriteTo(clt.buf)
	if err != nil {
		lib.Debugf("Failed in write: %s", err)
		clt.disconnect()
		return 0, err
	}

	err = clt.flush(r.ResponseChannel != nil) // Force flush if it's an sync command
	if err != nil {
		return 0, err
	}

	clt.queueChan <- r
	return c, err
}
func (clt *Client) flush(force bool) error {
	if force || clt.config.Pipeline == 0 && clt.pipelined >= clt.config.Pipeline || len(clt.requestChan) == 0 {
		err := clt.buf.Flush()
		clt.pipelined = 0
		if err != nil {
			lib.Debugf("Failed in flush: %s", err)
			clt.disconnect()
			return err
		}
	} else {
		// Don't flush yet, for pipelining
		clt.pipelined++
	}
	return nil
}

func (clt *Client) disconnect() {
	clt.Lock()
	defer clt.Unlock()

	clt.setConnected(false)
	if clt.queueChan != nil {
		clt.purgeRequests()
		close(clt.queueChan)
		clt.queueChan = nil
	}

	if clt.conn != nil {
		clt.buf.Flush()
		lib.Debugf("Closing connection")
		clt.conn.Close()
		clt.conn = nil
	}

	clt.database = 0
}

func (clt *Client) Exit() {
	clt.Lock()
	defer clt.Unlock()

	clt.setReady(false)
	if clt.requestChan != nil {
		close(clt.requestChan)
		clt.requestChan = nil
	}
}

// IsValid is used for pool management, it ignores disconnect clients
func (clt *Client) IsValid() bool {
	return clt.isReady() && clt.isConnected()
}

func (clt *Client) Send(req interface{}) (e error) {
	r := req.(*lib.Request)
	defer func() {
		r := recover() // To avoid panic due to closed channels
		if r != nil {
			log.Printf("sendRequest: Recovered from error %s, channel length %d", e, len(clt.requestChan))
			e = r.(error)
		}
	}()

	if clt.requestChan == nil {
		lib.Debugf("Nil channel in send request %s", clt.config.Host())
		return errKO
	}

	if !clt.isReady() {
		lib.Debugf("Client not ready %s", clt.config.Host())
		return errKO
	}

	if f := clt.failures; f > 0 {
		if clt.lastConnectFailure.Add(connectTimeout).After(time.Now()) {
			lib.Debugf("Client is failing to connect %s", clt.config.Host())
			return errKO
		}
	}

	if len(clt.requestChan) == requestBufferSize {
		log.Println("Redis overloaded", clt.config.Host())
		return errOverloaded
	}

	clt.requestChan <- r
	return nil
}
<commit_msg>Moved connection to listen goroutine<commit_after>package redis2

import (
	"fmt"
	"io"
	"log"
	"net"
	"sync"
	"sync/atomic"
	"time"

	"github.com/gallir/radix.improved/redis"
	"github.com/gallir/smart-relayer/lib"
	"github.com/gallir/smart-relayer/redis"
)

// Client is the thread that connect to the remote redis server
type Client struct {
	sync.Mutex
	config             lib.RelayerConfig
	mode               int
	ready              int32
	connected          int32
	conn               net.Conn
	buf                *lib.NetBuffedReadWriter
	requestChan        chan *lib.Request // The relayer sends the requests via this channel
	database           int               // The current selected database
	queueChan          chan *lib.Request // Requests sent to the Redis server, some pending of responses
	lastConnectFailure time.Time
	connectedAt        time.Time
	failures           int64
	pipelined          int
}

// NewClient creates a new client that connect to a Redis server
func NewClient(c *lib.RelayerConfig) lib.RelayerClient {
	clt := &Client{}
	clt.Reload(c)

	clt.requestChan = make(chan *lib.Request, requestBufferSize)
	clt.setReady(true)
	go clt.listen(clt.requestChan)
	lib.Debugf("Client %s for target %s ready", clt.config.Listen, clt.config.Host())

	return clt
}

func (clt *Client) Reload(c *lib.RelayerConfig) {
	clt.Lock()
	defer clt.Unlock()

	if clt.isReady() {
		clt.flush(true)
	}
	clt.config = *c
	clt.mode = clt.config.Type()
}

func (clt *Client) setReady(s bool) {
	if s {
		atomic.StoreInt32(&clt.ready, 1)
	} else {
		atomic.StoreInt32(&clt.ready, 0)
	}
}

func (clt *Client) isReady() bool {
	if atomic.LoadInt32(&clt.ready) == 1 {
		return true
	}
	return false
}

func (clt *Client) setConnected(s bool) {
	if s {
		atomic.StoreInt32(&clt.connected, 1)
	} else {
		atomic.StoreInt32(&clt.connected, 0)
	}
}

func (clt *Client) isConnected() bool {
	if atomic.LoadInt32(&clt.connected) == 1 {
		return true
	}
	return false
}

func (clt *Client) connect() bool {
	clt.Lock()
	defer clt.Unlock()

	if clt.failures > 10 {
		// The pool manager will see we are invalid and kill us
		clt.setReady(false)
		return false
	}
	if clt.lastConnectFailure.Add(200 * time.Millisecond).After(time.Now()) {
		// It failed too recently
		return false
	}

	conn, err := net.DialTimeout(clt.config.Scheme(), clt.config.Host(), connectTimeout)
	if err != nil {
		lib.Debugf("Failed to connect to %s", clt.config.Host())
		clt.conn = nil
		clt.lastConnectFailure = time.Now()
		clt.failures++
		return false
	}
	clt.failures = 0
	clt.connectedAt = time.Now()
	lib.Debugf("Connected to %s", conn.RemoteAddr())
	clt.conn = conn
	clt.queueChan = make(chan *lib.Request, requestBufferSize)
	clt.buf = lib.NewNetReadWriter(conn, time.Duration(clt.config.Timeout)*time.Second, 0)

	go clt.netListener(clt.buf, clt.queueChan)
	clt.setConnected(true)

	return true
}

// Listen for clients' messages from the requestChan
func (clt *Client) listen(ch chan *lib.Request) {
	defer lib.Debugf("Finished Redis client")
	defer func() {
		clt.setReady(false)
	}()

	timer := time.NewTimer(maxIdle)
	defer func() {
		// Clean timer gracefully
		if !timer.Stop() {
			select {
			case <-timer.C:
			default:
			}
		}
	}()

	clt.connect()
	for {
		select {
		case req, more := <-ch:
			if !more { // exit from the program because the channel was closed
				clt.disconnect()
				return
			}
			_, err := clt.write(req)
			if err != nil {
				log.Println("Error writing:", clt.config.Host(), err)
				sendAsyncResponse(req.ResponseChannel, respKO)
				clt.disconnect()
			}
			timer.Stop()
			timer.Reset(maxIdle)
		case <-timer.C:
			if clt.conn != nil {
				lib.Debugf("Closing by idle %s", clt.config.Host())
				clt.disconnect()
			}
		}
	}
}

// This goroutine listens for incoming answers from the Redis server
func (clt *Client) netListener(buf io.ReadWriter, queue chan *lib.Request) {
	lib.Debugf("Net listener started")
	reader := redis.NewRespReader(buf)
	for req := range queue {
		r := reader.Read()
		if req == nil {
			continue
		}

		if r.Err != nil && r.IsType(redis.IOErr) {
			if redis.IsTimeout(r) {
				log.Printf("Timeout (%d secs) at %s", clt.config.Timeout, clt.config.Host())
			} else {
				log.Printf("Error with server %s connection: %s", clt.config.Host(), r.Err)
			}
			sendAsyncResponse(req.ResponseChannel, respKO) // Send back and error immediately
			clt.disconnect()
			return
		}

		if clt.config.Compress || clt.config.Uncompress {
			r = compress.UResp(r)
		}
		sendAsyncResponse(req.ResponseChannel, r)
	}
	lib.Debugf("Net listener exiting")
}

func (clt *Client) purgeRequests() {
	for {
		select {
		case req := <-clt.queueChan:
			sendAsyncResponse(req.ResponseChannel, respKO)
		default:
			return
		}
	}
}

func (clt *Client) write(r *lib.Request) (int64, error) {
	if !clt.isConnected() && !clt.connect() {
		return 0, fmt.Errorf("Connection failed")
	}

	if r.Command == selectCommand {
		if clt.mode == lib.ModeSmart && clt.database == r.Database { // There is no need to select again
			return 0, nil
		}
		clt.database = r.Database
	} else if clt.database != r.Database {
		changer := redis.NewResp([]interface{}{
			selectCommand,
			fmt.Sprintf("%d", r.Database),
		})
		_, err := changer.WriteTo(clt.buf)
		if err != nil {
			log.Println("Error changing database", err)
			clt.disconnect()
			return 0, fmt.Errorf("Error in select")
		}
		err = clt.flush(false)
		if err != nil {
			return 0, err
		}
		clt.database = r.Database
		clt.queueChan <- nil
	}

	resp := r.Resp
	if clt.config.Compress {
		items, ok := compress.Items(r.Items)
		if ok {
			resp = redis.NewResp(items)
		}
	}

	c, err := resp.WriteTo(clt.buf)
	if err != nil {
		lib.Debugf("Failed in write: %s", err)
		clt.disconnect()
		return 0, err
	}

	err = clt.flush(r.ResponseChannel != nil) // Force flush if it's an sync command
	if err != nil {
		return 0, err
	}

	clt.queueChan <- r
	return c, err
}
func (clt *Client) flush(force bool) error {
	if force || clt.config.Pipeline == 0 && clt.pipelined >= clt.config.Pipeline || len(clt.requestChan) == 0 {
		err := clt.buf.Flush()
		clt.pipelined = 0
		if err != nil {
			lib.Debugf("Failed in flush: %s", err)
			clt.disconnect()
			return err
		}
	} else {
		// Don't flush yet, for pipelining
		clt.pipelined++
	}
	return nil
}

func (clt *Client) disconnect() {
	clt.Lock()
	defer clt.Unlock()

	clt.setConnected(false)
	if clt.queueChan != nil {
		clt.purgeRequests()
		close(clt.queueChan)
		clt.queueChan = nil
	}

	if clt.conn != nil {
		clt.buf.Flush()
		lib.Debugf("Closing connection")
		clt.conn.Close()
		clt.conn = nil
	}

	clt.database = 0
}

func (clt *Client) Exit() {
	clt.Lock()
	defer clt.Unlock()

	clt.setReady(false)
	if clt.requestChan != nil {
		close(clt.requestChan)
		clt.requestChan = nil
	}
}

// IsValid is used for pool management, it ignores disconnect clients
func (clt *Client) IsValid() bool {
	return clt.isReady() && clt.isConnected()
}

func (clt *Client) Send(req interface{}) (e error) {
	r := req.(*lib.Request)
	defer func() {
		r := recover() // To avoid panic due to closed channels
		if r != nil {
			log.Printf("sendRequest: Recovered from error %s, channel length %d", e, len(clt.requestChan))
			e = r.(error)
		}
	}()

	if clt.requestChan == nil {
		lib.Debugf("Nil channel in send request %s", clt.config.Host())
		return errKO
	}

	if !clt.isReady() {
		lib.Debugf("Client not ready %s", clt.config.Host())
		return errKO
	}

	if f := clt.failures; f > 0 {
		if clt.lastConnectFailure.Add(connectTimeout).After(time.Now()) {
			lib.Debugf("Client is failing to connect %s", clt.config.Host())
			return errKO
		}
	}

	if len(clt.requestChan) == requestBufferSize {
		log.Println("Redis overloaded", clt.config.Host())
		return errOverloaded
	}

	clt.requestChan <- r
	return nil
}
<|endoftext|>


<commit_before>package raft

import (
	"fmt"

	pb "github.com/coreos/etcd/raft/raftpb"
)

const (
	defaultCompactThreshold = 10000
)

type raftLog struct {
	ents             []pb.Entry
	unstable         int64
	committed        int64
	applied          int64
	offset           int64
	snapshot         pb.Snapshot
	unstableSnapshot pb.Snapshot

	// want a compact after the number of entries exceeds the threshold
	// TODO(xiangli) size might be a better criteria
	compactThreshold int64
}

func newLog() *raftLog {
	return &raftLog{
		ents:             make([]pb.Entry, 1),
		unstable:         1,
		committed:        0,
		applied:          0,
		compactThreshold: defaultCompactThreshold,
	}
}

func (l *raftLog) isEmpty() bool {
	return l.offset == 0 && len(l.ents) == 1
}

func (l *raftLog) String() string {
	return fmt.Sprintf("offset=%d committed=%d applied=%d len(ents)=%d", l.offset, l.committed, l.applied, len(l.ents))
}

func (l *raftLog) maybeAppend(index, logTerm, committed int64, ents ...pb.Entry) bool {
	if l.matchTerm(index, logTerm) {
		from := index + 1
		ci := l.findConflict(from, ents)
		switch {
		case ci == -1:
		case ci <= l.committed:
			panic("conflict with committed entry")
		default:
			l.append(ci-1, ents[ci-from:]...)
		}
		if l.committed < committed {
			l.committed = min(committed, l.lastIndex())
		}
		return true
	}
	return false
}

func (l *raftLog) append(after int64, ents ...pb.Entry) int64 {
	l.ents = append(l.slice(l.offset, after+1), ents...)
	l.unstable = min(l.unstable, after+1)
	return l.lastIndex()
}

func (l *raftLog) findConflict(from int64, ents []pb.Entry) int64 {
	for i, ne := range ents {
		if oe := l.at(from + int64(i)); oe == nil || oe.Term != ne.Term {
			return from + int64(i)
		}
	}
	return -1
}

func (l *raftLog) unstableEnts() []pb.Entry {
	ents := l.entries(l.unstable)
	if ents == nil {
		return nil
	}
	cpy := make([]pb.Entry, len(ents))
	copy(cpy, ents)
	return cpy
}

func (l *raftLog) resetUnstable() {
	l.unstable = l.lastIndex() + 1
}

// nextEnts returns all the available entries for execution.
// all the returned entries will be marked as applied.
func (l *raftLog) nextEnts() (ents []pb.Entry) {
	if l.committed > l.applied {
		return l.slice(l.applied+1, l.committed+1)
	}
	return nil
}

func (l *raftLog) resetNextEnts() {
	if l.committed > l.applied {
		l.applied = l.committed
	}
}

func (l *raftLog) lastIndex() int64 {
	return int64(len(l.ents)) - 1 + l.offset
}

func (l *raftLog) term(i int64) int64 {
	if e := l.at(i); e != nil {
		return e.Term
	}
	return -1
}

func (l *raftLog) entries(i int64) []pb.Entry {
	// never send out the first entry
	// first entry is only used for matching
	// prevLogTerm
	if i == l.offset {
		panic("cannot return the first entry in log")
	}
	return l.slice(i, l.lastIndex()+1)
}

func (l *raftLog) isUpToDate(i, term int64) bool {
	e := l.at(l.lastIndex())
	return term > e.Term || (term == e.Term && i >= l.lastIndex())
}

func (l *raftLog) matchTerm(i, term int64) bool {
	if e := l.at(i); e != nil {
		return e.Term == term
	}
	return false
}

func (l *raftLog) maybeCommit(maxIndex, term int64) bool {
	if maxIndex > l.committed && l.term(maxIndex) == term {
		l.committed = maxIndex
		return true
	}
	return false
}

// compact compacts all log entries until i.
// It removes the log entries before i, exclusive.
// i must be not smaller than the index of the first entry
// and not greater than the index of the last entry.
// the number of entries after compaction will be returned.
func (l *raftLog) compact(i int64) int64 {
	if l.isOutOfBounds(i) {
		panic(fmt.Sprintf("compact %d out of bounds [%d:%d]", i, l.offset, l.lastIndex()))
	}
	l.ents = l.slice(i, l.lastIndex()+1)
	l.unstable = max(i+1, l.unstable)
	l.offset = i
	return int64(len(l.ents))
}

func (l *raftLog) snap(d []byte, index, term int64, nodes []int64) {
	l.snapshot = pb.Snapshot{
		Data:  d,
		Nodes: nodes,
		Index: index,
		Term:  term,
	}
}

func (l *raftLog) shouldCompact() bool {
	return (l.applied - l.offset) > l.compactThreshold
}

func (l *raftLog) restore(s pb.Snapshot) {
	l.ents = []pb.Entry{{Term: s.Term}}
	l.unstable = s.Index + 1
	l.committed = s.Index
	l.applied = s.Index
	l.offset = s.Index
	l.snapshot = s
}

func (l *raftLog) at(i int64) *pb.Entry {
	if l.isOutOfBounds(i) {
		return nil
	}
	return &l.ents[i-l.offset]
}

// slice get a slice of log entries from lo through hi-1, inclusive.
func (l *raftLog) slice(lo int64, hi int64) []pb.Entry {
	if lo >= hi {
		return nil
	}
	if l.isOutOfBounds(lo) || l.isOutOfBounds(hi-1) {
		return nil
	}
	return l.ents[lo-l.offset : hi-l.offset]
}

func (l *raftLog) isOutOfBounds(i int64) bool {
	if i < l.offset || i > l.lastIndex() {
		return true
	}
	return false
}

func min(a, b int64) int64 {
	if a > b {
		return b
	}
	return a
}

func max(a, b int64) int64 {
	if a > b {
		return a
	}
	return b
}
<commit_msg>raft: log comment grammar fix<commit_after>package raft

import (
	"fmt"

	pb "github.com/coreos/etcd/raft/raftpb"
)

const (
	defaultCompactThreshold = 10000
)

type raftLog struct {
	ents             []pb.Entry
	unstable         int64
	committed        int64
	applied          int64
	offset           int64
	snapshot         pb.Snapshot
	unstableSnapshot pb.Snapshot

	// want a compact after the number of entries exceeds the threshold
	// TODO(xiangli) size might be a better criteria
	compactThreshold int64
}

func newLog() *raftLog {
	return &raftLog{
		ents:             make([]pb.Entry, 1),
		unstable:         1,
		committed:        0,
		applied:          0,
		compactThreshold: defaultCompactThreshold,
	}
}

func (l *raftLog) isEmpty() bool {
	return l.offset == 0 && len(l.ents) == 1
}

func (l *raftLog) String() string {
	return fmt.Sprintf("offset=%d committed=%d applied=%d len(ents)=%d", l.offset, l.committed, l.applied, len(l.ents))
}

func (l *raftLog) maybeAppend(index, logTerm, committed int64, ents ...pb.Entry) bool {
	if l.matchTerm(index, logTerm) {
		from := index + 1
		ci := l.findConflict(from, ents)
		switch {
		case ci == -1:
		case ci <= l.committed:
			panic("conflict with committed entry")
		default:
			l.append(ci-1, ents[ci-from:]...)
		}
		if l.committed < committed {
			l.committed = min(committed, l.lastIndex())
		}
		return true
	}
	return false
}

func (l *raftLog) append(after int64, ents ...pb.Entry) int64 {
	l.ents = append(l.slice(l.offset, after+1), ents...)
	l.unstable = min(l.unstable, after+1)
	return l.lastIndex()
}

func (l *raftLog) findConflict(from int64, ents []pb.Entry) int64 {
	for i, ne := range ents {
		if oe := l.at(from + int64(i)); oe == nil || oe.Term != ne.Term {
			return from + int64(i)
		}
	}
	return -1
}

func (l *raftLog) unstableEnts() []pb.Entry {
	ents := l.entries(l.unstable)
	if ents == nil {
		return nil
	}
	cpy := make([]pb.Entry, len(ents))
	copy(cpy, ents)
	return cpy
}

func (l *raftLog) resetUnstable() {
	l.unstable = l.lastIndex() + 1
}

// nextEnts returns all the available entries for execution.
// all the returned entries will be marked as applied.
func (l *raftLog) nextEnts() (ents []pb.Entry) {
	if l.committed > l.applied {
		return l.slice(l.applied+1, l.committed+1)
	}
	return nil
}

func (l *raftLog) resetNextEnts() {
	if l.committed > l.applied {
		l.applied = l.committed
	}
}

func (l *raftLog) lastIndex() int64 {
	return int64(len(l.ents)) - 1 + l.offset
}

func (l *raftLog) term(i int64) int64 {
	if e := l.at(i); e != nil {
		return e.Term
	}
	return -1
}

func (l *raftLog) entries(i int64) []pb.Entry {
	// never send out the first entry
	// first entry is only used for matching
	// prevLogTerm
	if i == l.offset {
		panic("cannot return the first entry in log")
	}
	return l.slice(i, l.lastIndex()+1)
}

func (l *raftLog) isUpToDate(i, term int64) bool {
	e := l.at(l.lastIndex())
	return term > e.Term || (term == e.Term && i >= l.lastIndex())
}

func (l *raftLog) matchTerm(i, term int64) bool {
	if e := l.at(i); e != nil {
		return e.Term == term
	}
	return false
}

func (l *raftLog) maybeCommit(maxIndex, term int64) bool {
	if maxIndex > l.committed && l.term(maxIndex) == term {
		l.committed = maxIndex
		return true
	}
	return false
}

// compact compacts all log entries until i.
// It removes the log entries before i, exclusive.
// i must be not smaller than the index of the first entry
// and not greater than the index of the last entry.
// the number of entries after compaction will be returned.
func (l *raftLog) compact(i int64) int64 {
	if l.isOutOfBounds(i) {
		panic(fmt.Sprintf("compact %d out of bounds [%d:%d]", i, l.offset, l.lastIndex()))
	}
	l.ents = l.slice(i, l.lastIndex()+1)
	l.unstable = max(i+1, l.unstable)
	l.offset = i
	return int64(len(l.ents))
}

func (l *raftLog) snap(d []byte, index, term int64, nodes []int64) {
	l.snapshot = pb.Snapshot{
		Data:  d,
		Nodes: nodes,
		Index: index,
		Term:  term,
	}
}

func (l *raftLog) shouldCompact() bool {
	return (l.applied - l.offset) > l.compactThreshold
}

func (l *raftLog) restore(s pb.Snapshot) {
	l.ents = []pb.Entry{{Term: s.Term}}
	l.unstable = s.Index + 1
	l.committed = s.Index
	l.applied = s.Index
	l.offset = s.Index
	l.snapshot = s
}

func (l *raftLog) at(i int64) *pb.Entry {
	if l.isOutOfBounds(i) {
		return nil
	}
	return &l.ents[i-l.offset]
}

// slice returns a slice of log entries from lo through hi-1, inclusive.
func (l *raftLog) slice(lo int64, hi int64) []pb.Entry {
	if lo >= hi {
		return nil
	}
	if l.isOutOfBounds(lo) || l.isOutOfBounds(hi-1) {
		return nil
	}
	return l.ents[lo-l.offset : hi-l.offset]
}

func (l *raftLog) isOutOfBounds(i int64) bool {
	if i < l.offset || i > l.lastIndex() {
		return true
	}
	return false
}

func min(a, b int64) int64 {
	if a > b {
		return b
	}
	return a
}

func max(a, b int64) int64 {
	if a > b {
		return a
	}
	return b
}
<|endoftext|>


<commit_before>package isumm

import (
	"fmt"
	"sort"
	"time"
)

// TimeseriesChart represents a chart where the x-axis is a timeseries.
type TimeseriesChart []timeSeriesPoint

func (t TimeseriesChart) Len() int {
	return len(t)
}

func (t TimeseriesChart) Less(i, j int) bool {
	return t[i].date.Before(t[j].date)
}

func (t TimeseriesChart) Swap(i, j int) {
	t[i], t[j] = t[j], t[i]
}

// timeSeriesPoint represents a chart point where x-axis is a timeseries.
// All fields are private because interesting result is the String().
type timeSeriesPoint struct {
	date  time.Time
	value float32
}

func (g timeSeriesPoint) String() string {
	return fmt.Sprintf("[%d, %.2f]", g.date.UnixNano()/1000000, g.value)
}

func AmountSummaryChart(invs []*Investment) TimeseriesChart {
	balanceByDate := make(map[time.Time]float32)
	for _, i := range invs {
		for _, s := range i.Ops.Summarize() {
			balanceByDate[s.Date] += s.Balance
		}
	}
	var chart TimeseriesChart
	for t, b := range balanceByDate {
		chart = append(chart, timeSeriesPoint{t, b})
	}
	// Very important because go map deliberate changes the map interation from
	// time to time.
	sort.Sort(chart)
	return chart
}

func InterestRateChart(invs []*Investment) TimeseriesChart {
	summByDate := make(map[time.Time]Summary)
	for _, i := range invs {
		for _, s := range i.Ops.Summarize() {
			v := summByDate[s.Date]
			v.Date = s.Date
			v.Balance += s.Balance
			v.Change += s.Change
			summByDate[s.Date] = v
		}
	}
	var summs Summaries
	for _, s := range summByDate {
		summs = append(summs, s)
	}
	// The formula needs sorted summs. More information at: https://github.com/danielfireman/isumm/issues/2
	sort.Sort(summs)
	var chart TimeseriesChart
	for i, s := range summs {
		t := timeSeriesPoint{date: s.Date}
		switch {
		case i == 0:
			t.value = 0
		default:
			t.value = float32(s.Balance-s.Change-summs[i-1].Balance) / float32(s.Change+summs[i-1].Balance)
		}
		chart = append(chart, t)
	}
	return chart
}
<commit_msg>Fixing units for interest rate chart.<commit_after>package isumm

import (
	"fmt"
	"sort"
	"time"
)

// TimeseriesChart represents a chart where the x-axis is a timeseries.
type TimeseriesChart []timeSeriesPoint

func (t TimeseriesChart) Len() int {
	return len(t)
}

func (t TimeseriesChart) Less(i, j int) bool {
	return t[i].date.Before(t[j].date)
}

func (t TimeseriesChart) Swap(i, j int) {
	t[i], t[j] = t[j], t[i]
}

// timeSeriesPoint represents a chart point where x-axis is a timeseries.
// All fields are private because interesting result is the String().
type timeSeriesPoint struct {
	date  time.Time
	value float32
}

func (g timeSeriesPoint) String() string {
	return fmt.Sprintf("[%d, %.2f]", g.date.UnixNano()/1000000, g.value)
}

func AmountSummaryChart(invs []*Investment) TimeseriesChart {
	balanceByDate := make(map[time.Time]float32)
	for _, i := range invs {
		for _, s := range i.Ops.Summarize() {
			balanceByDate[s.Date] += s.Balance
		}
	}
	var chart TimeseriesChart
	for t, b := range balanceByDate {
		chart = append(chart, timeSeriesPoint{t, b})
	}
	// Very important because go map deliberate changes the map interation from
	// time to time.
	sort.Sort(chart)
	return chart
}

func InterestRateChart(invs []*Investment) TimeseriesChart {
	summByDate := make(map[time.Time]Summary)
	for _, i := range invs {
		for _, s := range i.Ops.Summarize() {
			v := summByDate[s.Date]
			v.Date = s.Date
			v.Balance += s.Balance
			v.Change += s.Change
			summByDate[s.Date] = v
		}
	}
	var summs Summaries
	for _, s := range summByDate {
		summs = append(summs, s)
	}
	// The formula needs sorted summs. More information at: https://github.com/danielfireman/isumm/issues/2
	sort.Sort(summs)
	var chart TimeseriesChart
	for i, s := range summs {
		t := timeSeriesPoint{date: s.Date}
		switch {
		case i == 0:
			t.value = 0
		default:
			t.value = float32(s.Balance-s.Change-summs[i-1].Balance) / float32(s.Change+summs[i-1].Balance) * 100.0
		}
		chart = append(chart, t)
	}
	return chart
}
<|endoftext|>


<commit_before>// Copyright 2015 The tgbot-ng Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"math/rand"
	"net/http"
	"path"
	"regexp"
	"strconv"
	"strings"
	"time"

	"github.com/jroimartin/tgbot-ng/tg"
)

type cmdChive struct {
	re      *regexp.Regexp
	config  CmdConfigChive
	cli     *tg.Client
}

type CmdConfigChive struct {
	Enabled bool	`json:"enabled"`
	ApiKey	string	`json:"api_key"`
}

func NewCmdChive(config CmdConfigChive, cli *tg.Client) Command {
	return &cmdChive{
		re:      regexp.MustCompile(`^/chive(?:@[^ ]+?)?(?:$| +(.+)$)`),
		config:  config,
		cli:     cli,
	}
}

func (cmd *cmdChive) Match(text string) bool {
	return cmd.re.MatchString(text)
}

func (cmd *cmdChive) Run(chatID, replyID int, text string, from tg.User, reply_ID *tg.Message) error {
	var (
		img  tg.File
		err  error
		tags string
	)

	m := cmd.re.FindStringSubmatch(text)
	if len(m) == 2 {
		tags = m[1]
	}
	if tags == "" {
		img, err = cmd.randomPic()
	} else {
		img, err = cmd.searchTag(strings.Split(tags, ",")[0])
	}
	if err != nil {
		cmd.cli.SendText(chatID, "No photos found!")
		return err
	}

	if _, err := cmd.cli.SendText(chatID, "Keep calm and chive on..."); err != nil {
		return err
	}

	if path.Ext(img.Name) == ".gif" {
		_, err = cmd.cli.SendDocument(chatID, img)
	} else {
		_, err = cmd.cli.SendPhoto(chatID, img)
	}
	return err
}

func (cmd *cmdChive) randomPic() (img tg.File, err error) {
	var category struct {
		Post_Count struct {
			Total_Posts int
		}
		Posts []struct {
			Guid int
		}
	}

	var post struct {
		Posts []struct {
			Items []struct {
				Identity struct {
					URL string
				}
			}
		}
	}

	//Coger numero total de posts  de la categoria 60 'Girls'
	resp, err := http.Get("http://api.thechive.com/api/category/60?key=" + cmd.config.ApiKey)
	if err != nil {
		return tg.File{}, err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return tg.File{}, fmt.Errorf("HTTP error: %v (%v)", resp.Status, resp.StatusCode)
	}
	repBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return tg.File{}, err
	}
	err = json.Unmarshal(repBody, &category)
	if err != nil {
		return tg.File{}, err
	}

	//Coger una página aleatoria (cada página tiene 40 posts)
	rand.Seed(time.Now().Unix())
	randPage := rand.Intn(65) // Tomamos como total de posts 2500 (hay 3000 y pico), y se divide por 39 para obtener las paginas
	resp, err = http.Get("http://api.thechive.com/api/category/60?key=" + cmd.config.ApiKey + "&page=" + strconv.Itoa(randPage))
	if err != nil {
		return tg.File{}, err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return tg.File{}, fmt.Errorf("HTTP error: %v (%v)", resp.Status, resp.StatusCode)
	}
	repBody, err = ioutil.ReadAll(resp.Body)
	if err != nil {
		return tg.File{}, err
	}
	err = json.Unmarshal(repBody, &category)
	if err != nil {
		return tg.File{}, err
	}
	fmt.Println("-------\n" + string(repBody) + "\n-------\n")
	//Coger un post aleatorio de la página aleatoria seleccionada
	rand.Seed(time.Now().Unix())
	randPost := rand.Intn(39)
	//fmt.Println("LONGITUD: " + strconv.Itoa(len(category.Posts)))
	if len(category.Posts) < randPost+1 {
		return tg.File{}, errors.New("Posts argument empty!")
	}
	postNum := category.Posts[randPost].Guid

	resp, err = http.Get("http://api.thechive.com/api/post/" + strconv.Itoa(postNum) + "?key=" + cmd.config.ApiKey)
	if err != nil {
		return tg.File{}, err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return tg.File{}, fmt.Errorf("HTTP error: %v (%v)", resp.Status, resp.StatusCode)
	}
	repBody, err = ioutil.ReadAll(resp.Body)
	if err != nil {
		return tg.File{}, err
	}
	err = json.Unmarshal(repBody, &post)
	if err != nil {
		return tg.File{}, err
	}

	//Coger una foto aleatoria del post seleccionado
	rand.Seed(time.Now().Unix())
	randFoto := rand.Intn(len(post.Posts[0].Items))
	picUrl := post.Posts[0].Items[randFoto].Identity.URL

	resp, err = http.Get(picUrl)
	if err != nil {
		return tg.File{}, err
	}
	defer resp.Body.Close()
	imgData, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return tg.File{}, err
	}

	return tg.File{Name: picUrl, Data: imgData}, nil
}

func (cmd *cmdChive) searchTag(tag string) (img tg.File, err error) {
	var category struct {
		Post_Count struct {
			Total_Posts int
		}
		Posts []struct {
			Guid int
		}
	}

	var post struct {
		Posts []struct {
			Items []struct {
				Identity struct {
					URL string
				}
			}
		}
	}

	//fmt.Println(cmd.Api_key)

	//Coger numero total de posts  de la busqueda'
	resp, err := http.Get("http://api.thechive.com/api/search/" + tag + "?key=" + cmd.config.ApiKey)
	if err != nil {
		return tg.File{}, err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return tg.File{}, fmt.Errorf("HTTP error: %v (%v)", resp.Status, resp.StatusCode)
	}
	repBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return tg.File{}, err
	}
	err = json.Unmarshal(repBody, &category)
	if err != nil {
		return tg.File{}, err
	}

	//Coger un post aleatorio de la página aleatoria seleccionada
	rand.Seed(time.Now().Unix())
	if category.Post_Count.Total_Posts == 0 {
		err = errors.New("No photos!")
		return tg.File{}, err
	}
	randPost := rand.Intn(category.Post_Count.Total_Posts)
	postNum := category.Posts[randPost].Guid
	resp, err = http.Get("http://api.thechive.com/api/post/" + strconv.Itoa(postNum) + "?key=" + cmd.config.ApiKey)
	if err != nil {
		return tg.File{}, err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return tg.File{}, fmt.Errorf("HTTP error: %v (%v)", resp.Status, resp.StatusCode)
	}
	repBody, err = ioutil.ReadAll(resp.Body)
	if err != nil {
		return tg.File{}, err
	}
	err = json.Unmarshal(repBody, &post)
	if err != nil {
		return tg.File{}, err
	}

	//Coger una foto aleatoria del post seleccionado
	rand.Seed(time.Now().Unix())
	if len(post.Posts[0].Items) == 0 {
		err = errors.New("No photos!")
		return tg.File{}, err
	}
	randFoto := rand.Intn(len(post.Posts[0].Items))
	picUrl := post.Posts[0].Items[randFoto].Identity.URL

	resp, err = http.Get(picUrl)
	if err != nil {
		return tg.File{}, err
	}
	defer resp.Body.Close()
	imgData, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return tg.File{}, err
	}

	return tg.File{Name: picUrl, Data: imgData}, nil
}
<commit_msg>fixed api changes<commit_after>// Copyright 2015 The tgbot-ng Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"math/rand"
	"net/http"
	"path"
	"regexp"
	"strconv"
	"strings"
	"time"

	"github.com/jroimartin/tgbot-ng/tg"
)

type cmdChive struct {
	re      *regexp.Regexp
	config  CmdConfigChive
	cli     *tg.Client
}

type CmdConfigChive struct {
	Enabled bool	`json:"enabled"`
	ApiKey	string	`json:"api_key"`
}

func NewCmdChive(config CmdConfigChive, cli *tg.Client) Command {
	return &cmdChive{
		re:      regexp.MustCompile(`^/chive(?:@[^ ]+?)?(?:$| +(.+)$)`),
		config:  config,
		cli:     cli,
	}
}

func (cmd *cmdChive) Match(text string) bool {
	return cmd.re.MatchString(text)
}

func (cmd *cmdChive) Run(chatID, replyID int, text string, from tg.User, reply_ID *tg.Message) error {
	var (
		img  tg.File
		err  error
		tags string
	)

	m := cmd.re.FindStringSubmatch(text)
	if len(m) == 2 {
		tags = m[1]
	}
	if tags == "" {
		img, err = cmd.randomPic()
	} else {
		img, err = cmd.searchTag(strings.Split(tags, ",")[0])
	}
	if err != nil {
		cmd.cli.SendText(chatID, "No photos found!")
		return err
	}

	if _, err := cmd.cli.SendText(chatID, "Keep calm and chive on..."); err != nil {
		return err
	}

	if path.Ext(img.Name) == ".gif" {
		_, err = cmd.cli.SendDocument(chatID, img)
	} else {
		_, err = cmd.cli.SendPhoto(chatID, img)
	}
	return err
}

func (cmd *cmdChive) randomPic() (img tg.File, err error) {
	var category struct {
		Post_Count struct {
			Total_Posts int
		}
		Posts []struct {
			Guid int
		}
	}

	var post struct {
		Posts []struct {
			Items []struct {
				Identity struct {
					URL string
				}
			}
		}
	}

	//Coger numero total de posts  de la categoria 60 'Girls'
	/*resp, err := http.Get("http://api.thechive.com/api/category/60?key=" + cmd.config.ApiKey)
	if err != nil {
		return tg.File{}, err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return tg.File{}, fmt.Errorf("HTTP error: %v (%v)", resp.Status, resp.StatusCode)
	}
	repBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return tg.File{}, err
	}
	err = json.Unmarshal(repBody, &category)
	if err != nil {
		return tg.File{}, err
	}*/

	//Coger una página aleatoria (cada página tiene 40 posts)
	rand.Seed(time.Now().Unix())
	randPage := rand.Intn(65) // Tomamos como total de posts 2500 (hay 3000 y pico), y se divide por 39 para obtener las paginas
	resp, err := http.Get("http://api.thechive.com/api/category/404664888?key=" + cmd.config.ApiKey + "&page=" + strconv.Itoa(randPage))
	if err != nil {
		return tg.File{}, err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return tg.File{}, fmt.Errorf("HTTP error: %v (%v)", resp.Status, resp.StatusCode)
	}
	repBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return tg.File{}, err
	}
	err = json.Unmarshal(repBody, &category)
	if err != nil {
		return tg.File{}, err
	}
	//Coger un post aleatorio de la página aleatoria seleccionada
	rand.Seed(time.Now().Unix())
	randPost := rand.Intn(39)
	//fmt.Println("LONGITUD: " + strconv.Itoa(len(category.Posts)))
	if len(category.Posts) < randPost+1 {
		return tg.File{}, errors.New("Posts argument empty!")
	}
	postNum := category.Posts[randPost].Guid

	resp, err = http.Get("http://api.thechive.com/api/post/" + strconv.Itoa(postNum) + "?key=" + cmd.config.ApiKey)
	if err != nil {
		return tg.File{}, err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return tg.File{}, fmt.Errorf("HTTP error: %v (%v)", resp.Status, resp.StatusCode)
	}
	repBody, err = ioutil.ReadAll(resp.Body)
	if err != nil {
		return tg.File{}, err
	}
	err = json.Unmarshal(repBody, &post)
	if err != nil {
		return tg.File{}, err
	}

	//Coger una foto aleatoria del post seleccionado
	rand.Seed(time.Now().Unix())
	randFoto := rand.Intn(len(post.Posts[0].Items))
	picUrl := post.Posts[0].Items[randFoto].Identity.URL

	resp, err = http.Get(picUrl)
	if err != nil {
		return tg.File{}, err
	}
	defer resp.Body.Close()
	imgData, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return tg.File{}, err
	}

	return tg.File{Name: picUrl, Data: imgData}, nil
}

func (cmd *cmdChive) searchTag(tag string) (img tg.File, err error) {
	var category struct {
		Post_Count struct {
			Total_Posts int
		}
		Posts []struct {
			Guid int
		}
	}

	var post struct {
		Posts []struct {
			Items []struct {
				Identity struct {
					URL string
				}
			}
		}
	}

	//fmt.Println(cmd.Api_key)

	//Coger numero total de posts  de la busqueda'
	resp, err := http.Get("http://api.thechive.com/api/search/" + tag + "?key=" + cmd.config.ApiKey)
	if err != nil {
		return tg.File{}, err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return tg.File{}, fmt.Errorf("HTTP error: %v (%v)", resp.Status, resp.StatusCode)
	}
	repBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return tg.File{}, err
	}
	err = json.Unmarshal(repBody, &category)
	if err != nil {
		return tg.File{}, err
	}

	//Coger un post aleatorio de la página aleatoria seleccionada
	rand.Seed(time.Now().Unix())
	if category.Post_Count.Total_Posts == 0 {
		err = errors.New("No photos!")
		return tg.File{}, err
	}
	randPost := rand.Intn(category.Post_Count.Total_Posts)
	postNum := category.Posts[randPost].Guid
	resp, err = http.Get("http://api.thechive.com/api/post/" + strconv.Itoa(postNum) + "?key=" + cmd.config.ApiKey)
	if err != nil {
		return tg.File{}, err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return tg.File{}, fmt.Errorf("HTTP error: %v (%v)", resp.Status, resp.StatusCode)
	}
	repBody, err = ioutil.ReadAll(resp.Body)
	if err != nil {
		return tg.File{}, err
	}
	err = json.Unmarshal(repBody, &post)
	if err != nil {
		return tg.File{}, err
	}

	//Coger una foto aleatoria del post seleccionado
	rand.Seed(time.Now().Unix())
	if len(post.Posts[0].Items) == 0 {
		err = errors.New("No photos!")
		return tg.File{}, err
	}
	randFoto := rand.Intn(len(post.Posts[0].Items))
	picUrl := post.Posts[0].Items[randFoto].Identity.URL

	resp, err = http.Get(picUrl)
	if err != nil {
		return tg.File{}, err
	}
	defer resp.Body.Close()
	imgData, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return tg.File{}, err
	}

	return tg.File{Name: picUrl, Data: imgData}, nil
}
<|endoftext|>


<commit_before>// Package clock provides an abstraction for system time that enables
// testing of time-sensitive code.
//
// Where you'd use time.Now, instead use clk.Now where clk is an
// instance of Clock.
//
// When running your code in production, pass it a Clock given by
// Default() and when you're running it in your tests, pass it an
// instance of Clock from NewFake().
//
// When you do that, you can use FakeClock's Add and Set methods to
// control how time behaves in your code making them more reliable
// while also expanding the space of problems you can test.
//
// This code intentionally does not attempt to provide an abstraction
// over time.Ticker and time.Timer because Go does not have the
// runtime or API hooks available to do reliably. See
// https://github.com/golang/go/issues/8869
//
// Be sure to test Time equality with time.Time#Equal, not ==.
package clock

import (
	"sort"
	"sync"
	"time"
)

// Some in-use reflection-heavy systems, like facebookgo/inject, fail when given
// a value type like sysClock{}. Since it's hidden by an interface, this has
// surprised users. We fixed that by making systemClock a &sysClock.
var systemClock Clock = &sysClock{}

// New returns a Clock that matches the actual system time.
func New() Clock {
	// This is a method instead of a public var to prevent folks from
	// "making things work" by writing to the var instead of passing
	// in a Clock.
	return systemClock
}

// Default is just an alias for New
func Default() Clock {
	return systemClock
}

// Clock is an abstraction over system time. New instances of it can
// be made with Default and NewFake.
type Clock interface {
	// Now returns the Clock's current view of the time. Mutating the
	// returned Time will not mutate the clock's time.
	Now() time.Time

	// Sleep causes the current goroutine to sleep for the given duration.
	Sleep(time.Duration)

	// After returns a channel that fires after the given duration.
	After(time.Duration) <-chan time.Time

	// NewTimer makes a Timer based on this clock's time. Using Timers and
	// negative durations in the Clock or Timer API is undefined behavior and
	// may be changed.
	NewTimer(time.Duration) *Timer
}

type sysClock struct{}

func (s *sysClock) Now() time.Time {
	return time.Now()
}

func (s *sysClock) Sleep(d time.Duration) {
	time.Sleep(d)
}

func (s *sysClock) After(d time.Duration) <-chan time.Time {
	return time.After(d)
}

func (s *sysClock) NewTimer(d time.Duration) *Timer {
	tt := time.NewTimer(d)
	return &Timer{C: tt.C, timer: tt}
}

// NewFake returns a FakeClock to be used in tests that need to
// manipulate time. Its initial value is always the unix epoch in the
// UTC timezone. The FakeClock returned is thread-safe.
func NewFake() FakeClock {
	// We're explicit about this time construction to avoid early user
	// questions about why the time object doesn't have a Location by
	// default.
	return &fake{t: time.Unix(0, 0).UTC()}
}

// FakeClock is a Clock with additional controls. The return value of
// Now return can be modified with Add. Use NewFake to get a
// thread-safe FakeClock implementation.
type FakeClock interface {
	Clock
	// Adjust the time that will be returned by Now.
	Add(d time.Duration)

	// Set the Clock's time to exactly the time given.
	Set(t time.Time)
}

// To prevent mistakes with the API, we hide this behind NewFake. It's
// easy forget to create a pointer to a fake since time.Time (and
// sync.Mutex) are also simple values. The code will appear to work
// but the clock's time will never be adjusted.
type fake struct {
	sync.RWMutex
	t     time.Time
	sends sortedSends
}

func (f *fake) Now() time.Time {
	f.RLock()
	defer f.RUnlock()
	return f.t
}

func (f *fake) Sleep(d time.Duration) {
	if d < 0 {
		// time.Sleep just returns immediately. Do the same.
		return
	}
	f.Add(d)
}

func (f *fake) After(d time.Duration) <-chan time.Time {
	return f.NewTimer(d).C
}

func (f *fake) NewTimer(d time.Duration) *Timer {
	f.Lock()
	defer f.Unlock()
	ch := make(chan time.Time, 1)
	tt := f.t.Add(d)
	ft := &fakeTimer{c: ch, clk: f, active: true}
	t := &Timer{
		C:         ch,
		fakeTimer: ft,
	}
	s := f.addSend(tt, ft)
	ft.sends = []*send{s}
	return t
}

func (f *fake) Add(d time.Duration) {
	f.Lock()
	defer f.Unlock()
	f.t = f.t.Add(d)
	f.sendTimes()
}

func (f *fake) Set(t time.Time) {
	f.Lock()
	defer f.Unlock()
	f.t = t
	f.sendTimes()
}

// Only to be called while the fake's lock is held
func (f *fake) sendTimes() {
	newSends := make(sortedSends, 0)
	for _, s := range f.sends {
		if !s.active || !s.ft.active {
			continue
		}
		if s.target.Equal(f.t) || s.target.Before(f.t) {
			s.ft.active = false
			s.active = false
			// The select is to drop second sends from resets without a user
			// receiving from ft.c.
			select {
			case s.ft.c <- s.target:
			default:
			}
		}
		if s.active {
			newSends = append(newSends, s)
		}
	}
	f.sends = newSends
}

// Only to be called while the fake's lock is held
func (f *fake) addSend(target time.Time, ft *fakeTimer) *send {
	s := &send{target: target, ft: ft, active: true}
	f.sends = append(f.sends, s)
	// This will be a small enough slice to be fast. Can be replaced with a more
	// complicated container if someone is making many timers.
	sort.Sort(f.sends)
	return s
}

// send is a struct that represents a scheduled send of a time.Time to its
// fakeTimer's channel. They are actually sent when the relevant fake's time
// goes equal or past their target time, as long as the relevant fakeTimer has
// not been Reset or Stop'ed. When a Timer is Reset, the old sends are
// deactivated and will be removed from the clocks list on the next attempt to
// send.
type send struct {
	target time.Time
	active bool
	ft     *fakeTimer
}

type sortedSends []*send

func (s sortedSends) Len() int {
	return len(s)
}

func (s sortedSends) Less(i, j int) bool {
	return s[i].target.Before(s[j].target)
}

func (s sortedSends) Swap(i, j int) {
	s[i], s[j] = s[j], s[i]
}
<commit_msg>deprecate Default<commit_after>// Package clock provides an abstraction for system time that enables
// testing of time-sensitive code.
//
// Where you'd use time.Now, instead use clk.Now where clk is an
// instance of Clock.
//
// When running your code in production, pass it a Clock given by
// Default() and when you're running it in your tests, pass it an
// instance of Clock from NewFake().
//
// When you do that, you can use FakeClock's Add and Set methods to
// control how time behaves in your code making them more reliable
// while also expanding the space of problems you can test.
//
// This code intentionally does not attempt to provide an abstraction
// over time.Ticker and time.Timer because Go does not have the
// runtime or API hooks available to do reliably. See
// https://github.com/golang/go/issues/8869
//
// Be sure to test Time equality with time.Time#Equal, not ==.
package clock

import (
	"sort"
	"sync"
	"time"
)

// Some in-use reflection-heavy systems, like facebookgo/inject, fail when given
// a value type like sysClock{}. Since it's hidden by an interface, this has
// surprised users. We fixed that by making systemClock a &sysClock.
var systemClock Clock = &sysClock{}

// New returns a Clock that matches the actual system time.
func New() Clock {
	// This is a method instead of a public var to prevent folks from
	// "making things work" by writing to the var instead of passing
	// in a Clock.
	return systemClock
}

// Deprecated: Default is just an alias for New but less memorable.
func Default() Clock {
	return systemClock
}

// Clock is an abstraction over system time. New instances of it can
// be made with Default and NewFake.
type Clock interface {
	// Now returns the Clock's current view of the time. Mutating the
	// returned Time will not mutate the clock's time.
	Now() time.Time

	// Sleep causes the current goroutine to sleep for the given duration.
	Sleep(time.Duration)

	// After returns a channel that fires after the given duration.
	After(time.Duration) <-chan time.Time

	// NewTimer makes a Timer based on this clock's time. Using Timers and
	// negative durations in the Clock or Timer API is undefined behavior and
	// may be changed.
	NewTimer(time.Duration) *Timer
}

type sysClock struct{}

func (s *sysClock) Now() time.Time {
	return time.Now()
}

func (s *sysClock) Sleep(d time.Duration) {
	time.Sleep(d)
}

func (s *sysClock) After(d time.Duration) <-chan time.Time {
	return time.After(d)
}

func (s *sysClock) NewTimer(d time.Duration) *Timer {
	tt := time.NewTimer(d)
	return &Timer{C: tt.C, timer: tt}
}

// NewFake returns a FakeClock to be used in tests that need to
// manipulate time. Its initial value is always the unix epoch in the
// UTC timezone. The FakeClock returned is thread-safe.
func NewFake() FakeClock {
	// We're explicit about this time construction to avoid early user
	// questions about why the time object doesn't have a Location by
	// default.
	return &fake{t: time.Unix(0, 0).UTC()}
}

// FakeClock is a Clock with additional controls. The return value of
// Now return can be modified with Add. Use NewFake to get a
// thread-safe FakeClock implementation.
type FakeClock interface {
	Clock
	// Adjust the time that will be returned by Now.
	Add(d time.Duration)

	// Set the Clock's time to exactly the time given.
	Set(t time.Time)
}

// To prevent mistakes with the API, we hide this behind NewFake. It's
// easy forget to create a pointer to a fake since time.Time (and
// sync.Mutex) are also simple values. The code will appear to work
// but the clock's time will never be adjusted.
type fake struct {
	sync.RWMutex
	t     time.Time
	sends sortedSends
}

func (f *fake) Now() time.Time {
	f.RLock()
	defer f.RUnlock()
	return f.t
}

func (f *fake) Sleep(d time.Duration) {
	if d < 0 {
		// time.Sleep just returns immediately. Do the same.
		return
	}
	f.Add(d)
}

func (f *fake) After(d time.Duration) <-chan time.Time {
	return f.NewTimer(d).C
}

func (f *fake) NewTimer(d time.Duration) *Timer {
	f.Lock()
	defer f.Unlock()
	ch := make(chan time.Time, 1)
	tt := f.t.Add(d)
	ft := &fakeTimer{c: ch, clk: f, active: true}
	t := &Timer{
		C:         ch,
		fakeTimer: ft,
	}
	s := f.addSend(tt, ft)
	ft.sends = []*send{s}
	return t
}

func (f *fake) Add(d time.Duration) {
	f.Lock()
	defer f.Unlock()
	f.t = f.t.Add(d)
	f.sendTimes()
}

func (f *fake) Set(t time.Time) {
	f.Lock()
	defer f.Unlock()
	f.t = t
	f.sendTimes()
}

// Only to be called while the fake's lock is held
func (f *fake) sendTimes() {
	newSends := make(sortedSends, 0)
	for _, s := range f.sends {
		if !s.active || !s.ft.active {
			continue
		}
		if s.target.Equal(f.t) || s.target.Before(f.t) {
			s.ft.active = false
			s.active = false
			// The select is to drop second sends from resets without a user
			// receiving from ft.c.
			select {
			case s.ft.c <- s.target:
			default:
			}
		}
		if s.active {
			newSends = append(newSends, s)
		}
	}
	f.sends = newSends
}

// Only to be called while the fake's lock is held
func (f *fake) addSend(target time.Time, ft *fakeTimer) *send {
	s := &send{target: target, ft: ft, active: true}
	f.sends = append(f.sends, s)
	// This will be a small enough slice to be fast. Can be replaced with a more
	// complicated container if someone is making many timers.
	sort.Sort(f.sends)
	return s
}

// send is a struct that represents a scheduled send of a time.Time to its
// fakeTimer's channel. They are actually sent when the relevant fake's time
// goes equal or past their target time, as long as the relevant fakeTimer has
// not been Reset or Stop'ed. When a Timer is Reset, the old sends are
// deactivated and will be removed from the clocks list on the next attempt to
// send.
type send struct {
	target time.Time
	active bool
	ft     *fakeTimer
}

type sortedSends []*send

func (s sortedSends) Len() int {
	return len(s)
}

func (s sortedSends) Less(i, j int) bool {
	return s[i].target.Before(s[j].target)
}

func (s sortedSends) Swap(i, j int) {
	s[i], s[j] = s[j], s[i]
}
<|endoftext|>


<commit_before>package client

import "sync"

import common "github.com/couchbase/indexing/secondary/common"
import mclient "github.com/couchbase/indexing/secondary/manager/client"

type metadataClient struct {
	rw          sync.RWMutex // protects `topology`
	clusterURL  string
	serviceAddr string
	// TODO: Can we assume that nodeId will be unique to each indexer ?
	nodes      []common.NodeId
	mdClient   *mclient.MetadataProvider
	adminports map[common.NodeId]string
	queryports map[common.NodeId]string
	// sherlock topology management, multi-node & single-partition.
	topology map[common.NodeId][]*mclient.IndexMetadata
}

func newMetaBridgeClient(
	cluster, serviceAddr string) (c *metadataClient, err error) {

	cinfo := common.NewClusterInfoCache(cluster, "default" /*pooln*/) // TODO
	if err = cinfo.Fetch(); err != nil {
		return nil, err
	}
	b := &metadataClient{
		clusterURL:  cluster,
		serviceAddr: serviceAddr,
		nodes:       cinfo.GetNodesByServiceType("indexAdmin"),
	}
	// initialize meta-data-provide.
	b.mdClient, err = mclient.NewMetadataProvider(b.serviceAddr)
	if err != nil {
		return nil, err
	}
	// populate indexers' adminport and queryport
	if b.adminports, err = getIndexerAdminports(cinfo, b.nodes); err != nil {
		return nil, err
	}
	if b.queryports, err = getIndexerQueryports(cinfo, b.nodes); err != nil {
		return nil, err
	}
	// watch all indexers
	for _, adminport := range b.adminports {
		b.mdClient.WatchMetadata(adminport)
	}
	b.Refresh()
	return b, nil
}

// Refresh list all indexes.
func (b *metadataClient) Refresh() ([]*mclient.IndexMetadata, error) {
	mindexes := b.mdClient.ListIndex()
	indexes := make([]*mclient.IndexMetadata, 0, len(mindexes))
	for _, mindex := range mindexes {
		indexes = append(indexes, mindex)
	}
	b.rw.Lock()
	defer b.rw.Unlock()
	b.topology = make(map[common.NodeId][]*mclient.IndexMetadata)
	// gather topology of each index.
	// TODO: how to gather topology from each index.
	//for _, index := range indexes {
	//}
	return indexes, nil
}

// CreateIndex implements BridgeAccessor{} interface.
func (b *metadataClient) CreateIndex(
	indexName, bucket, using, exprType, partnExpr, whereExpr string,
	secExprs []string, isPrimary bool) (common.IndexDefnId, error) {

	nodeId, ok := b.makeTopology()
	if !ok {
		return common.IndexDefnId(0), ErrorNoHost
	}
	return b.mdClient.CreateIndex(
		indexName, bucket, using, exprType, partnExpr, whereExpr,
		b.adminports[nodeId], secExprs, isPrimary)
}

// DropIndex implements BridgeAccessor{} interface.
func (b *metadataClient) DropIndex(defnID common.IndexDefnId) error {
	nodeId, ok := b.getNode(defnID)
	if !ok {
		return ErrorIndexNotFound
	}
	return b.mdClient.DropIndex(defnID, b.adminports[nodeId])
}

// GetQueryports implements BridgeAccessor{} interface.
func (b *metadataClient) GetQueryports() (queryports []string) {
	if len(b.queryports) == 0 {
		return nil
	}
	queryports = make([]string, 0, len(b.queryports))
	for _, queryport := range b.queryports {
		queryports = append(queryports, queryport)
	}
	return queryports
}

// GetQueryport implements BridgeAccessor{} interface.
func (b *metadataClient) GetQueryport(
	defnID common.IndexDefnId) (queryport string, ok bool) {

	nodeId, ok := b.getNode(defnID)
	if !ok {
		return "", false
	}
	queryport, ok = b.queryports[nodeId]
	return queryport, ok
}

// close this bridge, to be called when a new indexer is added or
// an active indexer leaves the cluster or during system shutdown.
func (b *metadataClient) Close() {
	b.mdClient.Close()
}

// makeTopology for a new index based on round robin method.
func (b *metadataClient) makeTopology() (pickNode common.NodeId, ok bool) {
	b.rw.RLock()
	defer b.rw.RUnlock()

	if len(b.topology) == 0 {
		return 0, false
	}
	nIndexes := 0xFFFFFFFF
	for nodeId, indexes := range b.topology {
		if len(indexes) <= nIndexes {
			pickNode, nIndexes = nodeId, nIndexes
		}
	}
	return pickNode, true
}

// getNode hosting index with `defnID`.
func (b *metadataClient) getNode(
	defnID common.IndexDefnId) (nodeId common.NodeId, ok bool) {

	b.rw.RLock()
	defer b.rw.RUnlock()

	for nodeId, indexes := range b.topology {
		for _, index := range indexes {
			if defnID == index.Definition.DefnId {
				return nodeId, true
			}
		}
	}
	return 0, false
}

// return adminports for all known indexers.
func getIndexerAdminports(
	cinfo *common.ClusterInfoCache,
	nodes []common.NodeId) (map[common.NodeId]string, error) {

	iAdminports := make(map[common.NodeId]string)
	for _, node := range nodes {
		adminport, err := cinfo.GetServiceAddress(node, "indexAdmin")
		if err != nil {
			return nil, err
		}
		iAdminports[node] = adminport
	}
	return iAdminports, nil
}

// return queryports for all known indexers.
func getIndexerQueryports(
	cinfo *common.ClusterInfoCache,
	nodes []common.NodeId) (map[common.NodeId]string, error) {

	iQueryports := make(map[common.NodeId]string)
	for _, node := range nodes {
		queryport, err := cinfo.GetServiceAddress(node, "indexScan")
		if err != nil {
			return nil, err
		}
		iQueryports[node] = queryport
	}
	return iQueryports, nil
}
<commit_msg>Use indexer's adminport (not NodeId) for book-keeping.<commit_after>package client

import "sync"

import common "github.com/couchbase/indexing/secondary/common"
import mclient "github.com/couchbase/indexing/secondary/manager/client"

type metadataClient struct {
	rw          sync.RWMutex // protects `topology`
	clusterURL  string
	serviceAddr string
	mdClient    *mclient.MetadataProvider
	adminports  []string          // list of nodes represented by its adminport.
	queryports  map[string]string // adminport -> queryport
	// sherlock topology management, multi-node & single-partition.
	topology map[string][]*mclient.IndexMetadata // adminport -> indexes
}

func newMetaBridgeClient(
	cluster, serviceAddr string) (c *metadataClient, err error) {

	cinfo := common.NewClusterInfoCache(cluster, "default" /*pooln*/) // TODO
	if err = cinfo.Fetch(); err != nil {
		return nil, err
	}
	b := &metadataClient{
		clusterURL:  cluster,
		serviceAddr: serviceAddr,
	}
	// initialize meta-data-provide.
	b.mdClient, err = mclient.NewMetadataProvider(b.serviceAddr)
	if err != nil {
		return nil, err
	}
	// populate indexers' adminport and queryport
	if b.adminports, err = getIndexerAdminports(cinfo); err != nil {
		return nil, err
	}
	if b.queryports, err = getIndexerQueryports(cinfo); err != nil {
		return nil, err
	}
	// watch all indexers
	for _, adminport := range b.adminports {
		b.mdClient.WatchMetadata(adminport)
	}
	b.Refresh()
	return b, nil
}

// Refresh list all indexes.
func (b *metadataClient) Refresh() ([]*mclient.IndexMetadata, error) {
	mindexes := b.mdClient.ListIndex()
	indexes := make([]*mclient.IndexMetadata, 0, len(mindexes))
	for _, mindex := range mindexes {
		indexes = append(indexes, mindex)
	}
	b.rw.Lock()
	defer b.rw.Unlock()
	b.topology = make(map[string][]*mclient.IndexMetadata)
	// gather topology of each index.
	// TODO: how to gather topology from each index.
	//for _, index := range indexes {
	//}
	return indexes, nil
}

// CreateIndex implements BridgeAccessor{} interface.
func (b *metadataClient) CreateIndex(
	indexName, bucket, using, exprType, partnExpr, whereExpr string,
	secExprs []string, isPrimary bool) (common.IndexDefnId, error) {

	adminport, ok := b.makeTopology()
	if !ok {
		return common.IndexDefnId(0), ErrorNoHost
	}
	return b.mdClient.CreateIndex(
		indexName, bucket, using, exprType, partnExpr, whereExpr,
		adminport, secExprs, isPrimary)
}

// DropIndex implements BridgeAccessor{} interface.
func (b *metadataClient) DropIndex(defnID common.IndexDefnId) error {
	adminport, ok := b.getNode(defnID)
	if !ok {
		return ErrorIndexNotFound
	}
	return b.mdClient.DropIndex(defnID, adminport)
}

// GetQueryports implements BridgeAccessor{} interface.
func (b *metadataClient) GetQueryports() (queryports []string) {
	if len(b.queryports) == 0 {
		return nil
	}
	queryports = make([]string, 0, len(b.queryports))
	for _, queryport := range b.queryports {
		queryports = append(queryports, queryport)
	}
	return queryports
}

// GetQueryport implements BridgeAccessor{} interface.
func (b *metadataClient) GetQueryport(
	defnID common.IndexDefnId) (queryport string, ok bool) {

	adminport, ok := b.getNode(defnID)
	if !ok {
		return "", false
	}
	queryport, ok = b.queryports[adminport]
	return queryport, ok
}

// close this bridge, to be called when a new indexer is added or
// an active indexer leaves the cluster or during system shutdown.
func (b *metadataClient) Close() {
	b.mdClient.Close()
}

// makeTopology for a new index based on round robin method.
func (b *metadataClient) makeTopology() (adminport string, ok bool) {
	b.rw.RLock()
	defer b.rw.RUnlock()

	if len(b.topology) == 0 {
		return "", false
	}
	nIndexes := 0xFFFFFFFF
	for addr, indexes := range b.topology {
		if len(indexes) <= nIndexes {
			adminport, nIndexes = addr, nIndexes
		}
	}
	return adminport, true
}

// getNode hosting index with `defnID`.
func (b *metadataClient) getNode(
	defnID common.IndexDefnId) (adminport string, ok bool) {

	b.rw.RLock()
	defer b.rw.RUnlock()

	for addr, indexes := range b.topology {
		for _, index := range indexes {
			if defnID == index.Definition.DefnId {
				return addr, true
			}
		}
	}
	return "", false
}

// return adminports for all known indexers.
func getIndexerAdminports(
	cinfo *common.ClusterInfoCache) ([]string, error) {

	iAdminports := make([]string, 0)
	for _, node := range cinfo.GetNodesByServiceType("indexAdmin") {
		adminport, err := cinfo.GetServiceAddress(node, "indexAdmin")
		if err != nil {
			return nil, err
		}
		iAdminports = append(iAdminports, adminport)
	}
	return iAdminports, nil
}

// return queryports for all known indexers.
func getIndexerQueryports(
	cinfo *common.ClusterInfoCache) (map[string]string, error) {

	iQueryports := make(map[string]string)
	for _, node := range cinfo.GetNodesByServiceType("indexAdmin") {
		adminport, err := cinfo.GetServiceAddress(node, "indexAdmin")
		if err != nil {
			return nil, err
		}
		queryport, err := cinfo.GetServiceAddress(node, "indexScan")
		if err != nil {
			return nil, err
		}
		iQueryports[adminport] = queryport
	}
	return iQueryports, nil
}
<|endoftext|>


<commit_before>package sockaddr

import (
	"errors"
	"os/exec"
)

var cmds map[string][]string = map[string][]string{
	"ip": {"/sbin/ip", "route"},
}

type routeInfo struct {
	cmds map[string][]string
}

// NewRouteInfo returns a Linux-specific implementation of the RouteInfo
// interface.
func NewRouteInfo() (routeInfo, error) {
	return routeInfo{
		cmds: cmds,
	}, nil
}

// GetDefaultInterfaceName returns the interface name attached to the default
// route on the default interface.
func (ri routeInfo) GetDefaultInterfaceName() (string, error) {
	out, err := exec.Command(cmds["ip"][0], cmds["ip"][1:]...).Output()
	if err != nil {
		return "", err
	}

	var ifName string
	if ifName, err = parseDefaultIfNameFromIPCmd(string(out)); err != nil {
		return "", errors.New("No default interface found")
	}
	return ifName, nil
}
<commit_msg>Lookup IP on $PATH to support unusual locations<commit_after>package sockaddr

import (
	"errors"
	"os/exec"
)

type routeInfo struct {
	cmds map[string][]string
}

// NewRouteInfo returns a Linux-specific implementation of the RouteInfo
// interface.
func NewRouteInfo() (routeInfo, error) {
	// CoreOS Container Linux moved ip to /usr/bin/ip, so look it up on
	// $PATH and fallback to /sbin/ip on error.
	path, _ := exec.LookPath("ip")
	if path == "" {
		path = "/sbin/ip"
	}

	return routeInfo{
		cmds: map[string][]string{"ip": {path, "route"}},
	}, nil
}

// GetDefaultInterfaceName returns the interface name attached to the default
// route on the default interface.
func (ri routeInfo) GetDefaultInterfaceName() (string, error) {
	out, err := exec.Command(ri.cmds["ip"][0], ri.cmds["ip"][1:]...).Output()
	if err != nil {
		return "", err
	}

	var ifName string
	if ifName, err = parseDefaultIfNameFromIPCmd(string(out)); err != nil {
		return "", errors.New("No default interface found")
	}
	return ifName, nil
}
<|endoftext|>


<commit_before>package app

import (
	"github.com/moncho/dry/appui"
	termbox "github.com/nsf/termbox-go"
)

type servicesScreenEventHandler struct {
	baseEventHandler
}

func (h *servicesScreenEventHandler) handle(event termbox.Event) {
	handled := false
	focus := true

	switch event.Key {
	case termbox.KeyEnter:
		showServices := func(serviceID string) error {
			h.dry.ShowServiceTasks(serviceID)
			h.renderChan <- struct{}{}
			return nil
		}
		h.dry.state.activeWidget.OnEvent(showServices)
		handled = true
	}
	switch event.Ch {
	case 'l':
		showServiceLogs := func(serviceID string) error {
			logs, err := h.dry.ServiceLogs(serviceID)
			if err == nil {
				go appui.Stream(h.screen, logs, h.keyboardQueueForView, h.closeViewChan)
				return nil
			}
			return err
		}
		h.dry.state.activeWidget.OnEvent(showServiceLogs)
		handled = true
		focus = false
	}
	if !handled {
		h.baseEventHandler.handle(event)
	} else {
		h.setFocus(focus)
	}
}

type serviceTaskScreenEventHandler struct {
	baseEventHandler
}

func (h *serviceTaskScreenEventHandler) handle(event termbox.Event) {

	switch event.Key {
	case termbox.KeyEsc:
		h.dry.ShowServices()
	}

	h.baseEventHandler.handle(event)

}
<commit_msg>Handle event handling errors on service list screen<commit_after>package app

import (
	"github.com/moncho/dry/appui"
	termbox "github.com/nsf/termbox-go"
)

type servicesScreenEventHandler struct {
	baseEventHandler
}

func (h *servicesScreenEventHandler) handle(event termbox.Event) {
	handled := false
	focus := true

	switch event.Key {
	case termbox.KeyEnter:
		showServices := func(serviceID string) error {
			h.dry.ShowServiceTasks(serviceID)
			h.renderChan <- struct{}{}
			return nil
		}
		h.dry.state.activeWidget.OnEvent(showServices)
		handled = true
	}
	switch event.Ch {
	case 'l':
		showServiceLogs := func(serviceID string) error {
			logs, err := h.dry.ServiceLogs(serviceID)
			if err == nil {
				go appui.Stream(h.screen, logs, h.keyboardQueueForView, h.closeViewChan)
				return nil
			}
			return err
		}
		handled = true
		//TODO show error on screen
		if err := h.dry.state.activeWidget.OnEvent(showServiceLogs); err == nil {
			focus = false
		}
	}
	if !handled {
		h.baseEventHandler.handle(event)
	} else {
		h.setFocus(focus)
	}
}

type serviceTaskScreenEventHandler struct {
	baseEventHandler
}

func (h *serviceTaskScreenEventHandler) handle(event termbox.Event) {

	switch event.Key {
	case termbox.KeyEsc:
		h.dry.ShowServices()
	}

	h.baseEventHandler.handle(event)

}
<|endoftext|>


<commit_before>package lib

import (
	"fmt"
	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/mbndr/logo"
	"os"
	"path/filepath"
	"strings"
	"testing"
	"time"
)

type mockSessionTokenProvider struct {
	awsAssumeRoleProvider
}

// override Retrieve() and AssumeRole() so we can bypass/mock calls to AWS services
func (m *mockSessionTokenProvider) Retrieve() (credentials.Value, error) {
	// lazy load credentials
	c, err := m.credsFromFile()
	if err == nil {
		m.log.Debugf("Found cached session token credentials")
		m.creds = c
	}

	if m.IsExpired() {
		m.log.Debugf("Detected expired or unset session token credentials, refreshing")
		c = &CachableCredentials{
			Expiration: time.Now().Add(m.sessionTokenDuration).Unix(),
			Value: credentials.Value{
				AccessKeyID:     "MockSessionTokenAccessKey",
				SecretAccessKey: "MockSessionTokenSecretKey",
				SessionToken:    "MockSessionToken",
				ProviderName:    "MockCredentialsProvider",
			},
		}
		m.creds = c
		m.Store()
	}

	return m.creds.Value, nil
}

func (m *mockSessionTokenProvider) AssumeRole() (credentials.Value, error) {
	v := credentials.Value{
		AccessKeyID:     "MockAssumeRoleAccessKey",
		SecretAccessKey: "MockAssumeRoleSecretKey",
		SessionToken:    "MockAssumeRoleSessionToken",
		ProviderName:    "MockCredentialsProvider",
	}
	return v, nil
}

func newSessionTokenProvider() *mockSessionTokenProvider {
	opts := SessionTokenProviderOptions{
		LogLevel: logo.INFO,
	}

	m := new(mockSessionTokenProvider)
	m.setAttrs(new(AWSProfile), &opts)
	return m
}

func TestCacheFileDefault(t *testing.T) {
	os.Unsetenv("AWS_CONFIG_FILE")
	m := newSessionTokenProvider()
	if !strings.HasSuffix(m.CacheFile(), filepath.Join(string(filepath.Separator), ".aws_session_token_")) {
		t.Errorf("Cache file path does not have expected contents: %s", m.CacheFile())
	}
}

func ExampleCacheFileEnv() {
	os.Setenv("AWS_CONFIG_FILE", "aws.cfg")
	m := newSessionTokenProvider()
	fmt.Println(m.CacheFile())
	// Output:
	// .aws_session_token_
}

func TestExpirationTimeEpoch(t *testing.T) {
	m := newSessionTokenProvider()
	e := m.ExpirationTime()
	if !e.Equal(time.Unix(0, 0)) {
		t.Errorf("Expiration time for unset credentials != Unix epoch time")
	}
}

func TestIsExpiredNoCreds(t *testing.T) {
	m := newSessionTokenProvider()
	if !m.IsExpired() {
		t.Errorf("Expected IsExpired() for no credentials to be true, got %v", m.IsExpired())
	}
}

func ExampleSessionTokenRetrieve() {
	m := newSessionTokenProvider()
	c, err := m.Retrieve()
	if err != nil {
		fmt.Printf("Unexpected error during Retrieve(): %v", err)
	}
	fmt.Println(c.AccessKeyID)
	fmt.Println(c.SecretAccessKey)
	fmt.Println(c.SessionToken)
	fmt.Println(c.ProviderName)
	// Output:
	// MockSessionTokenAccessKey
	// MockSessionTokenSecretKey
	// MockSessionToken
	// MockCredentialsProvider
}

func TestSessionTokenExpired(t *testing.T) {

}

func ExampleAssumeRole() {
	m := newSessionTokenProvider()
	c, err := m.AssumeRole()
	if err != nil {
		fmt.Printf("Unexpected error during AssumeRole(): %v", err)
	}
	fmt.Println(c.AccessKeyID)
	fmt.Println(c.SecretAccessKey)
	fmt.Println(c.SessionToken)
	fmt.Println(c.ProviderName)
	// Output:
	// MockAssumeRoleAccessKey
	// MockAssumeRoleSecretKey
	// MockAssumeRoleSessionToken
	// MockCredentialsProvider
}
<commit_msg>add more tests for credential provider<commit_after>package lib

import (
	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/mbndr/logo"
	"os"
	"path/filepath"
	"strings"
	"testing"
	"time"
)

type mockSessionTokenProvider struct {
	awsAssumeRoleProvider
}

// override Retrieve() and AssumeRole() so we can bypass/mock calls to AWS services
func (m *mockSessionTokenProvider) Retrieve() (credentials.Value, error) {
	// lazy load credentials
	c, err := m.credsFromFile()
	if err == nil {
		m.log.Debugf("Found cached session token credentials")
		m.creds = c
	}

	if m.IsExpired() {
		m.log.Debugf("Detected expired or unset session token credentials, refreshing")
		c = &CachableCredentials{
			Expiration: time.Now().Add(m.sessionTokenDuration).Unix(),
			Value: credentials.Value{
				AccessKeyID:     "MockSessionTokenAccessKey",
				SecretAccessKey: "MockSessionTokenSecretKey",
				SessionToken:    "MockSessionToken",
				ProviderName:    "MockCredentialsProvider",
			},
		}
		m.creds = c
		m.Store()
	}

	return m.creds.Value, nil
}

func (m *mockSessionTokenProvider) AssumeRole() (credentials.Value, error) {
	v := credentials.Value{
		AccessKeyID:     "MockAssumeRoleAccessKey",
		SecretAccessKey: "MockAssumeRoleSecretKey",
		SessionToken:    "MockAssumeRoleSessionToken",
		ProviderName:    "MockCredentialsProvider",
	}
	return v, nil
}

func TestProviderDefaults(t *testing.T) {
	os.Unsetenv("AWS_CONFIG_FILE")
	m := new(mockSessionTokenProvider)
	m.setAttrs(new(AWSProfile), &SessionTokenProviderOptions{LogLevel: logo.INFO})

	t.Run("CacheFile", func(t *testing.T) {
		if !strings.HasSuffix(m.CacheFile(), filepath.Join(string(filepath.Separator), ".aws_session_token_")) {
			t.Errorf("Cache file path does not have expected contents: %s", m.CacheFile())
		}
	})
	t.Run("ExpirationEpoch", func(t *testing.T) {
		if !m.ExpirationTime().Equal(time.Unix(0, 0)) {
			t.Errorf("Expiration time for unset credentials != Unix epoch time")
		}
	})
	t.Run("NoCreds", func(t *testing.T) {
		if !m.IsExpired() {
			t.Errorf("Expected IsExpired() for unset credentials to be true, got %v", m.IsExpired())
		}
	})
	t.Run("AssumeRole", func(t *testing.T) {
		c, err := m.AssumeRole()
		if err != nil {
			t.Errorf("Unexpected error during AssumeRole(): %v", err)
		}
		if c.AccessKeyID != "MockAssumeRoleAccessKey" || c.SecretAccessKey != "MockAssumeRoleSecretKey" ||
			c.SessionToken != "MockAssumeRoleSessionToken" || c.ProviderName != "MockCredentialsProvider" {
			t.Errorf("Data mismatch when validating assume role credentials: %v", c)
		}
	})
}

func TestProviderCustomConfig(t *testing.T) {
	os.Setenv("AWS_CONFIG_FILE", "aws.cfg")
	p := AWSProfile{
		Region: "us-west-1",
	}

	opts := SessionTokenProviderOptions{
		LogLevel:             logo.INFO,
		SessionTokenDuration: 8 * time.Hour,
		RoleArn:              "arn:aws:iam::0123456789:role/mock-role",
		MfaSerial:            "mock-mfa",
	}
	m := new(mockSessionTokenProvider)
	m.setAttrs(&p, &opts)

	t.Run("CacheFile", func(t *testing.T) {
		if m.CacheFile() != ".aws_session_token_" {
			t.Errorf("Cache file path does not have expected contents: %s", m.CacheFile())
		}
	})
	t.Run("SessionTokenRetrieve", func(t *testing.T) {
		c, err := m.Retrieve()
		if err != nil {
			t.Errorf("Error in Retrieve(): %v", err)
		}
		if c.AccessKeyID != "MockSessionTokenAccessKey" || c.SecretAccessKey != "MockSessionTokenSecretKey" ||
			c.SessionToken != "MockSessionToken" || c.ProviderName != "MockCredentialsProvider" {
			t.Errorf("Data mismatch when validating assume role credentials: %v", c)
		}
	})

	// These tests require that something has called Retrieve()
	t.Run("SessionTokenExpirationTime", func(t *testing.T) {
		if m.ExpirationTime() == time.Unix(0, 0) {
			t.Errorf("Credentials have invalid expiration")
		}
	})
	t.Run("SessionTokenNotExpired", func(t *testing.T) {
		if m.IsExpired() {
			t.Errorf("Unexpected expired credentials")
		}
	})
	t.Run("CredsFromFile", func(t *testing.T) {
		c, err := m.credsFromFile()
		if err != nil {
			t.Errorf("Error loading cached credentials: %v", err)
		}
		if c == nil {
			t.Errorf("nil credentials read from file")
		}
	})
	t.Run("ExpiredCredentials", func(t *testing.T) {
		m.creds.Expiration = time.Now().Add(-5 * time.Second).Unix()
		if m.ExpirationTime().After(time.Now()) {
			t.Errorf("Unexpected future credential expiration time")
		}
		if !m.IsExpired() {
			t.Errorf("Expected expired credentials, but received valid")
		}
	})

	os.Remove(m.CacheFile())
	os.Unsetenv("AWS_CONFIG_FILE")
}

func TestNewProviderParams(t *testing.T) {
	os.Unsetenv("AWS_CONFIG_FILE")
	m := new(mockSessionTokenProvider)

	t.Run("NilProfile", func(t *testing.T) {
		defer func() {
			if x := recover(); x != nil {
				t.Logf("Got expected panic() from nil profile")
			} else {
				t.Errorf("Did not see expected panic() from nil profile")
			}
		}()
		m.setAttrs(nil, new(SessionTokenProviderOptions))
	})
	t.Run("NilOptions", func(t *testing.T) {
		defer func() {
			if x := recover(); x != nil {
				t.Logf("Got expected panic() from nil options")
			} else {
				t.Errorf("Did not see expected panic() from nil options")
			}
		}()
		m.setAttrs(new(AWSProfile), nil)
	})
	t.Run("BadArn", func(t *testing.T) {
		if err := m.setAttrs(new(AWSProfile), &SessionTokenProviderOptions{RoleArn: "bogus"}); err == nil {
			t.Errorf("Did not see expected error using bad RoleArn option")
		}
	})
	t.Run("DefaultSessionDuration", func(t *testing.T) {
		if m.sessionTokenDuration != SESSION_TOKEN_DEFAULT_DURATION {
			t.Errorf("Session token duration is not default value")
		}
	})
	t.Run("BelowMinSessionDuration", func(t *testing.T) {
		m.setAttrs(new(AWSProfile), &SessionTokenProviderOptions{SessionTokenDuration: 1 * time.Minute})
		if m.sessionTokenDuration != SESSION_TOKEN_MIN_DURATION {
			t.Errorf("Session token duration is not min value")
		}
	})
	t.Run("AboveMaxSessionDuration", func(t *testing.T) {
		m.setAttrs(new(AWSProfile), &SessionTokenProviderOptions{SessionTokenDuration: 100 * time.Hour})
		if m.sessionTokenDuration != SESSION_TOKEN_MAX_DURATION {
			t.Errorf("Session token duration is not max value")
		}
	})
	t.Run("BelowMinRoleDuration", func(t *testing.T) {
		m.setAttrs(new(AWSProfile), &SessionTokenProviderOptions{SessionTokenDuration: 1 * time.Minute})
		if m.assumeRoleDuration != ASSUME_ROLE_MIN_DURATION {
			t.Errorf("Assume role duration is not min value")
		}
	})
	t.Run("AboveMaxRoleDuration", func(t *testing.T) {
		m.setAttrs(new(AWSProfile), &SessionTokenProviderOptions{SessionTokenDuration: 18 * time.Hour})
		if m.assumeRoleDuration != ASSUME_ROLE_MAX_DURATION {
			t.Errorf("Assume role duration is not max value")
		}
	})
}
<|endoftext|>