<commit_before>package doozerconfig

import (
	"reflect"
	"github.com/ActiveState/doozer"
	"encoding/json"
	"errors" // TODO: create custom errors
)

type DoozerConfig struct {
	conn        *doozer.Conn
	configStruct interface{}
	prefix       string
}

func New(conn *doozer.Conn, configStruct interface{}, prefix string) *DoozerConfig{
	return &DoozerConfig{conn, configStruct, prefix}
}

// initialize the config data by loading from doozer.
// will return error if any of the config is not found
func (c *DoozerConfig) Load() error {
	elem := reflect.ValueOf(c.configStruct).Elem()
	elemType := elem.Type()
	for i := 0; i < elem.NumField(); i++ {
		field := elem.Field(i)
		fieldType := elemType.Field(i)

		// read json-encoded bytes from doozer
		path := fieldType.Tag.Get("doozer")
		if path == "" {
			// this field is not supposed to be loaded from doozer
			continue
		}
		data, _, err := c.conn.Get(c.prefix + path, nil)
		if err != nil {
			return err
		}

		// decode the json into interface{} type
		var val2 interface{}
		json.Unmarshal(data, &val2)

		// extract the value based on the field type
		// TODO: simplify this using interface{}
		switch(field.Kind()){
		case reflect.Int:
			var val int64
			json.Unmarshal(data, &val)
			field.SetInt(val)
		case reflect.String:
			var val string
			json.Unmarshal(data, &val)
			field.SetString(val)
		default:
			return errors.New("doozerconfig: unsupported field " + string(field.Kind()))
		}
	}
	return nil
}

<commit_msg>doozeconfig.Monitor for monitoring config changes<commit_after>// TODO: libraries should not use log.Fatal
// TODO: ... perhaps not log.Printf either.

package doozerconfig

import (
	"reflect"
	"github.com/ActiveState/doozer"
	"encoding/json"
	"errors" // TODO: create custom errors
	"log"
)

type DoozerConfig struct {
	conn        *doozer.Conn
	configStruct interface{}
	prefix       string
	fields       map[string]reflect.Value
}

func New(conn *doozer.Conn, configStruct interface{}, prefix string) *DoozerConfig{
	return &DoozerConfig{conn, configStruct, prefix, make(map[string]reflect.Value)}
}

// initialize the config data by loading from doozer.
// will return error if any of the config is not found
func (c *DoozerConfig) Load() error {
	elem := reflect.ValueOf(c.configStruct).Elem()
	elemType := elem.Type()
	for i := 0; i < elem.NumField(); i++ {
		field := elem.Field(i)
		fieldType := elemType.Field(i)

		// read json-encoded bytes from doozer
		path := fieldType.Tag.Get("doozer")
		if path == "" {
			// this field is not supposed to be loaded from doozer
			continue
		}
		path = c.prefix + path
		data, _, err := c.conn.Get(path, nil)
		if err != nil {
			return err
		}

		c.fields[path] = field

		// extract the value based on the field type
		err = setFieldWithData(field, data)
		if err != nil {
			return err
		}
	}
	return nil
}

func (c *DoozerConfig) Monitor(glob string, rev int64) {
	for evt := range doozerWatch(c.conn, glob, rev) {
		if field, ok := c.fields[evt.Path]; ok {
			err := setFieldWithData(field, evt.Body)
			if err != nil {
				log.Fatal(err)
			}
			log.Printf("New Config: %+v\n", c.configStruct)
		}
	}
}

// set this struct field with data json-decoded as the same tyhpe
func setFieldWithData(field reflect.Value, data []byte) error {
	// TODO: simplify this using interface{}
	switch(field.Kind()){
	case reflect.Int:
		var val int64
		json.Unmarshal(data, &val)
		field.SetInt(val)
	case reflect.String:
		var val string
		json.Unmarshal(data, &val)
		field.SetString(val)
	default:
		return errors.New("doozerconfig: unsupported field " + string(field.Kind()))
	}
	return nil
}

// monitor mutations on the given glob of keys and report them in the
// returned channel
func doozerWatch(c *doozer.Conn, glob string, rev int64) chan doozer.Event {
	ch := make(chan doozer.Event)
	go func() {
		for {
			evt, err := c.Wait(glob, rev)
			if err != nil {
				close(ch)
				// FIXME: on doozer watch errors, the entire basin process
				// must not go down. figure a way to report the error in
				// console and silently proceed.
				log.Fatal(err)
				return
			}
			rev = evt.Rev + 1
			ch <- evt
		}
	}()
	return ch
}

<|endoftext|>


<commit_before>package main

import (
	"./myutil"

	"os"
	"github.com/BurntSushi/toml"
	"gopkg.in/kataras/iris.v6"
	"gopkg.in/kataras/iris.v6/adaptors/httprouter"
	"strconv"
	"fmt"
	"math/rand"
	"io/ioutil"
	"strings"
	"os/exec"
)

type goIpAllowConfig struct {
	Envs                []string // 环境
	Mobiles             []string // 可以用于短信验证码校验的手机号码
	MobileTags          []string // 手机号码对应的人员标记，比如King，ZCX
	ListenPort          int      // 监听端口
	SendCaptcha         string   // 短信发送URL
	VerifyCaptcha       string   // 短信校验URL
	UpdateFirewallShell string   // 更新防火墙IP脚本
}

func readIpAllowConfig() goIpAllowConfig {
	fpath := "go-ip-allow.toml"
	if len(os.Args) > 1 {
		fpath = os.Args[1]
	}

	config := goIpAllowConfig{}
	if _, err := toml.DecodeFile(fpath, &config); err != nil {
		myutil.CheckErr(err)
	}

	return config
}

var config goIpAllowConfig

func main() {
	config = readIpAllowConfig()

	app := iris.New()
	app.Adapt(httprouter.New())

	app.Get("/", goIpAllowIndexHandler) // 首页
	app.Post("/smsCode", smsCodeHandler) // 发送验证码
	app.Post("/ipAllow", goIpAllowHandler) // 设置IP权限
	app.Listen(":" + strconv.Itoa(config.ListenPort))
}

func goIpAllowHandler(ctx *iris.Context) {
	officeIp := ctx.FormValue("officeIp")
	smsCode := ctx.FormValue("smsCode")
	env := ctx.FormValue("env")
	fmt.Println("smsCode:", smsCode, ",officeIp:", officeIp, ",env:", env)

	alreadyAllowed, allowedIps := isIpAlreadyAllowed(env, officeIp)
	if alreadyAllowed {
		ctx.WriteString(officeIp + `已设置，请不要重复设置`)
		return
	}

	mobile, _ := ioutil.ReadFile(officeIp + ".mobile")
	// curl -d "mobile=15951771111&path=iplogin&captcha=3232" http://127.0.0.1:8020/v1/notify/verify-captcha
	out, err := exec.Command("curl", "-d", `mobile=` + string(mobile) +
		`&path=iplogin&captcha=` + smsCode, config.VerifyCaptcha).Output()
	if err != nil {
		ctx.WriteString(`设置失败，发送短信错误` + err.Error());
		return
	} else {
		curlOut := string(out)
		fmt.Println(curlOut)
		if "true" != strings.TrimSpace(curlOut) {
			ctx.WriteString(`设置失败，发送短信返回` + curlOut);
			return
		}
	}

	if allowedIps == "" {
		allowedIps = officeIp
	} else {
		allowedIps += "," + officeIp
	}

	out, err = exec.Command("/bin/bash", config.UpdateFirewallShell, env, allowedIps).Output()
	if err != nil {
		ctx.WriteString(`设置失败，执行SHELL错误` + err.Error());
		return
	}

	shellOut := string(out)
	fmt.Println(shellOut)
	writeAllowIpFile(env, allowedIps)

	os.Remove(officeIp + ".mobile")
	ctx.WriteString(`设置成功`)
}

func smsCodeHandler(ctx *iris.Context) {
	ctx.Header().Set("Content-Type", "application/json")

	officeIp := ctx.FormValue("officeIp")
	env := ctx.FormValue("env")
	if ok, _ := isIpAlreadyAllowed(env, officeIp); ok {
		ctx.WriteString(`{"ok":false,"msg":"IP已设置，请不要重复设置"}`)
		return
	}

	randMobileIndex := rand.Intn(len(config.Mobiles))

	// curl -d "mobile=18512345678&templateId=1020481&path=iplogin&validMinutes=15" http://127.0.0.1:8020/v1/notify/send-captcha
	smsMobile := config.Mobiles[randMobileIndex]
	ioutil.WriteFile(officeIp + ".mobile", []byte(smsMobile), 0644)
	out, err := exec.Command("curl", "-d", `mobile=` + smsMobile +
		`&templateId=1020481&path=iplogin&validMinutes=15`, config.SendCaptcha).Output()
	if err != nil {
		fmt.Println("err:" + err.Error())
		ctx.WriteString(`{"ok":false,"msg":"` + err.Error() + `"}`)
		return
	} else {
		fmt.Println("out:" + string(out))
		ctx.WriteString(`{"ok":true,"tag":"` + config.MobileTags[randMobileIndex] + `"}`)
	}
}

func isIpAlreadyAllowed(env, ip string) (bool, string) {
	content, err := ioutil.ReadFile(env + "-AllowIps.txt")
	if err != nil {
		return false, ""
	}

	strContent := string(content)
	alreadyAllowed := strings.Contains(strContent, ip)
	return alreadyAllowed, strContent
}

func writeAllowIpFile(env, content string) {
	ioutil.WriteFile(env + "-AllowIps.txt", []byte(content), 0644)
}

func goIpAllowIndexHandler(ctx *iris.Context) {
	envCheckboxes := ""
	for _, env := range config.Envs {
		envCheckboxes += fmt.Sprintf("<input type='radio' name='env' value='%v'>%v</input>", env, env)
	}
	ctx.WriteString(`

请关闭所有代理，防止IP识别错误。
请检查以下显示的IP是：

<form name="envForm">` + envCheckboxes + `</form>

请输入验证码：<input type="input" id="smsCode" style="width:60px"/>
<input type="button" value="发验证码" onclick="sendSmsCode()"/>

<input type="button" value="设置" onclick="setIpAllow()"/>

	`)
}<commit_msg>allow multiple envs in ip-allow<commit_after>package main

import (
	"./myutil"

	"os"
	"github.com/BurntSushi/toml"
	"gopkg.in/kataras/iris.v6"
	"gopkg.in/kataras/iris.v6/adaptors/httprouter"
	"strconv"
	"fmt"
	"math/rand"
	"io/ioutil"
	"strings"
	"os/exec"
)

type goIpAllowConfig struct {
	Envs                []string // 环境
	Mobiles             []string // 可以用于短信验证码校验的手机号码
	MobileTags          []string // 手机号码对应的人员标记，比如King，ZCX
	ListenPort          int      // 监听端口
	SendCaptcha         string   // 短信发送URL
	VerifyCaptcha       string   // 短信校验URL
	UpdateFirewallShell string   // 更新防火墙IP脚本
}

func readIpAllowConfig() goIpAllowConfig {
	fpath := "go-ip-allow.toml"
	if len(os.Args) > 1 {
		fpath = os.Args[1]
	}

	config := goIpAllowConfig{}
	if _, err := toml.DecodeFile(fpath, &config); err != nil {
		myutil.CheckErr(err)
	}

	return config
}

var config goIpAllowConfig

func main() {
	config = readIpAllowConfig()

	app := iris.New()
	app.Adapt(httprouter.New())

	app.Get("/", goIpAllowIndexHandler) // 首页
	app.Post("/smsCode", smsCodeHandler) // 发送验证码
	app.Post("/ipAllow", goIpAllowHandler) // 设置IP权限
	app.Listen(":" + strconv.Itoa(config.ListenPort))
}

func goIpAllowHandler(ctx *iris.Context) {
	officeIp := ctx.FormValue("officeIp")
	smsCode := ctx.FormValue("smsCode")
	envs := ctx.FormValue("envs")
	fmt.Println("smsCode:", smsCode, ",officeIp:", officeIp, ",env:", envs)

	alreadyAllowed, allowedIps := isIpAlreadyAllowed(envs, officeIp)
	if alreadyAllowed {
		ctx.WriteString(officeIp + `已设置，请不要重复设置`)
		return
	}

	mobile, _ := ioutil.ReadFile(officeIp + ".mobile")
	// curl -d "mobile=15951771111&path=iplogin&captcha=3232" http://127.0.0.1:8020/v1/notify/verify-captcha
	out, err := exec.Command("curl", "-d", `mobile=` + string(mobile) +
		`&path=iplogin&captcha=` + smsCode, config.VerifyCaptcha).Output()
	if err != nil {
		ctx.WriteString(`设置失败，发送短信错误` + err.Error());
		return
	} else {
		curlOut := string(out)
		fmt.Println(curlOut)
		if "true" != strings.TrimSpace(curlOut) {
			ctx.WriteString(`设置失败，发送短信返回` + curlOut);
			return
		}
	}

	if allowedIps == "" {
		allowedIps = officeIp
	} else {
		allowedIps += "," + officeIp
	}

	out, err = exec.Command("/bin/bash", config.UpdateFirewallShell, envs, allowedIps).Output()
	if err != nil {
		ctx.WriteString(`设置失败，执行SHELL错误` + err.Error());
		return
	}

	shellOut := string(out)
	fmt.Println(shellOut)
	writeAllowIpFile(envs, allowedIps)

	os.Remove(officeIp + ".mobile")
	ctx.WriteString(`设置成功`)
}

func smsCodeHandler(ctx *iris.Context) {
	ctx.Header().Set("Content-Type", "application/json")

	officeIp := ctx.FormValue("officeIp")
	envs := ctx.FormValue("envs")
	if ok, _ := isIpAlreadyAllowed(envs, officeIp); ok {
		ctx.WriteString(`{"ok":false,"msg":"IP已设置，请不要重复设置"}`)
		return
	}

	randMobileIndex := rand.Intn(len(config.Mobiles))

	// curl -d "mobile=18512345678&templateId=1020481&path=iplogin&validMinutes=15" http://127.0.0.1:8020/v1/notify/send-captcha
	smsMobile := config.Mobiles[randMobileIndex]
	ioutil.WriteFile(officeIp + ".mobile", []byte(smsMobile), 0644)
	out, err := exec.Command("curl", "-d", `mobile=` + smsMobile +
		`&templateId=1020481&path=iplogin&validMinutes=15`, config.SendCaptcha).Output()
	if err != nil {
		fmt.Println("err:" + err.Error())
		ctx.WriteString(`{"ok":false,"msg":"` + err.Error() + `"}`)
	} else {
		fmt.Println("out:" + string(out))
		ctx.WriteString(`{"ok":true,"tag":"` + config.MobileTags[randMobileIndex] + `"}`)
	}
}

func isIpAlreadyAllowed(envs, ip string) (bool, string) {
	content, err := ioutil.ReadFile(envs + "-AllowIps.txt")
	if err != nil {
		return false, ""
	}

	strContent := string(content)
	alreadyAllowed := strings.Contains(strContent, ip)
	return alreadyAllowed, strContent
}

func writeAllowIpFile(env, content string) {
	ioutil.WriteFile(env + "-AllowIps.txt", []byte(content), 0644)
}

func goIpAllowIndexHandler(ctx *iris.Context) {
	envCheckboxes := ""
	for _, env := range config.Envs {
		envCheckboxes += fmt.Sprintf("<input class='env' type='checkbox' value='%v'>%v</input>
", env, env)
	}
	ctx.WriteString(`

请关闭所有代理，防止IP识别错误。
请检查以下显示的IP是：

` + envCheckboxes + `

请输入验证码：<input type="input" id="smsCode" style="width:60px"/>
<button onclick="sendSmsCode()" style="font-size: 14px;">发验证码</button>

<button onclick="setIpAllow()" style="font-size: 14px; padding: 3px 106px; ">设置</button>

	`)
}<|endoftext|>


<commit_before>package sarah

import (
	"context"
	"fmt"
	"golang.org/x/xerrors"
)

// ErrWatcherNotRunning is returned when ConfigWatcher.Unwatch() is called but the context is already canceled.
var ErrWatcherNotRunning = xerrors.New("context is already canceled")

// ErrAlreadySubscribing is returned when duplicated calls to ConfigWatcher.Watch() occur.
var ErrAlreadySubscribing = xerrors.New("already subscribing")

// ConfigNotFoundError is returned when corresponding configuration is not found.
// This is typically returned when the caller tries to see if there is any configuration available via ConfigWatcher.Read().
type ConfigNotFoundError struct {
	BotType BotType
	ID      string
}

// Error returns stringified representation of the error.
func (err *ConfigNotFoundError) Error() string {
	return fmt.Sprintf("no configuration found for %s:%s", err.BotType, err.ID)
}

var _ error = (*ConfigNotFoundError)(nil)

// ConfigWatcher provides an interface for such component that subscribes to any changes for Command and ScheduledTask.
// One example could be watchers.fileWatcher that subscribes to directory changes.
type ConfigWatcher interface {
	// Read reads the latest configuration value and apply that value to configPtr.
	Read(botCtx context.Context, botType BotType, id string, configPtr interface{}) error
	// Watch subscribes to given id's configuration.
	// When a change to the corresponding configuration value occurs, callback is called.
	// A call to callback function triggers go-sarah's core to call Read() to reflect the latest configuration value.
	Watch(botCtx context.Context, botType BotType, id string, callback func()) error
	// Unwatch is called when Bot is stopped and subscription is no longer required.
	Unwatch(botType BotType) error
}

type nullConfigWatcher struct{}

var _ ConfigWatcher = (*nullConfigWatcher)(nil)

func (*nullConfigWatcher) Read(_ context.Context, _ BotType, _ string, _ interface{}) error {
	return nil
}

func (*nullConfigWatcher) Watch(_ context.Context, _ BotType, _ string, _ func()) error {
	return nil
}

func (*nullConfigWatcher) Unwatch(_ BotType) error {
	return nil
}
<commit_msg>Add reference to another Watcher implementaion<commit_after>package sarah

import (
	"context"
	"fmt"
	"golang.org/x/xerrors"
)

// ErrWatcherNotRunning is returned when ConfigWatcher.Unwatch() is called but the context is already canceled.
var ErrWatcherNotRunning = xerrors.New("context is already canceled")

// ErrAlreadySubscribing is returned when duplicated calls to ConfigWatcher.Watch() occur.
var ErrAlreadySubscribing = xerrors.New("already subscribing")

// ConfigNotFoundError is returned when corresponding configuration is not found.
// This is typically returned when the caller tries to see if there is any configuration available via ConfigWatcher.Read().
type ConfigNotFoundError struct {
	BotType BotType
	ID      string
}

// Error returns stringified representation of the error.
func (err *ConfigNotFoundError) Error() string {
	return fmt.Sprintf("no configuration found for %s:%s", err.BotType, err.ID)
}

var _ error = (*ConfigNotFoundError)(nil)

// ConfigWatcher provides an interface for such a component that subscribes to any changes for configuration value of Command and ScheduledTask.
// One example could be watchers.fileWatcher that subscribes to directory changes:
// while another reference implementation to subscribe changes on GitHub repository is hosted at https://github.com/oklahomer/go-sarah-githubconfig .
type ConfigWatcher interface {
	// Read reads the latest configuration value and apply that value to configPtr.
	Read(botCtx context.Context, botType BotType, id string, configPtr interface{}) error
	// Watch subscribes to given id's configuration.
	// When a change to the corresponding configuration value occurs, callback is called.
	// A call to callback function triggers go-sarah's core to call Read() to reflect the latest configuration value.
	Watch(botCtx context.Context, botType BotType, id string, callback func()) error
	// Unwatch is called when Bot is stopped and subscription is no longer required.
	Unwatch(botType BotType) error
}

type nullConfigWatcher struct{}

var _ ConfigWatcher = (*nullConfigWatcher)(nil)

func (*nullConfigWatcher) Read(_ context.Context, _ BotType, _ string, _ interface{}) error {
	return nil
}

func (*nullConfigWatcher) Watch(_ context.Context, _ BotType, _ string, _ func()) error {
	return nil
}

func (*nullConfigWatcher) Unwatch(_ BotType) error {
	return nil
}
<|endoftext|>


<commit_before>package revel

import (
	"github.com/howeyc/fsnotify"
	"os"
	"path"
	"path/filepath"
	"strings"
	"sync"
)

// Listener is an interface for receivers of filesystem events.
type Listener interface {
	// Refresh is invoked by the watcher on relevant filesystem events.
	// If the listener returns an error, it is served to the user on the current request.
	Refresh() *Error
}

// DiscerningListener allows the receiver to selectively watch files.
type DiscerningListener interface {
	Listener
	WatchDir(info os.FileInfo) bool
	WatchFile(basename string) bool
}

// Watcher allows listeners to register to be notified of changes under a given
// directory.
type Watcher struct {
	// Parallel arrays of watcher/listener pairs.
	watchers     []*fsnotify.Watcher
	listeners    []Listener
	forceRefresh bool
	lastError    int
	notifyMutex  sync.Mutex
}

func NewWatcher() *Watcher {
	return &Watcher{
		forceRefresh: true,
		lastError:    -1,
	}
}

// Listen registers for events within the given root directories (recursively).
func (w *Watcher) Listen(listener Listener, roots ...string) {
	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		ERROR.Fatal(err)
	}

	// Replace the unbuffered Event channel with a buffered one.
	// Otherwise multiple change events only come out one at a time, across
	// multiple page views.  (There appears no way to "pump" the events out of
	// the watcher)
	watcher.Event = make(chan *fsnotify.FileEvent, 100)
	watcher.Error = make(chan error, 10)

	// Walk through all files / directories under the root, adding each to watcher.
	for _, p := range roots {
		fi, err := os.Stat(p)
		if err != nil {
			ERROR.Println("Failed to stat watched path", p, ":", err)
			continue
		}

		// If it is a file, watch that specific file.
		if !fi.IsDir() {
			err = watcher.Watch(p)
			if err != nil {
				ERROR.Println("Failed to watch", p, ":", err)
			}
			TRACE.Println("Watching:", p)
			continue
		}

		// Else, walk the directory tree.
		filepath.Walk(p, func(path string, info os.FileInfo, err error) error {
			if err != nil {
				ERROR.Println("Error walking path:", err)
				return nil
			}

			if info.IsDir() {
				if dl, ok := listener.(DiscerningListener); ok {
					if !dl.WatchDir(info) {
						return filepath.SkipDir
					}
				}

				err = watcher.Watch(path)
				if err != nil {
					ERROR.Println("Failed to watch", path, ":", err)
				}
				TRACE.Println("Watching:", path)
			}
			return nil
		})
	}

	w.watchers = append(w.watchers, watcher)
	w.listeners = append(w.listeners, listener)
}

// Notify causes the watcher to forward any change events to listeners.
// It returns the first (if any) error returned.
func (w *Watcher) Notify() *Error {
	// Serialize Notify() calls.
	w.notifyMutex.Lock()
	defer w.notifyMutex.Unlock()

	for i, watcher := range w.watchers {
		listener := w.listeners[i]

		// Pull all pending events / errors from the watcher.
		refresh := false
		for {
			select {
			case ev := <-watcher.Event:
				// Ignore changes to dotfiles.
				if !strings.HasPrefix(path.Base(ev.Name), ".") {
					if dl, ok := listener.(DiscerningListener); ok {
						if !dl.WatchFile(ev.Name) {
							continue
						}
					}

					refresh = true
				}
				continue
			case <-watcher.Error:
				continue
			default:
				// No events left to pull
			}
			break
		}

		if w.forceRefresh || refresh || w.lastError == i {
			err := listener.Refresh()
			if err != nil {
				w.lastError = i
				return err
			}
		}
	}

	w.forceRefresh = false
	w.lastError = -1
	return nil
}

var WatchFilter = func(c *Controller, fc []Filter) {
	if MainWatcher != nil {
		err := MainWatcher.Notify()
		if err != nil {
			c.Result = c.RenderError(err)
			return
		}
	}
	fc[0](c, fc[1:])
}
<commit_msg>Skip attribute events in watcher. Fixes issue #460<commit_after>package revel

import (
	"github.com/howeyc/fsnotify"
	"os"
	"path"
	"path/filepath"
	"strings"
	"sync"
)

// Listener is an interface for receivers of filesystem events.
type Listener interface {
	// Refresh is invoked by the watcher on relevant filesystem events.
	// If the listener returns an error, it is served to the user on the current request.
	Refresh() *Error
}

// DiscerningListener allows the receiver to selectively watch files.
type DiscerningListener interface {
	Listener
	WatchDir(info os.FileInfo) bool
	WatchFile(basename string) bool
}

// Watcher allows listeners to register to be notified of changes under a given
// directory.
type Watcher struct {
	// Parallel arrays of watcher/listener pairs.
	watchers     []*fsnotify.Watcher
	listeners    []Listener
	forceRefresh bool
	lastError    int
	notifyMutex  sync.Mutex
}

func NewWatcher() *Watcher {
	return &Watcher{
		forceRefresh: true,
		lastError:    -1,
	}
}

// Listen registers for events within the given root directories (recursively).
func (w *Watcher) Listen(listener Listener, roots ...string) {
	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		ERROR.Fatal(err)
	}

	// Replace the unbuffered Event channel with a buffered one.
	// Otherwise multiple change events only come out one at a time, across
	// multiple page views.  (There appears no way to "pump" the events out of
	// the watcher)
	watcher.Event = make(chan *fsnotify.FileEvent, 100)
	watcher.Error = make(chan error, 10)

	// Walk through all files / directories under the root, adding each to watcher.
	for _, p := range roots {
		fi, err := os.Stat(p)
		if err != nil {
			ERROR.Println("Failed to stat watched path", p, ":", err)
			continue
		}

		// If it is a file, watch that specific file.
		if !fi.IsDir() {
			err = watcher.Watch(p)
			if err != nil {
				ERROR.Println("Failed to watch", p, ":", err)
			}
			TRACE.Println("Watching:", p)
			continue
		}

		// Else, walk the directory tree.
		filepath.Walk(p, func(path string, info os.FileInfo, err error) error {
			if err != nil {
				ERROR.Println("Error walking path:", err)
				return nil
			}

			if info.IsDir() {
				if dl, ok := listener.(DiscerningListener); ok {
					if !dl.WatchDir(info) {
						return filepath.SkipDir
					}
				}

				err = watcher.Watch(path)
				if err != nil {
					ERROR.Println("Failed to watch", path, ":", err)
				}
				TRACE.Println("Watching:", path)
			}
			return nil
		})
	}

	w.watchers = append(w.watchers, watcher)
	w.listeners = append(w.listeners, listener)
}

// Notify causes the watcher to forward any change events to listeners.
// It returns the first (if any) error returned.
func (w *Watcher) Notify() *Error {
	// Serialize Notify() calls.
	w.notifyMutex.Lock()
	defer w.notifyMutex.Unlock()

	for i, watcher := range w.watchers {
		listener := w.listeners[i]

		// Pull all pending events / errors from the watcher.
		refresh := false
		for {
			select {
			case ev := <-watcher.Event:
				// Ignore changes to dotfiles.
				if !strings.HasPrefix(path.Base(ev.Name), ".") {
					if dl, ok := listener.(DiscerningListener); ok {
						if !dl.WatchFile(ev.Name) || ev.IsAttrib() {
							continue
						}
					}

					refresh = true
				}
				continue
			case <-watcher.Error:
				continue
			default:
				// No events left to pull
			}
			break
		}

		if w.forceRefresh || refresh || w.lastError == i {
			err := listener.Refresh()
			if err != nil {
				w.lastError = i
				return err
			}
		}
	}

	w.forceRefresh = false
	w.lastError = -1
	return nil
}

var WatchFilter = func(c *Controller, fc []Filter) {
	if MainWatcher != nil {
		err := MainWatcher.Notify()
		if err != nil {
			c.Result = c.RenderError(err)
			return
		}
	}
	fc[0](c, fc[1:])
}
<|endoftext|>


<commit_before>package curator

import (
	"sync"

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

type Watcher interface {
	process(event *zk.Event)
}

type simpleWatcher struct {
	Func func(event *zk.Event)
}

func NewWatcher(fn func(event *zk.Event)) Watcher {
	return &simpleWatcher{fn}
}

func (w *simpleWatcher) process(event *zk.Event) {
	w.Func(event)
}

type Watchers struct {
	lock     sync.Mutex
	watchers []Watcher
}

func NewWatchers(watchers ...Watcher) *Watchers {
	return &Watchers{watchers: watchers}
}

func (w *Watchers) Add(watcher Watcher) Watcher {
	w.lock.Lock()

	w.watchers = append(w.watchers, watcher)

	w.lock.Unlock()

	return watcher
}

func (w *Watchers) Remove(watcher Watcher) Watcher {
	w.lock.Lock()
	defer w.lock.Unlock()

	for i, v := range w.watchers {
		if v == watcher {
			copy(w.watchers[i:], w.watchers[i+1:])

			w.watchers = w.watchers[:len(w.watchers)-1]

			return watcher
		}
	}

	return nil
}

func (w *Watchers) Fire(event *zk.Event) {
	for _, watcher := range w.watchers {
		if watcher != nil {
			go watcher.process(event)
		}
	}
}

func (w *Watchers) Watch(events <-chan zk.Event) {
	for {
		if event, ok := <-events; !ok {
			break
		} else {
			w.Fire(&event)
		}
	}
}
<commit_msg>use append to remove element in slice<commit_after>package curator

import (
	"sync"

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

type Watcher interface {
	process(event *zk.Event)
}

type simpleWatcher struct {
	Func func(event *zk.Event)
}

func NewWatcher(fn func(event *zk.Event)) Watcher {
	return &simpleWatcher{fn}
}

func (w *simpleWatcher) process(event *zk.Event) {
	w.Func(event)
}

type Watchers struct {
	lock     sync.Mutex
	watchers []Watcher
}

func NewWatchers(watchers ...Watcher) *Watchers {
	return &Watchers{watchers: watchers}
}

func (w *Watchers) Len() int { return len(w.watchers) }

func (w *Watchers) Add(watcher Watcher) Watcher {
	w.lock.Lock()

	w.watchers = append(w.watchers, watcher)

	w.lock.Unlock()

	return watcher
}

func (w *Watchers) Remove(watcher Watcher) Watcher {
	w.lock.Lock()
	defer w.lock.Unlock()

	for i, v := range w.watchers {
		if v == watcher {
			w.watchers = append(w.watchers[:i], w.watchers[i+1:]...)

			return watcher
		}
	}

	return nil
}

func (w *Watchers) Fire(event *zk.Event) {
	for _, watcher := range w.watchers {
		if watcher != nil {
			go watcher.process(event)
		}
	}
}

func (w *Watchers) Watch(events <-chan zk.Event) {
	for {
		if event, ok := <-events; !ok {
			break
		} else {
			w.Fire(&event)
		}
	}
}
<|endoftext|>


<commit_before>package main

/**
 * Barista Server
 *
 * @author: Anant Bhardwaj
 * @date: 03/23/2014
 */

import "fmt"
import "barista"
import "git.apache.org/thrift.git/lib/go/thrift"
import "net"
import "strings"

const PORT = ":9000"

func main() {
  protocolFactory := thrift.NewTBinaryProtocolFactoryDefault()
  transportFactory := thrift.NewTTransportFactory()

  addrs, err := net.InterfaceAddrs()
  addr := ""

  if err != nil || len(addrs) < 2 {
     fmt.Println("Error getting ip: ", err)
     addr = "localhost" + PORT
  } else {
     addr = strings.Split(addrs[1].String(), "/")[0] + PORT
  }

  transport, err := thrift.NewTServerSocket(addr)

  if err != nil {
    fmt.Println("Error: ", err)
    return
  }

  handler := NewBaristaHandler([]string {addr}, 0)
  processor := barista.NewBaristaProcessor(handler)
  server := thrift.NewTSimpleServer4(processor, transport, transportFactory, protocolFactory)

  fmt.Println("Starting the Barista server on ", addr)
  server.Serve()
}<commit_msg>add all 5 servers to paxos<commit_after>package main

/**
 * Barista Server
 *
 * @author: Anant Bhardwaj
 * @date: 03/23/2014
 */

import "fmt"
import "barista"
import "git.apache.org/thrift.git/lib/go/thrift"
import "net"
import "strings"

const PORT = ":9000"
var ADDRS = []string {"128.52.161.243:9000", "128.52.160.104:9000", "128.52.161.242:9000", "128.52.160.122:9000", "128.52.161.24:9000"}

func main() {
  protocolFactory := thrift.NewTBinaryProtocolFactoryDefault()
  transportFactory := thrift.NewTTransportFactory()

  addrs, err := net.InterfaceAddrs()
  addr := ""

  if err != nil || len(addrs) < 2 {
     fmt.Println("Error getting ip: ", err)
     addr = "localhost" + PORT
  } else {
     addr = strings.Split(addrs[1].String(), "/")[0] + PORT
  }

  transport, err := thrift.NewTServerSocket(addr)

  if err != nil {
    fmt.Println("Error: ", err)
    return
  }

  me := -1
  for i, server := range ADDRS {
     if addr == server {
        me = i
     }
  }

  if me == -1 {
     fmt.Println("I am not listed in the servers")
     exit(1)
  }

  handler := NewBaristaHandler(ADDRS, me)
  processor := barista.NewBaristaProcessor(handler)
  server := thrift.NewTSimpleServer4(processor, transport, transportFactory, protocolFactory)

  fmt.Println("Starting the Barista server on ", addr)
  server.Serve()
}<|endoftext|>


<commit_before>package collector

import (
	"errors"
	"strings"

	"github.com/fsouza/go-dockerclient"
)

const appLabel = "collectd_docker_app"
const taskLabel = "collectd_docker_task"
const taskLocationLabel = "collectd_docker_task_label"

const appEnvPrefix = "COLLECTD_DOCKER_APP="
const taskEnvPrefix = "COLLECTD_DOCKER_TASK="
const taskEnvLocationPrefix = "COLLECTD_DOCKER_TASK_ENV="
const taskEnvLocationTrimPrefix = "COLLECTD_DOCKER_TASK_ENV_TRIM_PREFIX="

const defaultTask = "default"

// ErrNoNeedToMonitor is used to skip containers
// that shouldn't be monitored by collectd
var ErrNoNeedToMonitor = errors.New("container is not supposed to be monitored")

// MonitorDockerClient represents restricted interface for docker client
// that is used in monitor, docker.Client is a subset of this interface
type MonitorDockerClient interface {
	InspectContainer(id string) (*docker.Container, error)
	Stats(opts docker.StatsOptions) error
}

// Monitor is responsible for monitoring of a single container (task)
type Monitor struct {
	client   MonitorDockerClient
	id       string
	app      string
	task     string
	interval int
}

// NewMonitor creates new monitor with specified docker client,
// container id and stat updating interval
func NewMonitor(c MonitorDockerClient, id string, interval int) (*Monitor, error) {
	container, err := c.InspectContainer(id)
	if err != nil {
		return nil, err
	}

	app := sanitizeForGraphite(extractApp(container))
	if app == "" {
		return nil, ErrNoNeedToMonitor
	}

	task := sanitizeForGraphite(extractTask(container))

	return &Monitor{
		client:   c,
		id:       container.ID,
		app:      app,
		task:     task,
		interval: interval,
	}, nil
}

func (m *Monitor) handle(ch chan<- Stats) error {
	in := make(chan *docker.Stats)

	go func() {
		i := 0
		for s := range in {
			if i%m.interval != 0 {
				i++
				continue
			}

			ch <- Stats{
				App:   m.app,
				Task:  m.task,
				Stats: *s,
			}

			i++
		}
	}()

	return m.client.Stats(docker.StatsOptions{
		ID:    m.id,
		Stats: in,
	})
}

func extractApp(c *docker.Container) string {
	return extractMetadata(c, appLabel, appEnvPrefix, "")
}

func extractTask(c *docker.Container) string {
	task := defaultTask

	location := extractMetadata(c, taskLocationLabel, taskEnvLocationPrefix, "")
	if location != "" {
		task = extractMetadata(c, location, location+"=", defaultTask)
	} else {
		task = extractMetadata(c, taskLabel, taskEnvPrefix, defaultTask)
	}

	prefix := extractEnv(c, taskEnvLocationTrimPrefix)
	if prefix != "" {
		return strings.TrimPrefix(task, prefix)
	}

	return task
}

func extractMetadata(c *docker.Container, label, envPrefix, missing string) string {
	if app, ok := c.Config.Labels[label]; ok {
		return app
	}

	env := extractEnv(c, envPrefix)
	if env != "" {
		return env
	}

	return missing
}

func extractEnv(c *docker.Container, envPrefix string) string {
	for _, e := range c.Config.Env {
		if strings.HasPrefix(e, envPrefix) {
			return strings.TrimPrefix(e, envPrefix)
		}
	}

	return ""
}

func sanitizeForGraphite(s string) string {
	return strings.Replace(s, ".", "_", -1)
}
<commit_msg>Enable streaming in latest dockers, closes #14<commit_after>package collector

import (
	"errors"
	"strings"

	"github.com/fsouza/go-dockerclient"
)

const appLabel = "collectd_docker_app"
const taskLabel = "collectd_docker_task"
const taskLocationLabel = "collectd_docker_task_label"

const appEnvPrefix = "COLLECTD_DOCKER_APP="
const taskEnvPrefix = "COLLECTD_DOCKER_TASK="
const taskEnvLocationPrefix = "COLLECTD_DOCKER_TASK_ENV="
const taskEnvLocationTrimPrefix = "COLLECTD_DOCKER_TASK_ENV_TRIM_PREFIX="

const defaultTask = "default"

// ErrNoNeedToMonitor is used to skip containers
// that shouldn't be monitored by collectd
var ErrNoNeedToMonitor = errors.New("container is not supposed to be monitored")

// MonitorDockerClient represents restricted interface for docker client
// that is used in monitor, docker.Client is a subset of this interface
type MonitorDockerClient interface {
	InspectContainer(id string) (*docker.Container, error)
	Stats(opts docker.StatsOptions) error
}

// Monitor is responsible for monitoring of a single container (task)
type Monitor struct {
	client   MonitorDockerClient
	id       string
	app      string
	task     string
	interval int
}

// NewMonitor creates new monitor with specified docker client,
// container id and stat updating interval
func NewMonitor(c MonitorDockerClient, id string, interval int) (*Monitor, error) {
	container, err := c.InspectContainer(id)
	if err != nil {
		return nil, err
	}

	app := sanitizeForGraphite(extractApp(container))
	if app == "" {
		return nil, ErrNoNeedToMonitor
	}

	task := sanitizeForGraphite(extractTask(container))

	return &Monitor{
		client:   c,
		id:       container.ID,
		app:      app,
		task:     task,
		interval: interval,
	}, nil
}

func (m *Monitor) handle(ch chan<- Stats) error {
	in := make(chan *docker.Stats)

	go func() {
		i := 0
		for s := range in {
			if i%m.interval != 0 {
				i++
				continue
			}

			ch <- Stats{
				App:   m.app,
				Task:  m.task,
				Stats: *s,
			}

			i++
		}
	}()

	return m.client.Stats(docker.StatsOptions{
		ID:     m.id,
		Stats:  in,
		Stream: true,
	})
}

func extractApp(c *docker.Container) string {
	return extractMetadata(c, appLabel, appEnvPrefix, "")
}

func extractTask(c *docker.Container) string {
	task := defaultTask

	location := extractMetadata(c, taskLocationLabel, taskEnvLocationPrefix, "")
	if location != "" {
		task = extractMetadata(c, location, location+"=", defaultTask)
	} else {
		task = extractMetadata(c, taskLabel, taskEnvPrefix, defaultTask)
	}

	prefix := extractEnv(c, taskEnvLocationTrimPrefix)
	if prefix != "" {
		return strings.TrimPrefix(task, prefix)
	}

	return task
}

func extractMetadata(c *docker.Container, label, envPrefix, missing string) string {
	if app, ok := c.Config.Labels[label]; ok {
		return app
	}

	env := extractEnv(c, envPrefix)
	if env != "" {
		return env
	}

	return missing
}

func extractEnv(c *docker.Container, envPrefix string) string {
	for _, e := range c.Config.Env {
		if strings.HasPrefix(e, envPrefix) {
			return strings.TrimPrefix(e, envPrefix)
		}
	}

	return ""
}

func sanitizeForGraphite(s string) string {
	return strings.Replace(s, ".", "_", -1)
}
<|endoftext|>


<commit_before>package command

import (
	"encoding/json"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"testing"

	"github.com/google/go-cmp/cmp"
	"github.com/hashicorp/terraform/addrs"
	"github.com/hashicorp/terraform/configs/configschema"
	"github.com/hashicorp/terraform/helper/copy"
	"github.com/hashicorp/terraform/plans"
	"github.com/hashicorp/terraform/providers"
	"github.com/hashicorp/terraform/states"
	"github.com/hashicorp/terraform/terraform"
	"github.com/mitchellh/cli"
	"github.com/zclconf/go-cty/cty"
)

func TestShow(t *testing.T) {
	ui := new(cli.MockUi)
	c := &ShowCommand{
		Meta: Meta{
			testingOverrides: metaOverridesForProvider(testProvider()),
			Ui:               ui,
		},
	}

	args := []string{
		"bad",
		"bad",
	}
	if code := c.Run(args); code != 1 {
		t.Fatalf("bad: \n%s", ui.OutputWriter.String())
	}
}

func TestShow_noArgs(t *testing.T) {
	// Create the default state
	statePath := testStateFile(t, testState())
	defer testChdir(t, filepath.Dir(statePath))()

	ui := new(cli.MockUi)
	c := &ShowCommand{
		Meta: Meta{
			testingOverrides: metaOverridesForProvider(testProvider()),
			Ui:               ui,
		},
	}

	args := []string{}
	if code := c.Run(args); code != 0 {
		t.Fatalf("bad: \n%s", ui.OutputWriter.String())
	}
}

func TestShow_noArgsNoState(t *testing.T) {
	// Create the default state
	statePath := testStateFile(t, testState())
	defer testChdir(t, filepath.Dir(statePath))()

	ui := new(cli.MockUi)
	c := &ShowCommand{
		Meta: Meta{
			testingOverrides: metaOverridesForProvider(testProvider()),
			Ui:               ui,
		},
	}

	args := []string{}
	if code := c.Run(args); code != 0 {
		t.Fatalf("bad: \n%s", ui.OutputWriter.String())
	}
}

func TestShow_plan(t *testing.T) {
	planPath := testPlanFileNoop(t)

	ui := cli.NewMockUi()
	c := &ShowCommand{
		Meta: Meta{
			testingOverrides: metaOverridesForProvider(testProvider()),
			Ui:               ui,
		},
	}

	args := []string{
		planPath,
	}
	if code := c.Run(args); code != 0 {
		t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
	}

	want := `Terraform will perform the following actions`
	got := ui.OutputWriter.String()
	if !strings.Contains(got, want) {
		t.Errorf("missing expected output\nwant: %s\ngot:\n%s", want, got)
	}
}

func TestShow_plan_json(t *testing.T) {
	planPath := showFixturePlanFile(t)

	ui := new(cli.MockUi)
	c := &ShowCommand{
		Meta: Meta{
			testingOverrides: metaOverridesForProvider(showFixtureProvider()),
			Ui:               ui,
		},
	}

	args := []string{
		"-json",
		planPath,
	}
	if code := c.Run(args); code != 0 {
		t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
	}
}

func TestShow_state(t *testing.T) {
	originalState := testState()
	statePath := testStateFile(t, originalState)

	ui := new(cli.MockUi)
	c := &ShowCommand{
		Meta: Meta{
			testingOverrides: metaOverridesForProvider(testProvider()),
			Ui:               ui,
		},
	}

	args := []string{
		statePath,
	}
	if code := c.Run(args); code != 0 {
		t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
	}
}

func TestShow_json_output(t *testing.T) {
	fixtureDir := "testdata/show-json"
	testDirs, err := ioutil.ReadDir(fixtureDir)
	if err != nil {
		t.Fatal(err)
	}

	for _, entry := range testDirs {
		if !entry.IsDir() {
			continue
		}

		t.Run(entry.Name(), func(t *testing.T) {
			td := tempDir(t)
			inputDir := filepath.Join(fixtureDir, entry.Name())
			copy.CopyDir(inputDir, td)
			defer os.RemoveAll(td)
			defer testChdir(t, td)()

			expectError := strings.Contains(entry.Name(), "error")

			p := showFixtureProvider()
			ui := new(cli.MockUi)
			m := Meta{
				testingOverrides: metaOverridesForProvider(p),
				Ui:               ui,
			}

			// init
			ic := &InitCommand{
				Meta: m,
				providerInstaller: &mockProviderInstaller{
					Providers: map[string][]string{
						"test": []string{"1.2.3"},
					},
					Dir: m.pluginDir(),
				},
			}
			if code := ic.Run([]string{}); code != 0 {
				if expectError {
					// this should error, but not panic.
					return
				}
				t.Fatalf("init failed\n%s", ui.ErrorWriter)
			}

			pc := &PlanCommand{
				Meta: m,
			}

			args := []string{
				"-out=terraform.plan",
			}

			if code := pc.Run(args); code != 0 {
				t.Fatalf("wrong exit status %d; want 0\nstderr: %s", code, ui.ErrorWriter.String())
			}

			// flush the plan output from the mock ui
			ui.OutputWriter.Reset()
			sc := &ShowCommand{
				Meta: m,
			}

			args = []string{
				"-json",
				"terraform.plan",
			}
			defer os.Remove("terraform.plan")

			if code := sc.Run(args); code != 0 {
				t.Fatalf("wrong exit status %d; want 0\nstderr: %s", code, ui.ErrorWriter.String())
			}

			// compare ui output to wanted output
			var got, want plan

			gotString := ui.OutputWriter.String()
			json.Unmarshal([]byte(gotString), &got)

			wantFile, err := os.Open("output.json")
			if err != nil {
				t.Fatalf("err: %s", err)
			}
			defer wantFile.Close()
			byteValue, err := ioutil.ReadAll(wantFile)
			if err != nil {
				t.Fatalf("err: %s", err)
			}
			json.Unmarshal([]byte(byteValue), &want)

			if !cmp.Equal(got, want) {
				t.Fatalf("wrong result:\n %v\n", cmp.Diff(got, want))
			}

		})

	}
}

// similar test as above, without the plan
func TestShow_json_output_state(t *testing.T) {
	fixtureDir := "testdata/show-json-state"
	testDirs, err := ioutil.ReadDir(fixtureDir)
	if err != nil {
		t.Fatal(err)
	}

	for _, entry := range testDirs {
		if !entry.IsDir() {
			continue
		}

		t.Run(entry.Name(), func(t *testing.T) {
			td := tempDir(t)
			inputDir := filepath.Join(fixtureDir, entry.Name())
			copy.CopyDir(inputDir, td)
			defer os.RemoveAll(td)
			defer testChdir(t, td)()

			p := showFixtureProvider()
			ui := new(cli.MockUi)
			m := Meta{
				testingOverrides: metaOverridesForProvider(p),
				Ui:               ui,
			}

			// init
			ic := &InitCommand{
				Meta: m,
				providerInstaller: &mockProviderInstaller{
					Providers: map[string][]string{
						"test": []string{"1.2.3"},
					},
					Dir: m.pluginDir(),
				},
			}
			if code := ic.Run([]string{}); code != 0 {
				t.Fatalf("init failed\n%s", ui.ErrorWriter)
			}

			// flush the plan output from the mock ui
			ui.OutputWriter.Reset()
			sc := &ShowCommand{
				Meta: m,
			}

			if code := sc.Run([]string{"-json"}); code != 0 {
				t.Fatalf("wrong exit status %d; want 0\nstderr: %s", code, ui.ErrorWriter.String())
			}

			// compare ui output to wanted output
			type state struct {
				FormatVersion    string                 `json:"format_version,omitempty"`
				TerraformVersion string                 `json:"terraform_version"`
				Values           map[string]interface{} `json:"values,omitempty"`
			}
			var got, want state

			gotString := ui.OutputWriter.String()
			json.Unmarshal([]byte(gotString), &got)

			wantFile, err := os.Open("output.json")
			if err != nil {
				t.Fatalf("err: %s", err)
			}
			defer wantFile.Close()
			byteValue, err := ioutil.ReadAll(wantFile)
			if err != nil {
				t.Fatalf("err: %s", err)
			}
			json.Unmarshal([]byte(byteValue), &want)

			if !cmp.Equal(got, want) {
				t.Fatalf("wrong result:\n %v\n", cmp.Diff(got, want))
			}

		})

	}
}

// showFixtureSchema returns a schema suitable for processing the configuration
// in testdata/show. This schema should be assigned to a mock provider
// named "test".
func showFixtureSchema() *terraform.ProviderSchema {
	return &terraform.ProviderSchema{
		ResourceTypes: map[string]*configschema.Block{
			"test_instance": {
				Attributes: map[string]*configschema.Attribute{
					"id":  {Type: cty.String, Optional: true, Computed: true},
					"ami": {Type: cty.String, Optional: true},
				},
			},
		},
	}
}

// showFixtureProvider returns a mock provider that is configured for basic
// operation with the configuration in testdata/show. This mock has
// GetSchemaReturn, PlanResourceChangeFn, and ApplyResourceChangeFn populated,
// with the plan/apply steps just passing through the data determined by
// Terraform Core.
func showFixtureProvider() *terraform.MockProvider {
	p := testProvider()
	p.GetSchemaReturn = showFixtureSchema()
	p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
		idVal := req.ProposedNewState.GetAttr("id")
		amiVal := req.ProposedNewState.GetAttr("ami")
		if idVal.IsNull() {
			idVal = cty.UnknownVal(cty.String)
		}
		return providers.PlanResourceChangeResponse{
			PlannedState: cty.ObjectVal(map[string]cty.Value{
				"id":  idVal,
				"ami": amiVal,
			}),
		}
	}
	p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse {
		idVal := req.PlannedState.GetAttr("id")
		amiVal := req.PlannedState.GetAttr("ami")
		if !idVal.IsKnown() {
			idVal = cty.StringVal("placeholder")
		}
		return providers.ApplyResourceChangeResponse{
			NewState: cty.ObjectVal(map[string]cty.Value{
				"id":  idVal,
				"ami": amiVal,
			}),
		}
	}
	return p
}

// showFixturePlanFile creates a plan file at a temporary location containing a
// single change to create the test_instance.foo that is included in the "show"
// test fixture, returning the location of that plan file.
func showFixturePlanFile(t *testing.T) string {
	_, snap := testModuleWithSnapshot(t, "show")
	plannedVal := cty.ObjectVal(map[string]cty.Value{
		"id":  cty.UnknownVal(cty.String),
		"ami": cty.StringVal("bar"),
	})
	priorValRaw, err := plans.NewDynamicValue(cty.NullVal(plannedVal.Type()), plannedVal.Type())
	if err != nil {
		t.Fatal(err)
	}
	plannedValRaw, err := plans.NewDynamicValue(plannedVal, plannedVal.Type())
	if err != nil {
		t.Fatal(err)
	}
	plan := testPlan(t)
	plan.Changes.SyncWrapper().AppendResourceInstanceChange(&plans.ResourceInstanceChangeSrc{
		Addr: addrs.Resource{
			Mode: addrs.ManagedResourceMode,
			Type: "test_instance",
			Name: "foo",
		}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
		ProviderAddr: addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
		ChangeSrc: plans.ChangeSrc{
			Action: plans.Create,
			Before: priorValRaw,
			After:  plannedValRaw,
		},
	})
	return testPlanFile(
		t,
		snap,
		states.NewState(),
		plan,
	)
}

// this simplified plan struct allows us to preserve field order when marshaling
// the command output. NOTE: we are leaving "terraform_version" out of this test
// to avoid needing to constantly update the expected output; as a potential
// TODO we could write a jsonplan compare function.
type plan struct {
	FormatVersion   string                 `json:"format_version,omitempty"`
	Variables       map[string]interface{} `json:"variables,omitempty"`
	PlannedValues   map[string]interface{} `json:"planned_values,omitempty"`
	ResourceChanges []interface{}          `json:"resource_changes,omitempty"`
	OutputChanges   map[string]interface{} `json:"output_changes,omitempty"`
	PriorState      priorState             `json:"prior_state,omitempty"`
	Config          map[string]interface{} `json:"configuration,omitempty"`
}

type priorState struct {
	FormatVersion string                 `json:"format_version,omitempty"`
	Values        map[string]interface{} `json:"values,omitempty"`
}
<commit_msg>Add a test<commit_after>package command

import (
	"encoding/json"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"testing"

	"github.com/google/go-cmp/cmp"
	"github.com/hashicorp/terraform/addrs"
	"github.com/hashicorp/terraform/configs/configschema"
	"github.com/hashicorp/terraform/helper/copy"
	"github.com/hashicorp/terraform/plans"
	"github.com/hashicorp/terraform/providers"
	"github.com/hashicorp/terraform/states"
	"github.com/hashicorp/terraform/terraform"
	"github.com/mitchellh/cli"
	"github.com/zclconf/go-cty/cty"
)

func TestShow(t *testing.T) {
	ui := new(cli.MockUi)
	c := &ShowCommand{
		Meta: Meta{
			testingOverrides: metaOverridesForProvider(testProvider()),
			Ui:               ui,
		},
	}

	args := []string{
		"bad",
		"bad",
	}
	if code := c.Run(args); code != 1 {
		t.Fatalf("bad: \n%s", ui.OutputWriter.String())
	}
}

func TestShow_noArgs(t *testing.T) {
	// Create the default state
	statePath := testStateFile(t, testState())
	defer testChdir(t, filepath.Dir(statePath))()

	ui := new(cli.MockUi)
	c := &ShowCommand{
		Meta: Meta{
			testingOverrides: metaOverridesForProvider(testProvider()),
			Ui:               ui,
		},
	}

	args := []string{}
	if code := c.Run(args); code != 0 {
		t.Fatalf("bad: \n%s", ui.OutputWriter.String())
	}
}

func TestShow_noArgsNoState(t *testing.T) {
	// Create the default state
	statePath := testStateFile(t, testState())
	defer testChdir(t, filepath.Dir(statePath))()

	ui := new(cli.MockUi)
	c := &ShowCommand{
		Meta: Meta{
			testingOverrides: metaOverridesForProvider(testProvider()),
			Ui:               ui,
		},
	}

	args := []string{}
	if code := c.Run(args); code != 0 {
		t.Fatalf("bad: \n%s", ui.OutputWriter.String())
	}
}

func TestShow_plan(t *testing.T) {
	planPath := testPlanFileNoop(t)

	ui := cli.NewMockUi()
	c := &ShowCommand{
		Meta: Meta{
			testingOverrides: metaOverridesForProvider(testProvider()),
			Ui:               ui,
		},
	}

	args := []string{
		planPath,
	}
	if code := c.Run(args); code != 0 {
		t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
	}

	want := `Terraform will perform the following actions`
	got := ui.OutputWriter.String()
	if !strings.Contains(got, want) {
		t.Errorf("missing expected output\nwant: %s\ngot:\n%s", want, got)
	}
}

func TestShow_planWithChanges(t *testing.T) {
	planPathWithChanges := showFixturePlanFile(t, plans.DeleteThenCreate)

	ui := cli.NewMockUi()
	c := &ShowCommand{
		Meta: Meta{
			testingOverrides: metaOverridesForProvider(showFixtureProvider()),
			Ui:               ui,
		},
	}

	args := []string{
		planPathWithChanges,
	}

	if code := c.Run(args); code != 0 {
		t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
	}

	want := `test_instance.foo must be replaced`
	got := ui.OutputWriter.String()
	if !strings.Contains(got, want) {
		t.Errorf("missing expected output\nwant: %s\ngot:\n%s", want, got)
	}
}

func TestShow_plan_json(t *testing.T) {
	planPath := showFixturePlanFile(t, plans.Create)

	ui := new(cli.MockUi)
	c := &ShowCommand{
		Meta: Meta{
			testingOverrides: metaOverridesForProvider(showFixtureProvider()),
			Ui:               ui,
		},
	}

	args := []string{
		"-json",
		planPath,
	}
	if code := c.Run(args); code != 0 {
		t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
	}
}

func TestShow_state(t *testing.T) {
	originalState := testState()
	statePath := testStateFile(t, originalState)

	ui := new(cli.MockUi)
	c := &ShowCommand{
		Meta: Meta{
			testingOverrides: metaOverridesForProvider(testProvider()),
			Ui:               ui,
		},
	}

	args := []string{
		statePath,
	}
	if code := c.Run(args); code != 0 {
		t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
	}
}

func TestShow_json_output(t *testing.T) {
	fixtureDir := "testdata/show-json"
	testDirs, err := ioutil.ReadDir(fixtureDir)
	if err != nil {
		t.Fatal(err)
	}

	for _, entry := range testDirs {
		if !entry.IsDir() {
			continue
		}

		t.Run(entry.Name(), func(t *testing.T) {
			td := tempDir(t)
			inputDir := filepath.Join(fixtureDir, entry.Name())
			copy.CopyDir(inputDir, td)
			defer os.RemoveAll(td)
			defer testChdir(t, td)()

			expectError := strings.Contains(entry.Name(), "error")

			p := showFixtureProvider()
			ui := new(cli.MockUi)
			m := Meta{
				testingOverrides: metaOverridesForProvider(p),
				Ui:               ui,
			}

			// init
			ic := &InitCommand{
				Meta: m,
				providerInstaller: &mockProviderInstaller{
					Providers: map[string][]string{
						"test": []string{"1.2.3"},
					},
					Dir: m.pluginDir(),
				},
			}
			if code := ic.Run([]string{}); code != 0 {
				if expectError {
					// this should error, but not panic.
					return
				}
				t.Fatalf("init failed\n%s", ui.ErrorWriter)
			}

			pc := &PlanCommand{
				Meta: m,
			}

			args := []string{
				"-out=terraform.plan",
			}

			if code := pc.Run(args); code != 0 {
				t.Fatalf("wrong exit status %d; want 0\nstderr: %s", code, ui.ErrorWriter.String())
			}

			// flush the plan output from the mock ui
			ui.OutputWriter.Reset()
			sc := &ShowCommand{
				Meta: m,
			}

			args = []string{
				"-json",
				"terraform.plan",
			}
			defer os.Remove("terraform.plan")

			if code := sc.Run(args); code != 0 {
				t.Fatalf("wrong exit status %d; want 0\nstderr: %s", code, ui.ErrorWriter.String())
			}

			// compare ui output to wanted output
			var got, want plan

			gotString := ui.OutputWriter.String()
			json.Unmarshal([]byte(gotString), &got)

			wantFile, err := os.Open("output.json")
			if err != nil {
				t.Fatalf("err: %s", err)
			}
			defer wantFile.Close()
			byteValue, err := ioutil.ReadAll(wantFile)
			if err != nil {
				t.Fatalf("err: %s", err)
			}
			json.Unmarshal([]byte(byteValue), &want)

			if !cmp.Equal(got, want) {
				t.Fatalf("wrong result:\n %v\n", cmp.Diff(got, want))
			}

		})

	}
}

// similar test as above, without the plan
func TestShow_json_output_state(t *testing.T) {
	fixtureDir := "testdata/show-json-state"
	testDirs, err := ioutil.ReadDir(fixtureDir)
	if err != nil {
		t.Fatal(err)
	}

	for _, entry := range testDirs {
		if !entry.IsDir() {
			continue
		}

		t.Run(entry.Name(), func(t *testing.T) {
			td := tempDir(t)
			inputDir := filepath.Join(fixtureDir, entry.Name())
			copy.CopyDir(inputDir, td)
			defer os.RemoveAll(td)
			defer testChdir(t, td)()

			p := showFixtureProvider()
			ui := new(cli.MockUi)
			m := Meta{
				testingOverrides: metaOverridesForProvider(p),
				Ui:               ui,
			}

			// init
			ic := &InitCommand{
				Meta: m,
				providerInstaller: &mockProviderInstaller{
					Providers: map[string][]string{
						"test": []string{"1.2.3"},
					},
					Dir: m.pluginDir(),
				},
			}
			if code := ic.Run([]string{}); code != 0 {
				t.Fatalf("init failed\n%s", ui.ErrorWriter)
			}

			// flush the plan output from the mock ui
			ui.OutputWriter.Reset()
			sc := &ShowCommand{
				Meta: m,
			}

			if code := sc.Run([]string{"-json"}); code != 0 {
				t.Fatalf("wrong exit status %d; want 0\nstderr: %s", code, ui.ErrorWriter.String())
			}

			// compare ui output to wanted output
			type state struct {
				FormatVersion    string                 `json:"format_version,omitempty"`
				TerraformVersion string                 `json:"terraform_version"`
				Values           map[string]interface{} `json:"values,omitempty"`
			}
			var got, want state

			gotString := ui.OutputWriter.String()
			json.Unmarshal([]byte(gotString), &got)

			wantFile, err := os.Open("output.json")
			if err != nil {
				t.Fatalf("err: %s", err)
			}
			defer wantFile.Close()
			byteValue, err := ioutil.ReadAll(wantFile)
			if err != nil {
				t.Fatalf("err: %s", err)
			}
			json.Unmarshal([]byte(byteValue), &want)

			if !cmp.Equal(got, want) {
				t.Fatalf("wrong result:\n %v\n", cmp.Diff(got, want))
			}

		})

	}
}

// showFixtureSchema returns a schema suitable for processing the configuration
// in testdata/show. This schema should be assigned to a mock provider
// named "test".
func showFixtureSchema() *terraform.ProviderSchema {
	return &terraform.ProviderSchema{
		ResourceTypes: map[string]*configschema.Block{
			"test_instance": {
				Attributes: map[string]*configschema.Attribute{
					"id":  {Type: cty.String, Optional: true, Computed: true},
					"ami": {Type: cty.String, Optional: true},
				},
			},
		},
	}
}

// showFixtureProvider returns a mock provider that is configured for basic
// operation with the configuration in testdata/show. This mock has
// GetSchemaReturn, PlanResourceChangeFn, and ApplyResourceChangeFn populated,
// with the plan/apply steps just passing through the data determined by
// Terraform Core.
func showFixtureProvider() *terraform.MockProvider {
	p := testProvider()
	p.GetSchemaReturn = showFixtureSchema()
	p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
		idVal := req.ProposedNewState.GetAttr("id")
		amiVal := req.ProposedNewState.GetAttr("ami")
		if idVal.IsNull() {
			idVal = cty.UnknownVal(cty.String)
		}
		return providers.PlanResourceChangeResponse{
			PlannedState: cty.ObjectVal(map[string]cty.Value{
				"id":  idVal,
				"ami": amiVal,
			}),
		}
	}
	p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse {
		idVal := req.PlannedState.GetAttr("id")
		amiVal := req.PlannedState.GetAttr("ami")
		if !idVal.IsKnown() {
			idVal = cty.StringVal("placeholder")
		}
		return providers.ApplyResourceChangeResponse{
			NewState: cty.ObjectVal(map[string]cty.Value{
				"id":  idVal,
				"ami": amiVal,
			}),
		}
	}
	return p
}

// showFixturePlanFile creates a plan file at a temporary location containing a
// single change to create or update the test_instance.foo that is included in the "show"
// test fixture, returning the location of that plan file.
// `action` is the planned change you would like to elicit
func showFixturePlanFile(t *testing.T, action plans.Action) string {
	_, snap := testModuleWithSnapshot(t, "show")
	plannedVal := cty.ObjectVal(map[string]cty.Value{
		"id":  cty.UnknownVal(cty.String),
		"ami": cty.StringVal("bar"),
	})
	priorValRaw, err := plans.NewDynamicValue(cty.NullVal(plannedVal.Type()), plannedVal.Type())
	if err != nil {
		t.Fatal(err)
	}
	plannedValRaw, err := plans.NewDynamicValue(plannedVal, plannedVal.Type())
	if err != nil {
		t.Fatal(err)
	}
	plan := testPlan(t)
	plan.Changes.SyncWrapper().AppendResourceInstanceChange(&plans.ResourceInstanceChangeSrc{
		Addr: addrs.Resource{
			Mode: addrs.ManagedResourceMode,
			Type: "test_instance",
			Name: "foo",
		}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
		ProviderAddr: addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
		ChangeSrc: plans.ChangeSrc{
			Action: action,
			Before: priorValRaw,
			After:  plannedValRaw,
		},
	})
	return testPlanFile(
		t,
		snap,
		states.NewState(),
		plan,
	)
}

// this simplified plan struct allows us to preserve field order when marshaling
// the command output. NOTE: we are leaving "terraform_version" out of this test
// to avoid needing to constantly update the expected output; as a potential
// TODO we could write a jsonplan compare function.
type plan struct {
	FormatVersion   string                 `json:"format_version,omitempty"`
	Variables       map[string]interface{} `json:"variables,omitempty"`
	PlannedValues   map[string]interface{} `json:"planned_values,omitempty"`
	ResourceChanges []interface{}          `json:"resource_changes,omitempty"`
	OutputChanges   map[string]interface{} `json:"output_changes,omitempty"`
	PriorState      priorState             `json:"prior_state,omitempty"`
	Config          map[string]interface{} `json:"configuration,omitempty"`
}

type priorState struct {
	FormatVersion string                 `json:"format_version,omitempty"`
	Values        map[string]interface{} `json:"values,omitempty"`
}
<|endoftext|>


<commit_before>//  Copyright 2016 The goscope Authors
//
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.

package main

import (
	"context"
	"flag"
	"fmt"
	"image"
	"image/color"
	"log"
	"os"
	"runtime/pprof"
	"strings"
	"sync"
	"time"

	"github.com/zagrodzki/goscope/dummy"
	"github.com/zagrodzki/goscope/gui"
	"github.com/zagrodzki/goscope/scope"
	"github.com/zagrodzki/goscope/triggers"
	"github.com/zagrodzki/goscope/usb"
	"golang.org/x/exp/shiny/driver"
	"golang.org/x/exp/shiny/screen"
	"golang.org/x/time/rate"
)

var (
	device           = flag.String("device", "", "Device to use, autodetect if empty")
	list             = flag.Bool("list", false, "If set, only list available devices")
	triggerSource    = flag.String("trigger_source", "", "Name of the channel to use as a trigger source")
	triggerThresh    = flag.Float64("trigger_threshold", 0, "Trigger threshold")
	triggerEdge      = flag.String("trigger_edge", "rising", "Trigger edge, rising or falling")
	triggerMode      = flag.String("trigger_mode", "auto", "Trigger mode, auto, single or normal")
	useChan          = flag.String("channel", "sin", "one of the channels of dummy device: zero,random,sin,triangle,square")
	timeBase         = flag.Duration("timebase", time.Second, "timebase of the displayed waveform")
	perDiv           = flag.Float64("v_per_div", 2, "volts per div")
	screenWidth      = flag.Int("width", 800, "UI width, in pixels")
	screenHeight     = flag.Int("height", 600, "UI height, in pixels")
	refreshRateLimit = flag.Float64("refresh_rate", 25, "maximum refresh rate, in frames per second. 0 = no limit")
	cpuprofile       = flag.String("cpuprofile", "", "File to which the program should write it's CPU profile (performance stats)")
)

type waveform struct {
	tb    scope.Duration
	inter scope.Duration
	tp    map[scope.ChanID]scope.TraceParams

	mu      sync.Mutex
	plot    gui.Plot
	bufPlot gui.Plot
}

func (w *waveform) TimeBase() scope.Duration {
	return w.tb
}

var allColors = []color.RGBA{
	color.RGBA{255, 0, 0, 255},
	color.RGBA{0, 200, 0, 255},
	color.RGBA{0, 0, 255, 255},
	color.RGBA{255, 0, 255, 255},
	color.RGBA{255, 255, 0, 255},
}

func (w *waveform) swapPlot() {
	w.mu.Lock()
	defer w.mu.Unlock()
	w.plot, w.bufPlot = w.bufPlot, w.plot
}

func (w *waveform) keepReading(dataCh <-chan []scope.ChannelData) {
	var buf []scope.ChannelData
	var tbCount = int(w.tb / w.inter)
	chColor := make(map[scope.ChanID]color.RGBA)
	for data := range dataCh {
		if len(data) == 0 {
			continue
		}
		if buf == nil {
			buf = make([]scope.ChannelData, len(data))
			for i, d := range data {
				buf[i].ID = d.ID
				buf[i].Samples = make([]scope.Voltage, 0, 2*tbCount)
				chColor[d.ID] = allColors[i]
			}
		}
		for i, d := range data {
			buf[i].Samples = append(buf[i].Samples, d.Samples...)
		}
		if len(buf[0].Samples) >= tbCount {
			for i := range data {
				buf[i].Samples = buf[i].Samples[:tbCount]
			}

			// full timebase, draw and go to beginning
			w.bufPlot.DrawAll(buf, w.tp, chColor)
			w.swapPlot()
			// truncate the buffers
			for i := range buf {
				buf[i].Samples = buf[i].Samples[:0]
			}
		}
	}
}

func (w *waveform) Reset(inter scope.Duration, d <-chan []scope.ChannelData) {
	w.inter = inter
	go w.keepReading(d)
}

func (w *waveform) Error(error) {}

func (w *waveform) SetTimeBase(d scope.Duration) {
	w.tb = d
}

func (w *waveform) SetChannel(ch scope.ChanID, p scope.TraceParams) {
	if w.tp == nil {
		w.tp = make(map[scope.ChanID]scope.TraceParams)
	}
	w.tp[ch] = p
}

func (w *waveform) Render(ret *image.RGBA) {
	w.mu.Lock()
	defer w.mu.Unlock()
	copy(ret.Pix, w.plot.RGBA.Pix)
}

type system struct {
	name      string
	enumerate func() map[string]string
	open      func(string) (scope.Device, error)
}

var (
	systems = []system{
		{
			name:      "dummy",
			enumerate: dummy.Enumerate,
			open:      dummy.Open,
		},
		{
			name:      "usb",
			enumerate: usb.Enumerate,
			open:      usb.Open,
		},
	}
	systemsByName = make(map[string]int)
)

func main() {
	flag.Parse()

	var all []string
	for idx, sys := range systems {
		systemsByName[sys.name] = idx
		for id := range sys.enumerate() {
			all = append(all, fmt.Sprintf("%s:%s", sys.name, id))
		}
	}

	if len(all) == 0 {
		log.Fatalf("Did not find any supported devices")
	}
	if *list {
		fmt.Println("Devices found:")
		for _, d := range all {
			fmt.Println(d)
		}
		return
	}
	id := all[0]
	if *device != "" {
		for _, d := range all {
			if d == *device {
				id = d
				break
			}
		}
		if id != *device {
			log.Fatalf("Device %s not detected on the list. Available devices: %v", *device, all)
		}
	} else if len(all) > 1 {
		log.Printf("Multiple devices found: %v", all)
		log.Printf("Using the first device (%s)", id)
	}

	parts := strings.SplitN(id, ":", 2)
	s := systemsByName[parts[0]]
	osc, err := systems[s].open(parts[1])

	if err != nil {
		log.Fatalf("Open: %+v", err)
	}

	if *cpuprofile != "" {
		f, err := os.Create(*cpuprofile)
		if err != nil {
			log.Fatal(err)
		}
		pprof.StartCPUProfile(f)
		defer pprof.StopCPUProfile()
	}
	screenSize := image.Point{*screenWidth, *screenHeight}
	wf := &waveform{
		plot:    gui.NewPlot(screenSize),
		bufPlot: gui.NewPlot(screenSize),
	}
	wf.SetTimeBase(scope.DurationFromNano(*timeBase))

	for _, id := range osc.Channels() {
		wf.SetChannel(id, scope.TraceParams{Zero: 0.5, PerDiv: *perDiv})
	}

	tr := triggers.New(wf)
	tr.Source(scope.ChanID(*triggerSource))
	tr.Level(scope.Voltage(*triggerThresh))
	// For now, the names of params are hardcoded here, but in the future
	// names might change between devices and it's not very practical.
	// The intention is to have params initialized to defaults and then changed
	// only through the UI or by specifying the parameter name and value
	// on the commandline. But because there is no UI yet, it's not really feasible.
	for _, p := range tr.TriggerParams() {
		var err error
		switch pn := p.Name(); pn {
		case "Trigger edge":
			err = p.Set(*triggerEdge)
		case "Trigger mode":
			err = p.Set(*triggerMode)
		}
		if err != nil {
			log.Fatalf("TriggerParams[%q].Set(): %v", pn, err)
		}
	}

	osc.Attach(tr)
	osc.Start()
	defer osc.Stop()

	driver.Main(func(s screen.Screen) {
		w, err := s.NewWindow(&screen.NewWindowOptions{Width: screenSize.X, Height: screenSize.Y})
		if err != nil {
			log.Fatalf("NewWindow: %v", err)
		}
		defer w.Release()
		stop := make(chan struct{})
		go processEvents(w, stop)

		b, err := s.NewBuffer(screenSize)
		if err != nil {
			log.Fatalf("NewBuffer(): %v", err)
		}
		defer b.Release()

		var limiter *rate.Limiter
		if *refreshRateLimit > 0 {
			limiter = rate.NewLimiter(rate.Limit(*refreshRateLimit), 1)
		}
		sometimes := rate.NewLimiter(0.2, 1)
		for {
			select {
			case <-stop:
				return
			default:
			}
			if limiter != nil {
				limiter.Wait(context.Background())
			}
			t := time.Now()
			wf.Render(b.RGBA())
			w.Upload(image.Point{0, 0}, b, b.Bounds())
			w.Publish()
			if sometimes.Allow() {
				d := time.Since(t)
				fmt.Printf("Rendering 1 frame took %v (%.2ffps)\n", d, float64(time.Second)/float64(d))
			}
		}
	})
}
<commit_msg>Fix a typo, remove unnecessary parenthesis from the error message.<commit_after>//  Copyright 2016 The goscope Authors
//
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.

package main

import (
	"context"
	"flag"
	"fmt"
	"image"
	"image/color"
	"log"
	"os"
	"runtime/pprof"
	"strings"
	"sync"
	"time"

	"github.com/zagrodzki/goscope/dummy"
	"github.com/zagrodzki/goscope/gui"
	"github.com/zagrodzki/goscope/scope"
	"github.com/zagrodzki/goscope/triggers"
	"github.com/zagrodzki/goscope/usb"
	"golang.org/x/exp/shiny/driver"
	"golang.org/x/exp/shiny/screen"
	"golang.org/x/time/rate"
)

var (
	device           = flag.String("device", "", "Device to use, autodetect if empty")
	list             = flag.Bool("list", false, "If set, only list available devices")
	triggerSource    = flag.String("trigger_source", "", "Name of the channel to use as a trigger source")
	triggerThresh    = flag.Float64("trigger_threshold", 0, "Trigger threshold")
	triggerEdge      = flag.String("trigger_edge", "rising", "Trigger edge, rising or falling")
	triggerMode      = flag.String("trigger_mode", "auto", "Trigger mode, auto, single or normal")
	useChan          = flag.String("channel", "sin", "one of the channels of dummy device: zero,random,sin,triangle,square")
	timeBase         = flag.Duration("timebase", time.Second, "timebase of the displayed waveform")
	perDiv           = flag.Float64("v_per_div", 2, "volts per div")
	screenWidth      = flag.Int("width", 800, "UI width, in pixels")
	screenHeight     = flag.Int("height", 600, "UI height, in pixels")
	refreshRateLimit = flag.Float64("refresh_rate", 25, "maximum refresh rate, in frames per second. 0 = no limit")
	cpuprofile       = flag.String("cpuprofile", "", "File to which the program should write it's CPU profile (performance stats)")
)

type waveform struct {
	tb    scope.Duration
	inter scope.Duration
	tp    map[scope.ChanID]scope.TraceParams

	mu      sync.Mutex
	plot    gui.Plot
	bufPlot gui.Plot
}

func (w *waveform) TimeBase() scope.Duration {
	return w.tb
}

var allColors = []color.RGBA{
	color.RGBA{255, 0, 0, 255},
	color.RGBA{0, 200, 0, 255},
	color.RGBA{0, 0, 255, 255},
	color.RGBA{255, 0, 255, 255},
	color.RGBA{255, 255, 0, 255},
}

func (w *waveform) swapPlot() {
	w.mu.Lock()
	defer w.mu.Unlock()
	w.plot, w.bufPlot = w.bufPlot, w.plot
}

func (w *waveform) keepReading(dataCh <-chan []scope.ChannelData) {
	var buf []scope.ChannelData
	var tbCount = int(w.tb / w.inter)
	chColor := make(map[scope.ChanID]color.RGBA)
	for data := range dataCh {
		if len(data) == 0 {
			continue
		}
		if buf == nil {
			buf = make([]scope.ChannelData, len(data))
			for i, d := range data {
				buf[i].ID = d.ID
				buf[i].Samples = make([]scope.Voltage, 0, 2*tbCount)
				chColor[d.ID] = allColors[i]
			}
		}
		for i, d := range data {
			buf[i].Samples = append(buf[i].Samples, d.Samples...)
		}
		if len(buf[0].Samples) >= tbCount {
			for i := range data {
				buf[i].Samples = buf[i].Samples[:tbCount]
			}

			// full timebase, draw and go to beginning
			w.bufPlot.DrawAll(buf, w.tp, chColor)
			w.swapPlot()
			// truncate the buffers
			for i := range buf {
				buf[i].Samples = buf[i].Samples[:0]
			}
		}
	}
}

func (w *waveform) Reset(inter scope.Duration, d <-chan []scope.ChannelData) {
	w.inter = inter
	go w.keepReading(d)
}

func (w *waveform) Error(error) {}

func (w *waveform) SetTimeBase(d scope.Duration) {
	w.tb = d
}

func (w *waveform) SetChannel(ch scope.ChanID, p scope.TraceParams) {
	if w.tp == nil {
		w.tp = make(map[scope.ChanID]scope.TraceParams)
	}
	w.tp[ch] = p
}

func (w *waveform) Render(ret *image.RGBA) {
	w.mu.Lock()
	defer w.mu.Unlock()
	copy(ret.Pix, w.plot.RGBA.Pix)
}

type system struct {
	name      string
	enumerate func() map[string]string
	open      func(string) (scope.Device, error)
}

var (
	systems = []system{
		{
			name:      "dummy",
			enumerate: dummy.Enumerate,
			open:      dummy.Open,
		},
		{
			name:      "usb",
			enumerate: usb.Enumerate,
			open:      usb.Open,
		},
	}
	systemsByName = make(map[string]int)
)

func main() {
	flag.Parse()

	var all []string
	for idx, sys := range systems {
		systemsByName[sys.name] = idx
		for id := range sys.enumerate() {
			all = append(all, fmt.Sprintf("%s:%s", sys.name, id))
		}
	}

	if len(all) == 0 {
		log.Fatalf("Did not find any supported devices")
	}
	if *list {
		fmt.Println("Devices found:")
		for _, d := range all {
			fmt.Println(d)
		}
		return
	}
	id := all[0]
	if *device != "" {
		for _, d := range all {
			if d == *device {
				id = d
				break
			}
		}
		if id != *device {
			log.Fatalf("Device %s not detected on the list. Available devices: %v", *device, all)
		}
	} else if len(all) > 1 {
		log.Printf("Multiple devices found: %v", all)
		log.Printf("Using the first device (%s)", id)
	}

	parts := strings.SplitN(id, ":", 2)
	s := systemsByName[parts[0]]
	osc, err := systems[s].open(parts[1])

	if err != nil {
		log.Fatalf("Open: %+v", err)
	}

	if *cpuprofile != "" {
		f, err := os.Create(*cpuprofile)
		if err != nil {
			log.Fatal(err)
		}
		pprof.StartCPUProfile(f)
		defer pprof.StopCPUProfile()
	}
	screenSize := image.Point{*screenWidth, *screenHeight}
	wf := &waveform{
		plot:    gui.NewPlot(screenSize),
		bufPlot: gui.NewPlot(screenSize),
	}
	wf.SetTimeBase(scope.DurationFromNano(*timeBase))

	for _, id := range osc.Channels() {
		wf.SetChannel(id, scope.TraceParams{Zero: 0.5, PerDiv: *perDiv})
	}

	tr := triggers.New(wf)
	tr.Source(scope.ChanID(*triggerSource))
	tr.Level(scope.Voltage(*triggerThresh))
	// For now, the names of params are hardcoded here, but in the future
	// names might change between devices and it's not very practical.
	// The intention is to have params initialized to defaults and then changed
	// only through the UI or by specifying the parameter name and value
	// on the commandline. But because there is no UI yet, it's not really feasible.
	for _, p := range tr.TriggerParams() {
		var err error
		switch pn := p.Name(); pn {
		case "Trigger edge":
			err = p.Set(*triggerEdge)
		case "Trigger mode":
			err = p.Set(*triggerMode)
		}
		if err != nil {
			log.Fatalf("TriggerParams[%q].Set: %v", p.Name(), err)
		}
	}

	osc.Attach(tr)
	osc.Start()
	defer osc.Stop()

	driver.Main(func(s screen.Screen) {
		w, err := s.NewWindow(&screen.NewWindowOptions{Width: screenSize.X, Height: screenSize.Y})
		if err != nil {
			log.Fatalf("NewWindow: %v", err)
		}
		defer w.Release()
		stop := make(chan struct{})
		go processEvents(w, stop)

		b, err := s.NewBuffer(screenSize)
		if err != nil {
			log.Fatalf("NewBuffer(): %v", err)
		}
		defer b.Release()

		var limiter *rate.Limiter
		if *refreshRateLimit > 0 {
			limiter = rate.NewLimiter(rate.Limit(*refreshRateLimit), 1)
		}
		sometimes := rate.NewLimiter(0.2, 1)
		for {
			select {
			case <-stop:
				return
			default:
			}
			if limiter != nil {
				limiter.Wait(context.Background())
			}
			t := time.Now()
			wf.Render(b.RGBA())
			w.Upload(image.Point{0, 0}, b, b.Bounds())
			w.Publish()
			if sometimes.Allow() {
				d := time.Since(t)
				fmt.Printf("Rendering 1 frame took %v (%.2ffps)\n", d, float64(time.Second)/float64(d))
			}
		}
	})
}
<|endoftext|>


<commit_before>package boardgame

import (
	"reflect"
	"sort"
	"testing"
)

func TestComponentChest(t *testing.T) {

	chest := NewComponentChest(testGameName)

	if chest.gameName != testGameName {
		t.Error("new component chest had wrong name. got", chest.gameName, "wanted", testGameName)
	}

	if chest.DeckNames() != nil {
		t.Error("We got a deck names array before we'd added anything")
	}

	//TODO: make real decks here
	deckOne := &Deck{}

	componentOne := &Component{
		Values: &testingComponent{
			"foo",
			1,
		},
	}

	deckOne.AddComponent(componentOne)

	componentTwo := &Component{
		Values: &testingComponent{
			"bar",
			2,
		},
	}

	deckOne.AddComponent(componentTwo)

	if deckOne.Components() != nil {
		t.Error("We got non-nil components before it was added to the chest")
	}

	chest.AddDeck("test", deckOne)

	if !reflect.DeepEqual(deckOne.Components(), []*Component{componentOne, componentTwo}) {
		t.Error("Deck gave back wrong items after being added to chest")
	}

	deckOne.AddComponent(&Component{
		Values: &testingComponent{
			"illegal",
			-1,
		},
	})

	if !reflect.DeepEqual(deckOne.Components(), []*Component{componentOne, componentTwo}) {
		t.Error("Deck allowed itself to be mutated after it was added to chest")
	}

	if chest.DeckNames() != nil {
		t.Error("We got decknames before we called freeze")
	}

	if chest.Deck("test") != nil {
		t.Error("We got a deck back before freeze was called")
	}

	deckTwo := &Deck{}

	deckTwo.AddComponent(&Component{
		Values: &testingComponent{
			"another",
			3,
		},
	})

	chest.AddDeck("other", deckTwo)

	chest.Finish()

	chest.AddDeck("shouldfail", deckOne)

	if chest.decks["shouldfail"] != nil {
		t.Fatal("We were able to add a deck after freezing")
	}

	sortedDeckNames := chest.DeckNames()

	sort.Strings(sortedDeckNames)

	expectedDeckNames := []string{"other", "test"}

	if !reflect.DeepEqual(sortedDeckNames, expectedDeckNames) {
		t.Error("Got unexpected decknames. got", sortedDeckNames, "wanted", expectedDeckNames)
	}

	if chest.Deck("test") != deckOne {
		t.Error("Got wrong value for deck one. Got", chest.Deck("test"), "wanted", deckOne)
	}

	if chest.Deck("other") != deckTwo {
		t.Error("Got wrong value for deck two. Got", chest.Deck("other"), "wanted", deckTwo)
	}

	if deckOne.name != "test" {
		t.Error("DeckOne didn't have its name set when added to the chest. Got", deckOne.name, "wanted test")
	}

	if deckTwo.name != "other" {
		t.Error("DeckTwo didn't have its name set when added to the chest. Got", deckTwo.name, "wanted other")
	}

	for i, c := range deckOne.Components() {
		if c.Address.Deck != deckOne.name {
			t.Error("At position", i, "deck name was not set correctly in component. Got", c.Address.Deck, "wanted", deckOne.name)
		}
		if c.Address.Index != i {
			t.Error("At position", i, "index was not set correctly in component. Got", c.Address.Index, "wanted", i)
		}
		if c.GameName != testGameName {
			t.Error("The component at", i, "didn't have its gameName set correctly. Got", c.GameName, "wanted", testGameName)
		}
	}

}
<commit_msg>Remove an obsolte TODO<commit_after>package boardgame

import (
	"reflect"
	"sort"
	"testing"
)

func TestComponentChest(t *testing.T) {

	chest := NewComponentChest(testGameName)

	if chest.gameName != testGameName {
		t.Error("new component chest had wrong name. got", chest.gameName, "wanted", testGameName)
	}

	if chest.DeckNames() != nil {
		t.Error("We got a deck names array before we'd added anything")
	}

	deckOne := &Deck{}

	componentOne := &Component{
		Values: &testingComponent{
			"foo",
			1,
		},
	}

	deckOne.AddComponent(componentOne)

	componentTwo := &Component{
		Values: &testingComponent{
			"bar",
			2,
		},
	}

	deckOne.AddComponent(componentTwo)

	if deckOne.Components() != nil {
		t.Error("We got non-nil components before it was added to the chest")
	}

	chest.AddDeck("test", deckOne)

	if !reflect.DeepEqual(deckOne.Components(), []*Component{componentOne, componentTwo}) {
		t.Error("Deck gave back wrong items after being added to chest")
	}

	deckOne.AddComponent(&Component{
		Values: &testingComponent{
			"illegal",
			-1,
		},
	})

	if !reflect.DeepEqual(deckOne.Components(), []*Component{componentOne, componentTwo}) {
		t.Error("Deck allowed itself to be mutated after it was added to chest")
	}

	if chest.DeckNames() != nil {
		t.Error("We got decknames before we called freeze")
	}

	if chest.Deck("test") != nil {
		t.Error("We got a deck back before freeze was called")
	}

	deckTwo := &Deck{}

	deckTwo.AddComponent(&Component{
		Values: &testingComponent{
			"another",
			3,
		},
	})

	chest.AddDeck("other", deckTwo)

	chest.Finish()

	chest.AddDeck("shouldfail", deckOne)

	if chest.decks["shouldfail"] != nil {
		t.Fatal("We were able to add a deck after freezing")
	}

	sortedDeckNames := chest.DeckNames()

	sort.Strings(sortedDeckNames)

	expectedDeckNames := []string{"other", "test"}

	if !reflect.DeepEqual(sortedDeckNames, expectedDeckNames) {
		t.Error("Got unexpected decknames. got", sortedDeckNames, "wanted", expectedDeckNames)
	}

	if chest.Deck("test") != deckOne {
		t.Error("Got wrong value for deck one. Got", chest.Deck("test"), "wanted", deckOne)
	}

	if chest.Deck("other") != deckTwo {
		t.Error("Got wrong value for deck two. Got", chest.Deck("other"), "wanted", deckTwo)
	}

	if deckOne.name != "test" {
		t.Error("DeckOne didn't have its name set when added to the chest. Got", deckOne.name, "wanted test")
	}

	if deckTwo.name != "other" {
		t.Error("DeckTwo didn't have its name set when added to the chest. Got", deckTwo.name, "wanted other")
	}

	for i, c := range deckOne.Components() {
		if c.Address.Deck != deckOne.name {
			t.Error("At position", i, "deck name was not set correctly in component. Got", c.Address.Deck, "wanted", deckOne.name)
		}
		if c.Address.Index != i {
			t.Error("At position", i, "index was not set correctly in component. Got", c.Address.Index, "wanted", i)
		}
		if c.GameName != testGameName {
			t.Error("The component at", i, "didn't have its gameName set correctly. Got", c.GameName, "wanted", testGameName)
		}
	}

}
<|endoftext|>


<commit_before>package etcd

import (
	"bytes"
	"encoding/gob"
	"net/http"
	"strconv"
	"sync/atomic"
	"time"

	"golang.org/x/net/context"

	"github.com/Sirupsen/logrus"
	"github.com/coreos/etcd/etcdserver/stats"
	"github.com/coreos/etcd/pkg/types"
	etcdraft "github.com/coreos/etcd/raft"
	"github.com/coreos/etcd/raft/raftpb"
	"github.com/coreos/etcd/rafthttp"
	"github.com/relab/raft"
	"github.com/relab/raft/commonpb"
)

type tag struct {
	UID  uint64
	Data []byte
}

type future struct {
	res chan raft.Result
}

func (f *future) ResultCh() <-chan raft.Result {
	return f.res
}

// Wrapper wraps an etcd/raft.Node and implements relab/raft.Raft and
// etcd/rafthttp.Raft.
type Wrapper struct {
	n         etcdraft.Node
	sm        raft.StateMachine
	storage   *etcdraft.MemoryStorage
	transport *rafthttp.Transport
	logger    logrus.FieldLogger

	heartbeat time.Duration

	uid       uint64
	proposals map[uint64]chan<- raft.Result
}

func (w *Wrapper) encodeProposal(data []byte) (uint64, []byte, error) {
	uid := atomic.AddUint64(&w.uid, 1)
	t := &tag{
		UID:  uid,
		Data: data,
	}

	var b bytes.Buffer
	enc := gob.NewEncoder(&b)
	err := enc.Encode(t)
	if err != nil {
		return 0, nil, err
	}

	return uid, b.Bytes(), nil
}

func (w *Wrapper) decodeCommit(commit []byte) *tag {
	b := bytes.NewBuffer(commit)
	dec := gob.NewDecoder(b)

	var t tag
	err := dec.Decode(&t)

	if err != nil {
		panic("decodeCommit: " + err.Error())
	}

	return &t
}

func NewRaft(logger logrus.FieldLogger,
	sm raft.StateMachine, storage *etcdraft.MemoryStorage, cfg *etcdraft.Config,
	peers []etcdraft.Peer, heartbeat time.Duration,
) *Wrapper {
	w := &Wrapper{
		sm:        sm,
		storage:   storage,
		heartbeat: heartbeat,
		proposals: make(map[uint64]chan<- raft.Result),
		logger:    logger,
	}
	w.n = etcdraft.StartNode(cfg, append(peers, etcdraft.Peer{ID: cfg.ID}))

	ss := &stats.ServerStats{}
	ss.Initialize()

	transport := &rafthttp.Transport{
		ID:          types.ID(cfg.ID),
		ClusterID:   1,
		Raft:        w,
		ServerStats: ss,
		LeaderStats: stats.NewLeaderStats(strconv.FormatUint(cfg.ID, 10)),
		ErrorC:      make(chan error),
	}
	err := transport.Start()

	if err != nil {
		panic("start transport: " + err.Error())
	}

	for i, peer := range peers {
		w.logger.WithFields(logrus.Fields{
			"i":       i,
			"id":      peer.ID,
			"context": string(peer.Context),
		}).Warnln("Add peer")

		transport.AddPeer(types.ID(peer.ID), []string{string(peer.Context)})
	}

	w.transport = transport

	go w.run()
	return w
}

func (w *Wrapper) Handler() http.Handler {
	return w.transport.Handler()
}

func (w *Wrapper) ProposeCmd(ctx context.Context, req []byte) (raft.Future, error) {
	w.logger.Warnln("ProposeCmd")

	uid, prop, err := w.encodeProposal(req)

	if err != nil {
		return nil, err
	}

	res := make(chan raft.Result, 1)
	future := &future{res}
	w.proposals[uid] = res

	err = w.n.Propose(ctx, prop)

	if err != nil {
		delete(w.proposals, uid)
		return nil, err
	}

	return future, nil
}

func (w *Wrapper) ReadCmd(context.Context, []byte) (raft.Future, error) {
	w.logger.Warnln("ProposeRead")
	return nil, nil
}

func (w *Wrapper) ProposeConf(ctx context.Context, req *commonpb.ReconfRequest) (raft.Future, error) {
	cc := raftpb.ConfChange{
		Type:    raftpb.ConfChangeType(req.ReconfType),
		ID:      req.ServerID, // ?
		NodeID:  req.ServerID,
		Context: []byte(""), // ?
	}

	err := w.n.ProposeConfChange(ctx, cc)

	w.logger.WithError(err).WithFields(logrus.Fields{
		"req": req,
		"cc":  cc,
	}).Warnln("ProposeConf")

	if err != nil {
		return nil, err
	}

	panic("ProposeConf not implemented")
}

func (w *Wrapper) run() {
	s := time.NewTicker(w.heartbeat)

	for {
		select {
		case <-s.C:
			w.n.Tick()
		case rd := <-w.n.Ready():
			w.logger.WithField("rd", rd).Warnln("Ready")
			w.storage.Append(rd.Entries)
			if !etcdraft.IsEmptyHardState(rd.HardState) {
				w.logger.WithField("hardstate", rd.HardState).Warnln("HardState")
				w.storage.SetHardState(rd.HardState)
			}
			if !etcdraft.IsEmptySnap(rd.Snapshot) {
				w.logger.WithField("snapshot", rd.Snapshot).Warnln("Snapshot")
				w.storage.ApplySnapshot(rd.Snapshot)
			}
			w.logger.WithField("messages", rd.Messages).Warnln("Sending")
			w.transport.Send(rd.Messages)
			for _, entry := range rd.CommittedEntries {
				t := w.decodeCommit(entry.Data)

				switch entry.Type {
				case raftpb.EntryNormal:
					w.logger.WithField(
						"entry", etcdraft.DescribeEntry(entry, nil),
					).Warnln("Committed normal entry")

					w.handleNormal(&entry, t)
				case raftpb.EntryConfChange:
					w.logger.WithField(
						"entry", etcdraft.DescribeEntry(entry, nil),
					).Warnln("Committed conf change entry")

					w.handleConfChange(&entry, t)
				}

				// TODO Respond to future.
			}
			w.logger.Warnln("Advance")
			w.n.Advance()
		}
	}
}

func (w *Wrapper) handleNormal(entry *raftpb.Entry, t *tag) {
	res := w.sm.Apply(&commonpb.Entry{
		Term:      entry.Term,
		Index:     entry.Index,
		EntryType: commonpb.EntryNormal,
		Data:      t.Data,
	})

	if respCh, ok := w.proposals[t.UID]; ok {
		respCh <- raft.Result{
			Index: entry.Index,
			Value: res,
		}
		delete(w.proposals, t.UID)
	}
}

func (w *Wrapper) handleConfChange(entry *raftpb.Entry, t *tag) {
	var cc raftpb.ConfChange
	err := cc.Unmarshal(entry.Data)

	if err != nil {
		panic("unmarshal conf change: " + err.Error())
	}

	w.logger.WithField("cc", cc).Warnln("Applying conf change")
	w.n.ApplyConfChange(cc)
	switch cc.Type {
	case raftpb.ConfChangeAddNode:
		if len(cc.Context) > 0 {
			w.transport.AddPeer(types.ID(cc.NodeID), []string{string(cc.Context)})
		}
	case raftpb.ConfChangeRemoveNode:
		tid := types.ID(cc.NodeID)
		if tid == w.transport.ID {
			w.logger.Warnln("Shutting down")
			return
		}
		w.transport.RemovePeer(tid)
	}

	// Inform state machine about new configuration.
	w.sm.Apply(&commonpb.Entry{
		Term:      entry.Term,
		Index:     entry.Index,
		EntryType: commonpb.EntryReconf,
		Data:      t.Data,
	})

	// TODO Respond / Cleanup future.
}

func (w *Wrapper) Process(ctx context.Context, m raftpb.Message) error {
	w.logger.WithField("m", m).Warnln("Process")
	return w.n.Step(ctx, m)
}
func (w *Wrapper) IsIDRemoved(id uint64) bool                               { return false }
func (w *Wrapper) ReportUnreachable(id uint64)                              {}
func (w *Wrapper) ReportSnapshot(id uint64, status etcdraft.SnapshotStatus) {}
<commit_msg>etcd/raft.go: Work around initial commits stemming from etcd<commit_after>package etcd

import (
	"bytes"
	"encoding/gob"
	"net/http"
	"strconv"
	"sync/atomic"
	"time"

	"golang.org/x/net/context"

	"github.com/Sirupsen/logrus"
	"github.com/coreos/etcd/etcdserver/stats"
	"github.com/coreos/etcd/pkg/types"
	etcdraft "github.com/coreos/etcd/raft"
	"github.com/coreos/etcd/raft/raftpb"
	"github.com/coreos/etcd/rafthttp"
	"github.com/relab/raft"
	"github.com/relab/raft/commonpb"
)

type tag struct {
	UID  uint64
	Data []byte
}

type future struct {
	res chan raft.Result
}

func (f *future) ResultCh() <-chan raft.Result {
	return f.res
}

// Wrapper wraps an etcd/raft.Node and implements relab/raft.Raft and
// etcd/rafthttp.Raft.
type Wrapper struct {
	n         etcdraft.Node
	sm        raft.StateMachine
	storage   *etcdraft.MemoryStorage
	transport *rafthttp.Transport
	logger    logrus.FieldLogger

	heartbeat time.Duration

	uid       uint64
	proposals map[uint64]chan<- raft.Result
}

func (w *Wrapper) encodeProposal(data []byte) (uint64, []byte, error) {
	uid := atomic.AddUint64(&w.uid, 1)
	t := &tag{
		UID:  uid,
		Data: data,
	}

	var b bytes.Buffer
	enc := gob.NewEncoder(&b)
	err := enc.Encode(t)
	if err != nil {
		return 0, nil, err
	}

	return uid, b.Bytes(), nil
}

// Hack: Returns nil if commit fails to decode. This when etcd append the
// initial configuration to the log. Fix: Make state machine return
// clientID/clientSeq along with result, we can then use that to figure out who
// to respond to instead of using the tag struct.
func (w *Wrapper) decodeCommit(commit []byte) *tag {
	b := bytes.NewBuffer(commit)
	dec := gob.NewDecoder(b)

	var t tag
	err := dec.Decode(&t)

	if err != nil {
		return nil
	}

	return &t
}

func NewRaft(logger logrus.FieldLogger,
	sm raft.StateMachine, storage *etcdraft.MemoryStorage, cfg *etcdraft.Config,
	peers []etcdraft.Peer, heartbeat time.Duration,
) *Wrapper {
	w := &Wrapper{
		sm:        sm,
		storage:   storage,
		heartbeat: heartbeat,
		proposals: make(map[uint64]chan<- raft.Result),
		logger:    logger,
	}
	w.n = etcdraft.StartNode(cfg, append(peers, etcdraft.Peer{ID: cfg.ID}))

	ss := &stats.ServerStats{}
	ss.Initialize()

	transport := &rafthttp.Transport{
		ID:          types.ID(cfg.ID),
		ClusterID:   1,
		Raft:        w,
		ServerStats: ss,
		LeaderStats: stats.NewLeaderStats(strconv.FormatUint(cfg.ID, 10)),
		ErrorC:      make(chan error),
	}
	err := transport.Start()

	if err != nil {
		panic("start transport: " + err.Error())
	}

	for i, peer := range peers {
		w.logger.WithFields(logrus.Fields{
			"i":       i,
			"id":      peer.ID,
			"context": string(peer.Context),
		}).Warnln("Add peer")

		transport.AddPeer(types.ID(peer.ID), []string{string(peer.Context)})
	}

	w.transport = transport

	go w.run()
	return w
}

func (w *Wrapper) Handler() http.Handler {
	return w.transport.Handler()
}

func (w *Wrapper) ProposeCmd(ctx context.Context, req []byte) (raft.Future, error) {
	w.logger.Warnln("ProposeCmd")

	uid, prop, err := w.encodeProposal(req)

	if err != nil {
		return nil, err
	}

	res := make(chan raft.Result, 1)
	future := &future{res}
	w.proposals[uid] = res

	err = w.n.Propose(ctx, prop)

	if err != nil {
		delete(w.proposals, uid)
		return nil, err
	}

	return future, nil
}

func (w *Wrapper) ReadCmd(context.Context, []byte) (raft.Future, error) {
	w.logger.Warnln("ProposeRead")
	return nil, nil
}

func (w *Wrapper) ProposeConf(ctx context.Context, req *commonpb.ReconfRequest) (raft.Future, error) {
	cc := raftpb.ConfChange{
		Type:    raftpb.ConfChangeType(req.ReconfType),
		ID:      req.ServerID, // ?
		NodeID:  req.ServerID,
		Context: []byte(""), // ?
	}

	err := w.n.ProposeConfChange(ctx, cc)

	w.logger.WithError(err).WithFields(logrus.Fields{
		"req": req,
		"cc":  cc,
	}).Warnln("ProposeConf")

	if err != nil {
		return nil, err
	}

	panic("ProposeConf not implemented")
}

func (w *Wrapper) run() {
	s := time.NewTicker(w.heartbeat)

	for {
		select {
		case <-s.C:
			w.n.Tick()
		case rd := <-w.n.Ready():
			w.logger.WithField("rd", rd).Warnln("Ready")
			w.storage.Append(rd.Entries)
			if !etcdraft.IsEmptyHardState(rd.HardState) {
				w.logger.WithField("hardstate", rd.HardState).Warnln("HardState")
				w.storage.SetHardState(rd.HardState)
			}
			if !etcdraft.IsEmptySnap(rd.Snapshot) {
				w.logger.WithField("snapshot", rd.Snapshot).Warnln("Snapshot")
				w.storage.ApplySnapshot(rd.Snapshot)
			}
			w.logger.WithField("messages", rd.Messages).Warnln("Sending")
			w.transport.Send(rd.Messages)
			for _, entry := range rd.CommittedEntries {
				t := w.decodeCommit(entry.Data)

				switch entry.Type {
				case raftpb.EntryNormal:
					w.logger.WithField(
						"entry", etcdraft.DescribeEntry(entry, nil),
					).Warnln("Committed normal entry")

					w.handleNormal(&entry, t)
				case raftpb.EntryConfChange:
					w.logger.WithField(
						"entry", etcdraft.DescribeEntry(entry, nil),
					).Warnln("Committed conf change entry")

					w.handleConfChange(&entry, t)
				}

				// TODO Respond to future.
			}
			w.logger.Warnln("Advance")
			w.n.Advance()
		}
	}
}

func (w *Wrapper) handleNormal(entry *raftpb.Entry, t *tag) {
	if t == nil {
		return
	}

	res := w.sm.Apply(&commonpb.Entry{
		Term:      entry.Term,
		Index:     entry.Index,
		EntryType: commonpb.EntryNormal,
		Data:      t.Data,
	})

	if respCh, ok := w.proposals[t.UID]; ok {
		respCh <- raft.Result{
			Index: entry.Index,
			Value: res,
		}
		delete(w.proposals, t.UID)
	}
}

func (w *Wrapper) handleConfChange(entry *raftpb.Entry, t *tag) {
	var cc raftpb.ConfChange
	err := cc.Unmarshal(entry.Data)

	if err != nil {
		panic("unmarshal conf change: " + err.Error())
	}

	w.logger.WithField("cc", cc).Warnln("Applying conf change")
	w.n.ApplyConfChange(cc)
	switch cc.Type {
	case raftpb.ConfChangeAddNode:
		if len(cc.Context) > 0 {
			w.transport.AddPeer(types.ID(cc.NodeID), []string{string(cc.Context)})
		}
	case raftpb.ConfChangeRemoveNode:
		tid := types.ID(cc.NodeID)
		if tid == w.transport.ID {
			w.logger.Warnln("Shutting down")
			return
		}
		w.transport.RemovePeer(tid)
	}

	if t == nil {
		return
	}

	// Inform state machine about new configuration.
	w.sm.Apply(&commonpb.Entry{
		Term:      entry.Term,
		Index:     entry.Index,
		EntryType: commonpb.EntryReconf,
		Data:      t.Data,
	})

	// TODO Respond / Cleanup future.
}

func (w *Wrapper) Process(ctx context.Context, m raftpb.Message) error {
	w.logger.WithField("m", m).Warnln("Process")
	return w.n.Step(ctx, m)
}
func (w *Wrapper) IsIDRemoved(id uint64) bool                               { return false }
func (w *Wrapper) ReportUnreachable(id uint64)                              {}
func (w *Wrapper) ReportSnapshot(id uint64, status etcdraft.SnapshotStatus) {}
<|endoftext|>


<commit_before>package config

import (
	"fmt"
	"github.com/gruntwork-io/terragrunt/errors"
	"github.com/gruntwork-io/terragrunt/locks"
	"github.com/gruntwork-io/terragrunt/locks/dynamodb"
)

// lockFactory provides an implementation of Lock with the provided configuration map
type lockFactory func(map[string]string) (locks.Lock, error)

// ErrLockNotFound is the error returned if no Lock implementation could be found
// for the specified name
var ErrLockNotFound = fmt.Errorf("no Lock implementation found")

// lookupLock returns the implementation for the named lock or returns ErrLockNotFound
func lookupLock(name string, conf map[string]string) (locks.Lock, error) {
	factory, containsFactory := builtinLocks[name]
	if !containsFactory {
		return nil, errors.WithStackTrace(ErrLockNotFound)
	}

	return factory(conf)
}

var builtinLocks = map[string]lockFactory{
	"dynamodb": dynamodb.New,
}
<commit_msg>Remove unused lock config<commit_after><|endoftext|>