<commit_before>package main

import (
	"bytes"
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"io/ioutil"
	"net/http"
	"regexp"
	"sort"
	"strconv"
	"strings"

	"github.com/bwmarrin/discordgo"
	redis "gopkg.in/redis.v5"
)

///////////////////////////////////////////////////////////////////////////////
// CRBot is a call-and-response bot. It is taught to learn a call and
// response. When it sees the call, it replays the response. Look at the ?help
// documentation for a full list of commands.
//
// Licensed under MIT license, at project root.
///////////////////////////////////////////////////////////////////////////////

func main() {
	var filename = flag.String("filename", "secret.json", "Filename of configuration json")
	flag.Parse()

	redisClient := redis.NewClient(&redis.Options{
		Addr:     "localhost:6379",
		Password: "", // no password set
		DB:       0,  // use default DB
	})

	_, err := redisClient.Ping().Result()
	if err != nil {
		fatal("Could not ping Redis", err)
	}

	secret, e := ParseSecret(*filename)
	if e != nil {
		fatal("Secret parsing failed", e)
	}

	discord, err := discordgo.New("Bot " + secret.BotToken)
	if err != nil {
		fatal("Error initializing Discord client library", e)
	}

	discord.AddHandler(getHandleMessage(redisClient))

	if err := discord.Open(); err != nil {
		fatal("Error opening Discord session", err)
	}

	fmt.Println("CRBot running.")

	<-make(chan interface{})
}

///////////////////////////////////////////////////////////////////////////////
// Utility methods
///////////////////////////////////////////////////////////////////////////////

// fatal handles a non-recoverable error.
func fatal(msg string, err error) {
	panic(msg + ": " + err.Error())
}

func info(msg string, err error) {
	fmt.Printf(msg+": %v\n", err.Error())
}

///////////////////////////////////////////////////////////////////////////////
// Constants
///////////////////////////////////////////////////////////////////////////////

const (
	Type_None = iota
	Type_Unrecognized
	Type_Help
	Type_Learn
	Type_Custom
	Type_List

	Name_Help  = "?help"
	Name_Learn = "?learn"
	Name_List  = "?list"

	Redis_Hash = "crbot-custom-commands"
)

// TypeToString maps builtin types to their string names.
var TypeToString map[int]string = map[int]string{
	Type_Help:  Name_Help,
	Type_Learn: Name_Learn,
	Type_List:  Name_List,
}

// StringToType maps builtin names to their types.
var StringToType map[string]int = map[string]int{
	Name_Help:  Type_Help,
	Name_Learn: Type_Learn,
	Name_List:  Type_List,
}

// Tries to get a value from s and ?s
func getUserStringToType(s string) int {
	if t, ok := StringToType[s]; ok {
		return t
	} else if t, ok := StringToType["?"+s]; ok {
		return t
	}
	return Type_None
}

///////////////////////////////////////////////////////////////////////////////
// Configuration handling
///////////////////////////////////////////////////////////////////////////////

// Secret holds the serialized bot token.
type Secret struct {
	BotToken string `json:"bot_token"`
}

// ParseSecret reads the config from the given filename.
func ParseSecret(filename string) (*Secret, error) {
	f, e := ioutil.ReadFile(filename)
	if e != nil {
		return nil, e
	}
	var config Secret
	e = json.Unmarshal(f, &config)
	return &config, e
}

///////////////////////////////////////////////////////////////////////////////
// Controller methods
///////////////////////////////////////////////////////////////////////////////

// getHandleMessage returns a function to parse and handle incoming messages.
func getHandleMessage(redisClient *redis.Client) func(*discordgo.Session, *discordgo.MessageCreate) {
	return func(s *discordgo.Session, m *discordgo.MessageCreate) {

		// Never reply to a bot.
		if m.Author.Bot {
			return
		}

		command, err := parseCommand(redisClient, m.Content)
		if err != nil {
			info("Error processing command", err)
		}

		switch command.Type {
		case Type_Help:
			sendHelp(s, m.ChannelID, command)
		case Type_Learn:
			sendLearn(redisClient, s, m.ChannelID, command)
		case Type_Custom:
			sendCustom(redisClient, s, m.ChannelID, command)
		case Type_List:
			sendList(redisClient, s, m.ChannelID, command)
		}
	}
}

///////////////////////////////////////////////////////////////////////////////
// User message parsing
///////////////////////////////////////////////////////////////////////////////

// HelpData holds data for Help commands.
type HelpData struct {
	Type int
}

type LearnData struct {
	CallOpen bool
	Call     string
	Response string
}

type CustomData struct {
	Call string
	Args string
}

type Command struct {
	Type   int
	Help   *HelpData
	Learn  *LearnData
	Custom *CustomData
}

// Parses the raw text string from the user. Returns an executable command.
func parseCommand(redisClient *redis.Client, content string) (*Command, error) {
	if !strings.HasPrefix(content, "?") {
		return &Command{
			Type: Type_None,
		}, nil
	}
	splitContent := strings.Split(content, " ")

	// Parse builtins.
	switch splitContent[0] {
	case Name_Help:
		return parseHelp(splitContent)
	case Name_Learn:
		return parseLearn(redisClient, splitContent)
	case Name_List:
		return &Command{Type: Type_List}, nil
	}

	// See if it's a custom command.
	if redisClient.HExists(Redis_Hash, splitContent[0][1:]).Val() {
		return parseCustom(redisClient, splitContent)
	}

	// No such command!
	return &Command{
		Type: Type_Unrecognized,
	}, nil
}

func parseHelp(splitContent []string) (*Command, error) {
	if splitContent[0] != Name_Help {
		fatal("parseHelp called with non-help command", errors.New("wat"))
	}
	userType := Type_Unrecognized
	if len(splitContent) > 1 {
		userType = getUserStringToType(splitContent[1])
	}
	return &Command{
		Type: Type_Help,
		Help: &HelpData{
			Type: userType,
		},
	}, nil
}

func parseLearn(redisClient *redis.Client, splitContent []string) (*Command, error) {
	if splitContent[0] != Name_Learn {
		fatal("parseLearn called with non-learn command", errors.New("wat"))
	}

	callRegexp := regexp.MustCompile("(?s)^[[:alnum:]].*$")
	responseRegexp := regexp.MustCompile("(?s)^[^/?!].*$")

	// Show help when not enough data is present, or malicious data is present.
	if len(splitContent) < 3 || !callRegexp.MatchString(splitContent[1]) || !responseRegexp.MatchString(splitContent[2]) {
		return &Command{
			Type: Type_Help,
			Help: &HelpData{
				Type: Type_Learn,
			},
		}, nil
	}

	// Don't overwrite old or builtin commands.
	if redisClient.HExists(Redis_Hash, splitContent[1]).Val() || getUserStringToType(splitContent[1]) != Type_None {
		return &Command{
			Type: Type_Learn,
			Learn: &LearnData{
				CallOpen: false,
				Call:     splitContent[1],
			},
		}, nil
	}

	// Everything is good.
	response := strings.Join(splitContent[2:], " ")
	return &Command{
		Type: Type_Learn,
		Learn: &LearnData{
			CallOpen: true,
			Call:     splitContent[1],
			Response: response,
		},
	}, nil
}

func parseCustom(redisClient *redis.Client, splitContent []string) (*Command, error) {
	if !redisClient.HExists(Redis_Hash, splitContent[0][1:]).Val() {
		fatal("parseCustom called with non-custom command", errors.New("wat"))
	}
	return &Command{
		Type: Type_Custom,
		Custom: &CustomData{
			Call: splitContent[0][1:],
			Args: strings.Join(splitContent[1:], " "),
		},
	}, nil
}

///////////////////////////////////////////////////////////////////////////////
// User-visible messages
///////////////////////////////////////////////////////////////////////////////

const (
	MsgCustomNeedsArgs   = "This command takes args. Please type `?command <more text>` instead of `?command`"
	MsgDefaultHelp       = "Type `?help` for this message, `?list` to list all commands, or `?help <command>` to get help for a particular command."
	MsgGistAddress       = "The list of commands is here: "
	MsgGistPostFail      = "Unable to connect to Gist service. Give it a few minutes and try again"
	MsgGistResponseFail  = "Failure reading response from Gist service"
	MsgGistSerializeFail = "Unable to serialize Gist"
	MsgGistStatusCode    = "Failed to upload Gist :("
	MsgGistUrlFail       = "Failed getting url from Gist service"
	MsgHelpHelp          = "You're probably right. I probably didn't think of this case."
	MsgHelpLearn         = "Type `?learn <call> <the response the bot should read>`. When you type `?call`, the bot will reply with the response.\n\nThe first character of the call and response must be alphanumeric\n\nUse $1 in the response to substitute all arguments"
	MsgHelpList          = "Type `?list` to get the URL of a Gist with all builtin and learned commands"
	MsgLearnFail         = "I already know ?%s"
	MsgLearnSuccess      = "Learned about %s"
	MsgListBuiltins      = "List of builtins:"
	MsgListCustom        = "List of learned commands:"
)

///////////////////////////////////////////////////////////////////////////////
// Response
///////////////////////////////////////////////////////////////////////////////

func sendHelp(s *discordgo.Session, channel string, command *Command) {
	if command.Help == nil {
		fatal("Incorrectly generated help command", errors.New("wat"))
	}
	switch command.Help.Type {
	default:
		if _, err := s.ChannelMessageSend(channel, MsgDefaultHelp); err != nil {
			info("Failed to send default help message", err)
		}
	case Type_Help:
		s.ChannelMessageSend(channel, MsgHelpHelp)
	case Type_Learn:
		s.ChannelMessageSend(channel, MsgHelpLearn)
	case Type_List:
		s.ChannelMessageSend(channel, MsgHelpList)
	}
}

func sendLearn(redisClient *redis.Client, s *discordgo.Session, channel string, command *Command) {
	if command.Learn == nil {
		fatal("Incorrectly generated learn command", errors.New("wat"))
	}
	if !command.Learn.CallOpen {
		s.ChannelMessageSend(channel, fmt.Sprintf(MsgLearnFail, command.Learn.Call))
		return
	}

	// Teach the command.
	if redisClient.HExists(Redis_Hash, command.Learn.Call).Val() {
		fatal("Collision when adding a call for "+command.Learn.Call, errors.New("wat"))
	}
	redisClient.HSet(Redis_Hash, command.Learn.Call, command.Learn.Response)

	// Send ack.
	s.ChannelMessageSend(channel, fmt.Sprintf(MsgLearnSuccess, command.Learn.Call))
}

func sendCustom(redisClient *redis.Client, s *discordgo.Session, channel string, command *Command) {
	if command.Custom == nil {
		fatal("Incorrectly generated learn command", errors.New("wat"))
	}

	if !redisClient.HExists(Redis_Hash, command.Custom.Call).Val() {
		fatal("Accidentally found a mismatched call/response pair", errors.New("Call response mismatch"))
	}

	response := redisClient.HGet(Redis_Hash, command.Custom.Call).Val()

	if strings.Contains(response, "$1") {
		if command.Custom.Args == "" {
			s.ChannelMessageSend(channel, MsgCustomNeedsArgs)
			return
		}
		response = strings.Replace(response, "$1", command.Custom.Args, 1)
	}
	s.ChannelMessageSend(channel, response)
}

func sendList(redisClient *redis.Client, s *discordgo.Session, channel string, command *Command) {
	builtins := []string{}
	for name := range StringToType {
		builtins = append(builtins, name)
	}

	custom := []string{}
	for name := range redisClient.HGetAll(Redis_Hash).Val() {
		custom = append(custom, name)
	}

	sort.Strings(builtins)
	sort.Strings(custom)

	var buffer bytes.Buffer
	buffer.WriteString(MsgListBuiltins)
	buffer.WriteString("\n")
	for _, name := range builtins {
		buffer.WriteString(" - ")
		buffer.WriteString(name)
		buffer.WriteString("\n")
	}

	buffer.WriteString("\n")

	buffer.WriteString(MsgListCustom)
	buffer.WriteString("\n")
	for _, name := range custom {
		buffer.WriteString(" - ?")
		buffer.WriteString(name)
		buffer.WriteString("\n")
	}

	url, err := uploadCommandList(buffer.String())
	if err != nil {
		s.ChannelMessageSend(channel, err.Error())
		return
	}
	s.ChannelMessageSend(channel, MsgGistAddress+": "+url)
}

///////////////////////////////////////////////////////////////////////////////
// Gist handling
///////////////////////////////////////////////////////////////////////////////
type Gist struct {
	Description string           `json:"description"`
	Public      bool             `json:"public"`
	Files       map[string]*File `json:"files"`
}

// A file represents the contents of a Gist.
type File struct {
	Content string `json:"content"`
}

// simpleGist returns a Gist object with just the given contents.
func simpleGist(contents string) *Gist {
	return &Gist{
		Public:      false,
		Description: "CRBot command list",
		Files: map[string]*File{
			"commands": &File{
				Content: contents,
			},
		},
	}
}

func uploadCommandList(contents string) (string, error) {
	gist := simpleGist(contents)
	serializedGist, err := json.Marshal(gist)
	if err != nil {
		info("Error marshalling gist", err)
		return "", errors.New(MsgGistSerializeFail)
	}
	response, err := http.Post(
		"https://api.github.com/gists", "application/json", bytes.NewBuffer(serializedGist))
	if err != nil {
		info("Error POSTing gist", err)
		return "", errors.New(MsgGistPostFail)
	} else if response.StatusCode != 201 {
		info("Bad status code", errors.New("Code: "+strconv.Itoa(response.StatusCode)))
		return "", errors.New(MsgGistStatusCode)
	}

	responseMap := map[string]interface{}{}
	if err := json.NewDecoder(response.Body).Decode(&responseMap); err != nil {
		info("Error reading gist response", err)
		return "", errors.New(MsgGistResponseFail)
	}

	if finalUrl, ok := responseMap["html_url"]; ok {
		return finalUrl.(string), nil
	}
	return "", errors.New(MsgGistUrlFail)
}
<commit_msg>Updates the help docs for learn commands to reflect validation relaxation<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"io/ioutil"
	"net/http"
	"regexp"
	"sort"
	"strconv"
	"strings"

	"github.com/bwmarrin/discordgo"
	redis "gopkg.in/redis.v5"
)

///////////////////////////////////////////////////////////////////////////////
// CRBot is a call-and-response bot. It is taught to learn a call and
// response. When it sees the call, it replays the response. Look at the ?help
// documentation for a full list of commands.
//
// Licensed under MIT license, at project root.
///////////////////////////////////////////////////////////////////////////////

func main() {
	var filename = flag.String("filename", "secret.json", "Filename of configuration json")
	flag.Parse()

	redisClient := redis.NewClient(&redis.Options{
		Addr:     "localhost:6379",
		Password: "", // no password set
		DB:       0,  // use default DB
	})

	_, err := redisClient.Ping().Result()
	if err != nil {
		fatal("Could not ping Redis", err)
	}

	secret, e := ParseSecret(*filename)
	if e != nil {
		fatal("Secret parsing failed", e)
	}

	discord, err := discordgo.New("Bot " + secret.BotToken)
	if err != nil {
		fatal("Error initializing Discord client library", e)
	}

	discord.AddHandler(getHandleMessage(redisClient))

	if err := discord.Open(); err != nil {
		fatal("Error opening Discord session", err)
	}

	fmt.Println("CRBot running.")

	<-make(chan interface{})
}

///////////////////////////////////////////////////////////////////////////////
// Utility methods
///////////////////////////////////////////////////////////////////////////////

// fatal handles a non-recoverable error.
func fatal(msg string, err error) {
	panic(msg + ": " + err.Error())
}

func info(msg string, err error) {
	fmt.Printf(msg+": %v\n", err.Error())
}

///////////////////////////////////////////////////////////////////////////////
// Constants
///////////////////////////////////////////////////////////////////////////////

const (
	Type_None = iota
	Type_Unrecognized
	Type_Help
	Type_Learn
	Type_Custom
	Type_List

	Name_Help  = "?help"
	Name_Learn = "?learn"
	Name_List  = "?list"

	Redis_Hash = "crbot-custom-commands"
)

// TypeToString maps builtin types to their string names.
var TypeToString map[int]string = map[int]string{
	Type_Help:  Name_Help,
	Type_Learn: Name_Learn,
	Type_List:  Name_List,
}

// StringToType maps builtin names to their types.
var StringToType map[string]int = map[string]int{
	Name_Help:  Type_Help,
	Name_Learn: Type_Learn,
	Name_List:  Type_List,
}

// Tries to get a value from s and ?s
func getUserStringToType(s string) int {
	if t, ok := StringToType[s]; ok {
		return t
	} else if t, ok := StringToType["?"+s]; ok {
		return t
	}
	return Type_None
}

///////////////////////////////////////////////////////////////////////////////
// Configuration handling
///////////////////////////////////////////////////////////////////////////////

// Secret holds the serialized bot token.
type Secret struct {
	BotToken string `json:"bot_token"`
}

// ParseSecret reads the config from the given filename.
func ParseSecret(filename string) (*Secret, error) {
	f, e := ioutil.ReadFile(filename)
	if e != nil {
		return nil, e
	}
	var config Secret
	e = json.Unmarshal(f, &config)
	return &config, e
}

///////////////////////////////////////////////////////////////////////////////
// Controller methods
///////////////////////////////////////////////////////////////////////////////

// getHandleMessage returns a function to parse and handle incoming messages.
func getHandleMessage(redisClient *redis.Client) func(*discordgo.Session, *discordgo.MessageCreate) {
	return func(s *discordgo.Session, m *discordgo.MessageCreate) {

		// Never reply to a bot.
		if m.Author.Bot {
			return
		}

		command, err := parseCommand(redisClient, m.Content)
		if err != nil {
			info("Error processing command", err)
		}

		switch command.Type {
		case Type_Help:
			sendHelp(s, m.ChannelID, command)
		case Type_Learn:
			sendLearn(redisClient, s, m.ChannelID, command)
		case Type_Custom:
			sendCustom(redisClient, s, m.ChannelID, command)
		case Type_List:
			sendList(redisClient, s, m.ChannelID, command)
		}
	}
}

///////////////////////////////////////////////////////////////////////////////
// User message parsing
///////////////////////////////////////////////////////////////////////////////

// HelpData holds data for Help commands.
type HelpData struct {
	Type int
}

type LearnData struct {
	CallOpen bool
	Call     string
	Response string
}

type CustomData struct {
	Call string
	Args string
}

type Command struct {
	Type   int
	Help   *HelpData
	Learn  *LearnData
	Custom *CustomData
}

// Parses the raw text string from the user. Returns an executable command.
func parseCommand(redisClient *redis.Client, content string) (*Command, error) {
	if !strings.HasPrefix(content, "?") {
		return &Command{
			Type: Type_None,
		}, nil
	}
	splitContent := strings.Split(content, " ")

	// Parse builtins.
	switch splitContent[0] {
	case Name_Help:
		return parseHelp(splitContent)
	case Name_Learn:
		return parseLearn(redisClient, splitContent)
	case Name_List:
		return &Command{Type: Type_List}, nil
	}

	// See if it's a custom command.
	if redisClient.HExists(Redis_Hash, splitContent[0][1:]).Val() {
		return parseCustom(redisClient, splitContent)
	}

	// No such command!
	return &Command{
		Type: Type_Unrecognized,
	}, nil
}

func parseHelp(splitContent []string) (*Command, error) {
	if splitContent[0] != Name_Help {
		fatal("parseHelp called with non-help command", errors.New("wat"))
	}
	userType := Type_Unrecognized
	if len(splitContent) > 1 {
		userType = getUserStringToType(splitContent[1])
	}
	return &Command{
		Type: Type_Help,
		Help: &HelpData{
			Type: userType,
		},
	}, nil
}

func parseLearn(redisClient *redis.Client, splitContent []string) (*Command, error) {
	if splitContent[0] != Name_Learn {
		fatal("parseLearn called with non-learn command", errors.New("wat"))
	}

	callRegexp := regexp.MustCompile("(?s)^[[:alnum:]].*$")
	responseRegexp := regexp.MustCompile("(?s)^[^/?!].*$")

	// Show help when not enough data is present, or malicious data is present.
	if len(splitContent) < 3 || !callRegexp.MatchString(splitContent[1]) || !responseRegexp.MatchString(splitContent[2]) {
		return &Command{
			Type: Type_Help,
			Help: &HelpData{
				Type: Type_Learn,
			},
		}, nil
	}

	// Don't overwrite old or builtin commands.
	if redisClient.HExists(Redis_Hash, splitContent[1]).Val() || getUserStringToType(splitContent[1]) != Type_None {
		return &Command{
			Type: Type_Learn,
			Learn: &LearnData{
				CallOpen: false,
				Call:     splitContent[1],
			},
		}, nil
	}

	// Everything is good.
	response := strings.Join(splitContent[2:], " ")
	return &Command{
		Type: Type_Learn,
		Learn: &LearnData{
			CallOpen: true,
			Call:     splitContent[1],
			Response: response,
		},
	}, nil
}

func parseCustom(redisClient *redis.Client, splitContent []string) (*Command, error) {
	if !redisClient.HExists(Redis_Hash, splitContent[0][1:]).Val() {
		fatal("parseCustom called with non-custom command", errors.New("wat"))
	}
	return &Command{
		Type: Type_Custom,
		Custom: &CustomData{
			Call: splitContent[0][1:],
			Args: strings.Join(splitContent[1:], " "),
		},
	}, nil
}

///////////////////////////////////////////////////////////////////////////////
// User-visible messages
///////////////////////////////////////////////////////////////////////////////

const (
	MsgCustomNeedsArgs   = "This command takes args. Please type `?command <more text>` instead of `?command`"
	MsgDefaultHelp       = "Type `?help` for this message, `?list` to list all commands, or `?help <command>` to get help for a particular command."
	MsgGistAddress       = "The list of commands is here: "
	MsgGistPostFail      = "Unable to connect to Gist service. Give it a few minutes and try again"
	MsgGistResponseFail  = "Failure reading response from Gist service"
	MsgGistSerializeFail = "Unable to serialize Gist"
	MsgGistStatusCode    = "Failed to upload Gist :("
	MsgGistUrlFail       = "Failed getting url from Gist service"
	MsgHelpHelp          = "You're probably right. I probably didn't think of this case."
	MsgHelpLearn         = "Type `?learn <call> <the response the bot should read>`. When you type `?call`, the bot will reply with the response.\n\nThe first character of the call and response must not begin with /, ?, or !\n\nUse $1 in the response to substitute all arguments"
	MsgHelpList          = "Type `?list` to get the URL of a Gist with all builtin and learned commands"
	MsgLearnFail         = "I already know ?%s"
	MsgLearnSuccess      = "Learned about %s"
	MsgListBuiltins      = "List of builtins:"
	MsgListCustom        = "List of learned commands:"
)

///////////////////////////////////////////////////////////////////////////////
// Response
///////////////////////////////////////////////////////////////////////////////

func sendHelp(s *discordgo.Session, channel string, command *Command) {
	if command.Help == nil {
		fatal("Incorrectly generated help command", errors.New("wat"))
	}
	switch command.Help.Type {
	default:
		if _, err := s.ChannelMessageSend(channel, MsgDefaultHelp); err != nil {
			info("Failed to send default help message", err)
		}
	case Type_Help:
		s.ChannelMessageSend(channel, MsgHelpHelp)
	case Type_Learn:
		s.ChannelMessageSend(channel, MsgHelpLearn)
	case Type_List:
		s.ChannelMessageSend(channel, MsgHelpList)
	}
}

func sendLearn(redisClient *redis.Client, s *discordgo.Session, channel string, command *Command) {
	if command.Learn == nil {
		fatal("Incorrectly generated learn command", errors.New("wat"))
	}
	if !command.Learn.CallOpen {
		s.ChannelMessageSend(channel, fmt.Sprintf(MsgLearnFail, command.Learn.Call))
		return
	}

	// Teach the command.
	if redisClient.HExists(Redis_Hash, command.Learn.Call).Val() {
		fatal("Collision when adding a call for "+command.Learn.Call, errors.New("wat"))
	}
	redisClient.HSet(Redis_Hash, command.Learn.Call, command.Learn.Response)

	// Send ack.
	s.ChannelMessageSend(channel, fmt.Sprintf(MsgLearnSuccess, command.Learn.Call))
}

func sendCustom(redisClient *redis.Client, s *discordgo.Session, channel string, command *Command) {
	if command.Custom == nil {
		fatal("Incorrectly generated learn command", errors.New("wat"))
	}

	if !redisClient.HExists(Redis_Hash, command.Custom.Call).Val() {
		fatal("Accidentally found a mismatched call/response pair", errors.New("Call response mismatch"))
	}

	response := redisClient.HGet(Redis_Hash, command.Custom.Call).Val()

	if strings.Contains(response, "$1") {
		if command.Custom.Args == "" {
			s.ChannelMessageSend(channel, MsgCustomNeedsArgs)
			return
		}
		response = strings.Replace(response, "$1", command.Custom.Args, 1)
	}
	s.ChannelMessageSend(channel, response)
}

func sendList(redisClient *redis.Client, s *discordgo.Session, channel string, command *Command) {
	builtins := []string{}
	for name := range StringToType {
		builtins = append(builtins, name)
	}

	custom := []string{}
	for name := range redisClient.HGetAll(Redis_Hash).Val() {
		custom = append(custom, name)
	}

	sort.Strings(builtins)
	sort.Strings(custom)

	var buffer bytes.Buffer
	buffer.WriteString(MsgListBuiltins)
	buffer.WriteString("\n")
	for _, name := range builtins {
		buffer.WriteString(" - ")
		buffer.WriteString(name)
		buffer.WriteString("\n")
	}

	buffer.WriteString("\n")

	buffer.WriteString(MsgListCustom)
	buffer.WriteString("\n")
	for _, name := range custom {
		buffer.WriteString(" - ?")
		buffer.WriteString(name)
		buffer.WriteString("\n")
	}

	url, err := uploadCommandList(buffer.String())
	if err != nil {
		s.ChannelMessageSend(channel, err.Error())
		return
	}
	s.ChannelMessageSend(channel, MsgGistAddress+": "+url)
}

///////////////////////////////////////////////////////////////////////////////
// Gist handling
///////////////////////////////////////////////////////////////////////////////
type Gist struct {
	Description string           `json:"description"`
	Public      bool             `json:"public"`
	Files       map[string]*File `json:"files"`
}

// A file represents the contents of a Gist.
type File struct {
	Content string `json:"content"`
}

// simpleGist returns a Gist object with just the given contents.
func simpleGist(contents string) *Gist {
	return &Gist{
		Public:      false,
		Description: "CRBot command list",
		Files: map[string]*File{
			"commands": &File{
				Content: contents,
			},
		},
	}
}

func uploadCommandList(contents string) (string, error) {
	gist := simpleGist(contents)
	serializedGist, err := json.Marshal(gist)
	if err != nil {
		info("Error marshalling gist", err)
		return "", errors.New(MsgGistSerializeFail)
	}
	response, err := http.Post(
		"https://api.github.com/gists", "application/json", bytes.NewBuffer(serializedGist))
	if err != nil {
		info("Error POSTing gist", err)
		return "", errors.New(MsgGistPostFail)
	} else if response.StatusCode != 201 {
		info("Bad status code", errors.New("Code: "+strconv.Itoa(response.StatusCode)))
		return "", errors.New(MsgGistStatusCode)
	}

	responseMap := map[string]interface{}{}
	if err := json.NewDecoder(response.Body).Decode(&responseMap); err != nil {
		info("Error reading gist response", err)
		return "", errors.New(MsgGistResponseFail)
	}

	if finalUrl, ok := responseMap["html_url"]; ok {
		return finalUrl.(string), nil
	}
	return "", errors.New(MsgGistUrlFail)
}
<|endoftext|>


<commit_before>/*
   Copyright (c) 2014, Percona LLC and/or its affiliates. All rights reserved.

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU Affero General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU Affero General Public License for more details.

   You should have received a copy of the GNU Affero General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>
*/

package qan

import (
	"fmt"
	"github.com/percona/cloud-protocol/proto"
	"github.com/percona/go-mysql/event"
	"github.com/percona/go-mysql/log"
	parser "github.com/percona/go-mysql/log/slow"
	"github.com/percona/go-mysql/query"
	"github.com/percona/percona-agent/mysql"
	"github.com/percona/percona-agent/pct"
	"os"
	"time"
)

type Job struct {
	Id             string
	SlowLogFile    string
	RunTime        time.Duration
	StartOffset    int64
	EndOffset      int64
	ExampleQueries bool
	// --
	ZeroRunTime bool // testing
}

func (j *Job) String() string {
	return fmt.Sprintf("%s %d-%d", j.SlowLogFile, j.StartOffset, j.EndOffset)
}

type Worker interface {
	Name() string
	Status() string
	Run(job *Job) (*Result, error)
}

type WorkerFactory interface {
	Make(collectFrom, name string, mysqlConn mysql.Connector) Worker
}

// --------------------------------------------------------------------------

type RealWorkerFactory struct {
	logChan chan *proto.LogEntry
}

func NewRealWorkerFactory(logChan chan *proto.LogEntry) *RealWorkerFactory {
	f := &RealWorkerFactory{
		logChan: logChan,
	}
	return f
}

func (f *RealWorkerFactory) Make(collectFrom, name string, mysqlConn mysql.Connector) Worker {
	switch collectFrom {
	case "slowlog":
		return NewSlowLogWorker(pct.NewLogger(f.logChan, "qan-worker"), name)
	case "perfschema":
		return NewPfsWorker(pct.NewLogger(f.logChan, "qan-worker"), name, mysqlConn)
	}
	return nil
}

// --------------------------------------------------------------------------

type SlowLogWorker struct {
	logger *pct.Logger
	name   string
	// --
	status          *pct.Status
	queryChan       chan string
	fingerprintChan chan string
	errChan         chan interface{}
	doneChan        chan bool
}

func NewSlowLogWorker(logger *pct.Logger, name string) *SlowLogWorker {
	w := &SlowLogWorker{
		logger: logger,
		name:   name,
		// --
		status:          pct.NewStatus([]string{name}),
		queryChan:       make(chan string, 1),
		fingerprintChan: make(chan string, 1),
		errChan:         make(chan interface{}, 1),
		doneChan:        make(chan bool, 1),
	}
	return w
}

func (w *SlowLogWorker) Name() string {
	return w.name
}

func (w *SlowLogWorker) Status() string {
	return w.status.Get(w.name)
}

func (w *SlowLogWorker) Run(job *Job) (*Result, error) {
	w.logger.Debug("Run:call")
	defer w.logger.Debug("Run:return")

	w.status.Update(w.name, "Starting job "+job.Id)
	result := &Result{}

	// Open the slow log file.
	file, err := os.Open(job.SlowLogFile)
	if err != nil {
		return nil, err
	}
	defer file.Close()

	// Create a slow log parser and run it.  It sends log.Event via its channel.
	// Be sure to stop it when done, else we'll leak goroutines.
	opts := log.Options{
		StartOffset: uint64(job.StartOffset),
		FilterAdminCommand: map[string]bool{
			"Binlog Dump":      true,
			"Binlog Dump GTID": true,
		},
	}
	p := parser.NewSlowLogParser(file, opts)
	defer p.Stop()
	go func() {
		defer func() {
			if err := recover(); err != nil {
				errMsg := fmt.Sprintf("Slow log parser for %s crashed: %s", job, err)
				w.logger.Error(errMsg)
				result.Error = errMsg
			}
		}()
		if err := p.Start(); err != nil {
			w.logger.Warn(err)
			result.Error = err.Error()
		}
	}()

	// Make an event aggregate to do all the heavy lifting: fingerprint
	// queries, group, and aggregate.
	a := event.NewEventAggregator(job.ExampleQueries)

	// Misc runtime meta data.
	jobSize := job.EndOffset - job.StartOffset
	runtime := time.Duration(0)
	progress := "Not started"
	rateType := ""
	rateLimit := uint(0)

	go w.fingerprinter()
	defer func() { w.doneChan <- true }()

	t0 := time.Now()
EVENT_LOOP:
	for event := range p.EventChan() {
		runtime = time.Now().Sub(t0)
		progress = fmt.Sprintf("%.1f%% %d/%d %d %.1fs",
			float64(event.Offset)/float64(job.EndOffset)*100, event.Offset, job.EndOffset, jobSize, runtime.Seconds())
		w.status.Update(w.name, fmt.Sprintf("Parsing %s: %s", job.SlowLogFile, progress))

		// Check runtime, stop if exceeded.
		if runtime >= job.RunTime {
			errMsg := fmt.Sprintf("Timeout parsing %s: %s", job, progress)
			w.logger.Warn(errMsg)
			result.Error = errMsg
			break EVENT_LOOP
		}

		if int64(event.Offset) >= job.EndOffset {
			result.StopOffset = int64(event.Offset)
			break EVENT_LOOP
		}

		if event.RateType != "" {
			if rateType != "" {
				if rateType != event.RateType || rateLimit != event.RateLimit {
					errMsg := fmt.Sprintf("Slow log has mixed rate limits: %s/%d and %s/%d",
						rateType, rateLimit, event.RateType, event.RateLimit)
					w.logger.Warn(errMsg)
					result.Error = errMsg
					break EVENT_LOOP
				}
			} else {
				rateType = event.RateType
				rateLimit = event.RateLimit
			}
		}

		var fingerprint string
		w.queryChan <- event.Query
		select {
		case fingerprint = <-w.fingerprintChan:
			id := query.Id(fingerprint)
			a.AddEvent(event, id, fingerprint)
		case _ = <-w.errChan:
			w.logger.Warn(fmt.Sprintf("Cannot fingerprint '%s'", event.Query))
			go w.fingerprinter()
		}
	}

	if result.StopOffset == 0 {
		result.StopOffset, _ = file.Seek(0, os.SEEK_CUR)
	}

	// Finalize the global and class metrics, i.e. calculate metric stats.
	w.status.Update(w.name, "Finalizing job "+job.Id)
	r := a.Finalize()

	// The aggregator result is a map, but we need an array of classes for
	// the query report, so convert it.
	n := len(r.Class)
	classes := make([]*event.QueryClass, n)
	for _, class := range r.Class {
		n-- // can't classes[--n] in Go
		classes[n] = class
	}
	result.Global = r.Global
	result.Class = classes

	// Zero the runtime for testing.
	if !job.ZeroRunTime {
		result.RunTime = time.Now().Sub(t0).Seconds()
	}

	w.status.Update(w.name, "Done job "+job.Id)
	w.logger.Info(fmt.Sprintf("Parsed %s: %s", job, progress))
	return result, nil
}

func (w *SlowLogWorker) fingerprinter() {
	w.logger.Debug("fingerprinter:call")
	defer w.logger.Debug("fingerprinter:return")
	defer func() {
		if err := recover(); err != nil {
			w.errChan <- err
		}
	}()
	for {
		select {
		case q := <-w.queryChan:
			f := query.Fingerprint(q)
			w.fingerprintChan <- f
		case <-w.doneChan:
			return
		}
	}
}
<commit_msg>PCT-1085<commit_after>/*
   Copyright (c) 2014, Percona LLC and/or its affiliates. All rights reserved.

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU Affero General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU Affero General Public License for more details.

   You should have received a copy of the GNU Affero General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>
*/

package qan

import (
	"fmt"
	"github.com/percona/cloud-protocol/proto"
	"github.com/percona/go-mysql/event"
	"github.com/percona/go-mysql/log"
	parser "github.com/percona/go-mysql/log/slow"
	"github.com/percona/go-mysql/query"
	"github.com/percona/percona-agent/mysql"
	"github.com/percona/percona-agent/pct"
	"os"
	"strings"
	"time"
)

type Job struct {
	Id             string
	SlowLogFile    string
	RunTime        time.Duration
	StartOffset    int64
	EndOffset      int64
	ExampleQueries bool
	// --
	ZeroRunTime bool // testing
}

func (j *Job) String() string {
	return fmt.Sprintf("%s %d-%d", j.SlowLogFile, j.StartOffset, j.EndOffset)
}

type Worker interface {
	Name() string
	Status() string
	Run(job *Job) (*Result, error)
}

type WorkerFactory interface {
	Make(collectFrom, name string, mysqlConn mysql.Connector) Worker
}

// --------------------------------------------------------------------------

type RealWorkerFactory struct {
	logChan chan *proto.LogEntry
}

func NewRealWorkerFactory(logChan chan *proto.LogEntry) *RealWorkerFactory {
	f := &RealWorkerFactory{
		logChan: logChan,
	}
	return f
}

func (f *RealWorkerFactory) Make(collectFrom, name string, mysqlConn mysql.Connector) Worker {
	switch collectFrom {
	case "slowlog":
		return NewSlowLogWorker(pct.NewLogger(f.logChan, "qan-worker"), name)
	case "perfschema":
		return NewPfsWorker(pct.NewLogger(f.logChan, "qan-worker"), name, mysqlConn)
	}
	return nil
}

// --------------------------------------------------------------------------

type SlowLogWorker struct {
	logger *pct.Logger
	name   string
	// --
	status          *pct.Status
	queryChan       chan string
	fingerprintChan chan string
	errChan         chan interface{}
	doneChan        chan bool
}

func NewSlowLogWorker(logger *pct.Logger, name string) *SlowLogWorker {
	w := &SlowLogWorker{
		logger: logger,
		name:   name,
		// --
		status:          pct.NewStatus([]string{name}),
		queryChan:       make(chan string, 1),
		fingerprintChan: make(chan string, 1),
		errChan:         make(chan interface{}, 1),
		doneChan:        make(chan bool, 1),
	}
	return w
}

func (w *SlowLogWorker) Name() string {
	return w.name
}

func (w *SlowLogWorker) Status() string {
	return w.status.Get(w.name)
}

func (w *SlowLogWorker) Run(job *Job) (*Result, error) {
	w.logger.Debug("Run:call")
	defer w.logger.Debug("Run:return")

	w.status.Update(w.name, "Starting job "+job.Id)
	result := &Result{}

	// Open the slow log file.
	file, err := os.Open(job.SlowLogFile)
	if err != nil {
		return nil, err
	}
	defer file.Close()

	// Create a slow log parser and run it.  It sends log.Event via its channel.
	// Be sure to stop it when done, else we'll leak goroutines.
	opts := log.Options{
		StartOffset: uint64(job.StartOffset),
		FilterAdminCommand: map[string]bool{
			"Binlog Dump":      true,
			"Binlog Dump GTID": true,
		},
	}
	p := parser.NewSlowLogParser(file, opts)
	defer p.Stop()
	go func() {
		defer func() {
			if err := recover(); err != nil {
				errMsg := fmt.Sprintf("Slow log parser for %s crashed: %s", job, err)
				w.logger.Error(errMsg)
				result.Error = errMsg
			}
		}()
		if err := p.Start(); err != nil {
			w.logger.Warn(err)
			result.Error = err.Error()
		}
	}()

	// Make an event aggregate to do all the heavy lifting: fingerprint
	// queries, group, and aggregate.
	a := event.NewEventAggregator(job.ExampleQueries)

	// Misc runtime meta data.
	jobSize := job.EndOffset - job.StartOffset
	runtime := time.Duration(0)
	progress := "Not started"
	rateType := ""
	rateLimit := uint(0)

	go w.fingerprinter()
	defer func() { w.doneChan <- true }()

	t0 := time.Now()
EVENT_LOOP:
	for event := range p.EventChan() {
		runtime = time.Now().Sub(t0)
		progress = fmt.Sprintf("%.1f%% %d/%d %d %.1fs",
			float64(event.Offset)/float64(job.EndOffset)*100, event.Offset, job.EndOffset, jobSize, runtime.Seconds())
		w.status.Update(w.name, fmt.Sprintf("Parsing %s: %s", job.SlowLogFile, progress))

		// Check runtime, stop if exceeded.
		if runtime >= job.RunTime {
			errMsg := fmt.Sprintf("Timeout parsing %s: %s", job, progress)
			w.logger.Warn(errMsg)
			result.Error = errMsg
			break EVENT_LOOP
		}

		if int64(event.Offset) >= job.EndOffset {
			result.StopOffset = int64(event.Offset)
			break EVENT_LOOP
		}

		if event.RateType != "" {
			if rateType != "" {
				if rateType != event.RateType || rateLimit != event.RateLimit {
					errMsg := fmt.Sprintf("Slow log has mixed rate limits: %s/%d and %s/%d",
						rateType, rateLimit, event.RateType, event.RateLimit)
					w.logger.Warn(errMsg)
					result.Error = errMsg
					break EVENT_LOOP
				}
			} else {
				rateType = event.RateType
				rateLimit = event.RateLimit
			}
		}

		var fingerprint string
		w.queryChan <- event.Query
		select {
		case fingerprint = <-w.fingerprintChan:
			id := query.Id(fingerprint)
			a.AddEvent(event, id, fingerprint)
		case _ = <-w.errChan:
			w.logger.Warn(fmt.Sprintf("Cannot fingerprint '%s'", event.Query))
			go w.fingerprinter()
		}
	}

	if result.StopOffset == 0 {
		result.StopOffset, _ = file.Seek(0, os.SEEK_CUR)
	}

	// Finalize the global and class metrics, i.e. calculate metric stats.
	w.status.Update(w.name, "Finalizing job "+job.Id)
	r := a.Finalize()

	// The aggregator result is a map, but we need an array of classes for
	// the query report, so convert it.
	n := len(r.Class)
	classes := make([]*event.QueryClass, n)
	for _, class := range r.Class {
		n-- // can't classes[--n] in Go
		classes[n] = class
	}
	result.Global = r.Global
	result.Class = classes

	// Zero the runtime for testing.
	if !job.ZeroRunTime {
		result.RunTime = time.Now().Sub(t0).Seconds()
	}

	w.status.Update(w.name, "Done job "+job.Id)
	w.logger.Info(fmt.Sprintf("Parsed %s: %s", job, progress))
	return result, nil
}

func (w *SlowLogWorker) fingerprinter() {
	w.logger.Debug("fingerprinter:call")
	defer w.logger.Debug("fingerprinter:return")
	defer func() {
		if err := recover(); err != nil {
			w.errChan <- err
		}
	}()
	for {
		select {
		case q := <-w.queryChan:
			f := query.Fingerprint(q)
			if strings.Trim(f, " ") != "" {
				w.fingerprintChan <- f
			} else {
				w.errChan <- fmt.Errorf("Empty fingerprint for query: %s", q)
			}
		case <-w.doneChan:
			return
		}
	}
}
<|endoftext|>


<commit_before>package cloudDNS

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	rackspace "github.com/ghthor/gorackspace"
	"io/ioutil"
	"net/http"
)

type (
	// omitempty fields aren't needed when submiting a request to Add, Modify, Remove a Record
	Record struct {
		Name     string `json:"name"`
		Id       string `json:"id,omitempty"`
		Type     string `json:"type"`
		Data     string `json:"data"`
		Updated  string `json:"updated,omitempty"`
		Created  string `json:"created,omitempty"`
		TTL      int    `json:"ttl"`
		Comment  string `json:"comment,omitempty"`
		Priority int    `json:"priority,omitempty"`
	}

	RecordList struct {
		Records []Record `json:"records"`
	}

	RecordListResponse struct {
		Records      []Record `json:"records"`
		TotalEntries int      `json:"totalEntries"`
		rawJson      string
	}
)

func ListRecords(session rackspace.AuthSession, domain Domain) ([]Record, error) {
	// TODO: Inspect the Catalog to ensure this session has CloudDNS ability
	reqUrl := fmt.Sprintf("%s/domains/%d/records", session.ServiceCatalog().CloudDNS[0].PublicURL, domain.Id)
	req, _ := http.NewRequest("GET", reqUrl, nil)

	req.Header.Set("Accept", "application/json")
	req.Header.Set("X-Auth-Token", session.Id())

	resp, err := session.Client().Do(req)
	if err != nil {
		return nil, err
	}

	responseBody, _ := ioutil.ReadAll(resp.Body)

	switch resp.StatusCode {
	default:
		fallthrough
	case 401, 403, 400, 500, 503:
		return nil, errors.New(fmt.Sprintf("%s", responseBody))
	case 200, 203:
	}

	recordListResponse := &RecordListResponse{rawJson: string(responseBody)}

	// Parse Response Body
	err = json.Unmarshal(responseBody, recordListResponse)
	if err != nil {
		return nil, err
	}

	return recordListResponse.Records, nil
}

func AddRecord(session rackspace.AuthSession, domain Domain, newRecord Record) (*rackspace.JobStatus, error) {
	recordList := RecordList{[]Record{newRecord}}
	recordListJson, err := json.Marshal(recordList)
	if err != nil {
		return nil, err
	}

	// TODO: Inspect the Catalog to ensure this session has CloudDNS ability
	reqUrl := fmt.Sprintf("%s/domains/%d/records", session.ServiceCatalog().CloudDNS[0].PublicURL, domain.Id)
	req, _ := http.NewRequest("POST", reqUrl, bytes.NewBuffer(recordListJson))

	req.Header.Set("Content-type", "application/json")
	req.Header.Set("Accept", "application/json")
	req.Header.Set("X-Auth-Token", session.Id())

	resp, err := session.Client().Do(req)
	if err != nil {
		return nil, err
	}

	responseBody, _ := ioutil.ReadAll(resp.Body)

	switch resp.StatusCode {
	default:
		fallthrough
	case 400, 401, 404, 413, 500, 503:
		return nil, errors.New(fmt.Sprintf("%s", responseBody))
	case 200, 202:
	}

	jobStatus := &rackspace.JobStatus{}
	err = json.Unmarshal(responseBody, jobStatus)
	if err != nil {
		return nil, err
	}

	return jobStatus, nil
}
<commit_msg>Wrapped up adding a single record<commit_after>package cloudDNS

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	rackspace "github.com/ghthor/gorackspace"
	"io/ioutil"
	"net/http"
)

type (
	// omitempty fields aren't needed when submiting a request to Add, Modify, Remove a Record
	Record struct {
		Name     string `json:"name"`
		Id       string `json:"id,omitempty"`
		Type     string `json:"type"`
		Data     string `json:"data"`
		Updated  string `json:"updated,omitempty"`
		Created  string `json:"created,omitempty"`
		TTL      int    `json:"ttl"`
		Comment  string `json:"comment,omitempty"`
		Priority int    `json:"priority,omitempty"`
	}

	RecordList struct {
		Records []Record `json:"records"`
	}

	RecordListResponse struct {
		Records      []Record `json:"records"`
		TotalEntries int      `json:"totalEntries"`
		rawJson      string
	}
)

func ListRecords(session rackspace.AuthSession, domain Domain) ([]Record, error) {
	// TODO: Inspect the Catalog to ensure this session has CloudDNS ability
	reqUrl := fmt.Sprintf("%s/domains/%d/records", session.ServiceCatalog().CloudDNS[0].PublicURL, domain.Id)
	req, _ := http.NewRequest("GET", reqUrl, nil)

	req.Header.Set("Accept", "application/json")
	req.Header.Set("X-Auth-Token", session.Id())

	resp, err := session.Client().Do(req)
	if err != nil {
		return nil, err
	}

	responseBody, _ := ioutil.ReadAll(resp.Body)

	switch resp.StatusCode {
	default:
		fallthrough
	case 401, 403, 400, 500, 503:
		return nil, errors.New(fmt.Sprintf("%s", responseBody))
	case 200, 203:
	}

	recordListResponse := &RecordListResponse{rawJson: string(responseBody)}

	// Parse Response Body
	err = json.Unmarshal(responseBody, recordListResponse)
	if err != nil {
		return nil, err
	}

	return recordListResponse.Records, nil
}

func AddRecords(session rackspace.AuthSession, domain Domain, records []Record) (*rackspace.JobStatus, error) {
	recordList := RecordList{records}
	recordListJson, err := json.Marshal(recordList)
	if err != nil {
		return nil, err
	}

	// TODO: Inspect the Catalog to ensure this session has CloudDNS ability
	reqUrl := fmt.Sprintf("%s/domains/%d/records", session.ServiceCatalog().CloudDNS[0].PublicURL, domain.Id)
	req, _ := http.NewRequest("POST", reqUrl, bytes.NewBuffer(recordListJson))

	req.Header.Set("Content-type", "application/json")
	req.Header.Set("Accept", "application/json")
	req.Header.Set("X-Auth-Token", session.Id())

	resp, err := session.Client().Do(req)
	if err != nil {
		return nil, err
	}

	responseBody, _ := ioutil.ReadAll(resp.Body)

	switch resp.StatusCode {
	default:
		fallthrough
	case 400, 401, 404, 413, 500, 503:
		return nil, errors.New(fmt.Sprintf("%s", responseBody))
	case 200, 202:
	}

	jobStatus := &rackspace.JobStatus{}
	err = json.Unmarshal(responseBody, jobStatus)
	if err != nil {
		return nil, err
	}

	return jobStatus, nil
}

func AddRecord(session rackspace.AuthSession, domain Domain, newRecord Record) (*rackspace.JobStatus, error) {
	return AddRecords(session, domain, []Record{newRecord})
}
<|endoftext|>


<commit_before>/*
Copyright 2014 Google Inc. All rights reserved.

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 cmd

import (
	"fmt"
	"io"

	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl"
	"github.com/spf13/cobra"
)

const (
	updatePeriod = "1m0s"
	timeout      = "5m0s"
	pollInterval = "3s"
)

func (f *Factory) NewCmdRollingUpdate(out io.Writer) *cobra.Command {
	cmd := &cobra.Command{
		Use:   "rollingupdate <old-controller-name> -f <new-controller.json>",
		Short: "Perform a rolling update of the given replicationController",
		Long: `Perform a rolling update of the given replicationController.",

Replaces named controller with new controller, updating one pod at a time to use the
new PodTemplate. The new-controller.json must specify the same namespace as the
existing controller and overwrite at least one (common) label in its replicaSelector.

Examples:
$ kubectl rollingupdate frontend-v1 -f frontend-v2.json
  <update pods of frontend-v1 using new controller data in frontend-v2.json>

$ cat frontend-v2.json | kubectl rollingupdate frontend-v1 -f -
  <update pods of frontend-v1 using json data passed into stdin>`,
		Run: func(cmd *cobra.Command, args []string) {
			filename := GetFlagString(cmd, "filename")
			if len(filename) == 0 {
				usageError(cmd, "Must specify filename for new controller")
			}
			period := GetFlagDuration(cmd, "update-period")
			interval := GetFlagDuration(cmd, "poll-interval")
			timeout := GetFlagDuration(cmd, "timeout")
			if len(args) != 1 {
				usageError(cmd, "Must specify the controller to update")
			}
			oldName := args[0]
			schema, err := f.Validator(cmd)
			checkErr(err)
			mapping, namespace, newName, data := ResourceFromFile(cmd, filename, f.Typer, f.Mapper, schema)
			if mapping.Kind != "ReplicationController" {
				usageError(cmd, "%s does not specify a valid ReplicationController", filename)
			}
			err = CompareNamespaceFromFile(cmd, namespace)
			checkErr(err)

			client, err := f.ClientBuilder.Client()
			checkErr(err)
			obj, err := mapping.Codec.Decode(data)
			checkErr(err)
			newRc := obj.(*api.ReplicationController)

			updater := kubectl.NewRollingUpdater(namespace, client)

			// fetch rc
			oldRc, err := client.ReplicationControllers(namespace).Get(oldName)
			checkErr(err)

			var hasLabel bool
			for key, oldValue := range oldRc.Spec.Selector {
				if newValue, ok := newRc.Spec.Selector[key]; ok && newValue != oldValue {
					hasLabel = true
					break
				}
			}
			if !hasLabel {
				usageError(cmd, "%s must specify a matching key with non-equal value in Selector for %s",
					filename, oldName)
			}
			// TODO: handle resizes during rolling update
			if newRc.Spec.Replicas == 0 {
				newRc.Spec.Replicas = oldRc.Spec.Replicas
			}
			err = updater.Update(out, oldRc, newRc, period, interval, timeout)
			checkErr(err)

			fmt.Fprintf(out, "%s\n", newName)
		},
	}
	cmd.Flags().String("update-period", updatePeriod, `Time to wait between updating pods. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".`)
	cmd.Flags().String("poll-interval", pollInterval, `Time delay between polling controller status after update. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".`)
	cmd.Flags().String("timeout", timeout, `Max time to wait for a controller to update before giving up. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".`)
	cmd.Flags().StringP("filename", "f", "", "Filename or URL to file to use to create the new controller")
	return cmd
}
<commit_msg>Fix inflight merge conflict by adapting rollingupdate to #2861<commit_after>/*
Copyright 2014 Google Inc. All rights reserved.

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 cmd

import (
	"fmt"
	"io"

	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl"
	"github.com/spf13/cobra"
)

const (
	updatePeriod = "1m0s"
	timeout      = "5m0s"
	pollInterval = "3s"
)

func (f *Factory) NewCmdRollingUpdate(out io.Writer) *cobra.Command {
	cmd := &cobra.Command{
		Use:   "rollingupdate <old-controller-name> -f <new-controller.json>",
		Short: "Perform a rolling update of the given replicationController",
		Long: `Perform a rolling update of the given replicationController.",

Replaces named controller with new controller, updating one pod at a time to use the
new PodTemplate. The new-controller.json must specify the same namespace as the
existing controller and overwrite at least one (common) label in its replicaSelector.

Examples:
$ kubectl rollingupdate frontend-v1 -f frontend-v2.json
  <update pods of frontend-v1 using new controller data in frontend-v2.json>

$ cat frontend-v2.json | kubectl rollingupdate frontend-v1 -f -
  <update pods of frontend-v1 using json data passed into stdin>`,
		Run: func(cmd *cobra.Command, args []string) {
			filename := GetFlagString(cmd, "filename")
			if len(filename) == 0 {
				usageError(cmd, "Must specify filename for new controller")
			}
			period := GetFlagDuration(cmd, "update-period")
			interval := GetFlagDuration(cmd, "poll-interval")
			timeout := GetFlagDuration(cmd, "timeout")
			if len(args) != 1 {
				usageError(cmd, "Must specify the controller to update")
			}
			oldName := args[0]
			schema, err := f.Validator(cmd)
			checkErr(err)
			mapping, namespace, newName, data := ResourceFromFile(cmd, filename, f.Typer, f.Mapper, schema)
			if mapping.Kind != "ReplicationController" {
				usageError(cmd, "%s does not specify a valid ReplicationController", filename)
			}
			err = CompareNamespaceFromFile(cmd, namespace)
			checkErr(err)

			config, err := f.ClientConfig.ClientConfig()
			checkErr(err)
			client, err := client.New(config)
			checkErr(err)
			obj, err := mapping.Codec.Decode(data)
			checkErr(err)
			newRc := obj.(*api.ReplicationController)

			updater := kubectl.NewRollingUpdater(namespace, client)

			// fetch rc
			oldRc, err := client.ReplicationControllers(namespace).Get(oldName)
			checkErr(err)

			var hasLabel bool
			for key, oldValue := range oldRc.Spec.Selector {
				if newValue, ok := newRc.Spec.Selector[key]; ok && newValue != oldValue {
					hasLabel = true
					break
				}
			}
			if !hasLabel {
				usageError(cmd, "%s must specify a matching key with non-equal value in Selector for %s",
					filename, oldName)
			}
			// TODO: handle resizes during rolling update
			if newRc.Spec.Replicas == 0 {
				newRc.Spec.Replicas = oldRc.Spec.Replicas
			}
			err = updater.Update(out, oldRc, newRc, period, interval, timeout)
			checkErr(err)

			fmt.Fprintf(out, "%s\n", newName)
		},
	}
	cmd.Flags().String("update-period", updatePeriod, `Time to wait between updating pods. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".`)
	cmd.Flags().String("poll-interval", pollInterval, `Time delay between polling controller status after update. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".`)
	cmd.Flags().String("timeout", timeout, `Max time to wait for a controller to update before giving up. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".`)
	cmd.Flags().StringP("filename", "f", "", "Filename or URL to file to use to create the new controller")
	return cmd
}
<|endoftext|>


<commit_before>package smokescreen

import (
	"errors"
	"io/ioutil"
	"os"
	"time"

	"gopkg.in/yaml.v2"
)

type yamlConfigTls struct {
	CertFile string `yaml:"cert_file"`
	KeyFile string `yaml:"key_file"`
	ClientCAFiles []string `yaml:"client_ca_files"`
}

type yamlConfig struct{
	Ip string
	Port int
	DenyRanges	[]string `yaml:"deny_ranges"`
	AllowRanges	[]string `yaml:"allow_ranges"`
	ConnectTimeout time.Duration `yaml:"connect_timeout"`
	ExitTimeout time.Duration `yaml:"exit_timeout"`
	MaintenanceFile	string `yaml:"maintenance_file"`
	StatsdAddress string `yaml:"statsd_address"`
	EgressAclFile string `yaml:"acl_file"`
	SupportProxyProtocol bool `yaml:"support_proxy_protocol"`
	Tls *yamlConfigTls
	DenyMessage string `yaml:"deny_message"`
}

func UnmarshalConfig(rawYaml []byte) (Config, error) {
	var yc yamlConfig
	var c Config
	err := yaml.UnmarshalStrict(rawYaml, &yc)
	if err != nil {
		return c, err
	}

	c.Ip = yc.Ip
	c.Port = yc.Port

	err = c.SetDenyRanges(yc.DenyRanges)
	if err != nil {
		return c, err
	}

	err = c.SetAllowRanges(yc.AllowRanges)
	if err != nil {
		return c, err
	}

	c.ConnectTimeout = yc.ConnectTimeout
	c.ExitTimeout = yc.ExitTimeout

	c.MaintenanceFile = yc.MaintenanceFile
	if c.MaintenanceFile != "" {
		if _, err = os.Stat(c.MaintenanceFile); err != nil {
			return c, err
		}
	}

	err = c.SetupStatsd(yc.StatsdAddress)
	if err != nil {
		return c, err
	}

	if yc.EgressAclFile != "" {
		err = c.SetupEgressAcl(yc.EgressAclFile)
		if err != nil {
			return c, err
		}
	}

	c.SupportProxyProtocol = yc.SupportProxyProtocol

	if yc.Tls != nil {
		if yc.Tls.CertFile == "" {
			return c, errors.New("'tls' section requires 'cert_file'")
		}
		var key_file string
		if  yc.Tls.KeyFile != "" {
			key_file = yc.Tls.KeyFile
		} else {
			key_file = yc.Tls.CertFile
		}
		err = c.SetupTls(yc.Tls.CertFile, key_file, yc.Tls.ClientCAFiles)
		if err != nil {
			return c, err
		}
	}

	c.AdditionalErrorMessageOnDeny = yc.DenyMessage

	//TODO disable acl policy?

	return c, nil
}

func LoadConfig(filePath string) (Config, error) {
	bytes, err := ioutil.ReadFile(filePath)
	if err != nil {
		return Config{}, err
	}
	return UnmarshalConfig(bytes)
}
<commit_msg>rename denymessage<commit_after>package smokescreen

import (
	"errors"
	"io/ioutil"
	"os"
	"time"

	"gopkg.in/yaml.v2"
)

type yamlConfigTls struct {
	CertFile string `yaml:"cert_file"`
	KeyFile string `yaml:"key_file"`
	ClientCAFiles []string `yaml:"client_ca_files"`
}

type yamlConfig struct{
	Ip string
	Port int
	DenyRanges	[]string `yaml:"deny_ranges"`
	AllowRanges	[]string `yaml:"allow_ranges"`
	ConnectTimeout time.Duration `yaml:"connect_timeout"`
	ExitTimeout time.Duration `yaml:"exit_timeout"`
	MaintenanceFile	string `yaml:"maintenance_file"`
	StatsdAddress string `yaml:"statsd_address"`
	EgressAclFile string `yaml:"acl_file"`
	SupportProxyProtocol bool `yaml:"support_proxy_protocol"`
	Tls *yamlConfigTls
	DenyMessageExtra string `yaml:"deny_message_extra"`

	// Currently not configurable via YAML: RoleFromRequest, Log, DisabledAclPolicyActions
}

func UnmarshalConfig(rawYaml []byte) (Config, error) {
	var yc yamlConfig
	var c Config
	err := yaml.UnmarshalStrict(rawYaml, &yc)
	if err != nil {
		return c, err
	}

	c.Ip = yc.Ip
	c.Port = yc.Port

	err = c.SetDenyRanges(yc.DenyRanges)
	if err != nil {
		return c, err
	}

	err = c.SetAllowRanges(yc.AllowRanges)
	if err != nil {
		return c, err
	}

	c.ConnectTimeout = yc.ConnectTimeout
	c.ExitTimeout = yc.ExitTimeout

	c.MaintenanceFile = yc.MaintenanceFile
	if c.MaintenanceFile != "" {
		if _, err = os.Stat(c.MaintenanceFile); err != nil {
			return c, err
		}
	}

	err = c.SetupStatsd(yc.StatsdAddress)
	if err != nil {
		return c, err
	}

	if yc.EgressAclFile != "" {
		err = c.SetupEgressAcl(yc.EgressAclFile)
		if err != nil {
			return c, err
		}
	}

	c.SupportProxyProtocol = yc.SupportProxyProtocol

	if yc.Tls != nil {
		if yc.Tls.CertFile == "" {
			return c, errors.New("'tls' section requires 'cert_file'")
		}
		var key_file string
		if  yc.Tls.KeyFile != "" {
			key_file = yc.Tls.KeyFile
		} else {
			key_file = yc.Tls.CertFile
		}
		err = c.SetupTls(yc.Tls.CertFile, key_file, yc.Tls.ClientCAFiles)
		if err != nil {
			return c, err
		}
	}

	c.AdditionalErrorMessageOnDeny = yc.DenyMessageExtra

	//TODO disable acl policy?

	return c, nil
}

func LoadConfig(filePath string) (Config, error) {
	bytes, err := ioutil.ReadFile(filePath)
	if err != nil {
		return Config{}, err
	}
	return UnmarshalConfig(bytes)
}
<|endoftext|>


<commit_before>// Copyright 2016 CNI 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 sysctl

import (
	"fmt"
	"io/ioutil"
	"path/filepath"
	"strings"
)

// Sysctl provides a method to set/get values from /proc/sys - in linux systems
// new interface to set/get values of variables formerly handled by sysctl syscall
// If optional `params` have only one string value - this function will
// set this value into coresponding sysctl variable
func Sysctl(name string, params ...string) (string, error) {
	if len(params) > 1 {
		return "", fmt.Errorf("unexcepted additional parameters")
	} else if len(params) == 1 {
		return setSysctl(name, params[0])
	}
	return getSysctl(name)
}

func getSysctl(name string) (string, error) {
	fullName := filepath.Join("/proc/sys", strings.Replace(name, ".", "/", -1))
	fullName = filepath.Clean(fullName)
	data, err := ioutil.ReadFile(fullName)
	if err != nil {
		return "", err
	}

	return string(data[:len(data)-1]), nil
}

func setSysctl(name, value string) (string, error) {
	fullName := filepath.Join("/proc/sys", strings.Replace(name, ".", "/", -1))
	fullName = filepath.Clean(fullName)
	if err := ioutil.WriteFile(fullName, []byte(value), 0644); err != nil {
		return "", err
	}

	return getSysctl(name)
}
<commit_msg>pkg/utils/sysctl/sysctl_linux.go: fix typo.<commit_after>// Copyright 2016 CNI 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 sysctl

import (
	"fmt"
	"io/ioutil"
	"path/filepath"
	"strings"
)

// Sysctl provides a method to set/get values from /proc/sys - in linux systems
// new interface to set/get values of variables formerly handled by sysctl syscall
// If optional `params` have only one string value - this function will
// set this value into corresponding sysctl variable
func Sysctl(name string, params ...string) (string, error) {
	if len(params) > 1 {
		return "", fmt.Errorf("unexcepted additional parameters")
	} else if len(params) == 1 {
		return setSysctl(name, params[0])
	}
	return getSysctl(name)
}

func getSysctl(name string) (string, error) {
	fullName := filepath.Join("/proc/sys", strings.Replace(name, ".", "/", -1))
	fullName = filepath.Clean(fullName)
	data, err := ioutil.ReadFile(fullName)
	if err != nil {
		return "", err
	}

	return string(data[:len(data)-1]), nil
}

func setSysctl(name, value string) (string, error) {
	fullName := filepath.Join("/proc/sys", strings.Replace(name, ".", "/", -1))
	fullName = filepath.Clean(fullName)
	if err := ioutil.WriteFile(fullName, []byte(value), 0644); err != nil {
		return "", err
	}

	return getSysctl(name)
}
<|endoftext|>


<commit_before>package blockchain

import (
	"sync"
	"sync/atomic"
	"time"

	. "github.com/tendermint/tendermint/common"
	"github.com/tendermint/tendermint/types"
)

const (
	maxTries              = 3
	inputsChannelCapacity = 200
	requestIntervalMS     = 500
	maxPendingRequests    = 200
	maxTotalRequests      = 300
	maxRequestsPerPeer    = 300
)

var (
	requestTimeoutSeconds = time.Duration(3)
)

/*
	Peers self report their heights when a new peer joins the block pool.
	Starting from whatever we've got (pool.height), we request blocks
	in sequence from peers that reported higher heights than ours.
	Every so often we ask peers what height they're on so we can keep going.

	Requests are continuously made for blocks of heigher heights until
	the limits. If most of the requests have no available peers, and we
	are not at peer limits, we can probably switch to consensus reactor
*/

type BlockPool struct {
	// block requests
	requestsMtx   sync.Mutex
	requests      map[uint]*bpRequest
	height        uint  // the lowest key in requests.
	numUnassigned int32 // number of requests not yet assigned to a peer
	numPending    int32 // number of requests pending assignment or block response

	// peers
	peersMtx sync.Mutex
	peers    map[string]*bpPeer

	requestsCh chan<- BlockRequest
	timeoutsCh chan<- string
	repeater   *RepeatTimer

	running int32 // atomic
}

func NewBlockPool(start uint, requestsCh chan<- BlockRequest, timeoutsCh chan<- string) *BlockPool {
	return &BlockPool{
		peers: make(map[string]*bpPeer),

		requests:      make(map[uint]*bpRequest),
		height:        start,
		numUnassigned: 0,
		numPending:    0,

		requestsCh: requestsCh,
		timeoutsCh: timeoutsCh,
		repeater:   NewRepeatTimer("", requestIntervalMS*time.Millisecond),

		running: 0,
	}
}

func (pool *BlockPool) Start() {
	if atomic.CompareAndSwapInt32(&pool.running, 0, 1) {
		log.Info("Starting BlockPool")
		go pool.run()
	}
}

func (pool *BlockPool) Stop() {
	if atomic.CompareAndSwapInt32(&pool.running, 1, 0) {
		log.Info("Stopping BlockPool")
		pool.repeater.Stop()
	}
}

func (pool *BlockPool) IsRunning() bool {
	return atomic.LoadInt32(&pool.running) == 1
}

// Run spawns requests as needed.
func (pool *BlockPool) run() {
RUN_LOOP:
	for {
		if atomic.LoadInt32(&pool.running) == 0 {
			break RUN_LOOP
		}
		_, numPending := pool.GetStatus()
		if numPending >= maxPendingRequests {
			// sleep for a bit.
			time.Sleep(requestIntervalMS * time.Millisecond)
		} else if len(pool.requests) >= maxTotalRequests {
			// sleep for a bit.
			time.Sleep(requestIntervalMS * time.Millisecond)
		} else {
			// request for more blocks.
			pool.makeNextRequest()
		}
	}
}

func (pool *BlockPool) GetStatus() (uint, int32) {
	pool.requestsMtx.Lock() // Lock
	defer pool.requestsMtx.Unlock()

	return pool.height, pool.numPending
}

// We need to see the second block's Validation to validate the first block.
// So we peek two blocks at a time.
func (pool *BlockPool) PeekTwoBlocks() (first *types.Block, second *types.Block) {
	pool.requestsMtx.Lock() // Lock
	defer pool.requestsMtx.Unlock()

	if r := pool.requests[pool.height]; r != nil {
		first = r.block
	}
	if r := pool.requests[pool.height+1]; r != nil {
		second = r.block
	}
	return
}

// Pop the first block at pool.height
// It must have been validated by 'second'.Validation from PeekTwoBlocks().
func (pool *BlockPool) PopRequest() {
	pool.requestsMtx.Lock() // Lock
	defer pool.requestsMtx.Unlock()

	if r := pool.requests[pool.height]; r == nil || r.block == nil {
		panic("PopRequest() requires a valid block")
	}

	delete(pool.requests, pool.height)
	pool.height++
}

// Invalidates the block at pool.height.
// Remove the peer and request from others.
func (pool *BlockPool) RedoRequest(height uint) {
	pool.requestsMtx.Lock() // Lock
	defer pool.requestsMtx.Unlock()

	request := pool.requests[height]
	if request.block == nil {
		panic("Expected block to be non-nil")
	}
	// TODO: record this malfeasance
	// maybe punish peer on switch (an invalid block!)
	pool.RemovePeer(request.peerId) // Lock on peersMtx.
	request.block = nil
	request.peerId = ""
	pool.numPending++
	pool.numUnassigned++

	go requestRoutine(pool, height)
}

func (pool *BlockPool) hasBlock(height uint) bool {
	pool.requestsMtx.Lock() // Lock
	defer pool.requestsMtx.Unlock()

	request := pool.requests[height]
	return request != nil && request.block != nil
}

func (pool *BlockPool) setPeerForRequest(height uint, peerId string) {
	pool.requestsMtx.Lock() // Lock
	defer pool.requestsMtx.Unlock()

	request := pool.requests[height]
	if request == nil {
		return
	}
	pool.numUnassigned--
	request.peerId = peerId
}

func (pool *BlockPool) removePeerForRequest(height uint, peerId string) {
	pool.requestsMtx.Lock() // Lock
	defer pool.requestsMtx.Unlock()

	request := pool.requests[height]
	if request == nil {
		return
	}
	pool.numUnassigned++
	request.peerId = ""
}

func (pool *BlockPool) AddBlock(block *types.Block, peerId string) {
	pool.requestsMtx.Lock() // Lock
	defer pool.requestsMtx.Unlock()

	request := pool.requests[block.Height]
	if request == nil {
		return
	}
	if request.peerId != peerId {
		return
	}
	if request.block != nil {
		return
	}
	request.block = block
	pool.numPending--
}

func (pool *BlockPool) getPeer(peerId string) *bpPeer {
	pool.peersMtx.Lock() // Lock
	defer pool.peersMtx.Unlock()

	peer := pool.peers[peerId]
	return peer
}

// Sets the peer's alleged blockchain height.
func (pool *BlockPool) SetPeerHeight(peerId string, height uint) {
	pool.peersMtx.Lock() // Lock
	defer pool.peersMtx.Unlock()

	peer := pool.peers[peerId]
	if peer != nil {
		peer.height = height
	} else {
		peer = &bpPeer{
			height:      height,
			id:          peerId,
			numRequests: 0,
		}
		pool.peers[peerId] = peer
	}
}

func (pool *BlockPool) RemovePeer(peerId string) {
	pool.peersMtx.Lock() // Lock
	defer pool.peersMtx.Unlock()

	delete(pool.peers, peerId)
}

// Pick an available peer with at least the given minHeight.
// If no peers are available, returns nil.
func (pool *BlockPool) pickIncrAvailablePeer(minHeight uint) *bpPeer {
	pool.peersMtx.Lock()
	defer pool.peersMtx.Unlock()

	for _, peer := range pool.peers {
		if peer.numRequests >= maxRequestsPerPeer {
			continue
		}
		if peer.height < minHeight {
			continue
		}
		peer.numRequests++
		return peer
	}
	return nil
}

func (pool *BlockPool) decrPeer(peerId string) {
	pool.peersMtx.Lock()
	defer pool.peersMtx.Unlock()

	peer := pool.peers[peerId]
	if peer == nil {
		return
	}
	peer.numRequests--
}

func (pool *BlockPool) makeNextRequest() {
	pool.requestsMtx.Lock() // Lock
	defer pool.requestsMtx.Unlock()

	nextHeight := pool.height + uint(len(pool.requests)) + 1
	request := &bpRequest{
		height: nextHeight,
		peerId: "",
		block:  nil,
	}

	pool.requests[nextHeight] = request
	pool.numUnassigned++
	pool.numPending++

	go requestRoutine(pool, nextHeight)
}

func (pool *BlockPool) sendRequest(height uint, peerId string) {
	if atomic.LoadInt32(&pool.running) == 0 {
		return
	}
	pool.requestsCh <- BlockRequest{height, peerId}
}

func (pool *BlockPool) sendTimeout(peerId string) {
	if atomic.LoadInt32(&pool.running) == 0 {
		return
	}
	pool.timeoutsCh <- peerId
}

func (pool *BlockPool) debug() string {
	pool.requestsMtx.Lock() // Lock
	defer pool.requestsMtx.Unlock()

	str := ""
	for h := pool.height; h < pool.height+uint(len(pool.requests)); h++ {
		if pool.requests[h] == nil {
			str += Fmt("H(%v):X ", h)
		} else {
			str += Fmt("H(%v):", h)
			str += Fmt("B?(%v) ", pool.requests[h].block != nil)
		}
	}
	return str
}

//-------------------------------------

type bpPeer struct {
	id          string
	height      uint
	numRequests int32
}

type bpRequest struct {
	height uint
	peerId string
	block  *types.Block
}

//-------------------------------------

// Responsible for making more requests as necessary
// Returns only when a block is found (e.g. AddBlock() is called)
func requestRoutine(pool *BlockPool, height uint) {
	for {
		var peer *bpPeer = nil
	PICK_LOOP:
		for {
			if !pool.IsRunning() {
				log.Debug("BlockPool not running. Stopping requestRoutine", "height", height)
				return
			}
			peer = pool.pickIncrAvailablePeer(height)
			if peer == nil {
				//log.Debug("No peers available", "height", height)
				time.Sleep(requestIntervalMS * time.Millisecond)
				continue PICK_LOOP
			}
			break PICK_LOOP
		}

		// set the peer, decrement numUnassigned
		pool.setPeerForRequest(height, peer.id)

		for try := 0; try < maxTries; try++ {
			pool.sendRequest(height, peer.id)
			time.Sleep(requestTimeoutSeconds * time.Second)
			// if successful the block is either in the pool,
			if pool.hasBlock(height) {
				pool.decrPeer(peer.id)
				return
			}
			// or already processed and we've moved past it
			bpHeight, _ := pool.GetStatus()
			if height < bpHeight {
				pool.decrPeer(peer.id)
				return
			}
		}

		// unset the peer, increment numUnassigned
		pool.removePeerForRequest(height, peer.id)

		// this peer failed us, try again
		pool.RemovePeer(peer.id)
		pool.sendTimeout(peer.id)
	}
}

//-------------------------------------

type BlockRequest struct {
	Height uint
	PeerId string
}
<commit_msg>fixed off by one bug<commit_after>package blockchain

import (
	"sync"
	"sync/atomic"
	"time"

	. "github.com/tendermint/tendermint/common"
	"github.com/tendermint/tendermint/types"
)

const (
	maxTries              = 3
	inputsChannelCapacity = 200
	requestIntervalMS     = 500
	maxPendingRequests    = 200
	maxTotalRequests      = 300
	maxRequestsPerPeer    = 300
)

var (
	requestTimeoutSeconds = time.Duration(3)
)

/*
	Peers self report their heights when a new peer joins the block pool.
	Starting from pool.height (inclusive), we request blocks
	in sequence from peers that reported higher heights than ours.
	Every so often we ask peers what height they're on so we can keep going.

	Requests are continuously made for blocks of heigher heights until
	the limits. If most of the requests have no available peers, and we
	are not at peer limits, we can probably switch to consensus reactor
*/

type BlockPool struct {
	// block requests
	requestsMtx   sync.Mutex
	requests      map[uint]*bpRequest
	height        uint  // the lowest key in requests.
	numUnassigned int32 // number of requests not yet assigned to a peer
	numPending    int32 // number of requests pending assignment or block response

	// peers
	peersMtx sync.Mutex
	peers    map[string]*bpPeer

	requestsCh chan<- BlockRequest
	timeoutsCh chan<- string
	repeater   *RepeatTimer

	running int32 // atomic
}

func NewBlockPool(start uint, requestsCh chan<- BlockRequest, timeoutsCh chan<- string) *BlockPool {
	return &BlockPool{
		peers: make(map[string]*bpPeer),

		requests:      make(map[uint]*bpRequest),
		height:        start,
		numUnassigned: 0,
		numPending:    0,

		requestsCh: requestsCh,
		timeoutsCh: timeoutsCh,
		repeater:   NewRepeatTimer("", requestIntervalMS*time.Millisecond),

		running: 0,
	}
}

func (pool *BlockPool) Start() {
	if atomic.CompareAndSwapInt32(&pool.running, 0, 1) {
		log.Info("Starting BlockPool")
		go pool.run()
	}
}

func (pool *BlockPool) Stop() {
	if atomic.CompareAndSwapInt32(&pool.running, 1, 0) {
		log.Info("Stopping BlockPool")
		pool.repeater.Stop()
	}
}

func (pool *BlockPool) IsRunning() bool {
	return atomic.LoadInt32(&pool.running) == 1
}

// Run spawns requests as needed.
func (pool *BlockPool) run() {
RUN_LOOP:
	for {
		if atomic.LoadInt32(&pool.running) == 0 {
			break RUN_LOOP
		}
		_, numPending := pool.GetStatus()
		if numPending >= maxPendingRequests {
			// sleep for a bit.
			time.Sleep(requestIntervalMS * time.Millisecond)
		} else if len(pool.requests) >= maxTotalRequests {
			// sleep for a bit.
			time.Sleep(requestIntervalMS * time.Millisecond)
		} else {
			// request for more blocks.
			pool.makeNextRequest()
		}
	}
}

func (pool *BlockPool) GetStatus() (uint, int32) {
	pool.requestsMtx.Lock() // Lock
	defer pool.requestsMtx.Unlock()

	return pool.height, pool.numPending
}

// We need to see the second block's Validation to validate the first block.
// So we peek two blocks at a time.
func (pool *BlockPool) PeekTwoBlocks() (first *types.Block, second *types.Block) {
	pool.requestsMtx.Lock() // Lock
	defer pool.requestsMtx.Unlock()

	if r := pool.requests[pool.height]; r != nil {
		first = r.block
	}
	if r := pool.requests[pool.height+1]; r != nil {
		second = r.block
	}
	return
}

// Pop the first block at pool.height
// It must have been validated by 'second'.Validation from PeekTwoBlocks().
func (pool *BlockPool) PopRequest() {
	pool.requestsMtx.Lock() // Lock
	defer pool.requestsMtx.Unlock()

	if r := pool.requests[pool.height]; r == nil || r.block == nil {
		panic("PopRequest() requires a valid block")
	}

	delete(pool.requests, pool.height)
	pool.height++
}

// Invalidates the block at pool.height.
// Remove the peer and request from others.
func (pool *BlockPool) RedoRequest(height uint) {
	pool.requestsMtx.Lock() // Lock
	defer pool.requestsMtx.Unlock()

	request := pool.requests[height]
	if request.block == nil {
		panic("Expected block to be non-nil")
	}
	// TODO: record this malfeasance
	// maybe punish peer on switch (an invalid block!)
	pool.RemovePeer(request.peerId) // Lock on peersMtx.
	request.block = nil
	request.peerId = ""
	pool.numPending++
	pool.numUnassigned++

	go requestRoutine(pool, height)
}

func (pool *BlockPool) hasBlock(height uint) bool {
	pool.requestsMtx.Lock() // Lock
	defer pool.requestsMtx.Unlock()

	request := pool.requests[height]
	return request != nil && request.block != nil
}

func (pool *BlockPool) setPeerForRequest(height uint, peerId string) {
	pool.requestsMtx.Lock() // Lock
	defer pool.requestsMtx.Unlock()

	request := pool.requests[height]
	if request == nil {
		return
	}
	pool.numUnassigned--
	request.peerId = peerId
}

func (pool *BlockPool) removePeerForRequest(height uint, peerId string) {
	pool.requestsMtx.Lock() // Lock
	defer pool.requestsMtx.Unlock()

	request := pool.requests[height]
	if request == nil {
		return
	}
	pool.numUnassigned++
	request.peerId = ""
}

func (pool *BlockPool) AddBlock(block *types.Block, peerId string) {
	pool.requestsMtx.Lock() // Lock
	defer pool.requestsMtx.Unlock()

	request := pool.requests[block.Height]
	if request == nil {
		return
	}
	if request.peerId != peerId {
		return
	}
	if request.block != nil {
		return
	}
	request.block = block
	pool.numPending--
}

func (pool *BlockPool) getPeer(peerId string) *bpPeer {
	pool.peersMtx.Lock() // Lock
	defer pool.peersMtx.Unlock()

	peer := pool.peers[peerId]
	return peer
}

// Sets the peer's alleged blockchain height.
func (pool *BlockPool) SetPeerHeight(peerId string, height uint) {
	pool.peersMtx.Lock() // Lock
	defer pool.peersMtx.Unlock()

	peer := pool.peers[peerId]
	if peer != nil {
		peer.height = height
	} else {
		peer = &bpPeer{
			height:      height,
			id:          peerId,
			numRequests: 0,
		}
		pool.peers[peerId] = peer
	}
}

func (pool *BlockPool) RemovePeer(peerId string) {
	pool.peersMtx.Lock() // Lock
	defer pool.peersMtx.Unlock()

	delete(pool.peers, peerId)
}

// Pick an available peer with at least the given minHeight.
// If no peers are available, returns nil.
func (pool *BlockPool) pickIncrAvailablePeer(minHeight uint) *bpPeer {
	pool.peersMtx.Lock()
	defer pool.peersMtx.Unlock()

	for _, peer := range pool.peers {
		if peer.numRequests >= maxRequestsPerPeer {
			continue
		}
		if peer.height < minHeight {
			continue
		}
		peer.numRequests++
		return peer
	}
	return nil
}

func (pool *BlockPool) decrPeer(peerId string) {
	pool.peersMtx.Lock()
	defer pool.peersMtx.Unlock()

	peer := pool.peers[peerId]
	if peer == nil {
		return
	}
	peer.numRequests--
}

func (pool *BlockPool) makeNextRequest() {
	pool.requestsMtx.Lock() // Lock
	defer pool.requestsMtx.Unlock()

	nextHeight := pool.height + uint(len(pool.requests))
	request := &bpRequest{
		height: nextHeight,
		peerId: "",
		block:  nil,
	}

	pool.requests[nextHeight] = request
	pool.numUnassigned++
	pool.numPending++

	go requestRoutine(pool, nextHeight)
}

func (pool *BlockPool) sendRequest(height uint, peerId string) {
	if atomic.LoadInt32(&pool.running) == 0 {
		return
	}
	pool.requestsCh <- BlockRequest{height, peerId}
}

func (pool *BlockPool) sendTimeout(peerId string) {
	if atomic.LoadInt32(&pool.running) == 0 {
		return
	}
	pool.timeoutsCh <- peerId
}

func (pool *BlockPool) debug() string {
	pool.requestsMtx.Lock() // Lock
	defer pool.requestsMtx.Unlock()

	str := ""
	for h := pool.height; h < pool.height+uint(len(pool.requests)); h++ {
		if pool.requests[h] == nil {
			str += Fmt("H(%v):X ", h)
		} else {
			str += Fmt("H(%v):", h)
			str += Fmt("B?(%v) ", pool.requests[h].block != nil)
		}
	}
	return str
}

//-------------------------------------

type bpPeer struct {
	id          string
	height      uint
	numRequests int32
}

type bpRequest struct {
	height uint
	peerId string
	block  *types.Block
}

//-------------------------------------

// Responsible for making more requests as necessary
// Returns only when a block is found (e.g. AddBlock() is called)
func requestRoutine(pool *BlockPool, height uint) {
	for {
		var peer *bpPeer = nil
	PICK_LOOP:
		for {
			if !pool.IsRunning() {
				log.Debug("BlockPool not running. Stopping requestRoutine", "height", height)
				return
			}
			peer = pool.pickIncrAvailablePeer(height)
			if peer == nil {
				//log.Debug("No peers available", "height", height)
				time.Sleep(requestIntervalMS * time.Millisecond)
				continue PICK_LOOP
			}
			break PICK_LOOP
		}

		// set the peer, decrement numUnassigned
		pool.setPeerForRequest(height, peer.id)

		for try := 0; try < maxTries; try++ {
			pool.sendRequest(height, peer.id)
			time.Sleep(requestTimeoutSeconds * time.Second)
			// if successful the block is either in the pool,
			if pool.hasBlock(height) {
				pool.decrPeer(peer.id)
				return
			}
			// or already processed and we've moved past it
			bpHeight, _ := pool.GetStatus()
			if height < bpHeight {
				pool.decrPeer(peer.id)
				return
			}
		}

		// unset the peer, increment numUnassigned
		pool.removePeerForRequest(height, peer.id)

		// this peer failed us, try again
		pool.RemovePeer(peer.id)
		pool.sendTimeout(peer.id)
	}
}

//-------------------------------------

type BlockRequest struct {
	Height uint
	PeerId string
}
<|endoftext|>


<commit_before>package cluster

import (
	"sync"

	"github.com/AsynkronIT/protoactor-go/eventstream"
	"github.com/AsynkronIT/protoactor-go/remote"
)

var membershipSub *eventstream.Subscription

var ml = &memberlist{
	mutex:                &sync.Mutex{},
	members:              make(map[string]*MemberStatus),
	memberStrategyByKind: make(map[string]MemberStrategy),
}

func subscribeMemberlistToEventStream() {
	membershipSub = eventstream.
		Subscribe(updateClusterTopology).
		WithPredicate(func(m interface{}) bool {
			_, ok := m.(ClusterTopologyEvent)
			return ok
		})
}

func unsubMemberlistToEventStream() {
	eventstream.Unsubscribe(membershipSub)
}

func getMembers(kind string) []string {
	res := make([]string, 0)
	if memberStrategy, ok := ml.memberStrategyByKind[kind]; ok {
		members := memberStrategy.GetAllMembers()
		for _, m := range members {
			if m.Alive {
				res = append(res, m.Address())
			}
		}
	}
	return res
}

func getPartitionMember(name, kind string) string {
	var res string
	if memberStrategy, ok := ml.memberStrategyByKind[kind]; ok {
		res = memberStrategy.GetPartition(name)
	}
	return res
}

func getActivatorMember(kind string) string {
	var res string
	if memberStrategy, ok := ml.memberStrategyByKind[kind]; ok {
		res = memberStrategy.GetActivator()
	}
	return res
}

func updateClusterTopology(m interface{}) {

	ml.mutex.Lock()
	defer ml.mutex.Unlock()

	msg, _ := m.(ClusterTopologyEvent)

	//build a lookup for the new statuses
	tmp := make(map[string]*MemberStatus)
	for _, new := range msg {
		tmp[new.Address()] = new
	}

	//first remove old ones
	for key, old := range ml.members {
		new := tmp[key]
		if new == nil {
			ml.updateAndNotify(new, old)
		}
	}

	//find all the entries that exist in the new set
	for key, new := range tmp {
		old := ml.members[key]
		ml.members[key] = new
		ml.updateAndNotify(new, old)
	}
}

// memberlist is responsible to keep track of the current cluster topology
// it does so by listening to changes from the ClusterProvider.
// the default ClusterProvider is consul.ConsulProvider which uses the Consul HTTP API to scan for changes
type memberlist struct {
	mutex                *sync.Mutex
	members              map[string]*MemberStatus
	memberStrategyByKind map[string]MemberStrategy
}

func (a *memberlist) updateAndNotify(new *MemberStatus, old *MemberStatus) {

	if new == nil && old == nil {
		//ignore, not possible
		return
	}
	if new == nil {
		//update MemberStrategy
		for _, k := range old.Kinds {
			if s, ok := a.memberStrategyByKind[k]; ok {
				s.RemoveMember(old)
				if len(s.GetAllMembers()) == 0 {
					delete(a.memberStrategyByKind, k)
				}
			}
		}

		//notify left
		meta := MemberMeta{
			Host:  old.Host,
			Port:  old.Port,
			Kinds: old.Kinds,
		}
		left := &MemberLeftEvent{MemberMeta: meta}
		eventstream.Publish(left)
		delete(a.members, old.Address()) //remove this member as it has left

		rt := &remote.EndpointTerminatedEvent{
			Address: old.Address(),
		}
		eventstream.Publish(rt)

		return
	}
	if old == nil {
		//update MemberStrategy
		for _, k := range new.Kinds {
			if _, ok := a.memberStrategyByKind[k]; !ok {
				a.memberStrategyByKind[k] = cfg.MemberStrategyBuilder(k)
			}
			a.memberStrategyByKind[k].AddMember(new)
		}

		//notify joined
		meta := MemberMeta{
			Host:  new.Host,
			Port:  new.Port,
			Kinds: new.Kinds,
		}
		joined := &MemberJoinedEvent{MemberMeta: meta}
		eventstream.Publish(joined)

		return
	}

	//update MemberStrategy
	if new.Alive != old.Alive || new.MemberID != old.MemberID || new.StatusValue != nil && !new.StatusValue.IsSame(old.StatusValue) {
		for _, k := range new.Kinds {
			if _, ok := a.memberStrategyByKind[k]; !ok {
				a.memberStrategyByKind[k] = cfg.MemberStrategyBuilder(k)
			}
			a.memberStrategyByKind[k].AddMember(new)
		}
	}

	if new.MemberID != old.MemberID {
		//notify member rejoined
		meta := MemberMeta{
			Host:  new.Host,
			Port:  new.Port,
			Kinds: new.Kinds,
		}
		joined := &MemberRejoinedEvent{MemberMeta: meta}
		eventstream.Publish(joined)

		return
	}
	if old.Alive && !new.Alive {
		//notify member unavailable
		meta := MemberMeta{
			Host:  new.Host,
			Port:  new.Port,
			Kinds: new.Kinds,
		}
		unavailable := &MemberUnavailableEvent{MemberMeta: meta}
		eventstream.Publish(unavailable)

		return
	}
	if !old.Alive && new.Alive {
		//notify member reachable
		meta := MemberMeta{
			Host:  new.Host,
			Port:  new.Port,
			Kinds: new.Kinds,
		}
		available := &MemberAvailableEvent{MemberMeta: meta}
		eventstream.Publish(available)
	}
}
<commit_msg>Use RWLock in MemberList.<commit_after>package cluster

import (
	"sync"

	"github.com/AsynkronIT/protoactor-go/eventstream"
	"github.com/AsynkronIT/protoactor-go/remote"
)

var membershipSub *eventstream.Subscription

var ml = &memberlist{
	mutex:                &sync.RWMutex{},
	members:              make(map[string]*MemberStatus),
	memberStrategyByKind: make(map[string]MemberStrategy),
}

func subscribeMemberlistToEventStream() {
	membershipSub = eventstream.
		Subscribe(updateClusterTopology).
		WithPredicate(func(m interface{}) bool {
			_, ok := m.(ClusterTopologyEvent)
			return ok
		})
}

func unsubMemberlistToEventStream() {
	eventstream.Unsubscribe(membershipSub)
}

func getMembers(kind string) []string {
	ml.mutex.RLock()
	defer ml.mutex.RUnlock()

	res := make([]string, 0)
	if memberStrategy, ok := ml.memberStrategyByKind[kind]; ok {
		members := memberStrategy.GetAllMembers()
		for _, m := range members {
			if m.Alive {
				res = append(res, m.Address())
			}
		}
	}
	return res
}

func getPartitionMember(name, kind string) string {
	ml.mutex.RLock()
	defer ml.mutex.RUnlock()

	var res string
	if memberStrategy, ok := ml.memberStrategyByKind[kind]; ok {
		res = memberStrategy.GetPartition(name)
	}
	return res
}

func getActivatorMember(kind string) string {
	ml.mutex.RLock()
	defer ml.mutex.RUnlock()

	var res string
	if memberStrategy, ok := ml.memberStrategyByKind[kind]; ok {
		res = memberStrategy.GetActivator()
	}
	return res
}

func updateClusterTopology(m interface{}) {

	ml.mutex.Lock()
	defer ml.mutex.Unlock()

	msg, _ := m.(ClusterTopologyEvent)

	//build a lookup for the new statuses
	tmp := make(map[string]*MemberStatus)
	for _, new := range msg {
		tmp[new.Address()] = new
	}

	//first remove old ones
	for key, old := range ml.members {
		new := tmp[key]
		if new == nil {
			ml.updateAndNotify(new, old)
		}
	}

	//find all the entries that exist in the new set
	for key, new := range tmp {
		old := ml.members[key]
		ml.members[key] = new
		ml.updateAndNotify(new, old)
	}
}

// memberlist is responsible to keep track of the current cluster topology
// it does so by listening to changes from the ClusterProvider.
// the default ClusterProvider is consul.ConsulProvider which uses the Consul HTTP API to scan for changes
type memberlist struct {
	mutex                *sync.RWMutex
	members              map[string]*MemberStatus
	memberStrategyByKind map[string]MemberStrategy
}

func (a *memberlist) updateAndNotify(new *MemberStatus, old *MemberStatus) {

	if new == nil && old == nil {
		//ignore, not possible
		return
	}
	if new == nil {
		//update MemberStrategy
		for _, k := range old.Kinds {
			if s, ok := a.memberStrategyByKind[k]; ok {
				s.RemoveMember(old)
				if len(s.GetAllMembers()) == 0 {
					delete(a.memberStrategyByKind, k)
				}
			}
		}

		//notify left
		meta := MemberMeta{
			Host:  old.Host,
			Port:  old.Port,
			Kinds: old.Kinds,
		}
		left := &MemberLeftEvent{MemberMeta: meta}
		eventstream.Publish(left)
		delete(a.members, old.Address()) //remove this member as it has left

		rt := &remote.EndpointTerminatedEvent{
			Address: old.Address(),
		}
		eventstream.Publish(rt)

		return
	}
	if old == nil {
		//update MemberStrategy
		for _, k := range new.Kinds {
			if _, ok := a.memberStrategyByKind[k]; !ok {
				a.memberStrategyByKind[k] = cfg.MemberStrategyBuilder(k)
			}
			a.memberStrategyByKind[k].AddMember(new)
		}

		//notify joined
		meta := MemberMeta{
			Host:  new.Host,
			Port:  new.Port,
			Kinds: new.Kinds,
		}
		joined := &MemberJoinedEvent{MemberMeta: meta}
		eventstream.Publish(joined)

		return
	}

	//update MemberStrategy
	if new.Alive != old.Alive || new.MemberID != old.MemberID || new.StatusValue != nil && !new.StatusValue.IsSame(old.StatusValue) {
		for _, k := range new.Kinds {
			if _, ok := a.memberStrategyByKind[k]; !ok {
				a.memberStrategyByKind[k] = cfg.MemberStrategyBuilder(k)
			}
			a.memberStrategyByKind[k].AddMember(new)
		}
	}

	if new.MemberID != old.MemberID {
		//notify member rejoined
		meta := MemberMeta{
			Host:  new.Host,
			Port:  new.Port,
			Kinds: new.Kinds,
		}
		joined := &MemberRejoinedEvent{MemberMeta: meta}
		eventstream.Publish(joined)

		return
	}
	if old.Alive && !new.Alive {
		//notify member unavailable
		meta := MemberMeta{
			Host:  new.Host,
			Port:  new.Port,
			Kinds: new.Kinds,
		}
		unavailable := &MemberUnavailableEvent{MemberMeta: meta}
		eventstream.Publish(unavailable)

		return
	}
	if !old.Alive && new.Alive {
		//notify member reachable
		meta := MemberMeta{
			Host:  new.Host,
			Port:  new.Port,
			Kinds: new.Kinds,
		}
		available := &MemberAvailableEvent{MemberMeta: meta}
		eventstream.Publish(available)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"runtime"
	"strconv"
	"time"

	cniversion "github.com/containernetworking/cni/pkg/version"
	"github.com/containers/buildah/define"
	iversion "github.com/containers/image/v5/version"
	ispecs "github.com/opencontainers/image-spec/specs-go"
	rspecs "github.com/opencontainers/runtime-spec/specs-go"
	"github.com/spf13/cobra"
)

//Overwritten at build time
var (
	GitCommit  string
	buildInfo  string
	cniVersion string
)

type versionInfo struct {
	Version       string `json:"version"`
	GoVersion     string `json:"goVersion"`
	ImageSpec     string `json:"imageSpec"`
	RuntimeSpec   string `json:"runtimeSpec"`
	CniSpec       string `json:"cniSpec"`
	LibcniVersion string `json:"libcniVersion"`
	ImageVersion  string `json:"imageVersion"`
	GitCommit     string `json:"gitCommit"`
	Built         string `json:"built"`
	OsArch        string `json:"osArch"`
}

type versionOptions struct {
	json bool
}

func init() {
	var opts versionOptions

	//cli command to print out the version info of buildah
	versionCommand := &cobra.Command{
		Use:   "version",
		Short: "Display the Buildah version information",
		Long:  "Displays Buildah version information.",
		RunE: func(c *cobra.Command, args []string) error {
			return versionCmd(c, args, opts)
		},
		Args:    cobra.NoArgs,
		Example: `buildah version`,
	}
	versionCommand.SetUsageTemplate(UsageTemplate())

	flags := versionCommand.Flags()
	flags.BoolVar(&opts.json, "json", false, "output in JSON format")

	rootCmd.AddCommand(versionCommand)
}

func versionCmd(c *cobra.Command, args []string, opts versionOptions) error {
	var err error
	buildTime := int64(0)
	if buildInfo != "" {
		//converting unix time from string to int64
		buildTime, err = strconv.ParseInt(buildInfo, 10, 64)
		if err != nil {
			return err
		}
	}

	version := versionInfo{
		Version:       define.Version,
		GoVersion:     runtime.Version(),
		ImageSpec:     ispecs.Version,
		RuntimeSpec:   rspecs.Version,
		CniSpec:       cniversion.Current(),
		LibcniVersion: cniVersion,
		ImageVersion:  iversion.Version,
		GitCommit:     GitCommit,
		Built:         time.Unix(buildTime, 0).Format(time.ANSIC),
		OsArch:        runtime.GOOS + "/" + runtime.GOARCH,
	}

	if opts.json {
		data, err := json.MarshalIndent(version, "", "    ")
		if err != nil {
			return err
		}
		fmt.Printf("%s\n", data)
		return nil
	}

	fmt.Println("Version:        ", version.Version)
	fmt.Println("Go Version:     ", version.GoVersion)
	fmt.Println("Image Spec:     ", version.ImageSpec)
	fmt.Println("Runtime Spec:   ", version.RuntimeSpec)
	fmt.Println("CNI Spec:       ", version.CniSpec)
	fmt.Println("libcni Version: ", version.LibcniVersion)
	fmt.Println("image Version:  ", version.ImageVersion)
	fmt.Println("Git Commit:     ", version.GitCommit)

	//Prints out the build time in readable format
	fmt.Println("Built:          ", version.Built)
	fmt.Println("OS/Arch:        ", version.OsArch)

	return nil
}
<commit_msg>Remove unused function arguments<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"runtime"
	"strconv"
	"time"

	cniversion "github.com/containernetworking/cni/pkg/version"
	"github.com/containers/buildah/define"
	iversion "github.com/containers/image/v5/version"
	ispecs "github.com/opencontainers/image-spec/specs-go"
	rspecs "github.com/opencontainers/runtime-spec/specs-go"
	"github.com/spf13/cobra"
)

//Overwritten at build time
var (
	GitCommit  string
	buildInfo  string
	cniVersion string
)

type versionInfo struct {
	Version       string `json:"version"`
	GoVersion     string `json:"goVersion"`
	ImageSpec     string `json:"imageSpec"`
	RuntimeSpec   string `json:"runtimeSpec"`
	CniSpec       string `json:"cniSpec"`
	LibcniVersion string `json:"libcniVersion"`
	ImageVersion  string `json:"imageVersion"`
	GitCommit     string `json:"gitCommit"`
	Built         string `json:"built"`
	OsArch        string `json:"osArch"`
}

type versionOptions struct {
	json bool
}

func init() {
	var opts versionOptions

	//cli command to print out the version info of buildah
	versionCommand := &cobra.Command{
		Use:   "version",
		Short: "Display the Buildah version information",
		Long:  "Displays Buildah version information.",
		RunE: func(c *cobra.Command, args []string) error {
			return versionCmd(opts)
		},
		Args:    cobra.NoArgs,
		Example: `buildah version`,
	}
	versionCommand.SetUsageTemplate(UsageTemplate())

	flags := versionCommand.Flags()
	flags.BoolVar(&opts.json, "json", false, "output in JSON format")

	rootCmd.AddCommand(versionCommand)
}

func versionCmd(opts versionOptions) error {
	var err error
	buildTime := int64(0)
	if buildInfo != "" {
		//converting unix time from string to int64
		buildTime, err = strconv.ParseInt(buildInfo, 10, 64)
		if err != nil {
			return err
		}
	}

	version := versionInfo{
		Version:       define.Version,
		GoVersion:     runtime.Version(),
		ImageSpec:     ispecs.Version,
		RuntimeSpec:   rspecs.Version,
		CniSpec:       cniversion.Current(),
		LibcniVersion: cniVersion,
		ImageVersion:  iversion.Version,
		GitCommit:     GitCommit,
		Built:         time.Unix(buildTime, 0).Format(time.ANSIC),
		OsArch:        runtime.GOOS + "/" + runtime.GOARCH,
	}

	if opts.json {
		data, err := json.MarshalIndent(version, "", "    ")
		if err != nil {
			return err
		}
		fmt.Printf("%s\n", data)
		return nil
	}

	fmt.Println("Version:        ", version.Version)
	fmt.Println("Go Version:     ", version.GoVersion)
	fmt.Println("Image Spec:     ", version.ImageSpec)
	fmt.Println("Runtime Spec:   ", version.RuntimeSpec)
	fmt.Println("CNI Spec:       ", version.CniSpec)
	fmt.Println("libcni Version: ", version.LibcniVersion)
	fmt.Println("image Version:  ", version.ImageVersion)
	fmt.Println("Git Commit:     ", version.GitCommit)

	//Prints out the build time in readable format
	fmt.Println("Built:          ", version.Built)
	fmt.Println("OS/Arch:        ", version.OsArch)

	return nil
}
<|endoftext|>