<commit_before>// Copyright 2012, Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

/*
The agent listens on a zk node for new actions to perform.

It passes them off to a separate action process. Even though some
actions could be completed inline very quickly, the external process
makes it easy to track and interrupt complex actions that may wedge
due to external circumstances.
*/

package tabletmanager

import (
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"net"
	"os"
	"os/exec"
	"path"
	"sort"
	"strconv"
	"strings"
	"sync"
	"time"

	"code.google.com/p/vitess/go/relog"
	"code.google.com/p/vitess/go/vt/naming"
	"code.google.com/p/vitess/go/zk"
	"launchpad.net/gozk/zookeeper"
)

type ActionAgent struct {
	zconn           zk.Conn
	zkTabletPath    string // FIXME(msolomon) use tabletInfo
	zkActionPath    string
	vtActionBinFile string // path to vtaction binary
	MycnfFile       string // my.cnf file
	DBCredsFile     string // File that contains db connection credentials

	mutex   sync.Mutex
	_tablet *TabletInfo // must be accessed with lock - TabletInfo objects are not synchronized.
}

// bindAddr: the address for the query service advertised by this agent
func NewActionAgent(zconn zk.Conn, zkTabletPath, mycnfFile, dbCredsFile string) *ActionAgent {
	actionPath := TabletActionPath(zkTabletPath)
	return &ActionAgent{
		zconn:        zconn,
		zkTabletPath: zkTabletPath,
		zkActionPath: actionPath,
		MycnfFile:    mycnfFile,
		DBCredsFile:  dbCredsFile}
}

func (agent *ActionAgent) readTablet() error {
	// Reread in case there were changes
	tablet, err := ReadTablet(agent.zconn, agent.zkTabletPath)
	if err != nil {
		return err
	}
	agent.mutex.Lock()
	agent._tablet = tablet
	agent.mutex.Unlock()
	return nil
}

func (agent *ActionAgent) Tablet() *TabletInfo {
	agent.mutex.Lock()
	tablet := agent._tablet
	agent.mutex.Unlock()
	return tablet
}

// FIXME(msolomon) need a real path discovery mechanism, a config file
// or more command line args.
func (agent *ActionAgent) resolvePaths() error {
	vtActionBinPaths := []string{os.ExpandEnv("$VTROOT/src/code.google.com/p/vitess/go/cmd/vtaction/vtaction"),
		"/usr/local/bin/vtaction"}
	for _, path := range vtActionBinPaths {
		if _, err := os.Stat(path); err == nil {
			agent.vtActionBinFile = path
			break
		}
	}
	if agent.vtActionBinFile == "" {
		return errors.New("no vtaction binary found")
	}

	/* FIXME: Delete commented out code
	mycnfFile := []string{fmt.Sprintf("/vt/vt_%010d/my.cnf", agent.Tablet().Uid),
		"/var/lib/mysql/my.cnf", "/etc/my.cnf"}
	for _, path := range mycnfFile {
		if _, err := os.Stat(path); err == nil {
			agent.MycnfFile = path
			break
		}
	}
	if agent.MycnfFile == "" {
		return errors.New("no my.cnf found")
	}*/
	return nil
}

func (agent *ActionAgent) dispatchAction(actionPath string) {
	relog.Info("action dispatch %v", actionPath)
	data, _, err := agent.zconn.Get(actionPath)
	if err != nil {
		relog.Error("action dispatch failed: %v", err)
		return
	}

	actionNode, err := ActionNodeFromJson(data, actionPath)
	if err != nil {
		relog.Error("action decode failed: %v %v", actionPath, err)
		return
	}

	logfile := flag.Lookup("logfile").Value.String()
	if !strings.HasPrefix(logfile, "/dev") {
		logfile = path.Join(path.Dir(logfile), "vtaction.log")
	}
	cmd := []string{
		agent.vtActionBinFile,
		"-action", actionNode.Action,
		"-action-node", actionPath,
		"-action-guid", actionNode.ActionGuid,
		"-mycnf-file", agent.MycnfFile,
		"-logfile", flag.Lookup("logfile").Value.String(),
	}
	if agent.DBCredsFile != "" {
		cmd = append(cmd, "-db-credentials-file", agent.DBCredsFile)
	}
	relog.Info("action launch %v", cmd)
	vtActionCmd := exec.Command(cmd[0], cmd[1:]...)

	stdOut, vtActionErr := vtActionCmd.CombinedOutput()
	if vtActionErr != nil {
		relog.Error("action failed: %v %v\n%s", actionPath, vtActionErr, stdOut)
		return
	}

	relog.Info("action completed %v %s", actionPath, stdOut)

	// Actions should have side effects on the tablet, so reload the data.
	if err := agent.readTablet(); err != nil {
		relog.Warning("failed rereading tablet after action: %v %v", actionPath, err)
	}
}

func (agent *ActionAgent) handleActionQueue() (<-chan zookeeper.Event, error) {
	// This read may seem a bit pedantic, but it makes it easier for the system
	// to trend towards consistency if an action fails or somehow the action
	// queue gets mangled by an errant process.
	children, _, watch, err := agent.zconn.ChildrenW(agent.zkActionPath)
	if err != nil {
		return watch, err
	}
	if len(children) > 0 {
		sort.Strings(children)
		for _, child := range children {
			actionPath := agent.zkActionPath + "/" + child
			_, err := strconv.ParseUint(child, 10, 64)
			if err != nil {
				// This is handy if you want to restart a stuck queue.
				// FIXME(msolomon) could listen on the queue node for a change
				// generated by a "touch", but listening on two things is a bit
				// more complex.
				relog.Warning("remove invalid event from action queue: %v", child)
				agent.zconn.Delete(actionPath, -1)
			}
			agent.dispatchAction(actionPath)
		}
	}
	return watch, nil
}

func (agent *ActionAgent) verifyZkPaths() error {
	tablet := agent.Tablet()
	if tablet == nil {
		panic(fmt.Errorf("agent._tablet is nil"))
	}

	zkReplicationPath := tablet.ReplicationPath()

	_, err := agent.zconn.Create(zkReplicationPath, "", 0, zookeeper.WorldACL(zookeeper.PERM_ALL))
	if err != nil && err.(*zookeeper.Error).Code != zookeeper.ZNODEEXISTS {
		return err
	}

	// Ensure that the action node is there.
	_, err = agent.zconn.Create(agent.zkActionPath, "", 0, zookeeper.WorldACL(zookeeper.PERM_ALL))
	if err != nil && err.(*zookeeper.Error).Code != zookeeper.ZNODEEXISTS {
		return err
	}
	return nil
}

func (agent *ActionAgent) verifyZkServingAddrs() error {
	if !agent.Tablet().IsServingType() {
		return nil
	}
	// Load the shard and see if we are supposed to be serving. We might be a serving type,
	// but we might be in a transitional state. Only once the shard info is updated do we
	// put ourselves in the client serving graph.
	shardInfo, err := ReadShard(agent.zconn, agent.Tablet().ShardPath())
	if err != nil {
		return err
	}

	if !shardInfo.Contains(agent.Tablet().Tablet) {
		return nil
	}
	// Check to see our address is registered in the right place.
	zkPathName := naming.ZkPathForVtName(agent.Tablet().Tablet.Cell, agent.Tablet().Keyspace,
		agent.Tablet().Shard, string(agent.Tablet().Type))

	f := func(oldValue string, oldStat *zookeeper.Stat) (string, error) {
		return agent.updateEndpoints(oldValue, oldStat)
	}
	err = agent.zconn.RetryChange(zkPathName, 0, zookeeper.WorldACL(zookeeper.PERM_ALL), f)
	if err == skipUpdateErr {
		err = nil
		relog.Warning("skipped serving graph update")
	}
	return err
}

var skipUpdateErr = fmt.Errorf("skip update")

// A function conforming to the RetryChange protocl. If the data returned
// is identical, no update is performed.
func (agent *ActionAgent) updateEndpoints(oldValue string, oldStat *zookeeper.Stat) (newValue string, err error) {
	if oldStat == nil {
		// The incoming object doesn't exist - we haven't been placed in the serving
		// graph yet, so don't update. Assume the next process that rebuilds the graph
		// will get the updated tablet location.
		return "", skipUpdateErr
	}

	addrs := naming.NewAddrs()
	if oldValue != "" {
		err = json.Unmarshal([]byte(oldValue), addrs)
		if err != nil {
			return
		}

		foundTablet := false
		for _, entry := range addrs.Entries {
			if entry.Uid == agent.Tablet().Uid {
				foundTablet = true
				vtAddr := fmt.Sprintf("%v:%v", entry.Host, entry.NamedPortMap["_vtocc"])
				mysqlAddr := fmt.Sprintf("%v:%v", entry.Host, entry.NamedPortMap["_mysql"])
				if vtAddr != agent.Tablet().Addr || mysqlAddr != agent.Tablet().MysqlAddr {
					// update needed
					host, port := splitHostPort(agent.Tablet().Addr)
					entry.Host = host
					entry.NamedPortMap["_vtocc"] = port
					host, port = splitHostPort(agent.Tablet().MysqlAddr)
					entry.NamedPortMap["_mysql"] = port
				}
				break
			}
		}

		if !foundTablet {
			addrs.Entries = append(addrs.Entries, *vtnsAddrForTablet(agent.Tablet().Tablet))
		}
	} else {
		addrs.Entries = append(addrs.Entries, *vtnsAddrForTablet(agent.Tablet().Tablet))
	}
	return toJson(addrs), nil
}

func splitHostPort(addr string) (string, int) {
	host, port, err := net.SplitHostPort(addr)
	if err != nil {
		panic(err)
	}
	p, err := strconv.ParseInt(port, 10, 16)
	if err != nil {
		panic(err)
	}
	return host, int(p)
}

// Resolve an address where the host has been left blank, like ":3306"
func resolveAddr(addr string) string {
	host, port := splitHostPort(addr)
	if host == "" {
		hostname, err := os.Hostname()
		if err != nil {
			panic(err)
		}
		host = hostname
	}
	return fmt.Sprintf("%v:%v", host, port)
}

func vtnsAddrForTablet(tablet *Tablet) *naming.VtnsAddr {
	host, port := splitHostPort(tablet.Addr)
	entry := naming.NewAddr(tablet.Uid, host, 0)
	entry.NamedPortMap["_vtocc"] = port
	host, port = splitHostPort(tablet.MysqlAddr)
	entry.NamedPortMap["_mysql"] = port
	return entry
}

func (agent *ActionAgent) Start(bindAddr, mysqlAddr string) {
	var err error
	if err = agent.readTablet(); err != nil {
		panic(err)
	}

	if err = agent.resolvePaths(); err != nil {
		panic(err)
	}

	// Update bind addr for mysql and query service in the tablet node.
	f := func(oldValue string, oldStat *zookeeper.Stat) (string, error) {
		if oldValue == "" {
			return "", fmt.Errorf("no data for tablet addr update: %v", agent.zkTabletPath)
		}

		tablet := tabletFromJson(oldValue)
		tablet.Addr = resolveAddr(bindAddr)
		tablet.MysqlAddr = resolveAddr(mysqlAddr)
		return toJson(tablet), nil
	}
	err = agent.zconn.RetryChange(agent.Tablet().Path(), 0, zookeeper.WorldACL(zookeeper.PERM_ALL), f)
	if err != nil {
		panic(err)
	}

	// Reread in case there were changes
	if err := agent.readTablet(); err != nil {
		panic(err)
	}

	if err := zk.CreatePidNode(agent.zconn, agent.Tablet().PidPath()); err != nil {
		panic(err)
	}

	if err = agent.verifyZkPaths(); err != nil {
		panic(err)
	}

	if err = agent.verifyZkServingAddrs(); err != nil {
		panic(err)
	}

	go agent.actionEventLoop()
}

func (agent *ActionAgent) actionEventLoop() {
	for {
		// Process any pending actions when we startup, before we start listening
		// for events.
		watch, err := agent.handleActionQueue()
		if err != nil {
			relog.Warning("action queue failed: %v", err)
			time.Sleep(5 * time.Second)
			continue
		}

		event := <-watch
		if !event.Ok() {
			// NOTE(msolomon) The zk meta conn will reconnect automatically, or
			// error out. At this point, there isn't much to do.
			relog.Warning("zookeeper not OK: %v", event)
			time.Sleep(5 * time.Second)
		} else if event.Type == zookeeper.EVENT_CHILD {
			agent.handleActionQueue()
		}
	}
}
<commit_msg>fix vtaction logging - looks like a merge casualty<commit_after>// Copyright 2012, Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

/*
The agent listens on a zk node for new actions to perform.

It passes them off to a separate action process. Even though some
actions could be completed inline very quickly, the external process
makes it easy to track and interrupt complex actions that may wedge
due to external circumstances.
*/

package tabletmanager

import (
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"net"
	"os"
	"os/exec"
	"path"
	"sort"
	"strconv"
	"strings"
	"sync"
	"time"

	"code.google.com/p/vitess/go/relog"
	"code.google.com/p/vitess/go/vt/naming"
	"code.google.com/p/vitess/go/zk"
	"launchpad.net/gozk/zookeeper"
)

type ActionAgent struct {
	zconn           zk.Conn
	zkTabletPath    string // FIXME(msolomon) use tabletInfo
	zkActionPath    string
	vtActionBinFile string // path to vtaction binary
	MycnfFile       string // my.cnf file
	DBCredsFile     string // File that contains db connection credentials

	mutex   sync.Mutex
	_tablet *TabletInfo // must be accessed with lock - TabletInfo objects are not synchronized.
}

// bindAddr: the address for the query service advertised by this agent
func NewActionAgent(zconn zk.Conn, zkTabletPath, mycnfFile, dbCredsFile string) *ActionAgent {
	actionPath := TabletActionPath(zkTabletPath)
	return &ActionAgent{
		zconn:        zconn,
		zkTabletPath: zkTabletPath,
		zkActionPath: actionPath,
		MycnfFile:    mycnfFile,
		DBCredsFile:  dbCredsFile}
}

func (agent *ActionAgent) readTablet() error {
	// Reread in case there were changes
	tablet, err := ReadTablet(agent.zconn, agent.zkTabletPath)
	if err != nil {
		return err
	}
	agent.mutex.Lock()
	agent._tablet = tablet
	agent.mutex.Unlock()
	return nil
}

func (agent *ActionAgent) Tablet() *TabletInfo {
	agent.mutex.Lock()
	tablet := agent._tablet
	agent.mutex.Unlock()
	return tablet
}

// FIXME(msolomon) need a real path discovery mechanism, a config file
// or more command line args.
func (agent *ActionAgent) resolvePaths() error {
	vtActionBinPaths := []string{os.ExpandEnv("$VTROOT/src/code.google.com/p/vitess/go/cmd/vtaction/vtaction"),
		"/usr/local/bin/vtaction"}
	for _, path := range vtActionBinPaths {
		if _, err := os.Stat(path); err == nil {
			agent.vtActionBinFile = path
			break
		}
	}
	if agent.vtActionBinFile == "" {
		return errors.New("no vtaction binary found")
	}

	/* FIXME: Delete commented out code
	mycnfFile := []string{fmt.Sprintf("/vt/vt_%010d/my.cnf", agent.Tablet().Uid),
		"/var/lib/mysql/my.cnf", "/etc/my.cnf"}
	for _, path := range mycnfFile {
		if _, err := os.Stat(path); err == nil {
			agent.MycnfFile = path
			break
		}
	}
	if agent.MycnfFile == "" {
		return errors.New("no my.cnf found")
	}*/
	return nil
}

func (agent *ActionAgent) dispatchAction(actionPath string) {
	relog.Info("action dispatch %v", actionPath)
	data, _, err := agent.zconn.Get(actionPath)
	if err != nil {
		relog.Error("action dispatch failed: %v", err)
		return
	}

	actionNode, err := ActionNodeFromJson(data, actionPath)
	if err != nil {
		relog.Error("action decode failed: %v %v", actionPath, err)
		return
	}

	logfile := flag.Lookup("logfile").Value.String()
	if !strings.HasPrefix(logfile, "/dev") {
		logfile = path.Join(path.Dir(logfile), "vtaction.log")
	}
	cmd := []string{
		agent.vtActionBinFile,
		"-action", actionNode.Action,
		"-action-node", actionPath,
		"-action-guid", actionNode.ActionGuid,
		"-mycnf-file", agent.MycnfFile,
		"-logfile", logfile,
	}
	if agent.DBCredsFile != "" {
		cmd = append(cmd, "-db-credentials-file", agent.DBCredsFile)
	}
	relog.Info("action launch %v", cmd)
	vtActionCmd := exec.Command(cmd[0], cmd[1:]...)

	stdOut, vtActionErr := vtActionCmd.CombinedOutput()
	if vtActionErr != nil {
		relog.Error("action failed: %v %v\n%s", actionPath, vtActionErr, stdOut)
		return
	}

	relog.Info("action completed %v %s", actionPath, stdOut)

	// Actions should have side effects on the tablet, so reload the data.
	if err := agent.readTablet(); err != nil {
		relog.Warning("failed rereading tablet after action: %v %v", actionPath, err)
	}
}

func (agent *ActionAgent) handleActionQueue() (<-chan zookeeper.Event, error) {
	// This read may seem a bit pedantic, but it makes it easier for the system
	// to trend towards consistency if an action fails or somehow the action
	// queue gets mangled by an errant process.
	children, _, watch, err := agent.zconn.ChildrenW(agent.zkActionPath)
	if err != nil {
		return watch, err
	}
	if len(children) > 0 {
		sort.Strings(children)
		for _, child := range children {
			actionPath := agent.zkActionPath + "/" + child
			_, err := strconv.ParseUint(child, 10, 64)
			if err != nil {
				// This is handy if you want to restart a stuck queue.
				// FIXME(msolomon) could listen on the queue node for a change
				// generated by a "touch", but listening on two things is a bit
				// more complex.
				relog.Warning("remove invalid event from action queue: %v", child)
				agent.zconn.Delete(actionPath, -1)
			}
			agent.dispatchAction(actionPath)
		}
	}
	return watch, nil
}

func (agent *ActionAgent) verifyZkPaths() error {
	tablet := agent.Tablet()
	if tablet == nil {
		panic(fmt.Errorf("agent._tablet is nil"))
	}

	zkReplicationPath := tablet.ReplicationPath()

	_, err := agent.zconn.Create(zkReplicationPath, "", 0, zookeeper.WorldACL(zookeeper.PERM_ALL))
	if err != nil && err.(*zookeeper.Error).Code != zookeeper.ZNODEEXISTS {
		return err
	}

	// Ensure that the action node is there.
	_, err = agent.zconn.Create(agent.zkActionPath, "", 0, zookeeper.WorldACL(zookeeper.PERM_ALL))
	if err != nil && err.(*zookeeper.Error).Code != zookeeper.ZNODEEXISTS {
		return err
	}
	return nil
}

func (agent *ActionAgent) verifyZkServingAddrs() error {
	if !agent.Tablet().IsServingType() {
		return nil
	}
	// Load the shard and see if we are supposed to be serving. We might be a serving type,
	// but we might be in a transitional state. Only once the shard info is updated do we
	// put ourselves in the client serving graph.
	shardInfo, err := ReadShard(agent.zconn, agent.Tablet().ShardPath())
	if err != nil {
		return err
	}

	if !shardInfo.Contains(agent.Tablet().Tablet) {
		return nil
	}
	// Check to see our address is registered in the right place.
	zkPathName := naming.ZkPathForVtName(agent.Tablet().Tablet.Cell, agent.Tablet().Keyspace,
		agent.Tablet().Shard, string(agent.Tablet().Type))

	f := func(oldValue string, oldStat *zookeeper.Stat) (string, error) {
		return agent.updateEndpoints(oldValue, oldStat)
	}
	err = agent.zconn.RetryChange(zkPathName, 0, zookeeper.WorldACL(zookeeper.PERM_ALL), f)
	if err == skipUpdateErr {
		err = nil
		relog.Warning("skipped serving graph update")
	}
	return err
}

var skipUpdateErr = fmt.Errorf("skip update")

// A function conforming to the RetryChange protocl. If the data returned
// is identical, no update is performed.
func (agent *ActionAgent) updateEndpoints(oldValue string, oldStat *zookeeper.Stat) (newValue string, err error) {
	if oldStat == nil {
		// The incoming object doesn't exist - we haven't been placed in the serving
		// graph yet, so don't update. Assume the next process that rebuilds the graph
		// will get the updated tablet location.
		return "", skipUpdateErr
	}

	addrs := naming.NewAddrs()
	if oldValue != "" {
		err = json.Unmarshal([]byte(oldValue), addrs)
		if err != nil {
			return
		}

		foundTablet := false
		for _, entry := range addrs.Entries {
			if entry.Uid == agent.Tablet().Uid {
				foundTablet = true
				vtAddr := fmt.Sprintf("%v:%v", entry.Host, entry.NamedPortMap["_vtocc"])
				mysqlAddr := fmt.Sprintf("%v:%v", entry.Host, entry.NamedPortMap["_mysql"])
				if vtAddr != agent.Tablet().Addr || mysqlAddr != agent.Tablet().MysqlAddr {
					// update needed
					host, port := splitHostPort(agent.Tablet().Addr)
					entry.Host = host
					entry.NamedPortMap["_vtocc"] = port
					host, port = splitHostPort(agent.Tablet().MysqlAddr)
					entry.NamedPortMap["_mysql"] = port
				}
				break
			}
		}

		if !foundTablet {
			addrs.Entries = append(addrs.Entries, *vtnsAddrForTablet(agent.Tablet().Tablet))
		}
	} else {
		addrs.Entries = append(addrs.Entries, *vtnsAddrForTablet(agent.Tablet().Tablet))
	}
	return toJson(addrs), nil
}

func splitHostPort(addr string) (string, int) {
	host, port, err := net.SplitHostPort(addr)
	if err != nil {
		panic(err)
	}
	p, err := strconv.ParseInt(port, 10, 16)
	if err != nil {
		panic(err)
	}
	return host, int(p)
}

// Resolve an address where the host has been left blank, like ":3306"
func resolveAddr(addr string) string {
	host, port := splitHostPort(addr)
	if host == "" {
		hostname, err := os.Hostname()
		if err != nil {
			panic(err)
		}
		host = hostname
	}
	return fmt.Sprintf("%v:%v", host, port)
}

func vtnsAddrForTablet(tablet *Tablet) *naming.VtnsAddr {
	host, port := splitHostPort(tablet.Addr)
	entry := naming.NewAddr(tablet.Uid, host, 0)
	entry.NamedPortMap["_vtocc"] = port
	host, port = splitHostPort(tablet.MysqlAddr)
	entry.NamedPortMap["_mysql"] = port
	return entry
}

func (agent *ActionAgent) Start(bindAddr, mysqlAddr string) {
	var err error
	if err = agent.readTablet(); err != nil {
		panic(err)
	}

	if err = agent.resolvePaths(); err != nil {
		panic(err)
	}

	// Update bind addr for mysql and query service in the tablet node.
	f := func(oldValue string, oldStat *zookeeper.Stat) (string, error) {
		if oldValue == "" {
			return "", fmt.Errorf("no data for tablet addr update: %v", agent.zkTabletPath)
		}

		tablet := tabletFromJson(oldValue)
		tablet.Addr = resolveAddr(bindAddr)
		tablet.MysqlAddr = resolveAddr(mysqlAddr)
		return toJson(tablet), nil
	}
	err = agent.zconn.RetryChange(agent.Tablet().Path(), 0, zookeeper.WorldACL(zookeeper.PERM_ALL), f)
	if err != nil {
		panic(err)
	}

	// Reread in case there were changes
	if err := agent.readTablet(); err != nil {
		panic(err)
	}

	if err := zk.CreatePidNode(agent.zconn, agent.Tablet().PidPath()); err != nil {
		panic(err)
	}

	if err = agent.verifyZkPaths(); err != nil {
		panic(err)
	}

	if err = agent.verifyZkServingAddrs(); err != nil {
		panic(err)
	}

	go agent.actionEventLoop()
}

func (agent *ActionAgent) actionEventLoop() {
	for {
		// Process any pending actions when we startup, before we start listening
		// for events.
		watch, err := agent.handleActionQueue()
		if err != nil {
			relog.Warning("action queue failed: %v", err)
			time.Sleep(5 * time.Second)
			continue
		}

		event := <-watch
		if !event.Ok() {
			// NOTE(msolomon) The zk meta conn will reconnect automatically, or
			// error out. At this point, there isn't much to do.
			relog.Warning("zookeeper not OK: %v", event)
			time.Sleep(5 * time.Second)
		} else if event.Type == zookeeper.EVENT_CHILD {
			agent.handleActionQueue()
		}
	}
}
<|endoftext|>


<commit_before>// Copyright 2012, Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package vtgate

import (
	"fmt"
	"time"

	mproto "github.com/youtube/vitess/go/mysql/proto"
	"github.com/youtube/vitess/go/sqltypes"
	"github.com/youtube/vitess/go/sync2"
	tproto "github.com/youtube/vitess/go/vt/tabletserver/proto"
	"github.com/youtube/vitess/go/vt/topo"
)

// sandbox_test.go provides a sandbox for unit testing Barnacle.

func init() {
	RegisterDialer("sandbox", sandboxDialer)
}

var (
	// endPointCounter tracks how often GetEndPoints was called
	endPointCounter int

	// endPointMustFail specifies how often GetEndPoints must fail before succeeding
	endPointMustFail int

	// dialerCoun tracks how often sandboxDialer was called
	dialCounter int

	// dialMustFail specifies how often sandboxDialer must fail before succeeding
	dialMustFail int

	// transaction id generator
	transactionId sync2.AtomicInt64
)

func resetSandbox() {
	testConns = make(map[string]TabletConn)
	endPointCounter = 0
	dialCounter = 0
	dialMustFail = 0
}

type sandboxTopo struct {
}

func (sct *sandboxTopo) GetEndPoints(cell, keyspace, shard string, tabletType topo.TabletType) (*topo.EndPoints, error) {
	endPointCounter++
	if endPointMustFail > 0 {
		endPointMustFail--
		return nil, fmt.Errorf("topo error")
	}
	return &topo.EndPoints{Entries: []topo.EndPoint{
		{Host: shard, NamedPortMap: map[string]int{"vt": 1}},
	}}, nil
}

var testConns map[string]TabletConn

func sandboxDialer(addr, keyspace, shard, username, password string, encrypted bool) (TabletConn, error) {
	dialCounter++
	if dialMustFail > 0 {
		dialMustFail--
		return nil, OperationalError(fmt.Sprintf("conn error"))
	}
	tconn := testConns[addr]
	if tconn == nil {
		panic(fmt.Sprintf("can't find conn %s", addr))
	}
	return tconn, nil
}

type sandboxConn struct {
	mustFailRetry  int
	mustFailFatal  int
	mustFailServer int
	mustFailConn   int
	mustFailTxPool int
	mustFailNotTx  int
	mustDelay      time.Duration

	// These Count vars report how often the corresponding
	// functions were called.
	ExecCount     int
	BeginCount    int
	CommitCount   int
	RollbackCount int
	CloseCount    int

	// TransactionId is auto-generated on Begin
	transactionId int64
}

func (sbc *sandboxConn) getError() error {
	if sbc.mustFailRetry > 0 {
		sbc.mustFailRetry--
		return &ServerError{Code: ERR_RETRY, Err: "retry: err"}
	}
	if sbc.mustFailFatal > 0 {
		sbc.mustFailFatal--
		return &ServerError{Code: ERR_FATAL, Err: "fatal: err"}
	}
	if sbc.mustFailServer > 0 {
		sbc.mustFailServer--
		return &ServerError{Code: ERR_NORMAL, Err: "error: err"}
	}
	if sbc.mustFailConn > 0 {
		sbc.mustFailConn--
		return OperationalError(fmt.Sprintf("error: conn"))
	}
	if sbc.mustFailTxPool > 0 {
		sbc.mustFailTxPool--
		return &ServerError{Code: ERR_TX_POOL_FULL, Err: "tx_pool_full: err"}
	}
	if sbc.mustFailNotTx > 0 {
		sbc.mustFailNotTx--
		return &ServerError{Code: ERR_NOT_IN_TX, Err: "not_in_tx: err"}
	}
	return nil
}

func (sbc *sandboxConn) Execute(query string, bindVars map[string]interface{}) (*mproto.QueryResult, error) {
	sbc.ExecCount++
	if sbc.mustDelay != 0 {
		time.Sleep(sbc.mustDelay)
	}
	if err := sbc.getError(); err != nil {
		return nil, err
	}
	return singleRowResult, nil
}

func (sbc *sandboxConn) ExecuteBatch(queries []tproto.BoundQuery) (*tproto.QueryResultList, error) {
	sbc.ExecCount++
	if sbc.mustDelay != 0 {
		time.Sleep(sbc.mustDelay)
	}
	if err := sbc.getError(); err != nil {
		return nil, err
	}
	qrl := &tproto.QueryResultList{List: make([]mproto.QueryResult, 0, len(queries))}
	for _ = range queries {
		qrl.List = append(qrl.List, *singleRowResult)
	}
	return qrl, nil
}

func (sbc *sandboxConn) StreamExecute(query string, bindVars map[string]interface{}) (<-chan *mproto.QueryResult, ErrFunc) {
	sbc.ExecCount++
	if sbc.mustDelay != 0 {
		time.Sleep(sbc.mustDelay)
	}
	ch := make(chan *mproto.QueryResult, 1)
	ch <- singleRowResult
	close(ch)
	err := sbc.getError()
	return ch, func() error { return err }
}

func (sbc *sandboxConn) Begin() error {
	sbc.ExecCount++
	sbc.BeginCount++
	if sbc.mustDelay != 0 {
		time.Sleep(sbc.mustDelay)
	}
	err := sbc.getError()
	if err == nil {
		sbc.transactionId = transactionId.Add(1)
	}
	return err
}

func (sbc *sandboxConn) Commit() error {
	sbc.ExecCount++
	sbc.CommitCount++
	sbc.transactionId = 0
	if sbc.mustDelay != 0 {
		time.Sleep(sbc.mustDelay)
	}
	return sbc.getError()
}

func (sbc *sandboxConn) Rollback() error {
	sbc.ExecCount++
	sbc.RollbackCount++
	sbc.transactionId = 0
	if sbc.mustDelay != 0 {
		time.Sleep(sbc.mustDelay)
	}
	return sbc.getError()
}

func (sbc *sandboxConn) TransactionId() int64 {
	return sbc.transactionId
}

// Close does not change ExecCount
func (sbc *sandboxConn) Close() error {
	sbc.CloseCount++
	return nil
}

var singleRowResult = &mproto.QueryResult{
	Fields: []mproto.Field{
		{"id", 3},
		{"value", 253}},
	RowsAffected: 1,
	InsertId:     0,
	Rows: [][]sqltypes.Value{{
		{sqltypes.Numeric("1")},
		{sqltypes.String("foo")},
	}},
}
<commit_msg>fix race detector errors<commit_after>// Copyright 2012, Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package vtgate

import (
	"fmt"
	"sync"
	"time"

	mproto "github.com/youtube/vitess/go/mysql/proto"
	"github.com/youtube/vitess/go/sqltypes"
	"github.com/youtube/vitess/go/sync2"
	tproto "github.com/youtube/vitess/go/vt/tabletserver/proto"
	"github.com/youtube/vitess/go/vt/topo"
)

// sandbox_test.go provides a sandbox for unit testing Barnacle.

func init() {
	RegisterDialer("sandbox", sandboxDialer)
}

var (
	// Use sandmu to access the variables below
	sandmu sync.Mutex

	// endPointCounter tracks how often GetEndPoints was called
	endPointCounter int

	// endPointMustFail specifies how often GetEndPoints must fail before succeeding
	endPointMustFail int

	// dialerCoun tracks how often sandboxDialer was called
	dialCounter int

	// dialMustFail specifies how often sandboxDialer must fail before succeeding
	dialMustFail int
)

var (
	// transaction id generator
	transactionId sync2.AtomicInt64
)

func resetSandbox() {
	sandmu.Lock()
	defer sandmu.Unlock()
	testConns = make(map[string]TabletConn)
	endPointCounter = 0
	dialCounter = 0
	dialMustFail = 0
}

type sandboxTopo struct {
}

func (sct *sandboxTopo) GetEndPoints(cell, keyspace, shard string, tabletType topo.TabletType) (*topo.EndPoints, error) {
	sandmu.Lock()
	defer sandmu.Unlock()
	endPointCounter++
	if endPointMustFail > 0 {
		endPointMustFail--
		return nil, fmt.Errorf("topo error")
	}
	return &topo.EndPoints{Entries: []topo.EndPoint{
		{Host: shard, NamedPortMap: map[string]int{"vt": 1}},
	}}, nil
}

var testConns map[string]TabletConn

func sandboxDialer(addr, keyspace, shard, username, password string, encrypted bool) (TabletConn, error) {
	sandmu.Lock()
	defer sandmu.Unlock()
	dialCounter++
	if dialMustFail > 0 {
		dialMustFail--
		return nil, OperationalError(fmt.Sprintf("conn error"))
	}
	tconn := testConns[addr]
	if tconn == nil {
		panic(fmt.Sprintf("can't find conn %s", addr))
	}
	return tconn, nil
}

type sandboxConn struct {
	mustFailRetry  int
	mustFailFatal  int
	mustFailServer int
	mustFailConn   int
	mustFailTxPool int
	mustFailNotTx  int
	mustDelay      time.Duration

	// These Count vars report how often the corresponding
	// functions were called.
	ExecCount     int
	BeginCount    int
	CommitCount   int
	RollbackCount int
	CloseCount    int

	// TransactionId is auto-generated on Begin
	transactionId int64
}

func (sbc *sandboxConn) getError() error {
	if sbc.mustFailRetry > 0 {
		sbc.mustFailRetry--
		return &ServerError{Code: ERR_RETRY, Err: "retry: err"}
	}
	if sbc.mustFailFatal > 0 {
		sbc.mustFailFatal--
		return &ServerError{Code: ERR_FATAL, Err: "fatal: err"}
	}
	if sbc.mustFailServer > 0 {
		sbc.mustFailServer--
		return &ServerError{Code: ERR_NORMAL, Err: "error: err"}
	}
	if sbc.mustFailConn > 0 {
		sbc.mustFailConn--
		return OperationalError(fmt.Sprintf("error: conn"))
	}
	if sbc.mustFailTxPool > 0 {
		sbc.mustFailTxPool--
		return &ServerError{Code: ERR_TX_POOL_FULL, Err: "tx_pool_full: err"}
	}
	if sbc.mustFailNotTx > 0 {
		sbc.mustFailNotTx--
		return &ServerError{Code: ERR_NOT_IN_TX, Err: "not_in_tx: err"}
	}
	return nil
}

func (sbc *sandboxConn) Execute(query string, bindVars map[string]interface{}) (*mproto.QueryResult, error) {
	sbc.ExecCount++
	if sbc.mustDelay != 0 {
		time.Sleep(sbc.mustDelay)
	}
	if err := sbc.getError(); err != nil {
		return nil, err
	}
	return singleRowResult, nil
}

func (sbc *sandboxConn) ExecuteBatch(queries []tproto.BoundQuery) (*tproto.QueryResultList, error) {
	sbc.ExecCount++
	if sbc.mustDelay != 0 {
		time.Sleep(sbc.mustDelay)
	}
	if err := sbc.getError(); err != nil {
		return nil, err
	}
	qrl := &tproto.QueryResultList{List: make([]mproto.QueryResult, 0, len(queries))}
	for _ = range queries {
		qrl.List = append(qrl.List, *singleRowResult)
	}
	return qrl, nil
}

func (sbc *sandboxConn) StreamExecute(query string, bindVars map[string]interface{}) (<-chan *mproto.QueryResult, ErrFunc) {
	sbc.ExecCount++
	if sbc.mustDelay != 0 {
		time.Sleep(sbc.mustDelay)
	}
	ch := make(chan *mproto.QueryResult, 1)
	ch <- singleRowResult
	close(ch)
	err := sbc.getError()
	return ch, func() error { return err }
}

func (sbc *sandboxConn) Begin() error {
	sbc.ExecCount++
	sbc.BeginCount++
	if sbc.mustDelay != 0 {
		time.Sleep(sbc.mustDelay)
	}
	err := sbc.getError()
	if err == nil {
		sbc.transactionId = transactionId.Add(1)
	}
	return err
}

func (sbc *sandboxConn) Commit() error {
	sbc.ExecCount++
	sbc.CommitCount++
	sbc.transactionId = 0
	if sbc.mustDelay != 0 {
		time.Sleep(sbc.mustDelay)
	}
	return sbc.getError()
}

func (sbc *sandboxConn) Rollback() error {
	sbc.ExecCount++
	sbc.RollbackCount++
	sbc.transactionId = 0
	if sbc.mustDelay != 0 {
		time.Sleep(sbc.mustDelay)
	}
	return sbc.getError()
}

func (sbc *sandboxConn) TransactionId() int64 {
	return sbc.transactionId
}

// Close does not change ExecCount
func (sbc *sandboxConn) Close() error {
	sbc.CloseCount++
	return nil
}

var singleRowResult = &mproto.QueryResult{
	Fields: []mproto.Field{
		{"id", 3},
		{"value", 253}},
	RowsAffected: 1,
	InsertId:     0,
	Rows: [][]sqltypes.Value{{
		{sqltypes.Numeric("1")},
		{sqltypes.String("foo")},
	}},
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"encoding/json"
	"io/ioutil"
	"os"
	"path"
	"strings"
	"time"

	"github.com/codegangsta/cli"
	"github.com/xenolf/lego/acme"
)

func checkFolder(path string) error {
	if _, err := os.Stat(path); os.IsNotExist(err) {
		return os.MkdirAll(path, 0700)
	}
	return nil
}

func setup(c *cli.Context) (*Configuration, *Account, *acme.Client) {
	err := checkFolder(c.GlobalString("path"))
	if err != nil {
		logger().Fatalf("Cound not check/create path: %s", err.Error())
	}

	conf := NewConfiguration(c)
	if len(c.GlobalString("email")) == 0 {
		logger().Fatal("You have to pass an account (email address) to the program using --email or -m")
	}

	//TODO: move to account struct? Currently MUST pass email.
	acc := NewAccount(c.GlobalString("email"), conf)

	client, err := acme.NewClient(c.GlobalString("server"), acc, conf.RsaBits())
	if err != nil {
		logger().Fatalf("Could not create client: %s", err.Error())
	}

	if len(c.GlobalStringSlice("exclude")) > 0 {
		client.ExcludeChallenges(conf.ExcludedSolvers())
	}

	if c.GlobalIsSet("webroot") {
		provider, err := acme.NewHTTPProviderWebroot(c.GlobalString("webroot"))
		if err != nil {
			logger().Fatal(err)
		}

		client.SetChallengeProvider(HTTP01, provider)
	}
	if c.GlobalIsSet("http") {
		client.SetHTTPAddress(c.GlobalString("http"))
	}

	if c.GlobalIsSet("tls") {
		client.SetTLSAddress(c.GlobalString("tls"))
	}

	if c.GlobalIsSet("dns") {
		var err error
		var provider acme.ChallengeProvider
		switch c.GlobalString("dns") {
		case "cloudflare":
			provider, err = acme.NewDNSProviderCloudFlare("", "")
		case "digitalocean":
			authToken := os.Getenv("DO_AUTH_TOKEN")

			provider, err = acme.NewDNSProviderDigitalOcean(authToken)
		case "dnsimple":
			provider, err = acme.NewDNSProviderDNSimple("", "")
		case "route53":
			awsRegion := os.Getenv("AWS_REGION")
			provider, err = acme.NewDNSProviderRoute53("", "", awsRegion)
		case "rfc2136":
			nameserver := os.Getenv("RFC2136_NAMESERVER")
			zone := os.Getenv("RFC2136_ZONE")
			tsigKey := os.Getenv("RFC2136_TSIG_KEY")
			tsigSecret := os.Getenv("RFC2136_TSIG_SECRET")

			provider, err = acme.NewDNSProviderRFC2136(nameserver, zone, tsigKey, tsigSecret)
		case "manual":
			provider, err = acme.NewDNSProviderManual()
		}

		if err != nil {
			logger().Fatal(err)
		}

		client.SetChallengeProvider(acme.DNS01, provider)
	}

	return conf, acc, client
}

func saveCertRes(certRes acme.CertificateResource, conf *Configuration) {
	// We store the certificate, private key and metadata in different files
	// as web servers would not be able to work with a combined file.
	certOut := path.Join(conf.CertPath(), certRes.Domain+".crt")
	privOut := path.Join(conf.CertPath(), certRes.Domain+".key")
	metaOut := path.Join(conf.CertPath(), certRes.Domain+".json")

	err := ioutil.WriteFile(certOut, certRes.Certificate, 0600)
	if err != nil {
		logger().Fatalf("Unable to save Certificate for domain %s\n\t%s", certRes.Domain, err.Error())
	}

	err = ioutil.WriteFile(privOut, certRes.PrivateKey, 0600)
	if err != nil {
		logger().Fatalf("Unable to save PrivateKey for domain %s\n\t%s", certRes.Domain, err.Error())
	}

	jsonBytes, err := json.MarshalIndent(certRes, "", "\t")
	if err != nil {
		logger().Fatalf("Unable to marshal CertResource for domain %s\n\t%s", certRes.Domain, err.Error())
	}

	err = ioutil.WriteFile(metaOut, jsonBytes, 0600)
	if err != nil {
		logger().Fatalf("Unable to save CertResource for domain %s\n\t%s", certRes.Domain, err.Error())
	}
}

func run(c *cli.Context) {
	conf, acc, client := setup(c)
	if acc.Registration == nil {
		reg, err := client.Register()
		if err != nil {
			logger().Fatalf("Could not complete registration\n\t%s", err.Error())
		}

		acc.Registration = reg
		acc.Save()

		logger().Print("!!!! HEADS UP !!!!")
		logger().Printf(`
		Your account credentials have been saved in your Let's Encrypt
		configuration directory at "%s".
		You should make a secure backup	of this folder now. This
		configuration directory will also contain certificates and
		private keys obtained from Let's Encrypt so making regular
		backups of this folder is ideal.`, conf.AccountPath(c.GlobalString("email")))

	}

	if acc.Registration.Body.Agreement == "" {
		reader := bufio.NewReader(os.Stdin)
		logger().Printf("Please review the TOS at %s", acc.Registration.TosURL)

		for {
			logger().Println("Do you accept the TOS? Y/n")
			text, err := reader.ReadString('\n')
			if err != nil {
				logger().Fatalf("Could not read from console -> %s", err.Error())
			}

			text = strings.Trim(text, "\r\n")

			if text == "n" {
				logger().Fatal("You did not accept the TOS. Unable to proceed.")
			}

			if text == "Y" || text == "y" || text == "" {
				err = client.AgreeToTOS()
				if err != nil {
					logger().Fatalf("Could not agree to tos -> %s", err)
				}
				acc.Save()
				break
			}

			logger().Println("Your input was invalid. Please answer with one of Y/y, n or by pressing enter.")
		}
	}

	if len(c.GlobalStringSlice("domains")) == 0 {
		logger().Fatal("Please specify --domains or -d")
	}

	cert, failures := client.ObtainCertificate(c.GlobalStringSlice("domains"), true, nil)
	if len(failures) > 0 {
		for k, v := range failures {
			logger().Printf("[%s] Could not obtain certificates\n\t%s", k, v.Error())
		}

		// Make sure to return a non-zero exit code if ObtainSANCertificate
		// returned at least one error. Due to us not returning partial
		// certificate we can just exit here instead of at the end.
		os.Exit(1)
	}

	err := checkFolder(conf.CertPath())
	if err != nil {
		logger().Fatalf("Cound not check/create path: %s", err.Error())
	}

	saveCertRes(cert, conf)
}

func revoke(c *cli.Context) {

	conf, _, client := setup(c)

	err := checkFolder(conf.CertPath())
	if err != nil {
		logger().Fatalf("Cound not check/create path: %s", err.Error())
	}

	for _, domain := range c.GlobalStringSlice("domains") {
		logger().Printf("Trying to revoke certificate for domain %s", domain)

		certPath := path.Join(conf.CertPath(), domain+".crt")
		certBytes, err := ioutil.ReadFile(certPath)

		err = client.RevokeCertificate(certBytes)
		if err != nil {
			logger().Fatalf("Error while revoking the certificate for domain %s\n\t%s", domain, err.Error())
		} else {
			logger().Print("Certificate was revoked.")
		}
	}
}

func renew(c *cli.Context) {
	conf, _, client := setup(c)

	if len(c.GlobalStringSlice("domains")) <= 0 {
		logger().Fatal("Please specify at least one domain.")
	}

	domain := c.GlobalStringSlice("domains")[0]

	// load the cert resource from files.
	// We store the certificate, private key and metadata in different files
	// as web servers would not be able to work with a combined file.
	certPath := path.Join(conf.CertPath(), domain+".crt")
	privPath := path.Join(conf.CertPath(), domain+".key")
	metaPath := path.Join(conf.CertPath(), domain+".json")

	certBytes, err := ioutil.ReadFile(certPath)
	if err != nil {
		logger().Fatalf("Error while loading the certificate for domain %s\n\t%s", domain, err.Error())
	}

	if c.IsSet("days") {
		expTime, err := acme.GetPEMCertExpiration(certBytes)
		if err != nil {
			logger().Printf("Could not get Certification expiration for domain %s", domain)
		}

		if int(expTime.Sub(time.Now()).Hours()/24.0) > c.Int("days") {
			return
		}
	}

	metaBytes, err := ioutil.ReadFile(metaPath)
	if err != nil {
		logger().Fatalf("Error while loading the meta data for domain %s\n\t%s", domain, err.Error())
	}

	var certRes acme.CertificateResource
	err = json.Unmarshal(metaBytes, &certRes)
	if err != nil {
		logger().Fatalf("Error while marshalling the meta data for domain %s\n\t%s", domain, err.Error())
	}

	if c.Bool("reuse-key") {
		keyBytes, err := ioutil.ReadFile(privPath)
		if err != nil {
			logger().Fatalf("Error while loading the private key for domain %s\n\t%s", domain, err.Error())
		}
		certRes.PrivateKey = keyBytes
	}

	certRes.Certificate = certBytes

	newCert, err := client.RenewCertificate(certRes, true)
	if err != nil {
		logger().Fatalf("%s", err.Error())
	}

	saveCertRes(newCert, conf)
}
<commit_msg>Minor fix, forgot package name<commit_after>package main

import (
	"bufio"
	"encoding/json"
	"io/ioutil"
	"os"
	"path"
	"strings"
	"time"

	"github.com/codegangsta/cli"
	"github.com/xenolf/lego/acme"
)

func checkFolder(path string) error {
	if _, err := os.Stat(path); os.IsNotExist(err) {
		return os.MkdirAll(path, 0700)
	}
	return nil
}

func setup(c *cli.Context) (*Configuration, *Account, *acme.Client) {
	err := checkFolder(c.GlobalString("path"))
	if err != nil {
		logger().Fatalf("Cound not check/create path: %s", err.Error())
	}

	conf := NewConfiguration(c)
	if len(c.GlobalString("email")) == 0 {
		logger().Fatal("You have to pass an account (email address) to the program using --email or -m")
	}

	//TODO: move to account struct? Currently MUST pass email.
	acc := NewAccount(c.GlobalString("email"), conf)

	client, err := acme.NewClient(c.GlobalString("server"), acc, conf.RsaBits())
	if err != nil {
		logger().Fatalf("Could not create client: %s", err.Error())
	}

	if len(c.GlobalStringSlice("exclude")) > 0 {
		client.ExcludeChallenges(conf.ExcludedSolvers())
	}

	if c.GlobalIsSet("webroot") {
		provider, err := acme.NewHTTPProviderWebroot(c.GlobalString("webroot"))
		if err != nil {
			logger().Fatal(err)
		}

		client.SetChallengeProvider(acme.HTTP01, provider)
	}
	if c.GlobalIsSet("http") {
		client.SetHTTPAddress(c.GlobalString("http"))
	}

	if c.GlobalIsSet("tls") {
		client.SetTLSAddress(c.GlobalString("tls"))
	}

	if c.GlobalIsSet("dns") {
		var err error
		var provider acme.ChallengeProvider
		switch c.GlobalString("dns") {
		case "cloudflare":
			provider, err = acme.NewDNSProviderCloudFlare("", "")
		case "digitalocean":
			authToken := os.Getenv("DO_AUTH_TOKEN")

			provider, err = acme.NewDNSProviderDigitalOcean(authToken)
		case "dnsimple":
			provider, err = acme.NewDNSProviderDNSimple("", "")
		case "route53":
			awsRegion := os.Getenv("AWS_REGION")
			provider, err = acme.NewDNSProviderRoute53("", "", awsRegion)
		case "rfc2136":
			nameserver := os.Getenv("RFC2136_NAMESERVER")
			zone := os.Getenv("RFC2136_ZONE")
			tsigKey := os.Getenv("RFC2136_TSIG_KEY")
			tsigSecret := os.Getenv("RFC2136_TSIG_SECRET")

			provider, err = acme.NewDNSProviderRFC2136(nameserver, zone, tsigKey, tsigSecret)
		case "manual":
			provider, err = acme.NewDNSProviderManual()
		}

		if err != nil {
			logger().Fatal(err)
		}

		client.SetChallengeProvider(acme.DNS01, provider)
	}

	return conf, acc, client
}

func saveCertRes(certRes acme.CertificateResource, conf *Configuration) {
	// We store the certificate, private key and metadata in different files
	// as web servers would not be able to work with a combined file.
	certOut := path.Join(conf.CertPath(), certRes.Domain+".crt")
	privOut := path.Join(conf.CertPath(), certRes.Domain+".key")
	metaOut := path.Join(conf.CertPath(), certRes.Domain+".json")

	err := ioutil.WriteFile(certOut, certRes.Certificate, 0600)
	if err != nil {
		logger().Fatalf("Unable to save Certificate for domain %s\n\t%s", certRes.Domain, err.Error())
	}

	err = ioutil.WriteFile(privOut, certRes.PrivateKey, 0600)
	if err != nil {
		logger().Fatalf("Unable to save PrivateKey for domain %s\n\t%s", certRes.Domain, err.Error())
	}

	jsonBytes, err := json.MarshalIndent(certRes, "", "\t")
	if err != nil {
		logger().Fatalf("Unable to marshal CertResource for domain %s\n\t%s", certRes.Domain, err.Error())
	}

	err = ioutil.WriteFile(metaOut, jsonBytes, 0600)
	if err != nil {
		logger().Fatalf("Unable to save CertResource for domain %s\n\t%s", certRes.Domain, err.Error())
	}
}

func run(c *cli.Context) {
	conf, acc, client := setup(c)
	if acc.Registration == nil {
		reg, err := client.Register()
		if err != nil {
			logger().Fatalf("Could not complete registration\n\t%s", err.Error())
		}

		acc.Registration = reg
		acc.Save()

		logger().Print("!!!! HEADS UP !!!!")
		logger().Printf(`
		Your account credentials have been saved in your Let's Encrypt
		configuration directory at "%s".
		You should make a secure backup	of this folder now. This
		configuration directory will also contain certificates and
		private keys obtained from Let's Encrypt so making regular
		backups of this folder is ideal.`, conf.AccountPath(c.GlobalString("email")))

	}

	if acc.Registration.Body.Agreement == "" {
		reader := bufio.NewReader(os.Stdin)
		logger().Printf("Please review the TOS at %s", acc.Registration.TosURL)

		for {
			logger().Println("Do you accept the TOS? Y/n")
			text, err := reader.ReadString('\n')
			if err != nil {
				logger().Fatalf("Could not read from console -> %s", err.Error())
			}

			text = strings.Trim(text, "\r\n")

			if text == "n" {
				logger().Fatal("You did not accept the TOS. Unable to proceed.")
			}

			if text == "Y" || text == "y" || text == "" {
				err = client.AgreeToTOS()
				if err != nil {
					logger().Fatalf("Could not agree to tos -> %s", err)
				}
				acc.Save()
				break
			}

			logger().Println("Your input was invalid. Please answer with one of Y/y, n or by pressing enter.")
		}
	}

	if len(c.GlobalStringSlice("domains")) == 0 {
		logger().Fatal("Please specify --domains or -d")
	}

	cert, failures := client.ObtainCertificate(c.GlobalStringSlice("domains"), true, nil)
	if len(failures) > 0 {
		for k, v := range failures {
			logger().Printf("[%s] Could not obtain certificates\n\t%s", k, v.Error())
		}

		// Make sure to return a non-zero exit code if ObtainSANCertificate
		// returned at least one error. Due to us not returning partial
		// certificate we can just exit here instead of at the end.
		os.Exit(1)
	}

	err := checkFolder(conf.CertPath())
	if err != nil {
		logger().Fatalf("Cound not check/create path: %s", err.Error())
	}

	saveCertRes(cert, conf)
}

func revoke(c *cli.Context) {

	conf, _, client := setup(c)

	err := checkFolder(conf.CertPath())
	if err != nil {
		logger().Fatalf("Cound not check/create path: %s", err.Error())
	}

	for _, domain := range c.GlobalStringSlice("domains") {
		logger().Printf("Trying to revoke certificate for domain %s", domain)

		certPath := path.Join(conf.CertPath(), domain+".crt")
		certBytes, err := ioutil.ReadFile(certPath)

		err = client.RevokeCertificate(certBytes)
		if err != nil {
			logger().Fatalf("Error while revoking the certificate for domain %s\n\t%s", domain, err.Error())
		} else {
			logger().Print("Certificate was revoked.")
		}
	}
}

func renew(c *cli.Context) {
	conf, _, client := setup(c)

	if len(c.GlobalStringSlice("domains")) <= 0 {
		logger().Fatal("Please specify at least one domain.")
	}

	domain := c.GlobalStringSlice("domains")[0]

	// load the cert resource from files.
	// We store the certificate, private key and metadata in different files
	// as web servers would not be able to work with a combined file.
	certPath := path.Join(conf.CertPath(), domain+".crt")
	privPath := path.Join(conf.CertPath(), domain+".key")
	metaPath := path.Join(conf.CertPath(), domain+".json")

	certBytes, err := ioutil.ReadFile(certPath)
	if err != nil {
		logger().Fatalf("Error while loading the certificate for domain %s\n\t%s", domain, err.Error())
	}

	if c.IsSet("days") {
		expTime, err := acme.GetPEMCertExpiration(certBytes)
		if err != nil {
			logger().Printf("Could not get Certification expiration for domain %s", domain)
		}

		if int(expTime.Sub(time.Now()).Hours()/24.0) > c.Int("days") {
			return
		}
	}

	metaBytes, err := ioutil.ReadFile(metaPath)
	if err != nil {
		logger().Fatalf("Error while loading the meta data for domain %s\n\t%s", domain, err.Error())
	}

	var certRes acme.CertificateResource
	err = json.Unmarshal(metaBytes, &certRes)
	if err != nil {
		logger().Fatalf("Error while marshalling the meta data for domain %s\n\t%s", domain, err.Error())
	}

	if c.Bool("reuse-key") {
		keyBytes, err := ioutil.ReadFile(privPath)
		if err != nil {
			logger().Fatalf("Error while loading the private key for domain %s\n\t%s", domain, err.Error())
		}
		certRes.PrivateKey = keyBytes
	}

	certRes.Certificate = certBytes

	newCert, err := client.RenewCertificate(certRes, true)
	if err != nil {
		logger().Fatalf("%s", err.Error())
	}

	saveCertRes(newCert, conf)
}
<|endoftext|>


<commit_before>package senml_test

import (
	"encoding/base64"
	"fmt"
	"github.com/cisco/senml"
	"strconv"
	"testing"
)

func ExampleEncode1() {
	v := 23.1
	s := senml.SenML{
		Records: []senml.SenMLRecord{
			senml.SenMLRecord{Value: &v, Unit: "Cel", Name: "urn:dev:ow:10e2073a01080063"},
		},
	}

	dataOut, err := senml.Encode(s, senml.JSON, senml.OutputOptions{})
	if err != nil {
		fmt.Println("Encode of SenML failed")
	} else {
		fmt.Println(string(dataOut))
	}
	// Output: [{"n":"urn:dev:ow:10e2073a01080063","u":"Cel","v":23.1}]
}

func ExampleEncode2() {
	v1 := 23.5
	v2 := 23.6
	s := senml.SenML{
		Records: []senml.SenMLRecord{
			senml.SenMLRecord{Value: &v1, Unit: "Cel", BaseName: "urn:dev:ow:10e2073a01080063", Time: 1.276020076e+09},
			senml.SenMLRecord{Value: &v2, Unit: "Cel", Time: 1.276020091e+09},
		},
	}

	dataOut, err := senml.Encode(s, senml.JSON, senml.OutputOptions{})
	if err != nil {
		fmt.Println("Encode of SenML failed")
	} else {
		fmt.Println(string(dataOut))
	}
	// Output: [{"bn":"urn:dev:ow:10e2073a01080063","u":"Cel","t":1.276020076e+09,"v":23.5},{"u":"Cel","t":1.276020091e+09,"v":23.6}]
}

type TestVector struct {
	testDecode bool
	format     senml.Format
	binary     bool
	value      string
}

var testVectors = []TestVector{
	{true, senml.JSON, false, "W3siYm4iOiJkZXYxMjMiLCJidCI6LTQ1LjY3LCJidSI6ImRlZ0MiLCJidmVyIjo1LCJuIjoidGVtcCIsInUiOiJkZWdDIiwidCI6LTEsInV0IjoxMCwidiI6MjIuMSwicyI6MH0seyJuIjoicm9vbSIsInQiOi0xLCJ2cyI6ImtpdGNoZW4ifSx7Im4iOiJkYXRhIiwidmQiOiJhYmMifSx7Im4iOiJvayIsInZiIjp0cnVlfV0="},
	{true, senml.CBOR, true, "hKpiYm5mZGV2MTIzYmJ0+8BG1cKPXCj2YmJ1ZGRlZ0NkYnZlcgVhbmR0ZW1wYXP7AAAAAAAAAABhdPu/8AAAAAAAAGF1ZGRlZ0NidXT7QCQAAAAAAABhdvtANhmZmZmZmqNhbmRyb29tYXT7v/AAAAAAAABidnNna2l0Y2hlbqJhbmRkYXRhYnZkY2FiY6JhbmJva2J2YvU="},
	{true, senml.XML, false, "PHNlbnNtbCB4bWxucz0idXJuOmlldGY6cGFyYW1zOnhtbDpuczpzZW5tbCI+PHNlbm1sIGJuPSJkZXYxMjMiIGJ0PSItNDUuNjciIGJ1PSJkZWdDIiBidmVyPSI1IiBuPSJ0ZW1wIiB1PSJkZWdDIiB0PSItMSIgdXQ9IjEwIiB2PSIyMi4xIiBzdW09IjAiPjwvc2VubWw+PHNlbm1sIG49InJvb20iIHQ9Ii0xIiB2cz0ia2l0Y2hlbiI+PC9zZW5tbD48c2VubWwgbj0iZGF0YSIgdmQ9ImFiYyI+PC9zZW5tbD48c2VubWwgbj0ib2siIHZiPSJ0cnVlIj48L3Nlbm1sPjwvc2Vuc21sPg=="},
	{false, senml.CSV, false, "dGVtcCwyNTU2OC45OTk5ODgsMjIuMTAwMDAwLGRlZ0MNCg=="},
	{true, senml.MPACK, true, "lIqiYm6mZGV2MTIzomJ0y8BG1cKPXCj2omJ1pGRlZ0OkYnZlcgWhbqR0ZW1woXPLAAAAAAAAAAChdMu/8AAAAAAAAKF1pGRlZ0OidXTLQCQAAAAAAAChdstANhmZmZmZmoOhbqRyb29toXTLv/AAAAAAAACidnOna2l0Y2hlboKhbqRkYXRhonZko2FiY4KhbqJva6J2YsM="},
	{false, senml.LINEP, false, "Zmx1ZmZ5U2VubWwsbj10ZW1wLHU9ZGVnQyB2PTIyLjEgLTEwMDAwMDAwMDAK"},
}

func TestEncode(t *testing.T) {
	value := 22.1
	sum := 0.0
	vb := true
	s := senml.SenML{
		Records: []senml.SenMLRecord{
			senml.SenMLRecord{BaseName: "dev123",
				BaseTime:    -45.67,
				BaseUnit:    "degC",
				BaseVersion: 5,
				Value:       &value, Unit: "degC", Name: "temp", Time: -1.0, UpdateTime: 10.0, Sum: &sum},
			senml.SenMLRecord{StringValue: "kitchen", Name: "room", Time: -1.0},
			senml.SenMLRecord{DataValue: "abc", Name: "data"},
			senml.SenMLRecord{BoolValue: &vb, Name: "ok"},
		},
	}
	options := senml.OutputOptions{Topic: "fluffySenml", PrettyPrint: false}
	for i, vector := range testVectors {

		dataOut, err := senml.Encode(s, vector.format, options)
		if err != nil {
			t.Fail()
		}
		if vector.binary {
			fmt.Print("Test Encode " + strconv.Itoa(i) + " got: ")
			fmt.Println(dataOut)
		} else {
			fmt.Println("Test Encode " + strconv.Itoa(i) + " got: " + string(dataOut))
		}

		if base64.StdEncoding.EncodeToString(dataOut) != vector.value {
			t.Error("Failed Encode for format " + strconv.Itoa(i) + " got: " + base64.StdEncoding.EncodeToString(dataOut))
		}
	}

}

func TestDecode(t *testing.T) {
	for i, vector := range testVectors {
		if vector.testDecode {
			data, err := base64.StdEncoding.DecodeString(vector.value)
			if err != nil {
				t.Fail()
			}

			s, err := senml.Decode(data, vector.format)
			if err != nil {
				t.Fail()
			}

			dataOut, err := senml.Encode(s, senml.JSON, senml.OutputOptions{PrettyPrint: true})
			if err != nil {
				t.Fail()
			}

			fmt.Println("Test Decode " + strconv.Itoa(i) + " got: " + string(dataOut))
		}
	}
}

func TestNormalize(t *testing.T) {
	value := 22.1
	sum := 0.0
	vb := true
	s := senml.SenML{
		Records: []senml.SenMLRecord{
			senml.SenMLRecord{BaseName: "dev123/",
				BaseTime:    897845.67,
				BaseUnit:    "degC",
				BaseVersion: 5,
				Value:       &value, Unit: "degC", Name: "temp", Time: -1.0, UpdateTime: 10.0, Sum: &sum},
			senml.SenMLRecord{StringValue: "kitchen", Name: "room", Time: -1.0},
			senml.SenMLRecord{DataValue: "abc", Name: "data"},
			senml.SenMLRecord{BoolValue: &vb, Name: "ok"},
		},
	}

	n := senml.Normalize(s)

	dataOut, err := senml.Encode(n, senml.JSON, senml.OutputOptions{PrettyPrint: true})
	if err != nil {
		t.Fail()
	}
	fmt.Println("Test Normalize got: " + string(dataOut))

	if base64.StdEncoding.EncodeToString(dataOut) != "WwogIHsiYnZlciI6NSwibiI6ImRldjEyMy90ZW1wIiwidSI6ImRlZ0MiLCJ0Ijo4OTc4NDQuNjcsInV0IjoxMCwidiI6MjIuMSwicyI6MH0sCiAgeyJidmVyIjo1LCJuIjoiZGV2MTIzL3Jvb20iLCJ1IjoiZGVnQyIsInQiOjg5Nzg0NC42NywidnMiOiJraXRjaGVuIn0sCiAgeyJidmVyIjo1LCJuIjoiZGV2MTIzL29rIiwidSI6ImRlZ0MiLCJ0Ijo4OTc4NDUuNjcsInZiIjp0cnVlfQpdCg==" {
		t.Error("Failed Normalize got: " + base64.StdEncoding.EncodeToString(dataOut))
	}
}

func TestBadInput1(t *testing.T) {
	data := []byte(" foo ")
	_ , err := senml.Decode(data, senml.JSON)
	if err == nil {
		t.Fail()
	}
}

func TestBadInput2(t *testing.T) {
	data := []byte(" { \"n\":\"hi\" } ")
	_ , err := senml.Decode(data, senml.JSON)
	if err == nil {
		t.Fail()
	}
}

func TestBadInputNoValue(t *testing.T) {
	data := []byte("  [ { \"n\":\"hi\" } ] ")
	_ , err := senml.Decode(data, senml.JSON)
	if err == nil {
		t.Fail()
	}
}

func TestBadInputNumericName(t *testing.T) {
	data := []byte("  [ { \"n\":\"3hi\", \"v\":1.0 } ] ")
	_ , err := senml.Decode(data, senml.JSON)
	if err == nil {
		t.Fail()
	}
}

func TestInputWeirdName(t *testing.T) {
	data := []byte("  [ { \"n\":\"Az3-:._/\", \"v\":1.0 } ] ")
	_ , err := senml.Decode(data, senml.JSON)
	if err != nil {
		t.Fail()
	}
}

func TestBadInputWeirdName(t *testing.T) {
	data := []byte("  [ { \"n\":\"A;b\", \"v\":1.0 } ] ")
	_ , err := senml.Decode(data, senml.JSON)
	if err == nil {
		t.Fail()
	}
}

func TestBadInputNumericBaseName(t *testing.T) {
	data := []byte("  [ { \"bn\": \"3h\" , \"n\":\"i\" , \"v\":1.0} ] ")
	_ , err := senml.Decode(data, senml.JSON)
	if err == nil {
		t.Fail()
	}
}

func TestBadInputUnknownMtuField(t *testing.T) {
	data := []byte("  [ { \"n\":\"hi\", \"v\":1.0, \"mtu_\":1.0  } ] ")
	_ , err := senml.Decode(data, senml.JSON)
	if err == nil {
		t.Fail()
	}
}

<commit_msg>add tests for more types<commit_after>package senml_test

import (
	"encoding/base64"
	"fmt"
	"github.com/cisco/senml"
	"strconv"
	"testing"
)

func ExampleEncode1() {
	v := 23.1
	s := senml.SenML{
		Records: []senml.SenMLRecord{
			senml.SenMLRecord{Value: &v, Unit: "Cel", Name: "urn:dev:ow:10e2073a01080063"},
		},
	}

	dataOut, err := senml.Encode(s, senml.JSON, senml.OutputOptions{})
	if err != nil {
		fmt.Println("Encode of SenML failed")
	} else {
		fmt.Println(string(dataOut))
	}
	// Output: [{"n":"urn:dev:ow:10e2073a01080063","u":"Cel","v":23.1}]
}

func ExampleEncode2() {
	v1 := 23.5
	v2 := 23.6
	s := senml.SenML{
		Records: []senml.SenMLRecord{
			senml.SenMLRecord{Value: &v1, Unit: "Cel", BaseName: "urn:dev:ow:10e2073a01080063", Time: 1.276020076e+09},
			senml.SenMLRecord{Value: &v2, Unit: "Cel", Time: 1.276020091e+09},
		},
	}

	dataOut, err := senml.Encode(s, senml.JSON, senml.OutputOptions{})
	if err != nil {
		fmt.Println("Encode of SenML failed")
	} else {
		fmt.Println(string(dataOut))
	}
	// Output: [{"bn":"urn:dev:ow:10e2073a01080063","u":"Cel","t":1.276020076e+09,"v":23.5},{"u":"Cel","t":1.276020091e+09,"v":23.6}]
}

type TestVector struct {
	testDecode bool
	format     senml.Format
	binary     bool
	value      string
}

var testVectors = []TestVector{
	{true, senml.JSON, false, "W3siYm4iOiJkZXYxMjMiLCJidCI6LTQ1LjY3LCJidSI6ImRlZ0MiLCJidmVyIjo1LCJuIjoidGVtcCIsInUiOiJkZWdDIiwidCI6LTEsInV0IjoxMCwidiI6MjIuMSwicyI6MH0seyJuIjoicm9vbSIsInQiOi0xLCJ2cyI6ImtpdGNoZW4ifSx7Im4iOiJkYXRhIiwidmQiOiJhYmMifSx7Im4iOiJvayIsInZiIjp0cnVlfV0="},
	{true, senml.CBOR, true, "hKpiYm5mZGV2MTIzYmJ0+8BG1cKPXCj2YmJ1ZGRlZ0NkYnZlcgVhbmR0ZW1wYXP7AAAAAAAAAABhdPu/8AAAAAAAAGF1ZGRlZ0NidXT7QCQAAAAAAABhdvtANhmZmZmZmqNhbmRyb29tYXT7v/AAAAAAAABidnNna2l0Y2hlbqJhbmRkYXRhYnZkY2FiY6JhbmJva2J2YvU="},
	{true, senml.XML, false, "PHNlbnNtbCB4bWxucz0idXJuOmlldGY6cGFyYW1zOnhtbDpuczpzZW5tbCI+PHNlbm1sIGJuPSJkZXYxMjMiIGJ0PSItNDUuNjciIGJ1PSJkZWdDIiBidmVyPSI1IiBuPSJ0ZW1wIiB1PSJkZWdDIiB0PSItMSIgdXQ9IjEwIiB2PSIyMi4xIiBzdW09IjAiPjwvc2VubWw+PHNlbm1sIG49InJvb20iIHQ9Ii0xIiB2cz0ia2l0Y2hlbiI+PC9zZW5tbD48c2VubWwgbj0iZGF0YSIgdmQ9ImFiYyI+PC9zZW5tbD48c2VubWwgbj0ib2siIHZiPSJ0cnVlIj48L3Nlbm1sPjwvc2Vuc21sPg=="},
	{false, senml.CSV, false, "dGVtcCwyNTU2OC45OTk5ODgsMjIuMTAwMDAwLGRlZ0MNCg=="},
	{true, senml.MPACK, true, "lIqiYm6mZGV2MTIzomJ0y8BG1cKPXCj2omJ1pGRlZ0OkYnZlcgWhbqR0ZW1woXPLAAAAAAAAAAChdMu/8AAAAAAAAKF1pGRlZ0OidXTLQCQAAAAAAAChdstANhmZmZmZmoOhbqRyb29toXTLv/AAAAAAAACidnOna2l0Y2hlboKhbqRkYXRhonZko2FiY4KhbqJva6J2YsM="},
	{false, senml.LINEP, false, "Zmx1ZmZ5U2VubWwsbj10ZW1wLHU9ZGVnQyB2PTIyLjEgLTEwMDAwMDAwMDAK"},
}

func TestEncode(t *testing.T) {
	value := 22.1
	sum := 0.0
	vb := true
	s := senml.SenML{
		Records: []senml.SenMLRecord{
			senml.SenMLRecord{BaseName: "dev123",
				BaseTime:    -45.67,
				BaseUnit:    "degC",
				BaseVersion: 5,
				Value:       &value, Unit: "degC", Name: "temp", Time: -1.0, UpdateTime: 10.0, Sum: &sum},
			senml.SenMLRecord{StringValue: "kitchen", Name: "room", Time: -1.0},
			senml.SenMLRecord{DataValue: "abc", Name: "data"},
			senml.SenMLRecord{BoolValue: &vb, Name: "ok"},
		},
	}
	options := senml.OutputOptions{Topic: "fluffySenml", PrettyPrint: false}
	for i, vector := range testVectors {

		dataOut, err := senml.Encode(s, vector.format, options)
		if err != nil {
			t.Fail()
		}
		if vector.binary {
			fmt.Print("Test Encode " + strconv.Itoa(i) + " got: ")
			fmt.Println(dataOut)
		} else {
			fmt.Println("Test Encode " + strconv.Itoa(i) + " got: " + string(dataOut))
		}

		if base64.StdEncoding.EncodeToString(dataOut) != vector.value {
			t.Error("Failed Encode for format " + strconv.Itoa(i) + " got: " + base64.StdEncoding.EncodeToString(dataOut))
		}
	}

}

func TestDecode(t *testing.T) {
	for i, vector := range testVectors {
		if vector.testDecode {
			data, err := base64.StdEncoding.DecodeString(vector.value)
			if err != nil {
				t.Fail()
			}

			s, err := senml.Decode(data, vector.format)
			if err != nil {
				t.Fail()
			}

			dataOut, err := senml.Encode(s, senml.JSON, senml.OutputOptions{PrettyPrint: true})
			if err != nil {
				t.Fail()
			}

			fmt.Println("Test Decode " + strconv.Itoa(i) + " got: " + string(dataOut))
		}
	}
}

func TestNormalize(t *testing.T) {
	value := 22.1
	sum := 0.0
	vb := true
	s := senml.SenML{
		Records: []senml.SenMLRecord{
			senml.SenMLRecord{BaseName: "dev123/",
				BaseTime:    897845.67,
				BaseUnit:    "degC",
				BaseVersion: 5,
				Value:       &value, Unit: "degC", Name: "temp", Time: -1.0, UpdateTime: 10.0, Sum: &sum},
			senml.SenMLRecord{StringValue: "kitchen", Name: "room", Time: -1.0},
			senml.SenMLRecord{DataValue: "abc", Name: "data"},
			senml.SenMLRecord{BoolValue: &vb, Name: "ok"},
		},
	}

	n := senml.Normalize(s)

	dataOut, err := senml.Encode(n, senml.JSON, senml.OutputOptions{PrettyPrint: true})
	if err != nil {
		t.Fail()
	}
	fmt.Println("Test Normalize got: " + string(dataOut))

	if base64.StdEncoding.EncodeToString(dataOut) != "WwogIHsiYnZlciI6NSwibiI6ImRldjEyMy90ZW1wIiwidSI6ImRlZ0MiLCJ0Ijo4OTc4NDQuNjcsInV0IjoxMCwidiI6MjIuMSwicyI6MH0sCiAgeyJidmVyIjo1LCJuIjoiZGV2MTIzL3Jvb20iLCJ1IjoiZGVnQyIsInQiOjg5Nzg0NC42NywidnMiOiJraXRjaGVuIn0sCiAgeyJidmVyIjo1LCJuIjoiZGV2MTIzL29rIiwidSI6ImRlZ0MiLCJ0Ijo4OTc4NDUuNjcsInZiIjp0cnVlfQpdCg==" {
		t.Error("Failed Normalize got: " + base64.StdEncoding.EncodeToString(dataOut))
	}
}

func TestBadInput1(t *testing.T) {
	data := []byte(" foo ")
	_ , err := senml.Decode(data, senml.JSON)
	if err == nil {
		t.Fail()
	}
}

func TestBadInput2(t *testing.T) {
	data := []byte(" { \"n\":\"hi\" } ")
	_ , err := senml.Decode(data, senml.JSON)
	if err == nil {
		t.Fail()
	}
}

func TestBadInputNoValue(t *testing.T) {
	data := []byte("  [ { \"n\":\"hi\" } ] ")
	_ , err := senml.Decode(data, senml.JSON)
	if err == nil {
		t.Fail()
	}
}

func TestBadInputNumericName(t *testing.T) {
	data := []byte("  [ { \"n\":\"3hi\", \"v\":1.0 } ] ")
	_ , err := senml.Decode(data, senml.JSON)
	if err == nil {
		t.Fail()
	}
}

func TestInputWeirdName(t *testing.T) {
	data := []byte("  [ { \"n\":\"Az3-:._/\", \"v\":1.0 } ] ")
	_ , err := senml.Decode(data, senml.JSON)
	if err != nil {
		t.Fail()
	}
}

func TestBadInputWeirdName(t *testing.T) {
	data := []byte("  [ { \"n\":\"A;b\", \"v\":1.0 } ] ")
	_ , err := senml.Decode(data, senml.JSON)
	if err == nil {
		t.Fail()
	}
}

func TestBadInputNumericBaseName(t *testing.T) {
	data := []byte("[ { \"bn\": \"3h\" , \"n\":\"i\" , \"v\":1.0} ] ")
	_ , err := senml.Decode(data, senml.JSON)
	if err == nil {
		t.Fail()
	}
}

func TestBadInputUnknownMtuField(t *testing.T) {
	data := []byte("[ { \"n\":\"hi\", \"v\":1.0, \"mtu_\":1.0  } ] ")
	_ , err := senml.Decode(data, senml.JSON)
	if err == nil {
		t.Fail()
	}
}

func TestInputSumOnly(t *testing.T) {
	data := []byte( "[ { \"n\":\"a\", \"s\":1.0 } ] ")
	_ , err := senml.Decode(data, senml.JSON)
	if err != nil {
		t.Fail()
	}
}

func TestInputBoolean(t *testing.T) {
	data := []byte("[ { \"n\":\"a\", \"vd\": \"aGkgCg\" } ] ")
	_ , err := senml.Decode(data, senml.JSON)
	if err != nil {
		t.Fail()
	}
}

func TestInputData(t *testing.T) {
	data := []byte("  [ { \"n\":\"a\", \"vb\": true } ] ")
	_ , err := senml.Decode(data, senml.JSON)
	if err != nil {
		t.Fail()
	}
}

func TestInputString(t *testing.T) {
	data := []byte("  [ { \"n\":\"a\", \"vs\": \"Hi\" } ] ")
	_ , err := senml.Decode(data, senml.JSON)
	if err != nil {
		t.Fail()
	}
}<|endoftext|>


<commit_before>// Copyright 2014, Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package tabletmanager

import (
	"time"

	"github.com/youtube/vitess/go/vt/hook"
	"github.com/youtube/vitess/go/vt/logutil"
	"github.com/youtube/vitess/go/vt/mysqlctl/tmutils"
	"golang.org/x/net/context"

	querypb "github.com/youtube/vitess/go/vt/proto/query"
	replicationdatapb "github.com/youtube/vitess/go/vt/proto/replicationdata"
	tabletmanagerdatapb "github.com/youtube/vitess/go/vt/proto/tabletmanagerdata"
	topodatapb "github.com/youtube/vitess/go/vt/proto/topodata"
)

// TabletAction is the name of an action. It is a string (and not an
// enum) so we can print it easily.
type TabletAction string

const (
	// TabletActionPing checks a tablet is alive
	TabletActionPing TabletAction = "Ping"

	// TabletActionSleep will sleep for a duration (used for tests)
	TabletActionSleep TabletAction = "Sleep"

	// TabletActionExecuteHook will execute the provided hook remotely
	TabletActionExecuteHook TabletAction = "ExecuteHook"

	// TabletActionSetReadOnly makes the mysql instance read-only
	TabletActionSetReadOnly TabletAction = "SetReadOnly"

	// TabletActionSetReadWrite makes the mysql instance read-write
	TabletActionSetReadWrite TabletAction = "SetReadWrite"

	// TabletActionChangeType changes the type of the tablet
	TabletActionChangeType TabletAction = "ChangeType"

	// TabletActionResetReplication tells the tablet it should
	// reset its replication state
	TabletActionResetReplication TabletAction = "ResetReplication"

	// TabletActionInitMaster tells the tablet it should make itself the new
	// master for the shard it's currently in.
	TabletActionInitMaster TabletAction = "InitMaster"

	// TabletActionPopulateReparentJournal inserts an entry in the
	// _vt.reparent_journal table
	TabletActionPopulateReparentJournal TabletAction = "PopulateReparentJournal"

	// TabletActionInitSlave tells the tablet it should make
	// itself a slave to the provided master at the given position.
	TabletActionInitSlave TabletAction = "InitSlave"

	// TabletActionDemoteMaster tells the current master it's
	// about to not be a master any more, and should go read-only.
	TabletActionDemoteMaster TabletAction = "DemoteMaster"

	// TabletActionPromoteSlaveWhenCaughtUp tells the tablet to wait
	// for a given replication point, and when it reaches it
	// switch to be a master.
	TabletActionPromoteSlaveWhenCaughtUp TabletAction = "PromoteSlaveWhenCaughtUp"

	// TabletActionSlaveWasPromoted tells a tablet this previously slave
	// tablet is now the master. The tablet will update its
	// own topology record.
	TabletActionSlaveWasPromoted TabletAction = "SlaveWasPromoted"

	// TabletActionSetMaster tells a tablet it has a new master.
	// The tablet will reparent to the new master, and wait for
	// the reparent_journal entry.
	TabletActionSetMaster TabletAction = "SetMaster"

	// TabletActionSlaveWasRestarted tells a tablet the mysql
	// master was changed.  The tablet will check it is indeed the
	// case, and update its own topology record.
	TabletActionSlaveWasRestarted TabletAction = "SlaveWasRestarted"

	// TabletActionStopReplicationAndGetStatus will stop replication,
	// and return the current replication status.
	TabletActionStopReplicationAndGetStatus TabletAction = "StopReplicationAndGetStatus"

	// TabletActionPromoteSlave will make this tablet the master
	TabletActionPromoteSlave TabletAction = "PromoteSlave"

	// TabletActionStopSlave will stop MySQL replication.
	TabletActionStopSlave TabletAction = "StopSlave"

	// TabletActionStopSlaveMinimum will stop MySQL replication
	// after it reaches a minimum point.
	TabletActionStopSlaveMinimum TabletAction = "StopSlaveMinimum"

	// TabletActionStartSlave will start MySQL replication.
	TabletActionStartSlave TabletAction = "StartSlave"

	// TabletActionExternallyReparented is sent directly to the new master
	// tablet when it becomes the master. It is functionnaly equivalent
	// to calling "ShardExternallyReparented" on the topology.
	TabletActionExternallyReparented TabletAction = "TabletExternallyReparented"

	// TabletActionMasterPosition returns the current master position
	TabletActionMasterPosition TabletAction = "MasterPosition"

	// TabletActionSlaveStatus returns the current slave status
	TabletActionSlaveStatus TabletAction = "SlaveStatus"

	// TabletActionWaitBLPPosition waits until the slave reaches a
	// replication position in filtered replication
	TabletActionWaitBLPPosition TabletAction = "WaitBlpPosition"

	// TabletActionStopBLP stops filtered replication
	TabletActionStopBLP TabletAction = "StopBlp"

	// TabletActionStartBLP starts filtered replication
	TabletActionStartBLP TabletAction = "StartBlp"

	// TabletActionRunBLPUntil will run filtered replication until
	// it reaches the provided stop position.
	TabletActionRunBLPUntil TabletAction = "RunBlpUntil"

	// TabletActionGetSchema returns the tablet current schema.
	TabletActionGetSchema TabletAction = "GetSchema"

	// TabletActionRefreshState tells the tablet to refresh its
	// tablet record from the topo server.
	TabletActionRefreshState TabletAction = "RefreshState"

	// TabletActionRunHealthCheck tells the tablet to run a health check.
	TabletActionRunHealthCheck TabletAction = "RunHealthCheck"

	// TabletActionIgnoreHealthError sets the regexp for health errors to ignore.
	TabletActionIgnoreHealthError TabletAction = "IgnoreHealthError"

	// TabletActionReloadSchema tells the tablet to reload its schema.
	TabletActionReloadSchema TabletAction = "ReloadSchema"

	// TabletActionPreflightSchema will check a schema change works
	TabletActionPreflightSchema TabletAction = "PreflightSchema"

	// TabletActionApplySchema will actually apply the schema change
	TabletActionApplySchema TabletAction = "ApplySchema"

	// TabletActionExecuteFetchAsDba uses the DBA connection to run queries.
	TabletActionExecuteFetchAsDba TabletAction = "ExecuteFetchAsDba"

	// TabletActionExecuteFetchAsApp uses the App connection to run queries.
	TabletActionExecuteFetchAsApp TabletAction = "ExecuteFetchAsApp"

	// TabletActionGetPermissions returns the mysql permissions set
	TabletActionGetPermissions TabletAction = "GetPermissions"

	// TabletActionGetSlaves returns the current set of mysql
	// replication slaves.
	TabletActionGetSlaves TabletAction = "GetSlaves"

	// TabletActionBackup takes a db backup and stores it into BackupStorage
	TabletActionBackup TabletAction = "Backup"

	// TabletActionRestoreFromBackup wipes the local data and restores anew from backup.
	TabletActionRestoreFromBackup TabletAction = "RestoreFromBackup"
)

// RPCAgent defines the interface implemented by the Agent for RPCs.
// It is useful for RPC implementations to test their full stack.
type RPCAgent interface {
	// RPC calls

	// Various read-only methods

	Ping(ctx context.Context, args string) string

	GetSchema(ctx context.Context, tables, excludeTables []string, includeViews bool) (*tabletmanagerdatapb.SchemaDefinition, error)

	GetPermissions(ctx context.Context) (*tabletmanagerdatapb.Permissions, error)

	// Various read-write methods

	SetReadOnly(ctx context.Context, rdonly bool) error

	ChangeType(ctx context.Context, tabletType topodatapb.TabletType) error

	Sleep(ctx context.Context, duration time.Duration)

	ExecuteHook(ctx context.Context, hk *hook.Hook) *hook.HookResult

	RefreshState(ctx context.Context) error

	RunHealthCheck(ctx context.Context)

	IgnoreHealthError(ctx context.Context, pattern string) error

	ReloadSchema(ctx context.Context, waitPosition string) error

	PreflightSchema(ctx context.Context, changes []string) ([]*tabletmanagerdatapb.SchemaChangeResult, error)

	ApplySchema(ctx context.Context, change *tmutils.SchemaChange) (*tabletmanagerdatapb.SchemaChangeResult, error)

	ExecuteFetchAsDba(ctx context.Context, query []byte, dbName string, maxrows int, disableBinlogs bool, reloadSchema bool) (*querypb.QueryResult, error)

	ExecuteFetchAsApp(ctx context.Context, query []byte, maxrows int) (*querypb.QueryResult, error)

	// Replication related methods

	SlaveStatus(ctx context.Context) (*replicationdatapb.Status, error)

	MasterPosition(ctx context.Context) (string, error)

	StopSlave(ctx context.Context) error

	StopSlaveMinimum(ctx context.Context, position string, waitTime time.Duration) (string, error)

	StartSlave(ctx context.Context) error

	TabletExternallyReparented(ctx context.Context, externalID string) error

	GetSlaves(ctx context.Context) ([]string, error)

	WaitBlpPosition(ctx context.Context, blpPosition *tabletmanagerdatapb.BlpPosition, waitTime time.Duration) error

	StopBlp(ctx context.Context) ([]*tabletmanagerdatapb.BlpPosition, error)

	StartBlp(ctx context.Context) error

	RunBlpUntil(ctx context.Context, bpl []*tabletmanagerdatapb.BlpPosition, waitTime time.Duration) (string, error)

	// Reparenting related functions

	ResetReplication(ctx context.Context) error

	InitMaster(ctx context.Context) (string, error)

	PopulateReparentJournal(ctx context.Context, timeCreatedNS int64, actionName string, masterAlias *topodatapb.TabletAlias, pos string) error

	InitSlave(ctx context.Context, parent *topodatapb.TabletAlias, replicationPosition string, timeCreatedNS int64) error

	DemoteMaster(ctx context.Context) (string, error)

	PromoteSlaveWhenCaughtUp(ctx context.Context, replicationPosition string) (string, error)

	SlaveWasPromoted(ctx context.Context) error

	SetMaster(ctx context.Context, parent *topodatapb.TabletAlias, timeCreatedNS int64, forceStartSlave bool) error

	SlaveWasRestarted(ctx context.Context, parent *topodatapb.TabletAlias) error

	StopReplicationAndGetStatus(ctx context.Context) (*replicationdatapb.Status, error)

	PromoteSlave(ctx context.Context) (string, error)

	// Backup / restore related methods

	Backup(ctx context.Context, concurrency int, logger logutil.Logger) error

	RestoreFromBackup(ctx context.Context, logger logutil.Logger) error

	// HandleRPCPanic is to be called in a defer statement in each
	// RPC input point.
	HandleRPCPanic(ctx context.Context, name string, args, reply interface{}, verbose bool, err *error)
}
<commit_msg>Retiring TabletAction.<commit_after>// Copyright 2014, Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package tabletmanager

import (
	"time"

	"github.com/youtube/vitess/go/vt/hook"
	"github.com/youtube/vitess/go/vt/logutil"
	"github.com/youtube/vitess/go/vt/mysqlctl/tmutils"
	"golang.org/x/net/context"

	querypb "github.com/youtube/vitess/go/vt/proto/query"
	replicationdatapb "github.com/youtube/vitess/go/vt/proto/replicationdata"
	tabletmanagerdatapb "github.com/youtube/vitess/go/vt/proto/tabletmanagerdata"
	topodatapb "github.com/youtube/vitess/go/vt/proto/topodata"
)

// RPCAgent defines the interface implemented by the Agent for RPCs.
// It is useful for RPC implementations to test their full stack.
type RPCAgent interface {
	// RPC calls

	// Various read-only methods

	Ping(ctx context.Context, args string) string

	GetSchema(ctx context.Context, tables, excludeTables []string, includeViews bool) (*tabletmanagerdatapb.SchemaDefinition, error)

	GetPermissions(ctx context.Context) (*tabletmanagerdatapb.Permissions, error)

	// Various read-write methods

	SetReadOnly(ctx context.Context, rdonly bool) error

	ChangeType(ctx context.Context, tabletType topodatapb.TabletType) error

	Sleep(ctx context.Context, duration time.Duration)

	ExecuteHook(ctx context.Context, hk *hook.Hook) *hook.HookResult

	RefreshState(ctx context.Context) error

	RunHealthCheck(ctx context.Context)

	IgnoreHealthError(ctx context.Context, pattern string) error

	ReloadSchema(ctx context.Context, waitPosition string) error

	PreflightSchema(ctx context.Context, changes []string) ([]*tabletmanagerdatapb.SchemaChangeResult, error)

	ApplySchema(ctx context.Context, change *tmutils.SchemaChange) (*tabletmanagerdatapb.SchemaChangeResult, error)

	ExecuteFetchAsDba(ctx context.Context, query []byte, dbName string, maxrows int, disableBinlogs bool, reloadSchema bool) (*querypb.QueryResult, error)

	ExecuteFetchAsApp(ctx context.Context, query []byte, maxrows int) (*querypb.QueryResult, error)

	// Replication related methods

	SlaveStatus(ctx context.Context) (*replicationdatapb.Status, error)

	MasterPosition(ctx context.Context) (string, error)

	StopSlave(ctx context.Context) error

	StopSlaveMinimum(ctx context.Context, position string, waitTime time.Duration) (string, error)

	StartSlave(ctx context.Context) error

	TabletExternallyReparented(ctx context.Context, externalID string) error

	GetSlaves(ctx context.Context) ([]string, error)

	WaitBlpPosition(ctx context.Context, blpPosition *tabletmanagerdatapb.BlpPosition, waitTime time.Duration) error

	StopBlp(ctx context.Context) ([]*tabletmanagerdatapb.BlpPosition, error)

	StartBlp(ctx context.Context) error

	RunBlpUntil(ctx context.Context, bpl []*tabletmanagerdatapb.BlpPosition, waitTime time.Duration) (string, error)

	// Reparenting related functions

	ResetReplication(ctx context.Context) error

	InitMaster(ctx context.Context) (string, error)

	PopulateReparentJournal(ctx context.Context, timeCreatedNS int64, actionName string, masterAlias *topodatapb.TabletAlias, pos string) error

	InitSlave(ctx context.Context, parent *topodatapb.TabletAlias, replicationPosition string, timeCreatedNS int64) error

	DemoteMaster(ctx context.Context) (string, error)

	PromoteSlaveWhenCaughtUp(ctx context.Context, replicationPosition string) (string, error)

	SlaveWasPromoted(ctx context.Context) error

	SetMaster(ctx context.Context, parent *topodatapb.TabletAlias, timeCreatedNS int64, forceStartSlave bool) error

	SlaveWasRestarted(ctx context.Context, parent *topodatapb.TabletAlias) error

	StopReplicationAndGetStatus(ctx context.Context) (*replicationdatapb.Status, error)

	PromoteSlave(ctx context.Context) (string, error)

	// Backup / restore related methods

	Backup(ctx context.Context, concurrency int, logger logutil.Logger) error

	RestoreFromBackup(ctx context.Context, logger logutil.Logger) error

	// HandleRPCPanic is to be called in a defer statement in each
	// RPC input point.
	HandleRPCPanic(ctx context.Context, name string, args, reply interface{}, verbose bool, err *error)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	goflags "github.com/jessevdk/go-flags" // rename import to `goflags` (file scope) so we can use `var flags` (package scope)
	"os"
)

// flags holds the flags and arguments given to this process
// never write to this struct from outside this file
// flags is filled with defaults from the tags and the initFlags function
var flags struct {
	Verbose     bool   `short:"v" long:"verbose" description:"Show verbose debug information"`
	UnixSocket  string `long:"unix-socket" description:"Serve HTTP over unix socket"`
	PIDFilename string `long:"pid-file" description:"PID file for this process" default:"./npserver.pid"`
	HTTPFiles   string `long:"http-files" description:"location for the http files" default:"./http-files/"`
}

// initFlags parses the given flags.
// when the user asks for help (-h or --help): the application exists with status 0
// when unexpected flags is given: the application exits with status 1
func initFlags() {
	args, err := goflags.Parse(&flags)
	if err != nil {
		// assert the err to be a flags.Error
		flagError := err.(*goflags.Error)
		if flagError.Type == goflags.ErrHelp {
			// user asked for help on flags.
			// program can exit successfully
			os.Exit(0)
		}
		if flagError.Type == goflags.ErrUnknownFlag {
			fmt.Println("Use --help to view all available options.")
			os.Exit(1)
		}
		fmt.Printf("Error parsing flags: %s\n", err)
		os.Exit(1)
	}

	// check for unexpected arguments
	// when an unexpected argument is given: the application exists with status 1
	if len(args) > 0 {
		fmt.Printf("Unknown argument '%s'.\n", args[0])
		os.Exit(1)
	}

	//++ do checks (cant set unix-socket-filename when unix-socket is not requested)
}
<commit_msg>also removed PIDFilename from npserver's flags.go<commit_after>package main

import (
	"fmt"
	goflags "github.com/jessevdk/go-flags" // rename import to `goflags` (file scope) so we can use `var flags` (package scope)
	"os"
)

// flags holds the flags and arguments given to this process
// never write to this struct from outside this file
// flags is filled with defaults from the tags and the initFlags function
var flags struct {
	Verbose    bool   `short:"v" long:"verbose" description:"Show verbose debug information"`
	UnixSocket string `long:"unix-socket" description:"Serve HTTP over unix socket"`
	HTTPFiles  string `long:"http-files" description:"location for the http files" default:"./http-files/"`
}

// initFlags parses the given flags.
// when the user asks for help (-h or --help): the application exists with status 0
// when unexpected flags is given: the application exits with status 1
func initFlags() {
	args, err := goflags.Parse(&flags)
	if err != nil {
		// assert the err to be a flags.Error
		flagError := err.(*goflags.Error)
		if flagError.Type == goflags.ErrHelp {
			// user asked for help on flags.
			// program can exit successfully
			os.Exit(0)
		}
		if flagError.Type == goflags.ErrUnknownFlag {
			fmt.Println("Use --help to view all available options.")
			os.Exit(1)
		}
		fmt.Printf("Error parsing flags: %s\n", err)
		os.Exit(1)
	}

	// check for unexpected arguments
	// when an unexpected argument is given: the application exists with status 1
	if len(args) > 0 {
		fmt.Printf("Unknown argument '%s'.\n", args[0])
		os.Exit(1)
	}

	//++ do checks (cant set unix-socket-filename when unix-socket is not requested)
}
<|endoftext|>


<commit_before>// Copyright 2016 The etcd 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 clientv3

import (
	"sync"

	pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
	"golang.org/x/net/context"
)

// Txn is the interface that wraps mini-transactions.
//
//	 Tx.If(
//	  Compare(Value(k1), ">", v1),
//	  Compare(Version(k1), "=", 2)
//	 ).Then(
//	  OpPut(k2,v2), OpPut(k3,v3)
//	 ).Else(
//	  OpPut(k4,v4), OpPut(k5,v5)
//	 ).Commit()
//
type Txn interface {
	// If takes a list of comparison. If all comparisons passed in succeed,
	// the operations passed into Then() will be executed. Or the operations
	// passed into Else() will be executed.
	If(cs ...Cmp) Txn

	// Then takes a list of operations. The Ops list will be executed, if the
	// comparisons passed in If() succeed.
	Then(ops ...Op) Txn

	// Else takes a list of operations. The Ops list will be executed, if the
	// comparisons passed in If() fail.
	Else(ops ...Op) Txn

	// Commit tries to commit the transaction.
	Commit() (*TxnResponse, error)

	// TODO: add a Do for shortcut the txn without any condition?
}

type txn struct {
	kv  *kv
	ctx context.Context

	mu    sync.Mutex
	cif   bool
	cthen bool
	celse bool

	isWrite bool

	cmps []*pb.Compare

	sus []*pb.RequestOp
	fas []*pb.RequestOp
}

func (txn *txn) If(cs ...Cmp) Txn {
	txn.mu.Lock()
	defer txn.mu.Unlock()

	if txn.cif {
		panic("cannot call If twice!")
	}

	if txn.cthen {
		panic("cannot call If after Then!")
	}

	if txn.celse {
		panic("cannot call If after Else!")
	}

	txn.cif = true

	for i := range cs {
		txn.cmps = append(txn.cmps, (*pb.Compare)(&cs[i]))
	}

	return txn
}

func (txn *txn) Then(ops ...Op) Txn {
	txn.mu.Lock()
	defer txn.mu.Unlock()

	if txn.cthen {
		panic("cannot call Then twice!")
	}
	if txn.celse {
		panic("cannot call Then after Else!")
	}

	txn.cthen = true

	for _, op := range ops {
		txn.isWrite = txn.isWrite || op.isWrite()
		txn.sus = append(txn.sus, op.toRequestOp())
	}

	return txn
}

func (txn *txn) Else(ops ...Op) Txn {
	txn.mu.Lock()
	defer txn.mu.Unlock()

	if txn.celse {
		panic("cannot call Else twice!")
	}

	txn.celse = true

	for _, op := range ops {
		txn.isWrite = txn.isWrite || op.isWrite()
		txn.fas = append(txn.fas, op.toRequestOp())
	}

	return txn
}

func (txn *txn) Commit() (*TxnResponse, error) {
	txn.mu.Lock()
	defer txn.mu.Unlock()
	for {
		resp, err := txn.commit()
		if err == nil {
			return resp, err
		}
		if isHaltErr(txn.ctx, err) {
			return nil, toErr(txn.ctx, err)
		}
		if txn.isWrite {
			return nil, toErr(txn.ctx, err)
		}
	}
}

func (txn *txn) commit() (*TxnResponse, error) {
	r := &pb.TxnRequest{Compare: txn.cmps, Success: txn.sus, Failure: txn.fas}
	resp, err := txn.kv.remote.Txn(txn.ctx, r)
	if err != nil {
		return nil, err
	}
	return (*TxnResponse)(resp), nil
}
<commit_msg>clientv3: set failfast=false on read-only txns<commit_after>// Copyright 2016 The etcd 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 clientv3

import (
	"sync"

	pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
	"golang.org/x/net/context"
	"google.golang.org/grpc"
)

// Txn is the interface that wraps mini-transactions.
//
//	 Tx.If(
//	  Compare(Value(k1), ">", v1),
//	  Compare(Version(k1), "=", 2)
//	 ).Then(
//	  OpPut(k2,v2), OpPut(k3,v3)
//	 ).Else(
//	  OpPut(k4,v4), OpPut(k5,v5)
//	 ).Commit()
//
type Txn interface {
	// If takes a list of comparison. If all comparisons passed in succeed,
	// the operations passed into Then() will be executed. Or the operations
	// passed into Else() will be executed.
	If(cs ...Cmp) Txn

	// Then takes a list of operations. The Ops list will be executed, if the
	// comparisons passed in If() succeed.
	Then(ops ...Op) Txn

	// Else takes a list of operations. The Ops list will be executed, if the
	// comparisons passed in If() fail.
	Else(ops ...Op) Txn

	// Commit tries to commit the transaction.
	Commit() (*TxnResponse, error)

	// TODO: add a Do for shortcut the txn without any condition?
}

type txn struct {
	kv  *kv
	ctx context.Context

	mu    sync.Mutex
	cif   bool
	cthen bool
	celse bool

	isWrite bool

	cmps []*pb.Compare

	sus []*pb.RequestOp
	fas []*pb.RequestOp
}

func (txn *txn) If(cs ...Cmp) Txn {
	txn.mu.Lock()
	defer txn.mu.Unlock()

	if txn.cif {
		panic("cannot call If twice!")
	}

	if txn.cthen {
		panic("cannot call If after Then!")
	}

	if txn.celse {
		panic("cannot call If after Else!")
	}

	txn.cif = true

	for i := range cs {
		txn.cmps = append(txn.cmps, (*pb.Compare)(&cs[i]))
	}

	return txn
}

func (txn *txn) Then(ops ...Op) Txn {
	txn.mu.Lock()
	defer txn.mu.Unlock()

	if txn.cthen {
		panic("cannot call Then twice!")
	}
	if txn.celse {
		panic("cannot call Then after Else!")
	}

	txn.cthen = true

	for _, op := range ops {
		txn.isWrite = txn.isWrite || op.isWrite()
		txn.sus = append(txn.sus, op.toRequestOp())
	}

	return txn
}

func (txn *txn) Else(ops ...Op) Txn {
	txn.mu.Lock()
	defer txn.mu.Unlock()

	if txn.celse {
		panic("cannot call Else twice!")
	}

	txn.celse = true

	for _, op := range ops {
		txn.isWrite = txn.isWrite || op.isWrite()
		txn.fas = append(txn.fas, op.toRequestOp())
	}

	return txn
}

func (txn *txn) Commit() (*TxnResponse, error) {
	txn.mu.Lock()
	defer txn.mu.Unlock()
	for {
		resp, err := txn.commit()
		if err == nil {
			return resp, err
		}
		if isHaltErr(txn.ctx, err) {
			return nil, toErr(txn.ctx, err)
		}
		if txn.isWrite {
			return nil, toErr(txn.ctx, err)
		}
	}
}

func (txn *txn) commit() (*TxnResponse, error) {
	r := &pb.TxnRequest{Compare: txn.cmps, Success: txn.sus, Failure: txn.fas}

	var opts []grpc.CallOption
	if !txn.isWrite {
		opts = []grpc.CallOption{grpc.FailFast(false)}
	}
	resp, err := txn.kv.remote.Txn(txn.ctx, r, opts...)
	if err != nil {
		return nil, err
	}
	return (*TxnResponse)(resp), nil
}
<|endoftext|>