<commit_before>package kinesis

import (
	"bytes"
	"errors"
	"fmt"
	"log"
	"os"
	"sync"
	"text/template"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/kinesis"
	"github.com/gliderlabs/logspout/router"
)

// PutRecordsLimit is the maximum number of records allowed for a PutRecords request.
var PutRecordsLimit = 500

// RecordSizeLimit is the maximum allowed size per record.
var RecordSizeLimit int = 1 * 1024 * 1024 // 1MB

// PutRecordsSizeLimit is the maximum allowed size per PutRecords request.
var PutRecordsSizeLimit int = 5 * 1024 * 1024 // 5MB

type recordBuffer struct {
	client   *kinesis.Kinesis
	pKeyTmpl *template.Template
	input    *kinesis.PutRecordsInput
	count    int
	byteSize int
	mutex    sync.Mutex
}

func newRecordBuffer(client *kinesis.Kinesis, streamName string) (*recordBuffer, error) {
	pKeyTmpl, err := pKeyTmpl()
	if err != nil {
		return nil, err
	}

	input := &kinesis.PutRecordsInput{
		StreamName: aws.String(streamName),
		Records:    make([]*kinesis.PutRecordsRequestEntry, 0),
	}

	return &recordBuffer{
		client:   client,
		pKeyTmpl: pKeyTmpl,
		input:    input,
	}, nil
}

func pKeyTmpl() (*template.Template, error) {
	pKeyTmplString := os.Getenv("KINESIS_PARTITION_KEY_TEMPLATE")
	if pKeyTmplString == "" {
		return nil, errors.New("The partition key template is missing. Please set the KINESIS_PARTITION_KEY_TEMPLATE env variable")
	}

	pKeyTmpl, err := template.New("kinesisPartitionKey").Parse(pKeyTmplString)
	if err != nil {
		return nil, err
	}

	return pKeyTmpl, nil
}

type recordSizeLimitError struct {
	caller string
	length int
}

func (e *recordSizeLimitError) Error() string {
	return fmt.Sprintf("%s: log data byte size (%d) is over the limit.", e.caller, e.length)
}

// Add fills the buffer with new data, or flushes it if one of the limit
// has hit.
func (r *recordBuffer) Add(m *router.Message) error {
	data := m.Data
	dataLen := len(data)

	// This record is too large, we can't submit it to kinesis.
	if dataLen > RecordSizeLimit {
		return &recordSizeLimitError{
			caller: "recordBuffer.Add",
			length: dataLen,
		}
	}

	// Adding this event would make our request have too many records. Flush first.
	if r.count+1 > PutRecordsLimit {
		err := r.Flush()
		if err != nil {
			return err
		}
	}

	// Adding this event would make our request too large. Flush first.
	if r.byteSize+dataLen > PutRecordsSizeLimit {
		err := r.Flush()
		if err != nil {
			return err
		}
	}

	// Partition key
	pKey, err := pKey(r.pKeyTmpl, m)
	if err != nil {
		return err
	}

	// Add to count
	r.count += 1

	// Add data and partition key size to byteSize
	r.byteSize += dataLen + len(pKey)

	// Add record
	r.input.Records = append(r.input.Records, &kinesis.PutRecordsRequestEntry{
		Data:         []byte(data),
		PartitionKey: aws.String(pKey),
	})

	log.Printf("kinesis: record added, stream name: %s, partition key: %s, length: %d\n",
		*r.input.StreamName, pKey, len(r.input.Records))

	return nil
}

// Flush flushes the buffer.
func (r *recordBuffer) Flush() error {
	r.mutex.Lock()
	defer r.mutex.Unlock()

	if r.count == 0 {
		return nil
	}

	defer r.reset()

	_, err := r.client.PutRecords(r.input)
	if err != nil {
		return err
	}

	log.Printf("kinesis: buffer flushed, stream name: %s, length: %d\n",
		*r.input.StreamName, len(r.input.Records))

	return nil
}

func (r *recordBuffer) reset() {
	r.count = 0
	r.byteSize = 0
	r.input.Records = make([]*kinesis.PutRecordsRequestEntry, 0)

	log.Printf("kinesis: buffer reset, stream name: %s, length: %d\n",
		*r.input.StreamName, len(r.input.Records))
}

func pKey(tmpl *template.Template, m *router.Message) (string, error) {
	var pKey bytes.Buffer
	err := tmpl.Execute(&pKey, m)
	if err != nil {
		return "", err
	}

	return pKey.String(), nil
}
<commit_msg>Inspect response and log success vs. failures.<commit_after>package kinesis

import (
	"bytes"
	"errors"
	"fmt"
	"log"
	"os"
	"sync"
	"text/template"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/kinesis"
	"github.com/gliderlabs/logspout/router"
)

// PutRecordsLimit is the maximum number of records allowed for a PutRecords request.
var PutRecordsLimit = 500

// RecordSizeLimit is the maximum allowed size per record.
var RecordSizeLimit int = 1 * 1024 * 1024 // 1MB

// PutRecordsSizeLimit is the maximum allowed size per PutRecords request.
var PutRecordsSizeLimit int = 5 * 1024 * 1024 // 5MB

type recordBuffer struct {
	client   *kinesis.Kinesis
	pKeyTmpl *template.Template
	input    *kinesis.PutRecordsInput
	count    int
	byteSize int
	mutex    sync.Mutex
}

func newRecordBuffer(client *kinesis.Kinesis, streamName string) (*recordBuffer, error) {
	pKeyTmpl, err := pKeyTmpl()
	if err != nil {
		return nil, err
	}

	input := &kinesis.PutRecordsInput{
		StreamName: aws.String(streamName),
		Records:    make([]*kinesis.PutRecordsRequestEntry, 0),
	}

	return &recordBuffer{
		client:   client,
		pKeyTmpl: pKeyTmpl,
		input:    input,
	}, nil
}

func pKeyTmpl() (*template.Template, error) {
	pKeyTmplString := os.Getenv("KINESIS_PARTITION_KEY_TEMPLATE")
	if pKeyTmplString == "" {
		return nil, errors.New("The partition key template is missing. Please set the KINESIS_PARTITION_KEY_TEMPLATE env variable")
	}

	pKeyTmpl, err := template.New("kinesisPartitionKey").Parse(pKeyTmplString)
	if err != nil {
		return nil, err
	}

	return pKeyTmpl, nil
}

type recordSizeLimitError struct {
	caller string
	length int
}

func (e *recordSizeLimitError) Error() string {
	return fmt.Sprintf("%s: log data byte size (%d) is over the limit.", e.caller, e.length)
}

// Add fills the buffer with new data, or flushes it if one of the limit
// has hit.
func (r *recordBuffer) Add(m *router.Message) error {
	data := m.Data
	dataLen := len(data)

	// This record is too large, we can't submit it to kinesis.
	if dataLen > RecordSizeLimit {
		return &recordSizeLimitError{
			caller: "recordBuffer.Add",
			length: dataLen,
		}
	}

	// Adding this event would make our request have too many records. Flush first.
	if r.count+1 > PutRecordsLimit {
		err := r.Flush()
		if err != nil {
			return err
		}
	}

	// Adding this event would make our request too large. Flush first.
	if r.byteSize+dataLen > PutRecordsSizeLimit {
		err := r.Flush()
		if err != nil {
			return err
		}
	}

	// Partition key
	pKey, err := pKey(r.pKeyTmpl, m)
	if err != nil {
		return err
	}

	// Add to count
	r.count += 1

	// Add data and partition key size to byteSize
	r.byteSize += dataLen + len(pKey)

	// Add record
	r.input.Records = append(r.input.Records, &kinesis.PutRecordsRequestEntry{
		Data:         []byte(data),
		PartitionKey: aws.String(pKey),
	})

	log.Printf("kinesis: record added, stream name: %s, partition key: %s, length: %d\n",
		*r.input.StreamName, pKey, len(r.input.Records))

	return nil
}

// Flush flushes the buffer.
func (r *recordBuffer) Flush() error {
	r.mutex.Lock()
	defer r.mutex.Unlock()

	if r.count == 0 {
		return nil
	}

	defer r.reset()

	resp, err := r.client.PutRecords(r.input)
	if err != nil {
		return err
	}

	log.Printf("kinesis: buffer flushed, stream name: %s, records sent: %d, records failed: %d\n",
		*r.input.StreamName, len(r.input.Records), *resp.FailedRecordCount)

	return nil
}

func (r *recordBuffer) reset() {
	r.count = 0
	r.byteSize = 0
	r.input.Records = make([]*kinesis.PutRecordsRequestEntry, 0)

	log.Printf("kinesis: buffer reset, stream name: %s, length: %d\n",
		*r.input.StreamName, len(r.input.Records))
}

func pKey(tmpl *template.Template, m *router.Message) (string, error) {
	var pKey bytes.Buffer
	err := tmpl.Execute(&pKey, m)
	if err != nil {
		return "", err
	}

	return pKey.String(), nil
}
<|endoftext|>


<commit_before>/*
A pipeline wraps the executable part of a job including command execution and
logging
*/

package main

import (
	"errors"
	"fmt"
	"os"
	"os/exec"
)

/*
Type defining the pipeline
*/
type Pipeline struct {
	Cmds []*exec.Cmd
	Log  Log
	File *os.File
}

/*
Run/execute the pipeline, executing the commands it containes sequentially,
aborting if an error is encountered. This includes updating the logs file.
*/
func (p Pipeline) Run(path string) {
	// Always close the file after use
	defer p.File.Close()

	var err error

	// Write to the logs file that the job has started
	p.Log, err = p.Log.start(path)
	if err != nil {
		p.File.Close()
		p.Log.error(path, p.File)
		return
	}

	// Run the commands
	for i, cmd := range p.Cmds {
		err = cmd.Start()
		if err != nil {
			fmt.Fprintf(p.File, "ERROR: Failed to run script %d\n", i)
			p.Log.error(path, p.File)
			return
		}
		err = cmd.Wait()
		if err != nil {
			fmt.Println("Failed to wait for cmd")
			fmt.Fprintf(p.File, "ERROR: Failed to wait for script %d to finish\n", i)
			p.Log.error(path, p.File)
			return
		}
	}

	// Write to the logs file that the job has finished, terminating
	// any tails following the log, once the job has finished
	p.Log, _ = p.Log.finish(path, p.File)
	//TODO find a way of handling the error that might be thrown
}

/*
Build a pipeline from a job
*/
func buildPipeline(path, jobId string, log Log) (Pipeline, error) {
	setup, err := loadSetup(path)
	if err != nil {
		return Pipeline{}, err
	}

	var job Job
	jobFound := false
	for _, j := range setup.Jobs {
		if j.Id == jobId {
			job = j
			jobFound = true
			break
		}
	}

	if !jobFound {
		return Pipeline{}, errors.New("Job not found")
	}

	logPath := fmt.Sprintf("%s/logs/%s", path, log.Id)
	outfile, err := os.Create(logPath)
	if err != nil {
		return Pipeline{}, err
	}

	var pipeline Pipeline
	pipeline.File = outfile
	pipeline.Log = log
	for _, executable := range job.Pipeline {
		cmd, execErr := buildExecutable(path, executable, setup.Machines, log, outfile)
		if execErr != nil {
			return Pipeline{}, execErr
		}
		pipeline.Cmds = append(pipeline.Cmds, cmd)
	}

	return pipeline, nil
}

/*
Build a command executable by the OS from an executable as defined in the job
configuration
*/
func buildExecutable(path string, executable Executable, machines []Machine, log Log, file *os.File) (*exec.Cmd, error) {
	var cmd *exec.Cmd
	script := path + "/scripts/" + executable.Script
	if executable.Machine == "local" {
		cmd = exec.Command("/bin/bash", script)
	} else {
		var machine Machine
		for _, m := range machines {
			if m.Id == executable.Machine {
				machine = m
				break
			}
		}

		sshCommand := fmt.Sprintf(
			"ssh -o 'StrictHostKeyChecking no' %s@%s -p %s -i %s 'bash -s' < %s",
			machine.User,
			machine.Address,
			machine.Port,
			path+"/keys/"+machine.PrivateKey,
			script,
		)
		cmd = exec.Command("/bin/bash", "-c", sshCommand)
	}

	cmd.Stdout = file
	cmd.Stderr = file

	return cmd, nil
}
<commit_msg>Commands over SSH are now done in batch mode<commit_after>/*
A pipeline wraps the executable part of a job including command execution and
logging
*/

package main

import (
	"errors"
	"fmt"
	"os"
	"os/exec"
)

/*
Type defining the pipeline
*/
type Pipeline struct {
	Cmds []*exec.Cmd
	Log  Log
	File *os.File
}

/*
Run/execute the pipeline, executing the commands it containes sequentially,
aborting if an error is encountered. This includes updating the logs file.
*/
func (p Pipeline) Run(path string) {
	// Always close the file after use
	defer p.File.Close()

	var err error

	// Write to the logs file that the job has started
	p.Log, err = p.Log.start(path)
	if err != nil {
		p.File.Close()
		p.Log.error(path, p.File)
		return
	}

	// Run the commands
	for i, cmd := range p.Cmds {
		err = cmd.Start()
		if err != nil {
			fmt.Fprintf(p.File, "ERROR: Failed to run script %d\n", i)
			p.Log.error(path, p.File)
			return
		}
		err = cmd.Wait()
		if err != nil {
			fmt.Println("Failed to wait for cmd")
			fmt.Fprintf(p.File, "ERROR: Failed to wait for script %d to finish\n", i)
			p.Log.error(path, p.File)
			return
		}
	}

	// Write to the logs file that the job has finished, terminating
	// any tails following the log, once the job has finished
	p.Log, _ = p.Log.finish(path, p.File)
	//TODO find a way of handling the error that might be thrown
}

/*
Build a pipeline from a job
*/
func buildPipeline(path, jobId string, log Log) (Pipeline, error) {
	setup, err := loadSetup(path)
	if err != nil {
		return Pipeline{}, err
	}

	var job Job
	jobFound := false
	for _, j := range setup.Jobs {
		if j.Id == jobId {
			job = j
			jobFound = true
			break
		}
	}

	if !jobFound {
		return Pipeline{}, errors.New("Job not found")
	}

	logPath := fmt.Sprintf("%s/logs/%s", path, log.Id)
	outfile, err := os.Create(logPath)
	if err != nil {
		return Pipeline{}, err
	}

	var pipeline Pipeline
	pipeline.File = outfile
	pipeline.Log = log
	for _, executable := range job.Pipeline {
		cmd, execErr := buildExecutable(path, executable, setup.Machines, log, outfile)
		if execErr != nil {
			return Pipeline{}, execErr
		}
		pipeline.Cmds = append(pipeline.Cmds, cmd)
	}

	return pipeline, nil
}

/*
Build a command executable by the OS from an executable as defined in the job
configuration
*/
func buildExecutable(path string, executable Executable, machines []Machine, log Log, file *os.File) (*exec.Cmd, error) {
	var cmd *exec.Cmd
	script := path + "/scripts/" + executable.Script
	if executable.Machine == "local" {
		cmd = exec.Command("/bin/bash", script)
	} else {
		var machine Machine
		for _, m := range machines {
			if m.Id == executable.Machine {
				machine = m
				break
			}
		}

		sshCommand := fmt.Sprintf(
			"ssh -o 'StrictHostKeyChecking no' -o 'BatchMode yes' %s@%s -p %s -i %s 'bash -s' < %s",
			machine.User,
			machine.Address,
			machine.Port,
			path+"/keys/"+machine.PrivateKey,
			script,
		)
		cmd = exec.Command("/bin/bash", "-c", sshCommand)
	}

	cmd.Stdout = file
	cmd.Stderr = file

	return cmd, nil
}
<|endoftext|>


<commit_before>package aws

import (
	"fmt"
	"testing"

	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/terraform"
)

func TestAccAWSMainRouteTableAssociation_basic(t *testing.T) {
	resource.Test(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckMainRouteTableAssociationDestroy,
		Steps: []resource.TestStep{
			resource.TestStep{
				Config: testAccMainRouteTableAssociationConfig,
				Check: resource.ComposeTestCheckFunc(
					testAccCheckMainRouteTableAssociation(
						"aws_main_route_table_association.foo",
						"aws_vpc.foo",
						"aws_route_table.foo",
					),
				),
			},
			resource.TestStep{
				Config: testAccMainRouteTableAssociationConfigUpdate,
				Check: resource.ComposeTestCheckFunc(
					testAccCheckMainRouteTableAssociation(
						"aws_main_route_table_association.foo",
						"aws_vpc.foo",
						"aws_route_table.bar",
					),
				),
			},
		},
	})
}

func testAccCheckMainRouteTableAssociationDestroy(s *terraform.State) error {
	if len(s.RootModule().Resources) > 0 {
		return fmt.Errorf("Expected all resources to be gone, but found: %#v", s.RootModule().Resources)
	}

	return nil
}

func testAccCheckMainRouteTableAssociation(
	mainRouteTableAssociationResource string,
	vpcResource string,
	routeTableResource string) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		rs, ok := s.RootModule().Resources[mainRouteTableAssociationResource]
		if !ok {
			return fmt.Errorf("Not found: %s", mainRouteTableAssociationResource)
		}

		if rs.Primary.ID == "" {
			return fmt.Errorf("No ID is set")
		}

		vpc, ok := s.RootModule().Resources[vpcResource]
		if !ok {
			return fmt.Errorf("Not found: %s", vpcResource)
		}

		conn := testAccProvider.Meta().(*AWSClient).ec2conn
		mainAssociation, err := findMainRouteTableAssociation(conn, vpc.Primary.ID)
		if err != nil {
			return err
		}

		if *mainAssociation.RouteTableAssociationId != rs.Primary.ID {
			return fmt.Errorf("Found wrong main association: %s",
				*mainAssociation.RouteTableAssociationId)
		}

		return nil
	}
}

const testAccMainRouteTableAssociationConfig = `
resource "aws_vpc" "foo" {
	cidr_block = "10.1.0.0/16"
}

resource "aws_subnet" "foo" {
	vpc_id = "${aws_vpc.foo.id}"
	cidr_block = "10.1.1.0/24"
}

resource "aws_internet_gateway" "foo" {
	vpc_id = "${aws_vpc.foo.id}"
}

resource "aws_route_table" "foo" {
	vpc_id = "${aws_vpc.foo.id}"
	route {
		cidr_block = "10.0.0.0/8"
		gateway_id = "${aws_internet_gateway.foo.id}"
	}
}

resource "aws_main_route_table_association" "foo" {
	vpc_id = "${aws_vpc.foo.id}"
	route_table_id = "${aws_route_table.foo.id}"
}
`

const testAccMainRouteTableAssociationConfigUpdate = `
resource "aws_vpc" "foo" {
	cidr_block = "10.1.0.0/16"
}

resource "aws_subnet" "foo" {
	vpc_id = "${aws_vpc.foo.id}"
	cidr_block = "10.1.1.0/24"
}

resource "aws_internet_gateway" "foo" {
	vpc_id = "${aws_vpc.foo.id}"
}

// Need to keep the old route table around when we update the
// main_route_table_association, otherwise Terraform will try to destroy the
// route table too early, and will fail because it's still the main one
resource "aws_route_table" "foo" {
	vpc_id = "${aws_vpc.foo.id}"
	route {
		cidr_block = "10.0.0.0/8"
		gateway_id = "${aws_internet_gateway.foo.id}"
	}
}

resource "aws_route_table" "bar" {
	vpc_id = "${aws_vpc.foo.id}"
	route {
		cidr_block = "10.0.0.0/8"
		gateway_id = "${aws_internet_gateway.foo.id}"
	}
}

resource "aws_main_route_table_association" "foo" {
	vpc_id = "${aws_vpc.foo.id}"
	route_table_id = "${aws_route_table.bar.id}"
}
`
<commit_msg>provider/aws: fix CheckDestroy for main_route_table_association tests<commit_after>package aws

import (
	"fmt"
	"testing"

	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/terraform"
)

func TestAccAWSMainRouteTableAssociation_basic(t *testing.T) {
	resource.Test(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckMainRouteTableAssociationDestroy,
		Steps: []resource.TestStep{
			resource.TestStep{
				Config: testAccMainRouteTableAssociationConfig,
				Check: resource.ComposeTestCheckFunc(
					testAccCheckMainRouteTableAssociation(
						"aws_main_route_table_association.foo",
						"aws_vpc.foo",
						"aws_route_table.foo",
					),
				),
			},
			resource.TestStep{
				Config: testAccMainRouteTableAssociationConfigUpdate,
				Check: resource.ComposeTestCheckFunc(
					testAccCheckMainRouteTableAssociation(
						"aws_main_route_table_association.foo",
						"aws_vpc.foo",
						"aws_route_table.bar",
					),
				),
			},
		},
	})
}

func testAccCheckMainRouteTableAssociationDestroy(s *terraform.State) error {
	conn := testAccProvider.Meta().(*AWSClient).ec2conn

	for _, rs := range s.RootModule().Resources {
		if rs.Type != "aws_main_route_table_association" {
			continue
		}

		mainAssociation, err := findMainRouteTableAssociation(
			conn,
			rs.Primary.Attributes["vpc_id"],
		)
		if err != nil {
			// Verify the error is what we want
			if ae, ok := err.(awserr.Error); ok && ae.Code() == "ApplicationDoesNotExistException" {
				continue
			}
			return err
		}

		if mainAssociation != nil {
			return fmt.Errorf("still exists")
		}
	}

	return nil
}

func testAccCheckMainRouteTableAssociation(
	mainRouteTableAssociationResource string,
	vpcResource string,
	routeTableResource string) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		rs, ok := s.RootModule().Resources[mainRouteTableAssociationResource]
		if !ok {
			return fmt.Errorf("Not found: %s", mainRouteTableAssociationResource)
		}

		if rs.Primary.ID == "" {
			return fmt.Errorf("No ID is set")
		}

		vpc, ok := s.RootModule().Resources[vpcResource]
		if !ok {
			return fmt.Errorf("Not found: %s", vpcResource)
		}

		conn := testAccProvider.Meta().(*AWSClient).ec2conn
		mainAssociation, err := findMainRouteTableAssociation(conn, vpc.Primary.ID)
		if err != nil {
			return err
		}

		if *mainAssociation.RouteTableAssociationId != rs.Primary.ID {
			return fmt.Errorf("Found wrong main association: %s",
				*mainAssociation.RouteTableAssociationId)
		}

		return nil
	}
}

const testAccMainRouteTableAssociationConfig = `
resource "aws_vpc" "foo" {
	cidr_block = "10.1.0.0/16"
}

resource "aws_subnet" "foo" {
	vpc_id = "${aws_vpc.foo.id}"
	cidr_block = "10.1.1.0/24"
}

resource "aws_internet_gateway" "foo" {
	vpc_id = "${aws_vpc.foo.id}"
}

resource "aws_route_table" "foo" {
	vpc_id = "${aws_vpc.foo.id}"
	route {
		cidr_block = "10.0.0.0/8"
		gateway_id = "${aws_internet_gateway.foo.id}"
	}
}

resource "aws_main_route_table_association" "foo" {
	vpc_id = "${aws_vpc.foo.id}"
	route_table_id = "${aws_route_table.foo.id}"
}
`

const testAccMainRouteTableAssociationConfigUpdate = `
resource "aws_vpc" "foo" {
	cidr_block = "10.1.0.0/16"
}

resource "aws_subnet" "foo" {
	vpc_id = "${aws_vpc.foo.id}"
	cidr_block = "10.1.1.0/24"
}

resource "aws_internet_gateway" "foo" {
	vpc_id = "${aws_vpc.foo.id}"
}

// Need to keep the old route table around when we update the
// main_route_table_association, otherwise Terraform will try to destroy the
// route table too early, and will fail because it's still the main one
resource "aws_route_table" "foo" {
	vpc_id = "${aws_vpc.foo.id}"
	route {
		cidr_block = "10.0.0.0/8"
		gateway_id = "${aws_internet_gateway.foo.id}"
	}
}

resource "aws_route_table" "bar" {
	vpc_id = "${aws_vpc.foo.id}"
	route {
		cidr_block = "10.0.0.0/8"
		gateway_id = "${aws_internet_gateway.foo.id}"
	}
}

resource "aws_main_route_table_association" "foo" {
	vpc_id = "${aws_vpc.foo.id}"
	route_table_id = "${aws_route_table.bar.id}"
}
`
<|endoftext|>


<commit_before>package raft

import (
	"encoding/json"
	golog "log"
)

type Interface interface {
	Step(m Message)
	Msgs() []Message
}

type tick int

type config struct {
	NodeId  int
	Address string
}

type Node struct {
	// election timeout and heartbeat timeout in tick
	election  tick
	heartbeat tick

	// elapsed ticks after the last reset
	elapsed tick
	sm      *stateMachine
}

func New(id int, heartbeat, election tick) *Node {
	if election < heartbeat*3 {
		panic("election is least three times as heartbeat [election: %d, heartbeat: %d]")
	}

	n := &Node{
		heartbeat: heartbeat,
		election:  election,
		sm:        newStateMachine(id, []int{id}),
	}

	return n
}

func Dictate(n *Node) *Node {
	n.Step(Message{Type: msgHup})
	n.Add(n.Id())
	return n
}

func (n *Node) Id() int { return n.sm.id }

// Propose asynchronously proposes data be applied to the underlying state machine.
func (n *Node) Propose(data []byte) { n.propose(normal, data) }

func (n *Node) propose(t int, data []byte) {
	n.Step(Message{Type: msgProp, Entries: []Entry{{Type: t, Data: data}}})
}

func (n *Node) Add(id int) { n.updateConf(configAdd, &config{NodeId: id}) }

func (n *Node) Remove(id int) { n.updateConf(configRemove, &config{NodeId: id}) }

func (n *Node) Msgs() []Message { return n.sm.Msgs() }

func (n *Node) Step(m Message) {
	l := len(n.sm.msgs)
	n.sm.Step(m)
	for _, m := range n.sm.msgs[l:] {
		// reset elapsed in two cases:
		// msgAppResp -> heard from the leader of the same term
		// msgVoteResp with grant -> heard from the candidate the node voted for
		switch m.Type {
		case msgAppResp:
			n.elapsed = 0
		case msgVoteResp:
			if m.Index >= 0 {
				n.elapsed = 0
			}
		}
	}
}

// Next returns all the appliable entries
func (n *Node) Next() []Entry {
	ents := n.sm.nextEnts()
	for i := range ents {
		switch ents[i].Type {
		case normal:
		case configAdd:
			c := new(config)
			if err := json.Unmarshal(ents[i].Data, c); err != nil {
				golog.Println(err)
				continue
			}
			n.sm.Add(c.NodeId)
		case configRemove:
			c := new(config)
			if err := json.Unmarshal(ents[i].Data, c); err != nil {
				golog.Println(err)
				continue
			}
			n.sm.Remove(c.NodeId)
		default:
			panic("unexpected entry type")
		}
	}
	return ents
}

// Tick triggers the node to do a tick.
// If the current elapsed is greater or equal than the timeout,
// node will send corresponding message to the statemachine.
func (n *Node) Tick() {
	timeout, msgType := n.election, msgHup
	if n.sm.state == stateLeader {
		timeout, msgType = n.heartbeat, msgBeat
	}
	if n.elapsed >= timeout {
		n.Step(Message{Type: msgType})
		n.elapsed = 0
	} else {
		n.elapsed++
	}
}

func (n *Node) updateConf(t int, c *config) {
	data, err := json.Marshal(c)
	if err != nil {
		panic(err)
	}
	n.propose(t, data)
}
<commit_msg>raft: group Node fields<commit_after>package raft

import (
	"encoding/json"
	golog "log"
)

type Interface interface {
	Step(m Message)
	Msgs() []Message
}

type tick int

type config struct {
	NodeId  int
	Address string
}

type Node struct {
	sm *stateMachine

	elapsed   tick
	election  tick
	heartbeat tick
}

func New(id int, heartbeat, election tick) *Node {
	if election < heartbeat*3 {
		panic("election is least three times as heartbeat [election: %d, heartbeat: %d]")
	}

	n := &Node{
		heartbeat: heartbeat,
		election:  election,
		sm:        newStateMachine(id, []int{id}),
	}

	return n
}

func Dictate(n *Node) *Node {
	n.Step(Message{Type: msgHup})
	n.Add(n.Id())
	return n
}

func (n *Node) Id() int { return n.sm.id }

// Propose asynchronously proposes data be applied to the underlying state machine.
func (n *Node) Propose(data []byte) { n.propose(normal, data) }

func (n *Node) propose(t int, data []byte) {
	n.Step(Message{Type: msgProp, Entries: []Entry{{Type: t, Data: data}}})
}

func (n *Node) Add(id int) { n.updateConf(configAdd, &config{NodeId: id}) }

func (n *Node) Remove(id int) { n.updateConf(configRemove, &config{NodeId: id}) }

func (n *Node) Msgs() []Message { return n.sm.Msgs() }

func (n *Node) Step(m Message) {
	l := len(n.sm.msgs)
	n.sm.Step(m)
	for _, m := range n.sm.msgs[l:] {
		// reset elapsed in two cases:
		// msgAppResp -> heard from the leader of the same term
		// msgVoteResp with grant -> heard from the candidate the node voted for
		switch m.Type {
		case msgAppResp:
			n.elapsed = 0
		case msgVoteResp:
			if m.Index >= 0 {
				n.elapsed = 0
			}
		}
	}
}

// Next returns all the appliable entries
func (n *Node) Next() []Entry {
	ents := n.sm.nextEnts()
	for i := range ents {
		switch ents[i].Type {
		case normal:
		case configAdd:
			c := new(config)
			if err := json.Unmarshal(ents[i].Data, c); err != nil {
				golog.Println(err)
				continue
			}
			n.sm.Add(c.NodeId)
		case configRemove:
			c := new(config)
			if err := json.Unmarshal(ents[i].Data, c); err != nil {
				golog.Println(err)
				continue
			}
			n.sm.Remove(c.NodeId)
		default:
			panic("unexpected entry type")
		}
	}
	return ents
}

// Tick triggers the node to do a tick.
// If the current elapsed is greater or equal than the timeout,
// node will send corresponding message to the statemachine.
func (n *Node) Tick() {
	timeout, msgType := n.election, msgHup
	if n.sm.state == stateLeader {
		timeout, msgType = n.heartbeat, msgBeat
	}
	if n.elapsed >= timeout {
		n.Step(Message{Type: msgType})
		n.elapsed = 0
	} else {
		n.elapsed++
	}
}

func (n *Node) updateConf(t int, c *config) {
	data, err := json.Marshal(c)
	if err != nil {
		panic(err)
	}
	n.propose(t, data)
}
<|endoftext|>


<commit_before>package rain

import (
	"bytes"
	"crypto/rand"
	"encoding/binary"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net"
	"time"
)

// http://www.bittorrent.org/beps/bep_0020.html
var peerIDPrefix = []byte("-RN0001-")

type Rain struct {
	peerID   [20]byte
	listener net.Listener
	// downloads map[[20]byte]*Download
	// trackers  map[string]*Tracker
}

// New returns a pointer to new Rain BitTorrent client.
// Call ListenPeerPort method before starting Download to accept incoming connections.
func New() (*Rain, error) {
	r := &Rain{
	// downloads: make(map[[20]byte]*Download),
	// trackers:  make(map[string]*Tracker),
	}
	if err := r.generatePeerID(); err != nil {
		return nil, err
	}
	return r, nil
}

func (r *Rain) generatePeerID() error {
	buf := make([]byte, len(r.peerID)-len(peerIDPrefix))
	_, err := rand.Read(buf)
	if err != nil {
		return err
	}
	copy(r.peerID[:], peerIDPrefix)
	copy(r.peerID[len(peerIDPrefix):], buf)
	return nil
}

// ListenPeerPort starts to listen a TCP port to accept incoming peer connections.
func (r *Rain) ListenPeerPort(port int) error {
	var err error
	addr := &net.TCPAddr{Port: port}
	r.listener, err = net.ListenTCP("tcp4", addr)
	if err != nil {
		return err
	}
	log.Println("Listening peers on tcp://" + r.listener.Addr().String())
	// Update port number if it's been choosen randomly.
	go r.acceptor()
	return nil
}

func (r *Rain) acceptor() {
	for {
		conn, err := r.listener.Accept()
		if err != nil {
			log.Println(err)
			return
		}
		go r.servePeerConn(conn)
	}
}

const bitTorrent10pstrLen = 19

var bitTorrent10pstr = []byte("BitTorrent protocol")

func (r *Rain) servePeerConn(conn net.Conn) {
	defer conn.Close()

	// Give a minute for completing handshake.
	err := conn.SetDeadline(time.Now().Add(time.Minute))
	if err != nil {
		return
	}

	// Send handshake as soon as you see info_hash.
	var peerID [20]byte
	infoHashC := make(chan [20]byte, 1)
	errC := make(chan error, 1)
	go func() {
		var err error
		peerID, err = r.readHandShake(conn, infoHashC)
		if err != nil {
			errC <- err
		}
		close(errC)
	}()

	select {
	case infoHash := <-infoHashC:
		// TODO check if we have a torrent with info_hash
		err = r.sendHandShake(conn, infoHash)
		if err != nil {
			return
		}
	case <-errC:
		return
	}

	err = <-errC
	if err != nil {
		return
	}

	// TODO save peer with peerID
	r.communicateWithPeer(conn)
}

func (r *Rain) readHandShake(conn net.Conn, notifyInfoHash chan [20]byte) (peerID [20]byte, err error) {
	buf := make([]byte, bitTorrent10pstrLen)
	_, err = conn.Read(buf[:1]) // pstrlen
	if err != nil {
		return [20]byte{}, err
	}
	pstrlen := buf[0]
	if pstrlen != bitTorrent10pstrLen {
		return [20]byte{}, errors.New("unexpected pstrlen")
	}

	_, err = io.ReadFull(conn, buf) // pstr
	if err != nil {
		return [20]byte{}, err
	}
	if bytes.Compare(buf, bitTorrent10pstr) != 0 {
		return [20]byte{}, errors.New("unexpected pstr")
	}

	_, err = io.CopyN(ioutil.Discard, conn, 8) // reserved
	if err != nil {
		return [20]byte{}, err
	}

	var infoHash [20]byte
	_, err = io.ReadFull(conn, infoHash[:]) // info_hash
	if err != nil {
		return [20]byte{}, err
	}

	// The recipient must respond as soon as it sees the info_hash part of the handshake
	// (the peer id will presumably be sent after the recipient sends its own handshake).
	// The tracker's NAT-checking feature does not send the peer_id field of the handshake.
	if notifyInfoHash != nil {
		notifyInfoHash <- infoHash
	}

	_, err = io.ReadFull(conn, peerID[:]) // peer_id
	return peerID, err
}

func (r *Rain) sendHandShake(conn net.Conn, infoHash [20]byte) error {
	var handShake = struct {
		Pstrlen  byte
		Pstr     [bitTorrent10pstrLen]byte
		Reserved [8]byte
		InfoHash [20]byte
		PeerID   [20]byte
	}{
		Pstrlen:  19,
		InfoHash: infoHash,
		PeerID:   r.peerID,
	}
	copy(handShake.Pstr[:], bitTorrent10pstr)
	return binary.Write(conn, binary.BigEndian, &handShake)
}

// Download starts a download and waits for it to finish.
func (r *Rain) Download(filePath, where string) error {
	torrent, err := NewTorrentFile(filePath)
	if err != nil {
		return err
	}
	fmt.Printf("--- torrent: %#v\n", torrent)

	download := NewDownload(torrent)

	tracker, err := NewTracker(torrent.Announce, r.peerID)
	if err != nil {
		return err
	}

	err = tracker.Dial()
	if err != nil {
		return err
	}

	responseC := make(chan *AnnounceResponse)
	go tracker.announce(download, nil, nil, responseC)

	for {
		select {
		case resp := <-responseC:
			fmt.Printf("--- announce response: %#v\n", resp)
			for _, p := range resp.Peers {
				fmt.Printf("--- p: %s\n", p.TCPAddr())
				go r.connectToPeer(p, download)
			}
			// case
		}
	}

	return nil
}

func (r *Rain) connectToPeer(p *Peer, d *download) {
	conn, err := net.DialTCP("tcp4", nil, p.TCPAddr())
	if err != nil {
		log.Println(err)
		return
	}

	log.Printf("Connected to peer %s", conn.RemoteAddr().String())

	err = r.sendHandShake(conn, d.TorrentFile.InfoHash)
	if err != nil {
		return
	}

	_, err = r.readHandShake(conn, nil)
	if err != nil {
		return
	}

	fmt.Println("--- handshake completed")

	r.communicateWithPeer(conn)
}

// communicateWithPeer is the common method that is called after handshake.
// Peer connections are symmetrical.
func (r *Rain) communicateWithPeer(conn net.Conn) {
	// TODO adjust deadline to heartbeat
	err := conn.SetDeadline(time.Time{})
	if err != nil {
		return
	}
}
<commit_msg>use constant<commit_after>package rain

import (
	"bytes"
	"crypto/rand"
	"encoding/binary"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net"
	"time"
)

// http://www.bittorrent.org/beps/bep_0020.html
var peerIDPrefix = []byte("-RN0001-")

type Rain struct {
	peerID   [20]byte
	listener net.Listener
	// downloads map[[20]byte]*Download
	// trackers  map[string]*Tracker
}

// New returns a pointer to new Rain BitTorrent client.
// Call ListenPeerPort method before starting Download to accept incoming connections.
func New() (*Rain, error) {
	r := &Rain{
	// downloads: make(map[[20]byte]*Download),
	// trackers:  make(map[string]*Tracker),
	}
	if err := r.generatePeerID(); err != nil {
		return nil, err
	}
	return r, nil
}

func (r *Rain) generatePeerID() error {
	buf := make([]byte, len(r.peerID)-len(peerIDPrefix))
	_, err := rand.Read(buf)
	if err != nil {
		return err
	}
	copy(r.peerID[:], peerIDPrefix)
	copy(r.peerID[len(peerIDPrefix):], buf)
	return nil
}

// ListenPeerPort starts to listen a TCP port to accept incoming peer connections.
func (r *Rain) ListenPeerPort(port int) error {
	var err error
	addr := &net.TCPAddr{Port: port}
	r.listener, err = net.ListenTCP("tcp4", addr)
	if err != nil {
		return err
	}
	log.Println("Listening peers on tcp://" + r.listener.Addr().String())
	// Update port number if it's been choosen randomly.
	go r.acceptor()
	return nil
}

func (r *Rain) acceptor() {
	for {
		conn, err := r.listener.Accept()
		if err != nil {
			log.Println(err)
			return
		}
		go r.servePeerConn(conn)
	}
}

const bitTorrent10pstrLen = 19

var bitTorrent10pstr = []byte("BitTorrent protocol")

func (r *Rain) servePeerConn(conn net.Conn) {
	defer conn.Close()

	// Give a minute for completing handshake.
	err := conn.SetDeadline(time.Now().Add(time.Minute))
	if err != nil {
		return
	}

	// Send handshake as soon as you see info_hash.
	var peerID [20]byte
	infoHashC := make(chan [20]byte, 1)
	errC := make(chan error, 1)
	go func() {
		var err error
		peerID, err = r.readHandShake(conn, infoHashC)
		if err != nil {
			errC <- err
		}
		close(errC)
	}()

	select {
	case infoHash := <-infoHashC:
		// TODO check if we have a torrent with info_hash
		err = r.sendHandShake(conn, infoHash)
		if err != nil {
			return
		}
	case <-errC:
		return
	}

	err = <-errC
	if err != nil {
		return
	}

	// TODO save peer with peerID
	r.communicateWithPeer(conn)
}

func (r *Rain) readHandShake(conn net.Conn, notifyInfoHash chan [20]byte) (peerID [20]byte, err error) {
	buf := make([]byte, bitTorrent10pstrLen)
	_, err = conn.Read(buf[:1]) // pstrlen
	if err != nil {
		return [20]byte{}, err
	}
	pstrlen := buf[0]
	if pstrlen != bitTorrent10pstrLen {
		return [20]byte{}, errors.New("unexpected pstrlen")
	}

	_, err = io.ReadFull(conn, buf) // pstr
	if err != nil {
		return [20]byte{}, err
	}
	if bytes.Compare(buf, bitTorrent10pstr) != 0 {
		return [20]byte{}, errors.New("unexpected pstr")
	}

	_, err = io.CopyN(ioutil.Discard, conn, 8) // reserved
	if err != nil {
		return [20]byte{}, err
	}

	var infoHash [20]byte
	_, err = io.ReadFull(conn, infoHash[:]) // info_hash
	if err != nil {
		return [20]byte{}, err
	}

	// The recipient must respond as soon as it sees the info_hash part of the handshake
	// (the peer id will presumably be sent after the recipient sends its own handshake).
	// The tracker's NAT-checking feature does not send the peer_id field of the handshake.
	if notifyInfoHash != nil {
		notifyInfoHash <- infoHash
	}

	_, err = io.ReadFull(conn, peerID[:]) // peer_id
	return peerID, err
}

func (r *Rain) sendHandShake(conn net.Conn, infoHash [20]byte) error {
	var handShake = struct {
		Pstrlen  byte
		Pstr     [bitTorrent10pstrLen]byte
		Reserved [8]byte
		InfoHash [20]byte
		PeerID   [20]byte
	}{
		Pstrlen:  bitTorrent10pstrLen,
		InfoHash: infoHash,
		PeerID:   r.peerID,
	}
	copy(handShake.Pstr[:], bitTorrent10pstr)
	return binary.Write(conn, binary.BigEndian, &handShake)
}

// Download starts a download and waits for it to finish.
func (r *Rain) Download(filePath, where string) error {
	torrent, err := NewTorrentFile(filePath)
	if err != nil {
		return err
	}
	fmt.Printf("--- torrent: %#v\n", torrent)

	download := NewDownload(torrent)

	tracker, err := NewTracker(torrent.Announce, r.peerID)
	if err != nil {
		return err
	}

	err = tracker.Dial()
	if err != nil {
		return err
	}

	responseC := make(chan *AnnounceResponse)
	go tracker.announce(download, nil, nil, responseC)

	for {
		select {
		case resp := <-responseC:
			fmt.Printf("--- announce response: %#v\n", resp)
			for _, p := range resp.Peers {
				fmt.Printf("--- p: %s\n", p.TCPAddr())
				go r.connectToPeer(p, download)
			}
			// case
		}
	}

	return nil
}

func (r *Rain) connectToPeer(p *Peer, d *download) {
	conn, err := net.DialTCP("tcp4", nil, p.TCPAddr())
	if err != nil {
		log.Println(err)
		return
	}

	log.Printf("Connected to peer %s", conn.RemoteAddr().String())

	err = r.sendHandShake(conn, d.TorrentFile.InfoHash)
	if err != nil {
		return
	}

	_, err = r.readHandShake(conn, nil)
	if err != nil {
		return
	}

	fmt.Println("--- handshake completed")

	r.communicateWithPeer(conn)
}

// communicateWithPeer is the common method that is called after handshake.
// Peer connections are symmetrical.
func (r *Rain) communicateWithPeer(conn net.Conn) {
	// TODO adjust deadline to heartbeat
	err := conn.SetDeadline(time.Time{})
	if err != nil {
		return
	}
}
<|endoftext|>


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

package test

import (
	"bytes"
	"os"
	"path/filepath"
	"strings"

	"v.io/x/devtools/internal/collect"
	"v.io/x/devtools/internal/gitutil"
	"v.io/x/devtools/internal/test"
	"v.io/x/devtools/internal/tool"
	"v.io/x/devtools/internal/util"
)

func vanadiumSignupProxy(ctx *tool.Context, testName string, _ ...Opt) (_ *test.Result, e error) {
	root, err := util.V23Root()
	if err != nil {
		return nil, internalTestError{err, "VanadiumRoot"}
	}

	// Fetch emails addresses.
	var buffer bytes.Buffer
	{
		credentials := os.Getenv("CREDENTIALS")
		fetchSrc := filepath.Join(root, "infrastructure", "signup", "fetch.go")
		opts := ctx.Run().Opts()
		opts.Stdout = &buffer
		if err := ctx.Run().CommandWithOpts(opts, "v23", "go", "run", fetchSrc, "-credentials="+credentials); err != nil {
			return nil, internalTestError{err, "fetch"}
		}
	}

	// Create a feature branch in the infrastructure project.
	infraDir := tool.RootDirOpt(filepath.Join(root, "infrastructure"))
	if err := ctx.Git(infraDir).CreateAndCheckoutBranch("update"); err != nil {
		return nil, internalTestError{err, "create"}
	}
	defer collect.Error(func() error {
		if err := ctx.Git(infraDir).CheckoutBranch("master", gitutil.Force); err != nil {
			return internalTestError{err, "checkout"}
		}
		if err := ctx.Git(infraDir).DeleteBranch("update", gitutil.Force); err != nil {
			return internalTestError{err, "delete"}
		}
		return nil
	}, &e)

	// Update emails address whitelists.
	{
		whitelists := strings.Split(os.Getenv("WHITELISTS"), string(filepath.ListSeparator))
		mergeSrc := filepath.Join(root, "infrastructure", "signup", "merge.go")
		for _, whitelist := range whitelists {
			opts := ctx.Run().Opts()
			opts.Stdin = bytes.NewReader(buffer.Bytes())
			if err := ctx.Run().CommandWithOpts(opts, "v23", "go", "run", mergeSrc, "-whitelist="+whitelist); err != nil {
				return nil, internalTestError{err, "merge"}
			}
			if err := ctx.Git(infraDir).CommitFile(whitelist, "updating list of emails"); err != nil {
				return nil, internalTestError{err, "commit"}
			}
		}
	}

	// Push changes to master.
	if err := ctx.Git(infraDir).Push("origin", "master", !gitutil.Verify); err != nil {
		return nil, internalTestError{err, "push"}
	}

	return &test.Result{Status: test.Passed}, nil
}
<commit_msg>TBR devtools/v23: changing the signup proxy logic so that it tries to push to the remote repository only if there are changes to be pushed<commit_after>// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package test

import (
	"bytes"
	"os"
	"path/filepath"
	"strings"

	"v.io/x/devtools/internal/collect"
	"v.io/x/devtools/internal/gitutil"
	"v.io/x/devtools/internal/test"
	"v.io/x/devtools/internal/tool"
	"v.io/x/devtools/internal/util"
)

func vanadiumSignupProxy(ctx *tool.Context, testName string, _ ...Opt) (_ *test.Result, e error) {
	root, err := util.V23Root()
	if err != nil {
		return nil, internalTestError{err, "VanadiumRoot"}
	}

	// Fetch emails addresses.
	var buffer bytes.Buffer
	{
		credentials := os.Getenv("CREDENTIALS")
		fetchSrc := filepath.Join(root, "infrastructure", "signup", "fetch.go")
		opts := ctx.Run().Opts()
		opts.Stdout = &buffer
		if err := ctx.Run().CommandWithOpts(opts, "v23", "go", "run", fetchSrc, "-credentials="+credentials); err != nil {
			return nil, internalTestError{err, "fetch"}
		}
	}

	// Create a feature branch in the infrastructure project.
	infraDir := tool.RootDirOpt(filepath.Join(root, "infrastructure"))
	if err := ctx.Git(infraDir).CreateAndCheckoutBranch("update"); err != nil {
		return nil, internalTestError{err, "create"}
	}
	defer collect.Error(func() error {
		if err := ctx.Git(infraDir).CheckoutBranch("master", gitutil.Force); err != nil {
			return internalTestError{err, "checkout"}
		}
		if err := ctx.Git(infraDir).DeleteBranch("update", gitutil.Force); err != nil {
			return internalTestError{err, "delete"}
		}
		return nil
	}, &e)

	// Update emails address whitelists.
	{
		whitelists := strings.Split(os.Getenv("WHITELISTS"), string(filepath.ListSeparator))
		mergeSrc := filepath.Join(root, "infrastructure", "signup", "merge.go")
		for _, whitelist := range whitelists {
			opts := ctx.Run().Opts()
			opts.Stdin = bytes.NewReader(buffer.Bytes())
			if err := ctx.Run().CommandWithOpts(opts, "v23", "go", "run", mergeSrc, "-whitelist="+whitelist); err != nil {
				return nil, internalTestError{err, "merge"}
			}
			if err := ctx.Git(infraDir).Add(whitelist); err != nil {
				return nil, internalTestError{err, "commit"}
			}
		}
	}

	// Push changes (if any exist) to master.
	changed, err := ctx.Git(infraDir).HasUncommittedChanges()
	if err != nil {
		return nil, internalTestError{err, "changes"}
	}
	if changed {
		if err := ctx.Git(infraDir).CommitWithMessage("updating list of emails"); err != nil {
			return nil, internalTestError{err, "commit"}
		}
		if err := ctx.Git(infraDir).Push("origin", "update:master", !gitutil.Verify); err != nil {
			return nil, internalTestError{err, "push"}
		}
	}

	return &test.Result{Status: test.Passed}, nil
}
<|endoftext|>


<commit_before>/*§
  ===========================================================================
  MoonDeploy
  ===========================================================================
  Copyright (C) 2015-2016 Gianluca Costa
  ===========================================================================
  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 apps

import (
	"encoding/json"
	"fmt"
	"net/url"
	"runtime"

	"github.com/giancosta86/moondeploy/v3/versioning"
)

const AnyOS = "*"

type AppDescriptorV1V2 struct {
	DescriptorVersion string
	BaseURL           string

	Name      string
	Version   string
	Publisher string

	Description string
	IconPath    map[string]string

	SkipUpdateCheck   bool
	SkipPackageLevels int

	CommandLine map[string][]string

	PackageVersions map[string]string

	//Cache fields
	version         *versioning.Version
	declaredBaseURL *url.URL
	actualBaseURL   *url.URL
	iconPath        string
	commandLine     []string

	packageVersions map[string]*versioning.Version
}

func (descriptor *AppDescriptorV1V2) GetDescriptorVersion() (*versioning.Version, error) {
	return versioning.ParseVersion(descriptor.DescriptorVersion)
}

func (descriptor *AppDescriptorV1V2) GetActualBaseURL() *url.URL {
	return descriptor.actualBaseURL
}

func (descriptor *AppDescriptorV1V2) GetDeclaredBaseURL() *url.URL {
	return descriptor.declaredBaseURL
}

func (descriptor *AppDescriptorV1V2) GetDescriptorFileName() string {
	return DefaultDescriptorFileName
}

func (descriptor *AppDescriptorV1V2) GetName() string {
	return descriptor.Name
}

func (descriptor *AppDescriptorV1V2) GetAppVersion() *versioning.Version {
	return descriptor.version
}

func (descriptor *AppDescriptorV1V2) GetPublisher() string {
	return descriptor.Publisher
}

func (descriptor *AppDescriptorV1V2) GetDescription() string {
	return descriptor.Description
}

func (descriptor *AppDescriptorV1V2) GetPackageVersions() map[string]*versioning.Version {
	return descriptor.packageVersions
}

func (descriptor *AppDescriptorV1V2) GetCommandLine() []string {
	return descriptor.commandLine
}

func (descriptor *AppDescriptorV1V2) GetSkipPackageLevels() int {
	return descriptor.SkipPackageLevels
}

func (descriptor *AppDescriptorV1V2) IsSkipUpdateCheck() bool {
	return descriptor.SkipUpdateCheck
}

func (descriptor *AppDescriptorV1V2) GetIconPath() string {
	return descriptor.iconPath
}

func (descriptor *AppDescriptorV1V2) GetTitle() string {
	return fmt.Sprintf("%v %v", descriptor.Name, descriptor.Version)
}

func (descriptor *AppDescriptorV1V2) Validate() (err error) {
	if descriptor.BaseURL == "" {
		return fmt.Errorf("Base URL field is missing")
	}

	if descriptor.Name == "" {
		return fmt.Errorf("Name field is missing")
	}

	if descriptor.Version == "" {
		return fmt.Errorf("Version field is missing")
	}

	if descriptor.Publisher == "" {
		return fmt.Errorf("Publisher field is missing")
	}

	descriptor.version, err = versioning.ParseVersion(descriptor.Version)
	if err != nil {
		return err
	}

	err = descriptor.setDeclaredBaseURL()
	if err != nil {
		return err
	}

	descriptor.actualBaseURL = getActualBaseURL(descriptor)

	if descriptor.IconPath == nil {
		descriptor.IconPath = make(map[string]string)
	}

	descriptor.setIconPath()

	if descriptor.SkipPackageLevels < 0 {
		return fmt.Errorf("SkipPackageLevels field must be >= 0")
	}

	if descriptor.CommandLine == nil {
		descriptor.CommandLine = make(map[string][]string)
	}

	descriptor.setCommandLine()

	err = descriptor.setPackageVersions()
	if err != nil {
		return err
	}

	return nil
}

func (descriptor *AppDescriptorV1V2) setDeclaredBaseURL() (err error) {
	if descriptor.BaseURL[len(descriptor.BaseURL)-1] != '/' {
		descriptor.BaseURL = descriptor.BaseURL + "/"
	}

	descriptor.declaredBaseURL, err = url.Parse(descriptor.BaseURL)

	return err
}

func (descriptor *AppDescriptorV1V2) setIconPath() {
	osSpecificIconPath := descriptor.IconPath[runtime.GOOS]
	if osSpecificIconPath != "" {
		descriptor.iconPath = osSpecificIconPath
		return
	}

	genericIconPath := descriptor.IconPath[AnyOS]
	if genericIconPath != "" {
		descriptor.iconPath = genericIconPath
	}
}

func (descriptor *AppDescriptorV1V2) setCommandLine() {
	osSpecificCommandLine := descriptor.CommandLine[runtime.GOOS]
	if osSpecificCommandLine != nil {
		descriptor.commandLine = osSpecificCommandLine
	}

	genericCommandLine := descriptor.CommandLine[AnyOS]
	if genericCommandLine != nil {
		descriptor.commandLine = genericCommandLine
	}
}

func (descriptor *AppDescriptorV1V2) setPackageVersions() (err error) {
	if descriptor.PackageVersions == nil {
		descriptor.PackageVersions = make(map[string]string)
	}

	descriptor.packageVersions = make(map[string]*versioning.Version)
	for packageName, packageVersionString := range descriptor.PackageVersions {
		if packageVersionString != "" {
			descriptor.packageVersions[packageName], err = versioning.ParseVersion(packageVersionString)
			if err != nil {
				return fmt.Errorf("Invalid version string for package '%v': '%v'",
					packageName,
					packageVersionString)
			}
		} else {
			descriptor.packageVersions[packageName] = nil
		}
	}

	return nil
}

func (descriptor *AppDescriptorV1V2) CheckMatch(otherDescriptor AppDescriptor) (err error) {
	if descriptor.GetName() != otherDescriptor.GetName() {
		return fmt.Errorf("The descriptors have different Name values:\n\t'%v'\n\t'%v",
			descriptor.GetName(),
			otherDescriptor.GetName())
	}

	if descriptor.GetDescriptorFileName() != otherDescriptor.GetDescriptorFileName() {
		return fmt.Errorf("The descriptors have different DescriptorFileName values:\n\t'%v'\n\t'%v",
			descriptor.GetDescriptorFileName(),
			otherDescriptor.GetDescriptorFileName())
	}

	if descriptor.GetDeclaredBaseURL().String() != otherDescriptor.GetDeclaredBaseURL().String() {
		return fmt.Errorf("The descriptors have different BaseURL's:\n\t'%v'\n\t'%v'",
			descriptor.GetDeclaredBaseURL(),
			otherDescriptor.GetDeclaredBaseURL())
	}

	return nil
}

func (descriptor *AppDescriptorV1V2) CheckRequirements() (err error) {
	if descriptor.commandLine == nil {
		return fmt.Errorf("The app does does not provide a command line for this operating system: %v", runtime.GOOS)
	}

	return nil
}

func (descriptor *AppDescriptorV1V2) GetFileURL(relativePath string) (fileURL *url.URL, err error) {
	return getRelativeFileURL(descriptor, relativePath)
}

func (descriptor *AppDescriptorV1V2) GetBytes() (bytes []byte, err error) {
	return json.Marshal(*descriptor)
}
<commit_msg>Fix the command line parsing for the V1/V2 descriptor<commit_after>/*§
  ===========================================================================
  MoonDeploy
  ===========================================================================
  Copyright (C) 2015-2016 Gianluca Costa
  ===========================================================================
  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 apps

import (
	"encoding/json"
	"fmt"
	"net/url"
	"runtime"

	"github.com/giancosta86/moondeploy/v3/versioning"
)

const AnyOS = "*"

type AppDescriptorV1V2 struct {
	DescriptorVersion string
	BaseURL           string

	Name      string
	Version   string
	Publisher string

	Description string
	IconPath    map[string]string

	SkipUpdateCheck   bool
	SkipPackageLevels int

	CommandLine map[string][]string

	PackageVersions map[string]string

	//Cache fields
	version         *versioning.Version
	declaredBaseURL *url.URL
	actualBaseURL   *url.URL
	iconPath        string
	commandLine     []string

	packageVersions map[string]*versioning.Version
}

func (descriptor *AppDescriptorV1V2) GetDescriptorVersion() (*versioning.Version, error) {
	return versioning.ParseVersion(descriptor.DescriptorVersion)
}

func (descriptor *AppDescriptorV1V2) GetActualBaseURL() *url.URL {
	return descriptor.actualBaseURL
}

func (descriptor *AppDescriptorV1V2) GetDeclaredBaseURL() *url.URL {
	return descriptor.declaredBaseURL
}

func (descriptor *AppDescriptorV1V2) GetDescriptorFileName() string {
	return DefaultDescriptorFileName
}

func (descriptor *AppDescriptorV1V2) GetName() string {
	return descriptor.Name
}

func (descriptor *AppDescriptorV1V2) GetAppVersion() *versioning.Version {
	return descriptor.version
}

func (descriptor *AppDescriptorV1V2) GetPublisher() string {
	return descriptor.Publisher
}

func (descriptor *AppDescriptorV1V2) GetDescription() string {
	return descriptor.Description
}

func (descriptor *AppDescriptorV1V2) GetPackageVersions() map[string]*versioning.Version {
	return descriptor.packageVersions
}

func (descriptor *AppDescriptorV1V2) GetCommandLine() []string {
	return descriptor.commandLine
}

func (descriptor *AppDescriptorV1V2) GetSkipPackageLevels() int {
	return descriptor.SkipPackageLevels
}

func (descriptor *AppDescriptorV1V2) IsSkipUpdateCheck() bool {
	return descriptor.SkipUpdateCheck
}

func (descriptor *AppDescriptorV1V2) GetIconPath() string {
	return descriptor.iconPath
}

func (descriptor *AppDescriptorV1V2) GetTitle() string {
	return fmt.Sprintf("%v %v", descriptor.Name, descriptor.Version)
}

func (descriptor *AppDescriptorV1V2) Validate() (err error) {
	if descriptor.BaseURL == "" {
		return fmt.Errorf("Base URL field is missing")
	}

	if descriptor.Name == "" {
		return fmt.Errorf("Name field is missing")
	}

	if descriptor.Version == "" {
		return fmt.Errorf("Version field is missing")
	}

	if descriptor.Publisher == "" {
		return fmt.Errorf("Publisher field is missing")
	}

	descriptor.version, err = versioning.ParseVersion(descriptor.Version)
	if err != nil {
		return err
	}

	err = descriptor.setDeclaredBaseURL()
	if err != nil {
		return err
	}

	descriptor.actualBaseURL = getActualBaseURL(descriptor)

	if descriptor.IconPath == nil {
		descriptor.IconPath = make(map[string]string)
	}

	descriptor.setIconPath()

	if descriptor.SkipPackageLevels < 0 {
		return fmt.Errorf("SkipPackageLevels field must be >= 0")
	}

	if descriptor.CommandLine == nil {
		descriptor.CommandLine = make(map[string][]string)
	}

	descriptor.setCommandLine()

	err = descriptor.setPackageVersions()
	if err != nil {
		return err
	}

	return nil
}

func (descriptor *AppDescriptorV1V2) setDeclaredBaseURL() (err error) {
	if descriptor.BaseURL[len(descriptor.BaseURL)-1] != '/' {
		descriptor.BaseURL = descriptor.BaseURL + "/"
	}

	descriptor.declaredBaseURL, err = url.Parse(descriptor.BaseURL)

	return err
}

func (descriptor *AppDescriptorV1V2) setIconPath() {
	osSpecificIconPath := descriptor.IconPath[runtime.GOOS]
	if osSpecificIconPath != "" {
		descriptor.iconPath = osSpecificIconPath
		return
	}

	genericIconPath := descriptor.IconPath[AnyOS]
	if genericIconPath != "" {
		descriptor.iconPath = genericIconPath
	}
}

func (descriptor *AppDescriptorV1V2) setCommandLine() {
	osSpecificCommandLine := descriptor.CommandLine[runtime.GOOS]
	if osSpecificCommandLine != nil {
		descriptor.commandLine = osSpecificCommandLine
		return
	}

	genericCommandLine := descriptor.CommandLine[AnyOS]
	if genericCommandLine != nil {
		descriptor.commandLine = genericCommandLine
	}
}

func (descriptor *AppDescriptorV1V2) setPackageVersions() (err error) {
	if descriptor.PackageVersions == nil {
		descriptor.PackageVersions = make(map[string]string)
	}

	descriptor.packageVersions = make(map[string]*versioning.Version)
	for packageName, packageVersionString := range descriptor.PackageVersions {
		if packageVersionString != "" {
			descriptor.packageVersions[packageName], err = versioning.ParseVersion(packageVersionString)
			if err != nil {
				return fmt.Errorf("Invalid version string for package '%v': '%v'",
					packageName,
					packageVersionString)
			}
		} else {
			descriptor.packageVersions[packageName] = nil
		}
	}

	return nil
}

func (descriptor *AppDescriptorV1V2) CheckMatch(otherDescriptor AppDescriptor) (err error) {
	if descriptor.GetName() != otherDescriptor.GetName() {
		return fmt.Errorf("The descriptors have different Name values:\n\t'%v'\n\t'%v",
			descriptor.GetName(),
			otherDescriptor.GetName())
	}

	if descriptor.GetDescriptorFileName() != otherDescriptor.GetDescriptorFileName() {
		return fmt.Errorf("The descriptors have different DescriptorFileName values:\n\t'%v'\n\t'%v",
			descriptor.GetDescriptorFileName(),
			otherDescriptor.GetDescriptorFileName())
	}

	if descriptor.GetDeclaredBaseURL().String() != otherDescriptor.GetDeclaredBaseURL().String() {
		return fmt.Errorf("The descriptors have different BaseURL's:\n\t'%v'\n\t'%v'",
			descriptor.GetDeclaredBaseURL(),
			otherDescriptor.GetDeclaredBaseURL())
	}

	return nil
}

func (descriptor *AppDescriptorV1V2) CheckRequirements() (err error) {
	if descriptor.commandLine == nil {
		return fmt.Errorf("The app does does not provide a command line for this operating system: %v", runtime.GOOS)
	}

	return nil
}

func (descriptor *AppDescriptorV1V2) GetFileURL(relativePath string) (fileURL *url.URL, err error) {
	return getRelativeFileURL(descriptor, relativePath)
}

func (descriptor *AppDescriptorV1V2) GetBytes() (bytes []byte, err error) {
	return json.Marshal(*descriptor)
}
<|endoftext|>


<commit_before>package p2p

import (
	"bytes"
	"fmt"
	"math/rand"
	"reflect"
	"time"

	wire "github.com/tendermint/go-wire"
	cmn "github.com/tendermint/tmlibs/common"
)

const (
	// PexChannel is a channel for PEX messages
	PexChannel = byte(0x00)

	// period to ensure peers connected
	defaultEnsurePeersPeriod = 30 * time.Second
	minNumOutboundPeers      = 10
	maxPexMessageSize        = 1048576 // 1MB

	// maximum messages one peer can send to us during `msgCountByPeerFlushInterval`
	defaultMaxMsgCountByPeer    = 1000
	msgCountByPeerFlushInterval = 1 * time.Hour
)

// PEXReactor handles PEX (peer exchange) and ensures that an
// adequate number of peers are connected to the switch.
//
// It uses `AddrBook` (address book) to store `NetAddress`es of the peers.
//
// ## Preventing abuse
//
// For now, it just limits the number of messages from one peer to
// `defaultMaxMsgCountByPeer` messages per `msgCountByPeerFlushInterval` (1000
// msg/hour).
//
// NOTE [2017-01-17]:
//   Limiting is fine for now. Maybe down the road we want to keep track of the
//   quality of peer messages so if peerA keeps telling us about peers we can't
//   connect to then maybe we should care less about peerA. But I don't think
//   that kind of complexity is priority right now.
type PEXReactor struct {
	BaseReactor

	book              *AddrBook
	ensurePeersPeriod time.Duration

	// tracks message count by peer, so we can prevent abuse
	msgCountByPeer    *cmn.CMap
	maxMsgCountByPeer uint16
}

// NewPEXReactor creates new PEX reactor.
func NewPEXReactor(b *AddrBook) *PEXReactor {
	r := &PEXReactor{
		book:              b,
		ensurePeersPeriod: defaultEnsurePeersPeriod,
		msgCountByPeer:    cmn.NewCMap(),
		maxMsgCountByPeer: defaultMaxMsgCountByPeer,
	}
	r.BaseReactor = *NewBaseReactor("PEXReactor", r)
	return r
}

// OnStart implements BaseService
func (r *PEXReactor) OnStart() error {
	r.BaseReactor.OnStart()
	r.book.Start()
	go r.ensurePeersRoutine()
	go r.flushMsgCountByPeer()
	return nil
}

// OnStop implements BaseService
func (r *PEXReactor) OnStop() {
	r.BaseReactor.OnStop()
	r.book.Stop()
}

// GetChannels implements Reactor
func (r *PEXReactor) GetChannels() []*ChannelDescriptor {
	return []*ChannelDescriptor{
		&ChannelDescriptor{
			ID:                PexChannel,
			Priority:          1,
			SendQueueCapacity: 10,
		},
	}
}

// AddPeer implements Reactor by adding peer to the address book (if inbound)
// or by requesting more addresses (if outbound).
func (r *PEXReactor) AddPeer(p Peer) {
	if p.IsOutbound() {
		// For outbound peers, the address is already in the books.
		// Either it was added in DialSeeds or when we
		// received the peer's address in r.Receive
		if r.book.NeedMoreAddrs() {
			r.RequestPEX(p)
		}
	} else { // For inbound connections, the peer is its own source
		addr, err := NewNetAddressString(p.NodeInfo().ListenAddr)
		if err != nil {
			// peer gave us a bad ListenAddr. TODO: punish
			r.Logger.Error("Error in AddPeer: invalid peer address", "addr", p.NodeInfo().ListenAddr, "err", err)
			return
		}
		r.book.AddAddress(addr, addr)
	}
}

// RemovePeer implements Reactor.
func (r *PEXReactor) RemovePeer(p Peer, reason interface{}) {
	// If we aren't keeping track of local temp data for each peer here, then we
	// don't have to do anything.
}

// Receive implements Reactor by handling incoming PEX messages.
func (r *PEXReactor) Receive(chID byte, src Peer, msgBytes []byte) {
	srcAddrStr := src.NodeInfo().RemoteAddr
	srcAddr, err := NewNetAddressString(srcAddrStr)
	if err != nil {
		// this should never happen. TODO: cancel conn
		r.Logger.Error("Error in Receive: invalid peer address", "addr", srcAddrStr, "err", err)
		return
	}

	r.IncrementMsgCountForPeer(srcAddrStr)
	if r.ReachedMaxMsgCountForPeer(srcAddrStr) {
		r.Logger.Error("Maximum number of messages reached for peer", "peer", srcAddrStr)
		// TODO remove src from peers?
		return
	}

	_, msg, err := DecodeMessage(msgBytes)
	if err != nil {
		r.Logger.Error("Error decoding message", "err", err)
		return
	}
	r.Logger.Info("Received message", "msg", msg)

	switch msg := msg.(type) {
	case *pexRequestMessage:
		// src requested some peers.
		// NOTE: we might send an empty selection
		r.SendAddrs(src, r.book.GetSelection())
	case *pexAddrsMessage:
		// We received some peer addresses from src.
		// TODO: (We don't want to get spammed with bad peers)
		for _, addr := range msg.Addrs {
			if addr != nil {
				r.book.AddAddress(addr, srcAddr)
			}
		}
	default:
		r.Logger.Error(fmt.Sprintf("Unknown message type %v", reflect.TypeOf(msg)))
	}
}

// RequestPEX asks peer for more addresses.
func (r *PEXReactor) RequestPEX(p Peer) {
	p.Send(PexChannel, struct{ PexMessage }{&pexRequestMessage{}})
}

// SendAddrs sends addrs to the peer.
func (r *PEXReactor) SendAddrs(p Peer, addrs []*NetAddress) {
	p.Send(PexChannel, struct{ PexMessage }{&pexAddrsMessage{Addrs: addrs}})
}

// SetEnsurePeersPeriod sets period to ensure peers connected.
func (r *PEXReactor) SetEnsurePeersPeriod(d time.Duration) {
	r.ensurePeersPeriod = d
}

// SetMaxMsgCountByPeer sets maximum messages one peer can send to us during 'msgCountByPeerFlushInterval'.
func (r *PEXReactor) SetMaxMsgCountByPeer(v uint16) {
	r.maxMsgCountByPeer = v
}

// ReachedMaxMsgCountForPeer returns true if we received too many
// messages from peer with address `addr`.
// NOTE: assumes the value in the CMap is non-nil
func (r *PEXReactor) ReachedMaxMsgCountForPeer(addr string) bool {
	return r.msgCountByPeer.Get(addr).(uint16) >= r.maxMsgCountByPeer
}

// Increment or initialize the msg count for the peer in the CMap
func (r *PEXReactor) IncrementMsgCountForPeer(addr string) {
	var count uint16
	countI := r.msgCountByPeer.Get(addr)
	if countI != nil {
		count = countI.(uint16)
	}
	count++
	r.msgCountByPeer.Set(addr, count)
}

// Ensures that sufficient peers are connected. (continuous)
func (r *PEXReactor) ensurePeersRoutine() {
	// Randomize when routine starts
	ensurePeersPeriodMs := r.ensurePeersPeriod.Nanoseconds() / 1e6
	time.Sleep(time.Duration(rand.Int63n(ensurePeersPeriodMs)) * time.Millisecond)

	// fire once immediately.
	r.ensurePeers()

	// fire periodically
	ticker := time.NewTicker(r.ensurePeersPeriod)

	for {
		select {
		case <-ticker.C:
			r.ensurePeers()
		case <-r.Quit:
			ticker.Stop()
			return
		}
	}
}

// ensurePeers ensures that sufficient peers are connected. (once)
//
// Old bucket / New bucket are arbitrary categories to denote whether an
// address is vetted or not, and this needs to be determined over time via a
// heuristic that we haven't perfected yet, or, perhaps is manually edited by
// the node operator. It should not be used to compute what addresses are
// already connected or not.
//
// TODO Basically, we need to work harder on our good-peer/bad-peer marking.
// What we're currently doing in terms of marking good/bad peers is just a
// placeholder. It should not be the case that an address becomes old/vetted
// upon a single successful connection.
func (r *PEXReactor) ensurePeers() {
	numOutPeers, _, numDialing := r.Switch.NumPeers()
	numToDial := minNumOutboundPeers - (numOutPeers + numDialing)
	r.Logger.Info("Ensure peers", "numOutPeers", numOutPeers, "numDialing", numDialing, "numToDial", numToDial)
	if numToDial <= 0 {
		return
	}

	toDial := make(map[string]*NetAddress)

	// Try to pick numToDial addresses to dial.
	for i := 0; i < numToDial; i++ {
		// The purpose of newBias is to first prioritize old (more vetted) peers
		// when we have few connections, but to allow for new (less vetted) peers
		// if we already have many connections. This algorithm isn't perfect, but
		// it somewhat ensures that we prioritize connecting to more-vetted
		// peers.
		newBias := cmn.MinInt(numOutPeers, 8)*10 + 10
		var picked *NetAddress
		// Try to fetch a new peer 3 times.
		// This caps the maximum number of tries to 3 * numToDial.
		for j := 0; j < 3; j++ {
			try := r.book.PickAddress(newBias)
			if try == nil {
				break
			}
			_, alreadySelected := toDial[try.IP.String()]
			alreadyDialing := r.Switch.IsDialing(try)
			alreadyConnected := r.Switch.Peers().Has(try.IP.String())
			if alreadySelected || alreadyDialing || alreadyConnected {
				// r.Logger.Info("Cannot dial address", "addr", try,
				// 	"alreadySelected", alreadySelected,
				// 	"alreadyDialing", alreadyDialing,
				//  "alreadyConnected", alreadyConnected)
				continue
			} else {
				r.Logger.Info("Will dial address", "addr", try)
				picked = try
				break
			}
		}
		if picked == nil {
			continue
		}
		toDial[picked.IP.String()] = picked
	}

	// Dial picked addresses
	for _, item := range toDial {
		go func(picked *NetAddress) {
			_, err := r.Switch.DialPeerWithAddress(picked, false)
			if err != nil {
				r.book.MarkAttempt(picked)
			}
		}(item)
	}

	// If we need more addresses, pick a random peer and ask for more.
	if r.book.NeedMoreAddrs() {
		if peers := r.Switch.Peers().List(); len(peers) > 0 {
			i := rand.Int() % len(peers)
			peer := peers[i]
			r.Logger.Info("No addresses to dial. Sending pexRequest to random peer", "peer", peer)
			r.RequestPEX(peer)
		}
	}
}

func (r *PEXReactor) flushMsgCountByPeer() {
	ticker := time.NewTicker(msgCountByPeerFlushInterval)

	for {
		select {
		case <-ticker.C:
			r.msgCountByPeer.Clear()
		case <-r.Quit:
			ticker.Stop()
			return
		}
	}
}

//-----------------------------------------------------------------------------
// Messages

const (
	msgTypeRequest = byte(0x01)
	msgTypeAddrs   = byte(0x02)
)

// PexMessage is a primary type for PEX messages. Underneath, it could contain
// either pexRequestMessage, or pexAddrsMessage messages.
type PexMessage interface{}

var _ = wire.RegisterInterface(
	struct{ PexMessage }{},
	wire.ConcreteType{&pexRequestMessage{}, msgTypeRequest},
	wire.ConcreteType{&pexAddrsMessage{}, msgTypeAddrs},
)

// DecodeMessage implements interface registered above.
func DecodeMessage(bz []byte) (msgType byte, msg PexMessage, err error) {
	msgType = bz[0]
	n := new(int)
	r := bytes.NewReader(bz)
	msg = wire.ReadBinary(struct{ PexMessage }{}, r, maxPexMessageSize, n, &err).(struct{ PexMessage }).PexMessage
	return
}

/*
A pexRequestMessage requests additional peer addresses.
*/
type pexRequestMessage struct {
}

func (m *pexRequestMessage) String() string {
	return "[pexRequest]"
}

/*
A message with announced peer addresses.
*/
type pexAddrsMessage struct {
	Addrs []*NetAddress
}

func (m *pexAddrsMessage) String() string {
	return fmt.Sprintf("[pexAddrs %v]", m.Addrs)
}
<commit_msg>p2p/pex: simplify ensurePeers<commit_after>package p2p

import (
	"bytes"
	"fmt"
	"math/rand"
	"reflect"
	"time"

	wire "github.com/tendermint/go-wire"
	cmn "github.com/tendermint/tmlibs/common"
)

const (
	// PexChannel is a channel for PEX messages
	PexChannel = byte(0x00)

	// period to ensure peers connected
	defaultEnsurePeersPeriod = 30 * time.Second
	minNumOutboundPeers      = 10
	maxPexMessageSize        = 1048576 // 1MB

	// maximum messages one peer can send to us during `msgCountByPeerFlushInterval`
	defaultMaxMsgCountByPeer    = 1000
	msgCountByPeerFlushInterval = 1 * time.Hour
)

// PEXReactor handles PEX (peer exchange) and ensures that an
// adequate number of peers are connected to the switch.
//
// It uses `AddrBook` (address book) to store `NetAddress`es of the peers.
//
// ## Preventing abuse
//
// For now, it just limits the number of messages from one peer to
// `defaultMaxMsgCountByPeer` messages per `msgCountByPeerFlushInterval` (1000
// msg/hour).
//
// NOTE [2017-01-17]:
//   Limiting is fine for now. Maybe down the road we want to keep track of the
//   quality of peer messages so if peerA keeps telling us about peers we can't
//   connect to then maybe we should care less about peerA. But I don't think
//   that kind of complexity is priority right now.
type PEXReactor struct {
	BaseReactor

	book              *AddrBook
	ensurePeersPeriod time.Duration

	// tracks message count by peer, so we can prevent abuse
	msgCountByPeer    *cmn.CMap
	maxMsgCountByPeer uint16
}

// NewPEXReactor creates new PEX reactor.
func NewPEXReactor(b *AddrBook) *PEXReactor {
	r := &PEXReactor{
		book:              b,
		ensurePeersPeriod: defaultEnsurePeersPeriod,
		msgCountByPeer:    cmn.NewCMap(),
		maxMsgCountByPeer: defaultMaxMsgCountByPeer,
	}
	r.BaseReactor = *NewBaseReactor("PEXReactor", r)
	return r
}

// OnStart implements BaseService
func (r *PEXReactor) OnStart() error {
	r.BaseReactor.OnStart()
	r.book.Start()
	go r.ensurePeersRoutine()
	go r.flushMsgCountByPeer()
	return nil
}

// OnStop implements BaseService
func (r *PEXReactor) OnStop() {
	r.BaseReactor.OnStop()
	r.book.Stop()
}

// GetChannels implements Reactor
func (r *PEXReactor) GetChannels() []*ChannelDescriptor {
	return []*ChannelDescriptor{
		&ChannelDescriptor{
			ID:                PexChannel,
			Priority:          1,
			SendQueueCapacity: 10,
		},
	}
}

// AddPeer implements Reactor by adding peer to the address book (if inbound)
// or by requesting more addresses (if outbound).
func (r *PEXReactor) AddPeer(p Peer) {
	if p.IsOutbound() {
		// For outbound peers, the address is already in the books.
		// Either it was added in DialSeeds or when we
		// received the peer's address in r.Receive
		if r.book.NeedMoreAddrs() {
			r.RequestPEX(p)
		}
	} else { // For inbound connections, the peer is its own source
		addr, err := NewNetAddressString(p.NodeInfo().ListenAddr)
		if err != nil {
			// peer gave us a bad ListenAddr. TODO: punish
			r.Logger.Error("Error in AddPeer: invalid peer address", "addr", p.NodeInfo().ListenAddr, "err", err)
			return
		}
		r.book.AddAddress(addr, addr)
	}
}

// RemovePeer implements Reactor.
func (r *PEXReactor) RemovePeer(p Peer, reason interface{}) {
	// If we aren't keeping track of local temp data for each peer here, then we
	// don't have to do anything.
}

// Receive implements Reactor by handling incoming PEX messages.
func (r *PEXReactor) Receive(chID byte, src Peer, msgBytes []byte) {
	srcAddrStr := src.NodeInfo().RemoteAddr
	srcAddr, err := NewNetAddressString(srcAddrStr)
	if err != nil {
		// this should never happen. TODO: cancel conn
		r.Logger.Error("Error in Receive: invalid peer address", "addr", srcAddrStr, "err", err)
		return
	}

	r.IncrementMsgCountForPeer(srcAddrStr)
	if r.ReachedMaxMsgCountForPeer(srcAddrStr) {
		r.Logger.Error("Maximum number of messages reached for peer", "peer", srcAddrStr)
		// TODO remove src from peers?
		return
	}

	_, msg, err := DecodeMessage(msgBytes)
	if err != nil {
		r.Logger.Error("Error decoding message", "err", err)
		return
	}
	r.Logger.Info("Received message", "msg", msg)

	switch msg := msg.(type) {
	case *pexRequestMessage:
		// src requested some peers.
		// NOTE: we might send an empty selection
		r.SendAddrs(src, r.book.GetSelection())
	case *pexAddrsMessage:
		// We received some peer addresses from src.
		// TODO: (We don't want to get spammed with bad peers)
		for _, addr := range msg.Addrs {
			if addr != nil {
				r.book.AddAddress(addr, srcAddr)
			}
		}
	default:
		r.Logger.Error(fmt.Sprintf("Unknown message type %v", reflect.TypeOf(msg)))
	}
}

// RequestPEX asks peer for more addresses.
func (r *PEXReactor) RequestPEX(p Peer) {
	p.Send(PexChannel, struct{ PexMessage }{&pexRequestMessage{}})
}

// SendAddrs sends addrs to the peer.
func (r *PEXReactor) SendAddrs(p Peer, addrs []*NetAddress) {
	p.Send(PexChannel, struct{ PexMessage }{&pexAddrsMessage{Addrs: addrs}})
}

// SetEnsurePeersPeriod sets period to ensure peers connected.
func (r *PEXReactor) SetEnsurePeersPeriod(d time.Duration) {
	r.ensurePeersPeriod = d
}

// SetMaxMsgCountByPeer sets maximum messages one peer can send to us during 'msgCountByPeerFlushInterval'.
func (r *PEXReactor) SetMaxMsgCountByPeer(v uint16) {
	r.maxMsgCountByPeer = v
}

// ReachedMaxMsgCountForPeer returns true if we received too many
// messages from peer with address `addr`.
// NOTE: assumes the value in the CMap is non-nil
func (r *PEXReactor) ReachedMaxMsgCountForPeer(addr string) bool {
	return r.msgCountByPeer.Get(addr).(uint16) >= r.maxMsgCountByPeer
}

// Increment or initialize the msg count for the peer in the CMap
func (r *PEXReactor) IncrementMsgCountForPeer(addr string) {
	var count uint16
	countI := r.msgCountByPeer.Get(addr)
	if countI != nil {
		count = countI.(uint16)
	}
	count++
	r.msgCountByPeer.Set(addr, count)
}

// Ensures that sufficient peers are connected. (continuous)
func (r *PEXReactor) ensurePeersRoutine() {
	// Randomize when routine starts
	ensurePeersPeriodMs := r.ensurePeersPeriod.Nanoseconds() / 1e6
	time.Sleep(time.Duration(rand.Int63n(ensurePeersPeriodMs)) * time.Millisecond)

	// fire once immediately.
	r.ensurePeers()

	// fire periodically
	ticker := time.NewTicker(r.ensurePeersPeriod)

	for {
		select {
		case <-ticker.C:
			r.ensurePeers()
		case <-r.Quit:
			ticker.Stop()
			return
		}
	}
}

// ensurePeers ensures that sufficient peers are connected. (once)
//
// Old bucket / New bucket are arbitrary categories to denote whether an
// address is vetted or not, and this needs to be determined over time via a
// heuristic that we haven't perfected yet, or, perhaps is manually edited by
// the node operator. It should not be used to compute what addresses are
// already connected or not.
//
// TODO Basically, we need to work harder on our good-peer/bad-peer marking.
// What we're currently doing in terms of marking good/bad peers is just a
// placeholder. It should not be the case that an address becomes old/vetted
// upon a single successful connection.
func (r *PEXReactor) ensurePeers() {
	numOutPeers, _, numDialing := r.Switch.NumPeers()
	numToDial := minNumOutboundPeers - (numOutPeers + numDialing)
	r.Logger.Info("Ensure peers", "numOutPeers", numOutPeers, "numDialing", numDialing, "numToDial", numToDial)
	if numToDial <= 0 {
		return
	}

	// bias to prefer more vetted peers when we have fewer connections.
	// not perfect, but somewhate ensures that we prioritize connecting to more-vetted
	newBias := cmn.MinInt(numOutPeers, 8)*10 + 10

	toDial := make(map[string]*NetAddress)
	// Try maxAttempts times to pick numToDial addresses to dial
	maxAttempts := numToDial * 3
	for i := 0; i < maxAttempts && len(toDial) < numToDial; i++ {
		try := r.book.PickAddress(newBias)
		if try == nil {
			continue
		}
		_, alreadySelected := toDial[try.IP.String()]
		alreadyDialing := r.Switch.IsDialing(try)
		alreadyConnected := r.Switch.Peers().Has(try.IP.String())
		if alreadySelected || alreadyDialing || alreadyConnected {
			continue
		}
		r.Logger.Info("Will dial address", "addr", try)
		toDial[try.IP.String()] = try
	}

	// Dial picked addresses
	for _, item := range toDial {
		go func(picked *NetAddress) {
			_, err := r.Switch.DialPeerWithAddress(picked, false)
			if err != nil {
				r.book.MarkAttempt(picked)
			}
		}(item)
	}

	// If we need more addresses, pick a random peer and ask for more.
	if r.book.NeedMoreAddrs() {
		if peers := r.Switch.Peers().List(); len(peers) > 0 {
			i := rand.Int() % len(peers)
			peer := peers[i]
			r.Logger.Info("No addresses to dial. Sending pexRequest to random peer", "peer", peer)
			r.RequestPEX(peer)
		}
	}
}

func (r *PEXReactor) flushMsgCountByPeer() {
	ticker := time.NewTicker(msgCountByPeerFlushInterval)

	for {
		select {
		case <-ticker.C:
			r.msgCountByPeer.Clear()
		case <-r.Quit:
			ticker.Stop()
			return
		}
	}
}

//-----------------------------------------------------------------------------
// Messages

const (
	msgTypeRequest = byte(0x01)
	msgTypeAddrs   = byte(0x02)
)

// PexMessage is a primary type for PEX messages. Underneath, it could contain
// either pexRequestMessage, or pexAddrsMessage messages.
type PexMessage interface{}

var _ = wire.RegisterInterface(
	struct{ PexMessage }{},
	wire.ConcreteType{&pexRequestMessage{}, msgTypeRequest},
	wire.ConcreteType{&pexAddrsMessage{}, msgTypeAddrs},
)

// DecodeMessage implements interface registered above.
func DecodeMessage(bz []byte) (msgType byte, msg PexMessage, err error) {
	msgType = bz[0]
	n := new(int)
	r := bytes.NewReader(bz)
	msg = wire.ReadBinary(struct{ PexMessage }{}, r, maxPexMessageSize, n, &err).(struct{ PexMessage }).PexMessage
	return
}

/*
A pexRequestMessage requests additional peer addresses.
*/
type pexRequestMessage struct {
}

func (m *pexRequestMessage) String() string {
	return "[pexRequest]"
}

/*
A message with announced peer addresses.
*/
type pexAddrsMessage struct {
	Addrs []*NetAddress
}

func (m *pexAddrsMessage) String() string {
	return fmt.Sprintf("[pexAddrs %v]", m.Addrs)
}
<|endoftext|>


<commit_before>// dwms is a dwm status generator.
//
// Assign custom values to exported identifiers in config.go to configure.
package main

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"syscall"
	"time"

	"github.com/BurntSushi/xgb"
	"github.com/BurntSushi/xgb/xproto"
)

type statusFunc func() string

const (
	battSysPath = "/sys/class/power_supply"
	netSysPath  = "/sys/class/net"
)

var (
	ssidRE    = regexp.MustCompile(`SSID:\s+(.*)`)
	bitrateRE = regexp.MustCompile(`tx bitrate:\s+(\d+)`)
	signalRE  = regexp.MustCompile(`signal:\s+(-\d+)`)
	amixerRE  = regexp.MustCompile(`\[(\d+)%\].*\[(\w+)\]`)
	xconn     *xgb.Conn
	xroot     xproto.Window
)

var WifiFmt = func(dev, ssid string, bitrate, signal int, up bool) string {
	if !up {
		return ""
	}
	return fmt.Sprintf("ω%s/%d/%d", ssid, bitrate, signal)
}

var WiredFmt = func(dev string, speed int, up bool) string {
	if !up {
		return ""
	}
	return "ε" + strconv.Itoa(speed)
}

var NetFmt = func(devs []string) string {
	return strings.Join(filterEmpty(devs), " ")
}

var BatteryDevFmt = func(pct int, state string) string {
	return strconv.Itoa(pct) + map[string]string{"Charging": "+", "Discharging": "-"}[state]
}

var BatteryFmt = func(bats []string) string {
	return "β" + strings.Join(bats, "/")
}

var AudioFmt = func(vol int, muted bool) string {
	return map[bool]string{false: "ν", true: "μ"}[muted] + strconv.Itoa(vol)
}

var TimeFmt = func(t time.Time) string {
	return t.Format("τ01/02-15:04")
}

var StatusFmt = func(stats []string) string {
	return " " + strings.Join(filterEmpty(stats), " ") + " "
}

func wifiStatus(dev string) (string, int, int) {
	ssid, bitrate, signal := "", 0, 0
	out, err := exec.Command("iw", "dev", dev, "link").Output()
	if err != nil {
		return ssid, bitrate, signal
	}
	if match := ssidRE.FindSubmatch(out); len(match) >= 2 {
		ssid = string(match[1])
	}
	if match := bitrateRE.FindSubmatch(out); len(match) >= 2 {
		if br, err := strconv.Atoi(string(match[1])); err == nil {
			bitrate = br
		}
	}
	if match := signalRE.FindSubmatch(out); len(match) >= 2 {
		if sig, err := strconv.Atoi(string(match[1])); err == nil {
			signal = sig
		}
	}
	return ssid, bitrate, signal
}

func wiredStatus(dev string) int {
	speed, err := sysfsIntVal(filepath.Join(netSysPath, dev, "speed"))
	if err != nil {
		return 0
	}
	return speed
}

func netDevStatus(dev string) string {
	status, err := sysfsStringVal(filepath.Join(netSysPath, dev, "operstate"))
	up := err == nil && status == "up"
	if _, err = os.Stat(filepath.Join(netSysPath, dev, "wireless")); err == nil {
		ssid, bitrate, signal := wifiStatus(dev)
		return WifiFmt(dev, ssid, bitrate, signal, up)
	}
	speed := wiredStatus(dev)
	return WiredFmt(dev, speed, up)
}

func netStatus(devs ...string) statusFunc {
	return func() string {
		var netStats []string
		for _, dev := range devs {
			netStats = append(netStats, netDevStatus(dev))
		}
		return NetFmt(netStats)
	}
}

func batteryDevStatus(batt string) string {
	pct, err := sysfsIntVal(filepath.Join(battSysPath, batt, "capacity"))
	if err != nil {
		return Unknown
	}
	status, err := sysfsStringVal(filepath.Join(battSysPath, batt, "status"))
	if err != nil {
		return Unknown
	}
	return BatteryDevFmt(pct, status)
}

func batteryStatus(batts ...string) statusFunc {
	return func() string {
		var battStats []string
		for _, batt := range batts {
			battStats = append(battStats, batteryDevStatus(batt))
		}
		return BatteryFmt(battStats)
	}
}

func audioStatus(args ...string) statusFunc {
	args = append(args, []string{"get", "Master"}...)
	return func() string {
		out, err := exec.Command("amixer", args...).Output()
		if err != nil {
			return Unknown
		}
		match := amixerRE.FindSubmatch(out)
		if len(match) < 3 {
			return Unknown
		}
		vol, err := strconv.Atoi(string(match[1]))
		if err != nil {
			return Unknown
		}
		muted := (string(match[2]) == "off")
		return AudioFmt(vol, muted)
	}
}

func timeStatus() string {
	return TimeFmt(time.Now())
}

func status() string {
	var stats []string
	for _, item := range Items {
		stats = append(stats, item())
	}
	return StatusFmt(stats)
}

func setStatus(statusText string) {
	xproto.ChangeProperty(xconn, xproto.PropModeReplace, xroot, xproto.AtomWmName,
		xproto.AtomString, 8, uint32(len(statusText)), []byte(statusText))
}

func sysfsIntVal(path string) (int, error) {
	data, err := ioutil.ReadFile(path)
	if err != nil {
		return 0, err
	}
	val, err := strconv.Atoi(string(bytes.TrimSpace(data)))
	if err != nil {
		return 0, err
	}
	return val, nil
}

func sysfsStringVal(path string) (string, error) {
	data, err := ioutil.ReadFile(path)
	if err != nil {
		return "", err
	}
	return string(bytes.TrimSpace(data)), nil
}

func filterEmpty(strings []string) []string {
	filtStrings := strings[:0]
	for _, str := range strings {
		if str != "" {
			filtStrings = append(filtStrings, str)
		}
	}
	return filtStrings
}

func run() {
	setStatus(status())
	defer setStatus("") // cleanup
	sigs := make(chan os.Signal, 1)
	signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM, syscall.SIGUSR1)
	update := time.Tick(UpdatePeriod)
	for {
		select {
		case sig := <-sigs:
			switch sig {
			case syscall.SIGUSR1:
				setStatus(status())
			default:
				return
			}
		case <-update:
			setStatus(status())
		}
	}
}

func main() {
	var err error
	xconn, err = xgb.NewConn()
	if err != nil {
		log.Fatal(err)
	}
	defer xconn.Close()
	xroot = xproto.Setup(xconn).DefaultScreen(xconn).Root
	run()
}
<commit_msg>Use plain ASCII "icons" instead of Greek characters.<commit_after>// dwms is a dwm status generator.
//
// Assign custom values to exported identifiers in config.go to configure.
package main

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"syscall"
	"time"

	"github.com/BurntSushi/xgb"
	"github.com/BurntSushi/xgb/xproto"
)

type statusFunc func() string

const (
	battSysPath = "/sys/class/power_supply"
	netSysPath  = "/sys/class/net"
)

var (
	ssidRE    = regexp.MustCompile(`SSID:\s+(.*)`)
	bitrateRE = regexp.MustCompile(`tx bitrate:\s+(\d+)`)
	signalRE  = regexp.MustCompile(`signal:\s+(-\d+)`)
	amixerRE  = regexp.MustCompile(`\[(\d+)%\].*\[(\w+)\]`)
	xconn     *xgb.Conn
	xroot     xproto.Window
)

var WifiFmt = func(dev, ssid string, bitrate, signal int, up bool) string {
	if !up {
		return ""
	}
	return fmt.Sprintf("\\/%s/%d/%d", ssid, bitrate, signal)
}

var WiredFmt = func(dev string, speed int, up bool) string {
	if !up {
		return ""
	}
	return "[=" + strconv.Itoa(speed)
}

var NetFmt = func(devs []string) string {
	return strings.Join(filterEmpty(devs), " ")
}

var BatteryDevFmt = func(pct int, state string) string {
	return strconv.Itoa(pct) + map[string]string{"Charging": "+", "Discharging": "-"}[state]
}

var BatteryFmt = func(bats []string) string {
	return "[]" + strings.Join(bats, "/")
}

var AudioFmt = func(vol int, muted bool) string {
	return map[bool]string{false: "<)", true: "<X"}[muted] + strconv.Itoa(vol)
}

var TimeFmt = func(t time.Time) string {
	return t.Format("01/02-15:04")
}

var StatusFmt = func(stats []string) string {
	return " " + strings.Join(filterEmpty(stats), " ") + " "
}

func wifiStatus(dev string) (string, int, int) {
	ssid, bitrate, signal := "", 0, 0
	out, err := exec.Command("iw", "dev", dev, "link").Output()
	if err != nil {
		return ssid, bitrate, signal
	}
	if match := ssidRE.FindSubmatch(out); len(match) >= 2 {
		ssid = string(match[1])
	}
	if match := bitrateRE.FindSubmatch(out); len(match) >= 2 {
		if br, err := strconv.Atoi(string(match[1])); err == nil {
			bitrate = br
		}
	}
	if match := signalRE.FindSubmatch(out); len(match) >= 2 {
		if sig, err := strconv.Atoi(string(match[1])); err == nil {
			signal = sig
		}
	}
	return ssid, bitrate, signal
}

func wiredStatus(dev string) int {
	speed, err := sysfsIntVal(filepath.Join(netSysPath, dev, "speed"))
	if err != nil {
		return 0
	}
	return speed
}

func netDevStatus(dev string) string {
	status, err := sysfsStringVal(filepath.Join(netSysPath, dev, "operstate"))
	up := err == nil && status == "up"
	if _, err = os.Stat(filepath.Join(netSysPath, dev, "wireless")); err == nil {
		ssid, bitrate, signal := wifiStatus(dev)
		return WifiFmt(dev, ssid, bitrate, signal, up)
	}
	speed := wiredStatus(dev)
	return WiredFmt(dev, speed, up)
}

func netStatus(devs ...string) statusFunc {
	return func() string {
		var netStats []string
		for _, dev := range devs {
			netStats = append(netStats, netDevStatus(dev))
		}
		return NetFmt(netStats)
	}
}

func batteryDevStatus(batt string) string {
	pct, err := sysfsIntVal(filepath.Join(battSysPath, batt, "capacity"))
	if err != nil {
		return Unknown
	}
	status, err := sysfsStringVal(filepath.Join(battSysPath, batt, "status"))
	if err != nil {
		return Unknown
	}
	return BatteryDevFmt(pct, status)
}

func batteryStatus(batts ...string) statusFunc {
	return func() string {
		var battStats []string
		for _, batt := range batts {
			battStats = append(battStats, batteryDevStatus(batt))
		}
		return BatteryFmt(battStats)
	}
}

func audioStatus(args ...string) statusFunc {
	args = append(args, []string{"get", "Master"}...)
	return func() string {
		out, err := exec.Command("amixer", args...).Output()
		if err != nil {
			return Unknown
		}
		match := amixerRE.FindSubmatch(out)
		if len(match) < 3 {
			return Unknown
		}
		vol, err := strconv.Atoi(string(match[1]))
		if err != nil {
			return Unknown
		}
		muted := (string(match[2]) == "off")
		return AudioFmt(vol, muted)
	}
}

func timeStatus() string {
	return TimeFmt(time.Now())
}

func status() string {
	var stats []string
	for _, item := range Items {
		stats = append(stats, item())
	}
	return StatusFmt(stats)
}

func setStatus(statusText string) {
	xproto.ChangeProperty(xconn, xproto.PropModeReplace, xroot, xproto.AtomWmName,
		xproto.AtomString, 8, uint32(len(statusText)), []byte(statusText))
}

func sysfsIntVal(path string) (int, error) {
	data, err := ioutil.ReadFile(path)
	if err != nil {
		return 0, err
	}
	val, err := strconv.Atoi(string(bytes.TrimSpace(data)))
	if err != nil {
		return 0, err
	}
	return val, nil
}

func sysfsStringVal(path string) (string, error) {
	data, err := ioutil.ReadFile(path)
	if err != nil {
		return "", err
	}
	return string(bytes.TrimSpace(data)), nil
}

func filterEmpty(strings []string) []string {
	filtStrings := strings[:0]
	for _, str := range strings {
		if str != "" {
			filtStrings = append(filtStrings, str)
		}
	}
	return filtStrings
}

func run() {
	setStatus(status())
	defer setStatus("") // cleanup
	sigs := make(chan os.Signal, 1)
	signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM, syscall.SIGUSR1)
	update := time.Tick(UpdatePeriod)
	for {
		select {
		case sig := <-sigs:
			switch sig {
			case syscall.SIGUSR1:
				setStatus(status())
			default:
				return
			}
		case <-update:
			setStatus(status())
		}
	}
}

func main() {
	var err error
	xconn, err = xgb.NewConn()
	if err != nil {
		log.Fatal(err)
	}
	defer xconn.Close()
	xroot = xproto.Setup(xconn).DefaultScreen(xconn).Root
	run()
}
<|endoftext|>