<commit_before>package endpoints

import (
	"regexp"
)

func Partitions() []Partition {
	ps := make([]Partition, len(partitions))
	for i := 0; i < len(partitions); i++ {
		ps[i] = partitions[i].Partition()
	}
	return ps
}

type Partition struct {
	id string
	p  *partition
}

func (p Partition) Regions() []string {
	rs := make([]string, len(p.p.regions))
	for i, v := range p.p.regions {
		rs[i] = v
	}
	return rs
}

func PartitionForRegion(regionID string) string {
	for _, p := range partitions {
		if p.regionRegex.MatchString(regionID) {
			return p.id
		}
	}

	return ""
}

type partition struct {
	id          string
	regionRegex *regexp.Regexp
	regions     []string
}

func (p partition) Partition() Partition {
	return Partition{
		id: p.id,
		p:  &p,
	}
}

// TODO: this should be generated from the AWS SDK source data
// Data from https://github.com/aws/aws-sdk-go/blob/main/models/endpoints/endpoints.json.
var partitions = []partition{
	{
		id:          "aws",
		regionRegex: regexp.MustCompile(`^(us|eu|ap|sa|ca|me|af)\-\w+\-\d+$`),
		regions: []string{
			"af-south-1",     // Africa (Cape Town).
			"ap-east-1",      // Asia Pacific (Hong Kong).
			"ap-northeast-1", // Asia Pacific (Tokyo).
			"ap-northeast-2", // Asia Pacific (Seoul).
			"ap-northeast-3", // Asia Pacific (Osaka).
			"ap-south-1",     // Asia Pacific (Mumbai).
			"ap-southeast-1", // Asia Pacific (Singapore).
			"ap-southeast-2", // Asia Pacific (Sydney).
			"ap-southeast-3", // Asia Pacific (Jakarta).
			"ca-central-1",   // Canada (Central).
			"eu-central-1",   // Europe (Frankfurt).
			"eu-north-1",     // Europe (Stockholm).
			"eu-south-1",     // Europe (Milan).
			"eu-west-1",      // Europe (Ireland).
			"eu-west-2",      // Europe (London).
			"eu-west-3",      // Europe (Paris).
			"me-south-1",     // Middle East (Bahrain).
			"sa-east-1",      // South America (Sao Paulo).
			"us-east-1",      // US East (N. Virginia).
			"us-east-2",      // US East (Ohio).
			"us-west-1",      // US West (N. California).
			"us-west-2",      // US West (Oregon).
		},
	},
	{
		id:          "aws-cn",
		regionRegex: regexp.MustCompile(`^cn\-\w+\-\d+$`),
		regions: []string{
			"cn-north-1",     // China (Beijing).
			"cn-northwest-1", // China (Ningxia).
		},
	},
	{
		id:          "aws-us-gov",
		regionRegex: regexp.MustCompile(`^us\-gov\-\w+\-\d+$`),
		regions: []string{
			"us-gov-east-1", // AWS GovCloud (US-East).
			"us-gov-west-1", // AWS GovCloud (US-West).
		},
	},
	{
		id:          "aws-iso",
		regionRegex: regexp.MustCompile(`^us\-iso\-\w+\-\d+$`),
		regions: []string{
			"us-iso-east-1", // US ISO East.
			"us-iso-west-1", // US ISO WEST.
		},
	},
	{
		id:          "aws-iso-b",
		regionRegex: regexp.MustCompile(`^us\-isob\-\w+\-\d+$`),
		regions: []string{
			"us-isob-east-1", // US ISOB East (Ohio).
		},
	},
}
<commit_msg>Fixes linting issue<commit_after>package endpoints

import (
	"regexp"
)

func Partitions() []Partition {
	ps := make([]Partition, len(partitions))
	for i := 0; i < len(partitions); i++ {
		ps[i] = partitions[i].Partition()
	}
	return ps
}

type Partition struct {
	id string
	p  *partition
}

func (p Partition) Regions() []string {
	rs := make([]string, len(p.p.regions))
	copy(rs, p.p.regions)
	return rs
}

func PartitionForRegion(regionID string) string {
	for _, p := range partitions {
		if p.regionRegex.MatchString(regionID) {
			return p.id
		}
	}

	return ""
}

type partition struct {
	id          string
	regionRegex *regexp.Regexp
	regions     []string
}

func (p partition) Partition() Partition {
	return Partition{
		id: p.id,
		p:  &p,
	}
}

// TODO: this should be generated from the AWS SDK source data
// Data from https://github.com/aws/aws-sdk-go/blob/main/models/endpoints/endpoints.json.
var partitions = []partition{
	{
		id:          "aws",
		regionRegex: regexp.MustCompile(`^(us|eu|ap|sa|ca|me|af)\-\w+\-\d+$`),
		regions: []string{
			"af-south-1",     // Africa (Cape Town).
			"ap-east-1",      // Asia Pacific (Hong Kong).
			"ap-northeast-1", // Asia Pacific (Tokyo).
			"ap-northeast-2", // Asia Pacific (Seoul).
			"ap-northeast-3", // Asia Pacific (Osaka).
			"ap-south-1",     // Asia Pacific (Mumbai).
			"ap-southeast-1", // Asia Pacific (Singapore).
			"ap-southeast-2", // Asia Pacific (Sydney).
			"ap-southeast-3", // Asia Pacific (Jakarta).
			"ca-central-1",   // Canada (Central).
			"eu-central-1",   // Europe (Frankfurt).
			"eu-north-1",     // Europe (Stockholm).
			"eu-south-1",     // Europe (Milan).
			"eu-west-1",      // Europe (Ireland).
			"eu-west-2",      // Europe (London).
			"eu-west-3",      // Europe (Paris).
			"me-south-1",     // Middle East (Bahrain).
			"sa-east-1",      // South America (Sao Paulo).
			"us-east-1",      // US East (N. Virginia).
			"us-east-2",      // US East (Ohio).
			"us-west-1",      // US West (N. California).
			"us-west-2",      // US West (Oregon).
		},
	},
	{
		id:          "aws-cn",
		regionRegex: regexp.MustCompile(`^cn\-\w+\-\d+$`),
		regions: []string{
			"cn-north-1",     // China (Beijing).
			"cn-northwest-1", // China (Ningxia).
		},
	},
	{
		id:          "aws-us-gov",
		regionRegex: regexp.MustCompile(`^us\-gov\-\w+\-\d+$`),
		regions: []string{
			"us-gov-east-1", // AWS GovCloud (US-East).
			"us-gov-west-1", // AWS GovCloud (US-West).
		},
	},
	{
		id:          "aws-iso",
		regionRegex: regexp.MustCompile(`^us\-iso\-\w+\-\d+$`),
		regions: []string{
			"us-iso-east-1", // US ISO East.
			"us-iso-west-1", // US ISO WEST.
		},
	},
	{
		id:          "aws-iso-b",
		regionRegex: regexp.MustCompile(`^us\-isob\-\w+\-\d+$`),
		regions: []string{
			"us-isob-east-1", // US ISOB East (Ohio).
		},
	},
}
<|endoftext|>


<commit_before>package coordinate

import (
	"fmt"
	"math"
	"math/rand"
)

// Coordinate is a Vivaldi network coordinate.  Refer to the Vivaldi paper for a detailed
// description.
type Coordinate struct {
	// The unit of time used for the following fields is millisecond
	// The fields need to be public for them to be serializable
	Vec        []float64
	Height     float64
	Err        float64
	Adjustment float64
}

// NewCoordinate creates a new network coordinate located at the origin
func NewCoordinate(config *Config) (*Coordinate, error) {
	if err := config.Verify(); err != nil {
		return nil, err
	}

	return &Coordinate{
		Vec:        make([]float64, config.Dimension),
		Height:     config.MinHeightThreshold,
		Err:        config.VivaldiError,
		Adjustment: 0,
	}, nil
}

// Clone returns a copy of the receiver
func (c *Coordinate) Clone() *Coordinate {
	vec := make([]float64, len(c.Vec))
	copy(vec, c.Vec)
	return &Coordinate{
		Vec:        vec,
		Height:     c.Height,
		Err:        c.Err,
		Adjustment: c.Adjustment,
	}
}

// Add is used to add up two coordinates, returning the sum
func (c *Coordinate) Add(other *Coordinate, conf *Config) (*Coordinate, error) {
	if len(c.Vec) != len(other.Vec) {
		return nil, fmt.Errorf("adding two coordinates that have different dimensions:\n%+v\n%+v", c, other)
	} else {
		ret, err := NewCoordinate(conf)
		if err != nil {
			return nil, err
		}

		if ret.Height < conf.MinHeightThreshold {
			ret.Height = conf.MinHeightThreshold
		}

		for i, _ := range c.Vec {
			ret.Vec[i] = c.Vec[i] + other.Vec[i]
		}

		return ret, nil
	}
}

// Sub is used to subtract the second coordinate from the first, returning the diff
func (c *Coordinate) Sub(other *Coordinate, conf *Config) (*Coordinate, error) {
	if len(c.Vec) != len(other.Vec) {
		return nil, fmt.Errorf("subtracting two coordinates that have different dimensions:\n%+v\n%+v", c, other)
	} else {
		ret, err := NewCoordinate(conf)
		if err != nil {
			return nil, err
		}

		ret.Height = c.Height + other.Height

		for i, _ := range c.Vec {
			ret.Vec[i] = c.Vec[i] - other.Vec[i]
		}

		return ret, nil
	}
}

// Mul is used to multiply a given factor with the given coordinate, returning a new coordinate
func (c *Coordinate) Mul(factor float64, conf *Config) (*Coordinate, error) {
	ret, err := NewCoordinate(conf)
	if err != nil {
		return nil, err
	}

	ret.Height = c.Height * float64(factor)
	if ret.Height < conf.MinHeightThreshold {
		ret.Height = conf.MinHeightThreshold
	}

	for i, _ := range c.Vec {
		ret.Vec[i] = c.Vec[i] * float64(factor)
	}

	return ret, nil
}

// DistanceTo returns the distance between the receiver and the given coordinate
func (c *Coordinate) DistanceTo(coord *Coordinate, conf *Config) (float64, error) {
	tmp, err := c.Sub(coord, conf)
	if err != nil {
		return 0, err
	}

	sum := 0.0
	for i, _ := range tmp.Vec {
		sum += math.Pow(tmp.Vec[i], 2)
	}

	return math.Sqrt(sum) + tmp.Height, nil
}

// DirectionTo returns a coordinate other represents a unit-length Vector, which represents
// the direction from the receiver to the given coordinate.  In case the two coordinates are
// located together, a random direction is returned.
func (c *Coordinate) DirectionTo(coord *Coordinate, conf *Config) (*Coordinate, error) {
	tmp, err := c.Sub(coord, conf)
	if err != nil {
		return nil, err
	}

	dist, err := c.DistanceTo(coord, conf)
	if err != nil {
		return nil, err
	}

	if dist != c.Height+coord.Height {
		tmp, err = tmp.Mul(1.0/dist, conf)
		if err != nil {
			return nil, err
		}
		return tmp, nil
	} else {
		for i, _ := range c.Vec {
			tmp.Vec[i] = (10-0.1)*rand.Float64() + 0.1
		}

		origin, err := NewCoordinate(conf)
		if err != nil {
			return nil, err
		}

		dist, err = tmp.DistanceTo(origin, conf)
		if err != nil {
			return nil, err
		}

		tmp, err = tmp.Mul(1.0/dist, conf)
		if err != nil {
			return nil, err
		}

		return tmp, nil
	}
}
<commit_msg>Adjust other comments<commit_after>package coordinate

import (
	"errors"
	"math"
	"math/rand"
)

var (
	ErrDimensionalityConflict = errors.New("coordinate dimensionality does not match")
)

// Coordinate is a Vivaldi network coordinate.  Refer to the Vivaldi paper for a detailed
// description.
type Coordinate struct {
	// The unit of time used for the following fields is millisecond
	// The fields need to be public for them to be serializable
	Vec        []float64
	Height     float64
	Err        float64
	Adjustment float64
}

// NewCoordinate creates a new network coordinate located at the origin
func NewCoordinate(config *Config) (*Coordinate, error) {
	if err := config.Verify(); err != nil {
		return nil, err
	}

	return &Coordinate{
		Vec:        make([]float64, config.Dimension),
		Height:     config.MinHeightThreshold,
		Err:        config.VivaldiError,
		Adjustment: 0,
	}, nil
}

// Clone returns a copy of the receiver
func (c *Coordinate) Clone() *Coordinate {
	vec := make([]float64, len(c.Vec))
	copy(vec, c.Vec)
	return &Coordinate{
		Vec:        vec,
		Height:     c.Height,
		Err:        c.Err,
		Adjustment: c.Adjustment,
	}
}

// Add is used to add up two coordinates, returning the sum
func (c *Coordinate) Add(other *Coordinate, conf *Config) (*Coordinate, error) {
	if len(c.Vec) != len(other.Vec) {
		return nil, ErrDimensionalityConflict
	}
	ret, err := NewCoordinate(conf)
	if err != nil {
		return nil, err
	}

	if ret.Height < conf.MinHeightThreshold {
		ret.Height = conf.MinHeightThreshold
	}

	for i, _ := range c.Vec {
		ret.Vec[i] = c.Vec[i] + other.Vec[i]
	}

	return ret, nil
}

// Sub is used to subtract the second coordinate from the first, returning the diff
func (c *Coordinate) Sub(other *Coordinate, conf *Config) (*Coordinate, error) {
	if len(c.Vec) != len(other.Vec) {
		return nil, ErrDimensionalityConflict
	}
	ret, err := NewCoordinate(conf)
	if err != nil {
		return nil, err
	}

	ret.Height = c.Height + other.Height

	for i, _ := range c.Vec {
		ret.Vec[i] = c.Vec[i] - other.Vec[i]
	}

	return ret, nil
}

// Mul is used to multiply a given factor with the given coordinate, returning a new coordinate
func (c *Coordinate) Mul(factor float64, conf *Config) (*Coordinate, error) {
	ret, err := NewCoordinate(conf)
	if err != nil {
		return nil, err
	}

	ret.Height = c.Height * float64(factor)
	if ret.Height < conf.MinHeightThreshold {
		ret.Height = conf.MinHeightThreshold
	}

	for i, _ := range c.Vec {
		ret.Vec[i] = c.Vec[i] * float64(factor)
	}

	return ret, nil
}

// DistanceTo returns the distance between the receiver and the given coordinate
func (c *Coordinate) DistanceTo(coord *Coordinate, conf *Config) (float64, error) {
	tmp, err := c.Sub(coord, conf)
	if err != nil {
		return 0, err
	}

	sum := 0.0
	for i, _ := range tmp.Vec {
		sum += math.Pow(tmp.Vec[i], 2)
	}

	return math.Sqrt(sum) + tmp.Height, nil
}

// DirectionTo returns a coordinate other represents a unit-length Vector, which represents
// the direction from the receiver to the given coordinate.  In case the two coordinates are
// located together, a random direction is returned.
func (c *Coordinate) DirectionTo(coord *Coordinate, conf *Config) (*Coordinate, error) {
	tmp, err := c.Sub(coord, conf)
	if err != nil {
		return nil, err
	}

	dist, err := c.DistanceTo(coord, conf)
	if err != nil {
		return nil, err
	}

	if dist != c.Height+coord.Height {
		tmp, err = tmp.Mul(1.0/dist, conf)
		if err != nil {
			return nil, err
		}
		return tmp, nil
	} else {
		for i, _ := range c.Vec {
			tmp.Vec[i] = (10-0.1)*rand.Float64() + 0.1
		}

		origin, err := NewCoordinate(conf)
		if err != nil {
			return nil, err
		}

		dist, err = tmp.DistanceTo(origin, conf)
		if err != nil {
			return nil, err
		}

		tmp, err = tmp.Mul(1.0/dist, conf)
		if err != nil {
			return nil, err
		}

		return tmp, nil
	}
}
<|endoftext|>


<commit_before>// Copyright (c) 2021 - The Event Horizon 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 kafka

import (
	"crypto/rand"
	"encoding/hex"
	"fmt"
	"os"
	"testing"
	"time"

	eh "github.com/looplab/eventhorizon"
	"github.com/looplab/eventhorizon/eventbus"
)

func TestAddHandlerIntegration(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping integration test")
	}

	bus1, _, err := newTestEventBus("")
	if err != nil {
		t.Fatal("there should be no error:", err)
	}

	eventbus.TestAddHandler(t, bus1)
}

func TestEventBusIntegration(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping integration test")
	}

	bus1, appID, err := newTestEventBus("")
	if err != nil {
		t.Fatal("there should be no error:", err)
	}

	bus2, _, err := newTestEventBus(appID)
	if err != nil {
		t.Fatal("there should be no error:", err)
	}

	t.Logf("using topic: %s_events", appID)

	eventbus.AcceptanceTest(t, bus1, bus2, time.Second)
}

func TestEventBusLoadtest(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping integration test")
	}

	bus, appID, err := newTestEventBus("")
	if err != nil {
		t.Fatal("there should be no error:", err)
	}

	t.Logf("using topic: %s_events", appID)

	eventbus.LoadTest(t, bus)
}

func BenchmarkEventBus(b *testing.B) {
	bus, appID, err := newTestEventBus("")
	if err != nil {
		b.Fatal("there should be no error:", err)
	}

	b.Logf("using topic: %s_events", appID)

	eventbus.Benchmark(b, bus)
}

func newTestEventBus(appID string) (eh.EventBus, string, error) {
	// Connect to localhost if not running inside docker
	addr := os.Getenv("KAFKA_ADDR")
	if addr == "" {
		addr = "localhost:9093"
	}

	// Get a random app ID.
	if appID == "" {
		b := make([]byte, 8)
		if _, err := rand.Read(b); err != nil {
			return nil, "", fmt.Errorf("could not randomize app ID: %w", err)
		}

		appID = "app-" + hex.EncodeToString(b)
	}

	bus, err := NewEventBus(addr, appID)
	if err != nil {
		return nil, "", fmt.Errorf("could not create event bus: %w", err)
	}

	return bus, appID, nil
}
<commit_msg>test: Add Kafka reconnect case<commit_after>// Copyright (c) 2021 - The Event Horizon 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 kafka

import (
	"crypto/rand"
	"encoding/hex"
	"fmt"
	"os"
	"testing"
	"time"

	eh "github.com/looplab/eventhorizon"
	"github.com/looplab/eventhorizon/eventbus"
)

func TestAddHandlerIntegration(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping integration test")
	}

	bus1, appID, err := newTestEventBus("")
	if err != nil {
		t.Fatal("there should be no error:", err)
	}

	eventbus.TestAddHandler(t, bus1)

	if err := bus1.Close(); err != nil {
		t.Error("there should be no error:", err)
	}

	bus2, _, err := newTestEventBus(appID)
	if err != nil {
		t.Fatal("there should be no error:", err)
	}

	eventbus.TestAddHandler(t, bus2)

	if err := bus2.Close(); err != nil {
		t.Error("there should be no error:", err)
	}
}

func TestEventBusIntegration(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping integration test")
	}

	bus1, appID, err := newTestEventBus("")
	if err != nil {
		t.Fatal("there should be no error:", err)
	}

	bus2, _, err := newTestEventBus(appID)
	if err != nil {
		t.Fatal("there should be no error:", err)
	}

	t.Logf("using topic: %s_events", appID)

	eventbus.AcceptanceTest(t, bus1, bus2, time.Second)
}

func TestEventBusLoadtest(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping integration test")
	}

	bus, appID, err := newTestEventBus("")
	if err != nil {
		t.Fatal("there should be no error:", err)
	}

	t.Logf("using topic: %s_events", appID)

	eventbus.LoadTest(t, bus)
}

func BenchmarkEventBus(b *testing.B) {
	bus, appID, err := newTestEventBus("")
	if err != nil {
		b.Fatal("there should be no error:", err)
	}

	b.Logf("using topic: %s_events", appID)

	eventbus.Benchmark(b, bus)
}

func newTestEventBus(appID string) (eh.EventBus, string, error) {
	// Connect to localhost if not running inside docker
	addr := os.Getenv("KAFKA_ADDR")
	if addr == "" {
		addr = "localhost:9093"
	}

	// Get a random app ID.
	if appID == "" {
		b := make([]byte, 8)
		if _, err := rand.Read(b); err != nil {
			return nil, "", fmt.Errorf("could not randomize app ID: %w", err)
		}

		appID = "app-" + hex.EncodeToString(b)
	}

	bus, err := NewEventBus(addr, appID)
	if err != nil {
		return nil, "", fmt.Errorf("could not create event bus: %w", err)
	}

	return bus, appID, nil
}
<|endoftext|>


<commit_before>package service

import (
	"errors"
	"fmt"
	"net"
	"net/http"
	"os"
	"os/exec"
	"runtime"
	"strings"
	"syscall"
	"time"
)

type Service struct {
	URLTemplate string
	CmdTemplate []string
	url         string
	command     *exec.Cmd
}

type addressInfo struct {
	Address string
	Host    string
	Port    string
}

func (s *Service) URL() (string, error) {
	if s.command == nil {
		return "", errors.New("not running")
	}

	return s.url, nil
}

func (s *Service) Start(debug bool) error {
	if s.command != nil {
		return errors.New("already running")
	}

	address, err := freeAddress()
	if err != nil {
		return fmt.Errorf("failed to locate a free port: %s", err)
	}

	if s.url, err = buildURL(s.URLTemplate, address); err != nil {
		return fmt.Errorf("failed to parse URL: %s", err)
	}

	command, err := buildCommand(s.CmdTemplate, address)
	if err != nil {
		return fmt.Errorf("failed to parse command: %s", err)
	}

	if debug {
		command.Stdout = os.Stdout
		command.Stderr = os.Stderr
	}

	if err := command.Start(); err != nil {
		return fmt.Errorf("failed to run command: %s", err)
	}

	s.command = command

	return nil
}

func (s *Service) Stop() error {
	if s.command == nil {
		return errors.New("already stopped")
	}

	var err error
	if runtime.GOOS == "windows" {
		err = s.command.Process.Kill()
	} else {
		err = s.command.Process.Signal(syscall.SIGTERM)
	}
	if err != nil {
		return fmt.Errorf("failed to stop command: %s", err)
	}

	s.command.Wait()
	s.command = nil

	return nil
}

func freeAddress() (addressInfo, error) {
	listener, err := net.Listen("tcp", "127.0.0.1:0")
	if err != nil {
		return addressInfo{}, err
	}
	defer listener.Close()

	address := listener.Addr().String()
	addressParts := strings.SplitN(address, ":", 2)
	return addressInfo{address, addressParts[0], addressParts[1]}, nil
}

func (s *Service) WaitForBoot(timeout time.Duration) error {
	timeoutChan := time.After(timeout)
	failedChan := make(chan struct{}, 1)
	startedChan := make(chan struct{})

	go func() {
		up := s.checkStatus()
		for !up {
			select {
			case <-failedChan:
				return
			default:
				time.Sleep(500 * time.Millisecond)
				up = s.checkStatus()
			}
		}
		startedChan <- struct{}{}
	}()

	select {
	case <-timeoutChan:
		failedChan <- struct{}{}
		return errors.New("failed to start before timeout")
	case <-startedChan:
		return nil
	}
}

func (s *Service) checkStatus() bool {
	client := &http.Client{}
	request, _ := http.NewRequest("GET", fmt.Sprintf("%s/status", s.url), nil)
	response, err := client.Do(request)
	if err == nil && response.StatusCode == 200 {
		return true
	}
	return false
}
<commit_msg>Added debug output when command fails to start.<commit_after>package service

import (
	"errors"
	"fmt"
	"net"
	"net/http"
	"os"
	"os/exec"
	"runtime"
	"strings"
	"syscall"
	"time"
)

type Service struct {
	URLTemplate string
	CmdTemplate []string
	url         string
	command     *exec.Cmd
}

type addressInfo struct {
	Address string
	Host    string
	Port    string
}

func (s *Service) URL() (string, error) {
	if s.command == nil {
		return "", errors.New("not running")
	}

	return s.url, nil
}

func (s *Service) Start(debug bool) error {
	if s.command != nil {
		return errors.New("already running")
	}

	address, err := freeAddress()
	if err != nil {
		return fmt.Errorf("failed to locate a free port: %s", err)
	}

	if s.url, err = buildURL(s.URLTemplate, address); err != nil {
		return fmt.Errorf("failed to parse URL: %s", err)
	}

	command, err := buildCommand(s.CmdTemplate, address)
	if err != nil {
		return fmt.Errorf("failed to parse command: %s", err)
	}

	if debug {
		command.Stdout = os.Stdout
		command.Stderr = os.Stderr
	}

	if err := command.Start(); err != nil {
		err = fmt.Errorf("failed to run command: %s", err)
		if debug {
			os.Stderr.WriteString("ERROR: " + err.Error() + "\n")
		}
		return err
	}

	s.command = command

	return nil
}

func (s *Service) Stop() error {
	if s.command == nil {
		return errors.New("already stopped")
	}

	var err error
	if runtime.GOOS == "windows" {
		err = s.command.Process.Kill()
	} else {
		err = s.command.Process.Signal(syscall.SIGTERM)
	}
	if err != nil {
		return fmt.Errorf("failed to stop command: %s", err)
	}

	s.command.Wait()
	s.command = nil

	return nil
}

func freeAddress() (addressInfo, error) {
	listener, err := net.Listen("tcp", "127.0.0.1:0")
	if err != nil {
		return addressInfo{}, err
	}
	defer listener.Close()

	address := listener.Addr().String()
	addressParts := strings.SplitN(address, ":", 2)
	return addressInfo{address, addressParts[0], addressParts[1]}, nil
}

func (s *Service) WaitForBoot(timeout time.Duration) error {
	timeoutChan := time.After(timeout)
	failedChan := make(chan struct{}, 1)
	startedChan := make(chan struct{})

	go func() {
		up := s.checkStatus()
		for !up {
			select {
			case <-failedChan:
				return
			default:
				time.Sleep(500 * time.Millisecond)
				up = s.checkStatus()
			}
		}
		startedChan <- struct{}{}
	}()

	select {
	case <-timeoutChan:
		failedChan <- struct{}{}
		return errors.New("failed to start before timeout")
	case <-startedChan:
		return nil
	}
}

func (s *Service) checkStatus() bool {
	client := &http.Client{}
	request, _ := http.NewRequest("GET", fmt.Sprintf("%s/status", s.url), nil)
	response, err := client.Do(request)
	if err == nil && response.StatusCode == 200 {
		return true
	}
	return false
}
<|endoftext|>


<commit_before>package vollocal

import (
	"fmt"
	"os"
	"time"

	"code.cloudfoundry.org/clock"
	"code.cloudfoundry.org/lager"
	"code.cloudfoundry.org/volman"
	"github.com/tedsuo/ifrit"
)

type Syncer struct {
	logger       lager.Logger
	registry     volman.PluginRegistry
	scanInterval time.Duration
	clock        clock.Clock
	discoverer   []volman.Discoverer
}

func NewSyncer(logger lager.Logger, registry volman.PluginRegistry, discoverer []volman.Discoverer, scanInterval time.Duration, clock clock.Clock) *Syncer {
	return &Syncer{
		logger:       logger,
		registry:     registry,
		scanInterval: scanInterval,
		clock:        clock,
		discoverer:   discoverer,
	}
}

func NewSyncerWithShims(logger lager.Logger, registry volman.PluginRegistry, discoverer []volman.Discoverer, scanInterval time.Duration, clock clock.Clock) *Syncer {
	return &Syncer{
		logger:       logger,
		registry:     registry,
		scanInterval: scanInterval,
		clock:        clock,
		discoverer:   discoverer,
	}
}

func (p *Syncer) Runner() ifrit.Runner {
	return p
}

func (p *Syncer) Run(signals <-chan os.Signal, ready chan<- struct{}) error {
	logger := p.logger.Session("sync-plugin")
	logger.Info("start")
	defer logger.Info("end")

	logger.Info("running-discovery")
	allPlugins, err := discoverAllplugins(logger, p.discoverer)
	if err != nil {
		return err
	}

	p.registry.Set(allPlugins)

	timer := p.clock.NewTimer(p.scanInterval)
	defer timer.Stop()

	close(ready)

	for {
		select {
		case <-timer.C():
			go func() {
				logger.Info("running-re-discovery")
				allPlugins, err := discoverAllplugins(logger, p.discoverer)
				if err != nil {
					logger.Error("failed-discover", err)
				}
				p.registry.Set(allPlugins)
				timer.Reset(p.scanInterval)
			}()
		case signal := <-signals:
			logger.Info("received-signal", lager.Data{"signal": signal.String()})
			return nil
		}
	}
}

func discoverAllplugins(logger lager.Logger, discoverers []volman.Discoverer) (map[string]volman.Plugin, error) {
	allPlugins := map[string]volman.Plugin{}
	for _, discoverer := range discoverers {
		plugins, err := discoverer.Discover(logger)
		logger.Debug(fmt.Sprintf("plugins found: %#v", plugins))
		if err != nil {
			logger.Error("failed-discover", err)
			return map[string]volman.Plugin{}, err
		}
		for k, v := range plugins {
			allPlugins[k] = v
		}
	}
	return allPlugins, nil
}
<commit_msg>Standardize signal receipt message.<commit_after>package vollocal

import (
	"fmt"
	"os"
	"time"

	"code.cloudfoundry.org/clock"
	"code.cloudfoundry.org/lager"
	"code.cloudfoundry.org/volman"
	"github.com/tedsuo/ifrit"
)

type Syncer struct {
	logger       lager.Logger
	registry     volman.PluginRegistry
	scanInterval time.Duration
	clock        clock.Clock
	discoverer   []volman.Discoverer
}

func NewSyncer(logger lager.Logger, registry volman.PluginRegistry, discoverer []volman.Discoverer, scanInterval time.Duration, clock clock.Clock) *Syncer {
	return &Syncer{
		logger:       logger,
		registry:     registry,
		scanInterval: scanInterval,
		clock:        clock,
		discoverer:   discoverer,
	}
}

func NewSyncerWithShims(logger lager.Logger, registry volman.PluginRegistry, discoverer []volman.Discoverer, scanInterval time.Duration, clock clock.Clock) *Syncer {
	return &Syncer{
		logger:       logger,
		registry:     registry,
		scanInterval: scanInterval,
		clock:        clock,
		discoverer:   discoverer,
	}
}

func (p *Syncer) Runner() ifrit.Runner {
	return p
}

func (p *Syncer) Run(signals <-chan os.Signal, ready chan<- struct{}) error {
	logger := p.logger.Session("sync-plugin")
	logger.Info("start")
	defer logger.Info("end")

	logger.Info("running-discovery")
	allPlugins, err := discoverAllplugins(logger, p.discoverer)
	if err != nil {
		return err
	}

	p.registry.Set(allPlugins)

	timer := p.clock.NewTimer(p.scanInterval)
	defer timer.Stop()

	close(ready)

	for {
		select {
		case <-timer.C():
			go func() {
				logger.Info("running-re-discovery")
				allPlugins, err := discoverAllplugins(logger, p.discoverer)
				if err != nil {
					logger.Error("failed-discover", err)
				}
				p.registry.Set(allPlugins)
				timer.Reset(p.scanInterval)
			}()
		case signal := <-signals:
			logger.Info("signalled", lager.Data{"signal": signal.String()})
			return nil
		}
	}
}

func discoverAllplugins(logger lager.Logger, discoverers []volman.Discoverer) (map[string]volman.Plugin, error) {
	allPlugins := map[string]volman.Plugin{}
	for _, discoverer := range discoverers {
		plugins, err := discoverer.Discover(logger)
		logger.Debug(fmt.Sprintf("plugins found: %#v", plugins))
		if err != nil {
			logger.Error("failed-discover", err)
			return map[string]volman.Plugin{}, err
		}
		for k, v := range plugins {
			allPlugins[k] = v
		}
	}
	return allPlugins, nil
}
<|endoftext|>


<commit_before>package roller

import (
	"fmt"
	"path"
	"strings"
	"sync"
	"time"

	"github.com/gorilla/mux"

	"go.skia.org/infra/autoroll/go/modes"
	"go.skia.org/infra/autoroll/go/recent_rolls"
	"go.skia.org/infra/autoroll/go/repo_manager"
	"go.skia.org/infra/autoroll/go/state_machine"
	"go.skia.org/infra/go/autoroll"
	"go.skia.org/infra/go/cleanup"
	"go.skia.org/infra/go/comment"
	"go.skia.org/infra/go/gerrit"
	"go.skia.org/infra/go/metrics2"
	"go.skia.org/infra/go/sklog"
	"go.skia.org/infra/go/util"
)

// AutoRoller is a struct which automates the merging new revisions of one
// project into another.
type AutoRoller struct {
	cqExtraTrybots  string
	currentRoll     RollImpl
	emails          []string
	emailsMtx       sync.RWMutex
	gerrit          *gerrit.Gerrit
	lastError       error
	liveness        metrics2.Liveness
	modeHistory     *modes.ModeHistory
	recent          *recent_rolls.RecentRolls
	retrieveRoll    func(*AutoRoller, int64) (RollImpl, error)
	rm              repo_manager.RepoManager
	runningMtx      sync.Mutex
	sm              *state_machine.AutoRollStateMachine
	status          *AutoRollStatusCache
	rollIntoAndroid bool
}

// newAutoRoller returns an AutoRoller instance.
func newAutoRoller(workdir, childPath, cqExtraTrybots string, emails []string, gerrit *gerrit.Gerrit, rm repo_manager.RepoManager, retrieveRoll func(*AutoRoller, int64) (RollImpl, error), tc *state_machine.ThrottleConfig) (*AutoRoller, error) {
	recent, err := recent_rolls.NewRecentRolls(path.Join(workdir, "recent_rolls.db"))
	if err != nil {
		return nil, err
	}

	mh, err := modes.NewModeHistory(path.Join(workdir, "autoroll_modes.db"))
	if err != nil {
		return nil, err
	}

	arb := &AutoRoller{
		cqExtraTrybots: cqExtraTrybots,
		emails:         emails,
		gerrit:         gerrit,
		liveness:       metrics2.NewLiveness("last_autoroll_landed", map[string]string{"child_path": childPath}),
		modeHistory:    mh,
		recent:         recent,
		retrieveRoll:   retrieveRoll,
		rm:             rm,
		status:         &AutoRollStatusCache{},
	}
	sm, err := state_machine.New(arb, workdir, nil)
	if err != nil {
		return nil, err
	}
	arb.sm = sm
	current := recent.CurrentRoll()
	if current != nil {
		roll, err := arb.retrieveRoll(arb, current.Issue)
		if err != nil {
			return nil, err
		}
		arb.currentRoll = roll
	}
	return arb, nil
}

// NewAndroidAutoRoller returns an AutoRoller instance which rolls into Android.
func NewAndroidAutoRoller(workdir, parentBranch, childPath, childBranch, cqExtraTrybots string, emails []string, gerrit *gerrit.Gerrit, strategy repo_manager.NextRollStrategy, preUploadSteps []string, serverURL string, tc *state_machine.ThrottleConfig) (*AutoRoller, error) {
	rm, err := repo_manager.NewAndroidRepoManager(workdir, parentBranch, childPath, childBranch, gerrit, strategy, preUploadSteps, serverURL)
	if err != nil {
		return nil, err
	}
	retrieveRoll := func(arb *AutoRoller, issue int64) (RollImpl, error) {
		return newGerritAndroidRoll(arb.gerrit, arb.rm, arb.recent, issue)
	}
	return newAutoRoller(workdir, childPath, cqExtraTrybots, emails, gerrit, rm, retrieveRoll, tc)
}

// NewDEPSAutoRoller returns an AutoRoller instance which rolls using DEPS.
func NewDEPSAutoRoller(workdir, parentRepo, parentBranch, childPath, childBranch, cqExtraTrybots string, emails []string, gerrit *gerrit.Gerrit, depot_tools string, strategy repo_manager.NextRollStrategy, preUploadSteps []string, includeLog bool, depsCustomVars []string, serverURL string, tc *state_machine.ThrottleConfig) (*AutoRoller, error) {
	rm, err := repo_manager.NewDEPSRepoManager(workdir, parentRepo, parentBranch, childPath, childBranch, depot_tools, gerrit, strategy, preUploadSteps, includeLog, depsCustomVars, serverURL)
	if err != nil {
		return nil, err
	}
	retrieveRoll := func(arb *AutoRoller, issue int64) (RollImpl, error) {
		return newGerritRoll(arb.gerrit, arb.rm, arb.recent, issue)
	}
	return newAutoRoller(workdir, childPath, cqExtraTrybots, emails, gerrit, rm, retrieveRoll, tc)
}

// NewManifestAutoRoller returns an AutoRoller instance which rolls using DEPS.
func NewManifestAutoRoller(workdir, parentRepo, parentBranch, childPath, childBranch, cqExtraTrybots string, emails []string, gerrit *gerrit.Gerrit, depot_tools string, strategy repo_manager.NextRollStrategy, preUploadSteps []string, serverURL string, tc *state_machine.ThrottleConfig) (*AutoRoller, error) {
	rm, err := repo_manager.NewManifestRepoManager(workdir, parentRepo, parentBranch, childPath, childBranch, depot_tools, gerrit, strategy, preUploadSteps, serverURL)
	if err != nil {
		return nil, err
	}
	retrieveRoll := func(arb *AutoRoller, issue int64) (RollImpl, error) {
		return newGerritRoll(arb.gerrit, arb.rm, arb.recent, issue)
	}
	return newAutoRoller(workdir, childPath, cqExtraTrybots, emails, gerrit, rm, retrieveRoll, tc)
}

// isSyncError returns true iff the error looks like a sync error.
func isSyncError(err error) bool {
	// TODO(borenet): Remove extra logging.
	sklog.Infof("Encountered error: %q", err.Error())
	if strings.Contains(err.Error(), "Invalid revision range") {
		sklog.Infof("Is sync error (invalid revision range)")
		return true
	} else if strings.Contains(err.Error(), "The remote end hung up unexpectedly") {
		sklog.Infof("Is sync error (remote hung up)")
		return true
	} else if strings.Contains(err.Error(), "remote error: internal server error") {
		sklog.Infof("Is sync error (internal server error)")
		return true
	}
	sklog.Infof("Not a sync error.")
	return false
}

// Start initiates the AutoRoller's loop.
func (r *AutoRoller) Start(tickFrequency, repoFrequency time.Duration) {
	sklog.Infof("Starting autoroller.")
	repo_manager.Start(r.rm, repoFrequency)
	lv := metrics2.NewLiveness("last_successful_autoroll_tick")
	cleanup.Repeat(tickFrequency, func() {
		if err := r.Tick(); err != nil {
			// Hack: we frequently get failures from GoB which trigger error-rate alerts.
			// These alerts are noise and sometimes hide real failures. If the error is
			// due to a sync failure, log it as a warning instead of an error. We'll rely
			// on the liveness alert in the case where we see persistent sync failures.
			if isSyncError(err) {
				sklog.Warningf("Failed to run autoroll: %s", err)
			} else {
				sklog.Errorf("Failed to run autoroll: %s", err)
			}
		} else {
			lv.Reset()
		}
	}, func() {
		util.LogErr(r.recent.Close())
		util.LogErr(r.modeHistory.Close())
	})
}

// See documentation for state_machine.AutoRollerImpl interface.
func (r *AutoRoller) GetActiveRoll() state_machine.RollCLImpl {
	return r.currentRoll
}

// GetEmails returns the list of email addresses which are copied on rolls.
func (r *AutoRoller) GetEmails() []string {
	r.emailsMtx.RLock()
	defer r.emailsMtx.RUnlock()
	rv := make([]string, len(r.emails))
	copy(rv, r.emails)
	return rv
}

// SetEmails sets the list of email addresses which are copied on rolls.
func (r *AutoRoller) SetEmails(e []string) {
	r.emailsMtx.Lock()
	defer r.emailsMtx.Unlock()
	emails := make([]string, len(e))
	copy(emails, e)
	r.emails = emails
}

// See documentation for state_machine.AutoRollerImpl interface.
func (r *AutoRoller) GetMode() string {
	return r.modeHistory.CurrentMode().Mode
}

// SetMode sets the desired mode of the bot. This forces the bot to run and
// blocks until it finishes.
func (r *AutoRoller) SetMode(m, user, message string) error {
	if err := r.modeHistory.Add(m, user, message); err != nil {
		return err
	}
	return r.Tick()
}

// Return the roll-up status of the bot.
func (r *AutoRoller) GetStatus(includeError bool) *AutoRollStatus {
	return r.status.Get(includeError, nil)
}

// Return minimal status information for the bot.
func (r *AutoRoller) GetMiniStatus() *AutoRollMiniStatus {
	return r.status.GetMini()
}

// Return the AutoRoll user.
func (r *AutoRoller) GetUser() string {
	return r.rm.User()
}

// See documentation for state_machine.AutoRollerImpl interface.
func (r *AutoRoller) UploadNewRoll(from, to string, dryRun bool) error {
	issueNum, err := r.rm.CreateNewRoll(from, to, r.GetEmails(), r.cqExtraTrybots, dryRun)
	if err != nil {
		return err
	}
	roll, err := r.retrieveRoll(r, issueNum)
	if err != nil {
		return err
	}
	if err := roll.InsertIntoDB(); err != nil {
		return err
	}
	r.currentRoll = roll
	return nil
}

// See documentation for state_machine.AutoRollerImpl interface.
func (r *AutoRoller) GetCurrentRev() string {
	return r.rm.LastRollRev()
}

// See documentation for state_machine.AutoRollerImpl interface.
func (r *AutoRoller) GetNextRollRev() string {
	return r.rm.NextRollRev()
}

// See documentation for state_machine.AutoRollerImpl interface.
func (r *AutoRoller) RolledPast(rev string) (bool, error) {
	return r.rm.RolledPast(rev)
}

// See documentation for state_machine.AutoRollerImpl interface.
func (r *AutoRoller) UpdateRepos() error {
	return r.rm.Update()
}

// Run one iteration of the roller.
func (r *AutoRoller) Tick() error {
	r.runningMtx.Lock()
	defer r.runningMtx.Unlock()

	sklog.Infof("Running autoroller.")
	// Run the state machine.
	lastErr := r.sm.NextTransitionSequence()

	// Update the status information.
	lastErrorStr := ""
	if lastErr != nil {
		lastErrorStr = lastErr.Error()
	}
	recent := r.recent.GetRecentRolls()
	numFailures := 0
	for _, roll := range recent {
		if roll.Failed() {
			numFailures++
		} else if roll.Succeeded() {
			break
		}
	}
	sklog.Infof("Updating status (%d)", r.rm.CommitsNotRolled())
	if err := r.status.Set(&AutoRollStatus{
		AutoRollMiniStatus: AutoRollMiniStatus{
			NumFailedRolls:      numFailures,
			NumNotRolledCommits: r.rm.CommitsNotRolled(),
		},
		CurrentRoll:    r.recent.CurrentRoll(),
		Error:          lastErrorStr,
		FullHistoryUrl: r.gerrit.Url(0) + "/q/owner:" + r.GetUser(),
		IssueUrlBase:   r.gerrit.Url(0) + "/c/",
		LastRoll:       r.recent.LastRoll(),
		LastRollRev:    r.rm.LastRollRev(),
		Mode:           r.modeHistory.CurrentMode(),
		Recent:         recent,
		Status:         string(r.sm.Current()),
	}); err != nil {
		return err
	}
	sklog.Infof("Autoroller state %s", r.sm.Current())
	if lastRoll := r.recent.LastRoll(); lastRoll != nil && util.In(lastRoll.Result, []string{autoroll.ROLL_RESULT_DRY_RUN_SUCCESS, autoroll.ROLL_RESULT_SUCCESS}) {
		r.liveness.ManualReset(lastRoll.Modified)
	}
	return lastErr
}

// Add a comment to the given roll CL.
func (r *AutoRoller) AddComment(issueNum int64, message, user string, timestamp time.Time) error {
	roll, err := r.recent.Get(issueNum)
	if err != nil {
		return fmt.Errorf("No such issue %d", issueNum)
	}
	id := fmt.Sprintf("%d_%d", issueNum, len(roll.Comments))
	roll.Comments = append(roll.Comments, comment.New(id, message, user))
	return r.recent.Update(roll)
}

// Required for main.AutoRollerI. No specific HTTP handlers.
func (r *AutoRoller) AddHandlers(*mux.Router) {}
<commit_msg>[autoroll] Add more cases to IsSyncError<commit_after>package roller

import (
	"fmt"
	"path"
	"strings"
	"sync"
	"time"

	"github.com/gorilla/mux"

	"go.skia.org/infra/autoroll/go/modes"
	"go.skia.org/infra/autoroll/go/recent_rolls"
	"go.skia.org/infra/autoroll/go/repo_manager"
	"go.skia.org/infra/autoroll/go/state_machine"
	"go.skia.org/infra/go/autoroll"
	"go.skia.org/infra/go/cleanup"
	"go.skia.org/infra/go/comment"
	"go.skia.org/infra/go/gerrit"
	"go.skia.org/infra/go/metrics2"
	"go.skia.org/infra/go/sklog"
	"go.skia.org/infra/go/util"
)

// AutoRoller is a struct which automates the merging new revisions of one
// project into another.
type AutoRoller struct {
	cqExtraTrybots  string
	currentRoll     RollImpl
	emails          []string
	emailsMtx       sync.RWMutex
	gerrit          *gerrit.Gerrit
	lastError       error
	liveness        metrics2.Liveness
	modeHistory     *modes.ModeHistory
	recent          *recent_rolls.RecentRolls
	retrieveRoll    func(*AutoRoller, int64) (RollImpl, error)
	rm              repo_manager.RepoManager
	runningMtx      sync.Mutex
	sm              *state_machine.AutoRollStateMachine
	status          *AutoRollStatusCache
	rollIntoAndroid bool
}

// newAutoRoller returns an AutoRoller instance.
func newAutoRoller(workdir, childPath, cqExtraTrybots string, emails []string, gerrit *gerrit.Gerrit, rm repo_manager.RepoManager, retrieveRoll func(*AutoRoller, int64) (RollImpl, error), tc *state_machine.ThrottleConfig) (*AutoRoller, error) {
	recent, err := recent_rolls.NewRecentRolls(path.Join(workdir, "recent_rolls.db"))
	if err != nil {
		return nil, err
	}

	mh, err := modes.NewModeHistory(path.Join(workdir, "autoroll_modes.db"))
	if err != nil {
		return nil, err
	}

	arb := &AutoRoller{
		cqExtraTrybots: cqExtraTrybots,
		emails:         emails,
		gerrit:         gerrit,
		liveness:       metrics2.NewLiveness("last_autoroll_landed", map[string]string{"child_path": childPath}),
		modeHistory:    mh,
		recent:         recent,
		retrieveRoll:   retrieveRoll,
		rm:             rm,
		status:         &AutoRollStatusCache{},
	}
	sm, err := state_machine.New(arb, workdir, nil)
	if err != nil {
		return nil, err
	}
	arb.sm = sm
	current := recent.CurrentRoll()
	if current != nil {
		roll, err := arb.retrieveRoll(arb, current.Issue)
		if err != nil {
			return nil, err
		}
		arb.currentRoll = roll
	}
	return arb, nil
}

// NewAndroidAutoRoller returns an AutoRoller instance which rolls into Android.
func NewAndroidAutoRoller(workdir, parentBranch, childPath, childBranch, cqExtraTrybots string, emails []string, gerrit *gerrit.Gerrit, strategy repo_manager.NextRollStrategy, preUploadSteps []string, serverURL string, tc *state_machine.ThrottleConfig) (*AutoRoller, error) {
	rm, err := repo_manager.NewAndroidRepoManager(workdir, parentBranch, childPath, childBranch, gerrit, strategy, preUploadSteps, serverURL)
	if err != nil {
		return nil, err
	}
	retrieveRoll := func(arb *AutoRoller, issue int64) (RollImpl, error) {
		return newGerritAndroidRoll(arb.gerrit, arb.rm, arb.recent, issue)
	}
	return newAutoRoller(workdir, childPath, cqExtraTrybots, emails, gerrit, rm, retrieveRoll, tc)
}

// NewDEPSAutoRoller returns an AutoRoller instance which rolls using DEPS.
func NewDEPSAutoRoller(workdir, parentRepo, parentBranch, childPath, childBranch, cqExtraTrybots string, emails []string, gerrit *gerrit.Gerrit, depot_tools string, strategy repo_manager.NextRollStrategy, preUploadSteps []string, includeLog bool, depsCustomVars []string, serverURL string, tc *state_machine.ThrottleConfig) (*AutoRoller, error) {
	rm, err := repo_manager.NewDEPSRepoManager(workdir, parentRepo, parentBranch, childPath, childBranch, depot_tools, gerrit, strategy, preUploadSteps, includeLog, depsCustomVars, serverURL)
	if err != nil {
		return nil, err
	}
	retrieveRoll := func(arb *AutoRoller, issue int64) (RollImpl, error) {
		return newGerritRoll(arb.gerrit, arb.rm, arb.recent, issue)
	}
	return newAutoRoller(workdir, childPath, cqExtraTrybots, emails, gerrit, rm, retrieveRoll, tc)
}

// NewManifestAutoRoller returns an AutoRoller instance which rolls using DEPS.
func NewManifestAutoRoller(workdir, parentRepo, parentBranch, childPath, childBranch, cqExtraTrybots string, emails []string, gerrit *gerrit.Gerrit, depot_tools string, strategy repo_manager.NextRollStrategy, preUploadSteps []string, serverURL string, tc *state_machine.ThrottleConfig) (*AutoRoller, error) {
	rm, err := repo_manager.NewManifestRepoManager(workdir, parentRepo, parentBranch, childPath, childBranch, depot_tools, gerrit, strategy, preUploadSteps, serverURL)
	if err != nil {
		return nil, err
	}
	retrieveRoll := func(arb *AutoRoller, issue int64) (RollImpl, error) {
		return newGerritRoll(arb.gerrit, arb.rm, arb.recent, issue)
	}
	return newAutoRoller(workdir, childPath, cqExtraTrybots, emails, gerrit, rm, retrieveRoll, tc)
}

// isSyncError returns true iff the error looks like a sync error.
func isSyncError(err error) bool {
	// TODO(borenet): Remove extra logging.
	sklog.Infof("Encountered error: %q", err.Error())
	if strings.Contains(err.Error(), "Invalid revision range") {
		// Not really an error in the sync itself but indicates that
		// the repo is not up to date, likely due to a server frontend
		// lagging behind.
		sklog.Infof("Is sync error (invalid revision range)")
		return true
	} else if strings.Contains(err.Error(), "The remote end hung up unexpectedly") {
		sklog.Infof("Is sync error (remote hung up)")
		return true
	} else if strings.Contains(err.Error(), "remote error: internal server error") {
		sklog.Infof("Is sync error (internal server error)")
		return true
	} else if strings.Contains(err.Error(), "The requested URL returned error: 502") {
		sklog.Infof("Is sync error (URL returned 502)")
		return true
	} else if strings.Contains(err.Error(), "fatal: bad object") {
		// Not really an error in the sync itself but indicates that
		// the repo is not up to date, likely due to a server frontend
		// lagging behind.
		sklog.Infof("Is sync error (bad object)")
		return true
	}
	sklog.Infof("Not a sync error.")
	return false
}

// Start initiates the AutoRoller's loop.
func (r *AutoRoller) Start(tickFrequency, repoFrequency time.Duration) {
	sklog.Infof("Starting autoroller.")
	repo_manager.Start(r.rm, repoFrequency)
	lv := metrics2.NewLiveness("last_successful_autoroll_tick")
	cleanup.Repeat(tickFrequency, func() {
		if err := r.Tick(); err != nil {
			// Hack: we frequently get failures from GoB which trigger error-rate alerts.
			// These alerts are noise and sometimes hide real failures. If the error is
			// due to a sync failure, log it as a warning instead of an error. We'll rely
			// on the liveness alert in the case where we see persistent sync failures.
			if isSyncError(err) {
				sklog.Warningf("Failed to run autoroll: %s", err)
			} else {
				sklog.Errorf("Failed to run autoroll: %s", err)
			}
		} else {
			lv.Reset()
		}
	}, func() {
		util.LogErr(r.recent.Close())
		util.LogErr(r.modeHistory.Close())
	})
}

// See documentation for state_machine.AutoRollerImpl interface.
func (r *AutoRoller) GetActiveRoll() state_machine.RollCLImpl {
	return r.currentRoll
}

// GetEmails returns the list of email addresses which are copied on rolls.
func (r *AutoRoller) GetEmails() []string {
	r.emailsMtx.RLock()
	defer r.emailsMtx.RUnlock()
	rv := make([]string, len(r.emails))
	copy(rv, r.emails)
	return rv
}

// SetEmails sets the list of email addresses which are copied on rolls.
func (r *AutoRoller) SetEmails(e []string) {
	r.emailsMtx.Lock()
	defer r.emailsMtx.Unlock()
	emails := make([]string, len(e))
	copy(emails, e)
	r.emails = emails
}

// See documentation for state_machine.AutoRollerImpl interface.
func (r *AutoRoller) GetMode() string {
	return r.modeHistory.CurrentMode().Mode
}

// SetMode sets the desired mode of the bot. This forces the bot to run and
// blocks until it finishes.
func (r *AutoRoller) SetMode(m, user, message string) error {
	if err := r.modeHistory.Add(m, user, message); err != nil {
		return err
	}
	return r.Tick()
}

// Return the roll-up status of the bot.
func (r *AutoRoller) GetStatus(includeError bool) *AutoRollStatus {
	return r.status.Get(includeError, nil)
}

// Return minimal status information for the bot.
func (r *AutoRoller) GetMiniStatus() *AutoRollMiniStatus {
	return r.status.GetMini()
}

// Return the AutoRoll user.
func (r *AutoRoller) GetUser() string {
	return r.rm.User()
}

// See documentation for state_machine.AutoRollerImpl interface.
func (r *AutoRoller) UploadNewRoll(from, to string, dryRun bool) error {
	issueNum, err := r.rm.CreateNewRoll(from, to, r.GetEmails(), r.cqExtraTrybots, dryRun)
	if err != nil {
		return err
	}
	roll, err := r.retrieveRoll(r, issueNum)
	if err != nil {
		return err
	}
	if err := roll.InsertIntoDB(); err != nil {
		return err
	}
	r.currentRoll = roll
	return nil
}

// See documentation for state_machine.AutoRollerImpl interface.
func (r *AutoRoller) GetCurrentRev() string {
	return r.rm.LastRollRev()
}

// See documentation for state_machine.AutoRollerImpl interface.
func (r *AutoRoller) GetNextRollRev() string {
	return r.rm.NextRollRev()
}

// See documentation for state_machine.AutoRollerImpl interface.
func (r *AutoRoller) RolledPast(rev string) (bool, error) {
	return r.rm.RolledPast(rev)
}

// See documentation for state_machine.AutoRollerImpl interface.
func (r *AutoRoller) UpdateRepos() error {
	return r.rm.Update()
}

// Run one iteration of the roller.
func (r *AutoRoller) Tick() error {
	r.runningMtx.Lock()
	defer r.runningMtx.Unlock()

	sklog.Infof("Running autoroller.")
	// Run the state machine.
	lastErr := r.sm.NextTransitionSequence()

	// Update the status information.
	lastErrorStr := ""
	if lastErr != nil {
		lastErrorStr = lastErr.Error()
	}
	recent := r.recent.GetRecentRolls()
	numFailures := 0
	for _, roll := range recent {
		if roll.Failed() {
			numFailures++
		} else if roll.Succeeded() {
			break
		}
	}
	sklog.Infof("Updating status (%d)", r.rm.CommitsNotRolled())
	if err := r.status.Set(&AutoRollStatus{
		AutoRollMiniStatus: AutoRollMiniStatus{
			NumFailedRolls:      numFailures,
			NumNotRolledCommits: r.rm.CommitsNotRolled(),
		},
		CurrentRoll:    r.recent.CurrentRoll(),
		Error:          lastErrorStr,
		FullHistoryUrl: r.gerrit.Url(0) + "/q/owner:" + r.GetUser(),
		IssueUrlBase:   r.gerrit.Url(0) + "/c/",
		LastRoll:       r.recent.LastRoll(),
		LastRollRev:    r.rm.LastRollRev(),
		Mode:           r.modeHistory.CurrentMode(),
		Recent:         recent,
		Status:         string(r.sm.Current()),
	}); err != nil {
		return err
	}
	sklog.Infof("Autoroller state %s", r.sm.Current())
	if lastRoll := r.recent.LastRoll(); lastRoll != nil && util.In(lastRoll.Result, []string{autoroll.ROLL_RESULT_DRY_RUN_SUCCESS, autoroll.ROLL_RESULT_SUCCESS}) {
		r.liveness.ManualReset(lastRoll.Modified)
	}
	return lastErr
}

// Add a comment to the given roll CL.
func (r *AutoRoller) AddComment(issueNum int64, message, user string, timestamp time.Time) error {
	roll, err := r.recent.Get(issueNum)
	if err != nil {
		return fmt.Errorf("No such issue %d", issueNum)
	}
	id := fmt.Sprintf("%d_%d", issueNum, len(roll.Comments))
	roll.Comments = append(roll.Comments, comment.New(id, message, user))
	return r.recent.Update(roll)
}

// Required for main.AutoRollerI. No specific HTTP handlers.
func (r *AutoRoller) AddHandlers(*mux.Router) {}
<|endoftext|>


<commit_before>package datasource

import (
	"io/ioutil"
	"log"
	"math"
	"net/http"
	"time"
)

type Datasource interface {
	Fetch() ([]byte, error)
	Type() string
}

func fetchURL(url string) ([]byte, error) {
	resp, err := getWithExponentialBackoff(url)
	if err != nil {
		return []byte{}, err
	}
	defer resp.Body.Close()

	respBytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	return respBytes, nil
}

// getWithExponentialBackoff issues a GET to the specified URL. If the
// response is a non-2xx or produces an error, retry the GET forever using
// an exponential backoff.
func getWithExponentialBackoff(url string) (*http.Response, error) {
	var err error
	var resp *http.Response
	for i := 0; ; i++ {
		resp, err = http.Get(url)
		if err == nil && resp.StatusCode/100 == 2 {
			return resp, nil
		}
		duration := time.Millisecond * time.Duration((math.Pow(float64(2), float64(i)) * 100))
		log.Printf("unable to fetch user-data from %s, try again in %s", url, duration)
		time.Sleep(duration)
	}
	return resp, err
}
<commit_msg>Adds sleep cap to exponential backoff so it does not go too high<commit_after>package datasource

import (
	"io/ioutil"
	"log"
	"math"
	"net/http"
	"time"
)

const maxTimeout = time.Second * 5

type Datasource interface {
	Fetch() ([]byte, error)
	Type() string
}

func fetchURL(url string) ([]byte, error) {
	resp, err := getWithExponentialBackoff(url)
	if err != nil {
		return []byte{}, err
	}
	defer resp.Body.Close()

	respBytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	return respBytes, nil
}

// getWithExponentialBackoff issues a GET to the specified URL. If the
// response is a non-2xx or produces an error, retry the GET forever using
// an exponential backoff.
func getWithExponentialBackoff(url string) (*http.Response, error) {
	var err error
	var resp *http.Response
	for i := 0; ; i++ {
		resp, err = http.Get(url)
		if err == nil && resp.StatusCode/100 == 2 {
			return resp, nil
		}
		duration := time.Millisecond * time.Duration((math.Pow(float64(2), float64(i)) * 100))
		if duration > maxTimeout {
			duration = maxTimeout
		}

		log.Printf("unable to fetch user-data from %s, try again in %s", url, duration)
		time.Sleep(duration)
	}
	return resp, err
}
<|endoftext|>


<commit_before>package volume

import (
	"fmt"
	"log"
	"os"
	"strings"
	"time"

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

	"github.com/libopenstorage/openstorage/api"
	clusterclient "github.com/libopenstorage/openstorage/api/client/cluster"
	volumeclient "github.com/libopenstorage/openstorage/api/client/volume"
	"github.com/libopenstorage/openstorage/cluster"
	"github.com/libopenstorage/openstorage/volume"
)

var (
	nodes  []string
	docker *dockerclient.Client
)

type driver struct {
	hostConfig     *dockerclient.HostConfig
	clusterManager cluster.Cluster
	volDriver      volume.VolumeDriver
}

func (d *driver) String() string {
	return "pxd"
}

func (d *driver) Init() error {
	log.Printf("Using the Portworx volume driver.\n")

	n := "127.0.0.1"
	if len(nodes) > 0 {
		n = nodes[0]
	}

	if clnt, err := clusterclient.NewClusterClient("http://"+n+":9001", "v1"); err != nil {
		return err
	} else {
		d.clusterManager = clusterclient.ClusterManager(clnt)
	}

	if clnt, err := volumeclient.NewDriverClient("http://"+n+":9001", "pxd", ""); err != nil {
		return err
	} else {
		d.volDriver = volumeclient.VolumeDriver(clnt)
	}

	if cluster, err := d.clusterManager.Enumerate(); err != nil {
		return err
	} else {
		log.Printf("The following Portworx nodes are in the cluster:\n")
		for _, n := range cluster.Nodes {
			log.Printf(
				"\tNode ID: %v\tNode IP: %v\tNode Status: %v\n",
				n.Id,
				n.DataIp,
				n.Status,
			)
		}
	}

	return nil
}

func (d *driver) RemoveVolume(name string) error {
	locator := &api.VolumeLocator{}

	volumes, err := d.volDriver.Enumerate(locator, nil)
	if err != nil {
		return err
	}

	for _, v := range volumes {
		if v.Locator.Name == name {
			// First unmount this volume at all mount paths...
			for _, path := range v.AttachPath {
				if err = d.volDriver.Unmount(v.Id, path); err != nil {
					err = fmt.Errorf(
						"Error while unmounting %v at %v because of: %v",
						v.Id,
						path,
						err,
					)
					log.Printf("%v", err)
					return err
				}
			}

			if err = d.volDriver.Detach(v.Id); err != nil {
				err = fmt.Errorf(
					"Error while detaching %v because of: %v",
					v.Id,
					err,
				)
				log.Printf("%v", err)
				return err
			}

			if err = d.volDriver.Delete(v.Id); err != nil {
				err = fmt.Errorf(
					"Error while deleting %v because of: %v",
					v.Id,
					err,
				)
				log.Printf("%v", err)
				return err
			}

			log.Printf("Succesfully removed Portworx volume %v\n", name)

			return nil
		}
	}

	return nil
}

func (d *driver) DetachVolume(name string) error {
	locator := &api.VolumeLocator{}

	volumes, err := d.volDriver.Enumerate(locator, nil)
	if err != nil {
		return err
	}

	for _, v := range volumes {
		if v.Locator.Name == name {
			if err = d.volDriver.Detach(v.Id); err != nil {
				err = fmt.Errorf(
					"Error while detaching %v because of: %v",
					v.Id,
					err,
				)
				log.Printf("%v", err)
				return err
			}

			log.Printf("Succesfully detached Portworx volume %v\n", name)

			return nil
		}
	}

	return nil
}

// Portworx runs as a container - so all we need to do is ask docker to
// stop the running portworx container.
func (d *driver) Stop(Ip string) error {
	endpoint := "tcp://" + Ip + ":2375"
	docker, err := dockerclient.NewClient(endpoint)
	if err != nil {
		return err
	} else {
		if err = docker.Ping(); err != nil {
			return err
		}
	}

	// Find and stop the Portworx container
	lo := dockerclient.ListContainersOptions{
		All:  true,
		Size: false,
	}
	if allContainers, err := docker.ListContainers(lo); err != nil {
		return err
	} else {
		for _, c := range allContainers {
			if info, err := docker.InspectContainer(c.ID); err != nil {
				return err
			} else {
				if strings.Contains(info.Config.Image, "portworx/px") {
					if !info.State.Running {
						return fmt.Errorf(
							"Portworx container with ID %v is not running.",
							c.ID,
						)
					}

					d.hostConfig = info.HostConfig
					log.Printf("Stopping Portworx container with ID: %v\n", c.ID)
					if err = docker.StopContainer(c.ID, 0); err != nil {
						return err
					}
					return nil
				}
			}
		}
	}

	return fmt.Errorf("Could not find the Portworx container on %v", Ip)
}

func (d *driver) Start(Ip string) error {
	endpoint := "tcp://" + Ip + ":2375"
	docker, err := dockerclient.NewClient(endpoint)
	if err != nil {
		return err
	} else {
		if err = docker.Ping(); err != nil {
			return err
		}
	}

	// Find and stop the Portworx container
	lo := dockerclient.ListContainersOptions{
		All:  true,
		Size: false,
	}
	if allContainers, err := docker.ListContainers(lo); err != nil {
		return err
	} else {
		for _, c := range allContainers {
			if info, err := docker.InspectContainer(c.ID); err != nil {
				return err
			} else {
				if strings.Contains(info.Config.Image, "portworx/px") {
					if info.State.Running {
						return fmt.Errorf(
							"Portworx container with ID %v is not stopped.",
							c.ID,
						)
					}

					log.Printf("Starting Portworx container with ID: %v\n", c.ID)
					if err = docker.StartContainer(c.ID, d.hostConfig); err != nil {
						return err
					}

					// Wait for Portworx to become usable.
					status, _ := d.clusterManager.NodeStatus()
					for i := 0; status != api.Status_STATUS_ERROR; i++ {
						if i > 60 {
							return fmt.Errorf("Portworx did not start up in time.")
						}

						time.Sleep(1 * time.Second)
						status, _ = d.clusterManager.NodeStatus()
					}

					return nil
				}
			}
		}
	}

	log.Printf("Could not fine the Portworx container.\n")
	return fmt.Errorf("Could not find the Portworx container on %v", Ip)
}

func init() {
	nodes = strings.Split(os.Getenv("CLUSTER_NODES"), ",")

	register("pxd", &driver{})
}
<commit_msg>add more force delete test cases<commit_after>package volume

import (
	"fmt"
	"log"
	"os"
	"strings"
	"time"

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

	"github.com/libopenstorage/openstorage/api"
	clusterclient "github.com/libopenstorage/openstorage/api/client/cluster"
	volumeclient "github.com/libopenstorage/openstorage/api/client/volume"
	"github.com/libopenstorage/openstorage/cluster"
	"github.com/libopenstorage/openstorage/volume"
)

var (
	nodes  []string
	docker *dockerclient.Client
)

type driver struct {
	hostConfig     *dockerclient.HostConfig
	clusterManager cluster.Cluster
	volDriver      volume.VolumeDriver
}

func (d *driver) String() string {
	return "pxd"
}

func (d *driver) Init() error {
	log.Printf("Using the Portworx volume driver.\n")

	n := "127.0.0.1"
	if len(nodes) > 0 {
		n = nodes[0]
	}

	if clnt, err := clusterclient.NewClusterClient("http://"+n+":9001", "v1"); err != nil {
		return err
	} else {
		d.clusterManager = clusterclient.ClusterManager(clnt)
	}

	if clnt, err := volumeclient.NewDriverClient("http://"+n+":9001", "pxd", ""); err != nil {
		return err
	} else {
		d.volDriver = volumeclient.VolumeDriver(clnt)
	}

	if cluster, err := d.clusterManager.Enumerate(); err != nil {
		return err
	} else {
		log.Printf("The following Portworx nodes are in the cluster:\n")
		for _, n := range cluster.Nodes {
			log.Printf(
				"\tNode ID: %v\tNode IP: %v\tNode Status: %v\n",
				n.Id,
				n.DataIp,
				n.Status,
			)
		}
	}

	return nil
}

func (d *driver) RemoveVolume(name string) error {
	locator := &api.VolumeLocator{}

	volumes, err := d.volDriver.Enumerate(locator, nil)
	if err != nil {
		return err
	}

	for _, v := range volumes {
		if v.Locator.Name == name {
			// First unmount this volume at all mount paths...
			for _, path := range v.AttachPath {
				if err = d.volDriver.Unmount(v.Id, path); err != nil {
					err = fmt.Errorf(
						"Error while unmounting %v at %v because of: %v",
						v.Id,
						path,
						err,
					)
					log.Printf("%v", err)
					return err
				}
			}

			if err = d.volDriver.Detach(v.Id); err != nil {
				err = fmt.Errorf(
					"Error while detaching %v because of: %v",
					v.Id,
					err,
				)
				log.Printf("%v", err)
				return err
			}

			if err = d.volDriver.Delete(v.Id); err != nil {
				err = fmt.Errorf(
					"Error while deleting %v because of: %v",
					v.Id,
					err,
				)
				log.Printf("%v", err)
				return err
			}

			log.Printf("Succesfully removed Portworx volume %v\n", name)

			return nil
		}
	}

	return nil
}

func (d *driver) DetachVolume(name string) error {
	locator := &api.VolumeLocator{}

	volumes, err := d.volDriver.Enumerate(locator, nil)
	if err != nil {
		return err
	}

	for _, v := range volumes {
		if v.Locator.Name == name {
			if err = d.volDriver.Detach(v.Id); err != nil {
				err = fmt.Errorf(
					"Error while detaching %v because of: %v",
					v.Id,
					err,
				)
				log.Printf("%v", err)
				return err
			}

			log.Printf("Succesfully detached Portworx volume %v\n", name)

			return nil
		}
	}

	return nil
}

// Portworx runs as a container - so all we need to do is ask docker to
// stop the running portworx container.
func (d *driver) Stop(Ip string) error {
	endpoint := "tcp://" + Ip + ":2375"
	docker, err := dockerclient.NewClient(endpoint)
	if err != nil {
		return err
	} else {
		if err = docker.Ping(); err != nil {
			return err
		}
	}

	// Find and stop the Portworx container
	lo := dockerclient.ListContainersOptions{
		All:  true,
		Size: false,
	}
	if allContainers, err := docker.ListContainers(lo); err != nil {
		return err
	} else {
		for _, c := range allContainers {
			if info, err := docker.InspectContainer(c.ID); err != nil {
				return err
			} else {
				if strings.Contains(info.Config.Image, "portworx/px") {
					if !info.State.Running {
						return fmt.Errorf(
							"Portworx container with ID %v is not running.",
							c.ID,
						)
					}

					d.hostConfig = info.HostConfig
					log.Printf("Stopping Portworx container with ID: %v\n", c.ID)
					if err = docker.StopContainer(c.ID, 0); err != nil {
						return err
					}
					return nil
				}
			}
		}
	}

	return fmt.Errorf("Could not find the Portworx container on %v", Ip)
}

func (d *driver) Start(Ip string) error {
	endpoint := "tcp://" + Ip + ":2375"
	docker, err := dockerclient.NewClient(endpoint)
	if err != nil {
		return err
	} else {
		if err = docker.Ping(); err != nil {
			return err
		}
	}

	// Find and stop the Portworx container
	lo := dockerclient.ListContainersOptions{
		All:  true,
		Size: false,
	}
	if allContainers, err := docker.ListContainers(lo); err != nil {
		return err
	} else {
		for _, c := range allContainers {
			if info, err := docker.InspectContainer(c.ID); err != nil {
				return err
			} else {
				if strings.Contains(info.Config.Image, "portworx/px") {
					if info.State.Running {
						return fmt.Errorf(
							"Portworx container with ID %v is not stopped.",
							c.ID,
						)
					}

					log.Printf("Starting Portworx container with ID: %v\n", c.ID)
					if err = docker.StartContainer(c.ID, d.hostConfig); err != nil {
						return err
					}

					// Wait for Portworx to become usable.
					status, _ := d.clusterManager.NodeStatus()
					for i := 0; status != api.Status_STATUS_OK; i++ {
						if i > 60 {
							return fmt.Errorf(
								"Portworx did not start up in time: Status is %v",
								status,
							)
						}

						time.Sleep(1 * time.Second)
						status, _ = d.clusterManager.NodeStatus()
					}

					return nil
				}
			}
		}
	}

	log.Printf("Could not fine the Portworx container.\n")
	return fmt.Errorf("Could not find the Portworx container on %v", Ip)
}

func init() {
	nodes = strings.Split(os.Getenv("CLUSTER_NODES"), ",")

	register("pxd", &driver{})
}
<|endoftext|>


<commit_before>package expr

import (
	"fmt"
	"math"
	"strings"

	"github.com/grafana/metrictank/batch"
	"github.com/grafana/metrictank/errors"
	"github.com/grafana/metrictank/schema"
	log "github.com/sirupsen/logrus"

	"github.com/grafana/metrictank/api/models"
	"github.com/grafana/metrictank/consolidation"
	"github.com/raintank/dur"
)

type FuncMovingWindow struct {
	in           GraphiteFunc
	windowSize   string
	fn           string
	xFilesFactor float64

	shiftOffset uint32
}

// NewMovingWindowConstructor takes an agg string and returns a constructor function
func NewMovingWindowConstructor(name string) func() GraphiteFunc {
	return func() GraphiteFunc {
		return &FuncMovingWindow{fn: name}
	}
}

func NewMovingWindow() GraphiteFunc {
	return &FuncMovingWindow{fn: "average", xFilesFactor: 0}
}

func (s *FuncMovingWindow) Signature() ([]Arg, []Arg) {
	if s.fn == "" {
		return []Arg{
				ArgSeriesList{val: &s.in},
				ArgString{key: "windowSize", val: &s.windowSize, validator: []Validator{IsSignedIntervalString}},
				ArgString{key: "func", opt: true, val: &s.fn, validator: []Validator{IsConsolFunc}},
				ArgFloat{key: "xFilesFactor", opt: true, val: &s.xFilesFactor, validator: []Validator{WithinZeroOneInclusiveInterval}},
			}, []Arg{
				ArgSeriesList{},
			}
	}

	return []Arg{
			ArgSeriesList{val: &s.in},
			ArgString{key: "windowSize", val: &s.windowSize, validator: []Validator{IsSignedIntervalString}},
			ArgFloat{key: "xFilesFactor", opt: true, val: &s.xFilesFactor, validator: []Validator{WithinZeroOneInclusiveInterval}},
		}, []Arg{
			ArgSeriesList{},
		}
}

func (s *FuncMovingWindow) Context(context Context) Context {
	var err error
	s.shiftOffset, err = s.getWindowSeconds()
	if err != nil {
		// shouldn't happen, validated above
		log.Warnf("movingWindow: encountered error in getWindowSeconds, %s", err)
		return context
	}

	// Adjust to fetch from the shifted context.from with
	// context.to unchanged
	context.from -= s.shiftOffset
	return context
}

func (s *FuncMovingWindow) Exec(dataMap DataMap) ([]models.Series, error) {
	series, err := s.in.Exec(dataMap)
	if err != nil {
		return nil, err
	}

	aggFunc := consolidation.GetAggFunc(consolidation.FromConsolidateBy(s.fn))
	aggFuncName := fmt.Sprintf("moving%s", strings.Title(s.fn))
	formatStr := fmt.Sprintf("%s(%%s,\"%s\")", aggFuncName, s.windowSize)

	newName := func(oldName string) string {
		return fmt.Sprintf(formatStr, oldName)
	}

	outputs := make([]models.Series, 0, len(series))
	for _, serie := range series {

		from, points, err := aggregateOnWindow(serie, aggFunc, s.shiftOffset, s.xFilesFactor)
		if err != nil {
			return nil, err
		}

		serie.Target = newName(serie.Target)
		serie.QueryPatt = newName(serie.QueryPatt)
		serie.Tags = serie.CopyTagsWith(aggFuncName, s.windowSize)
		serie.QueryFrom = from
		serie.Datapoints = points

		outputs = append(outputs, serie)
	}

	dataMap.Add(Req{}, outputs...)
	return outputs, nil
}

func aggregateOnWindow(serie models.Series, aggFunc batch.AggFunc, shiftOffset uint32, xFilesFactor float64) (uint32, []schema.Point, error) {
	out := pointSlicePool.Get()

	numPoints := len(serie.Datapoints)
	serieStart, serieEnd := serie.QueryFrom, serie.QueryTo
	queryStart := uint32(serieStart + shiftOffset)

	// Sanity check to ensure that queryStart is between serieStart and
	// serieEnd for the loop below to work (this should always be the case)"
	if queryStart < serieStart || queryStart > serieEnd {
		return 0, nil, errors.NewInternalf("query start %d doesn't lie within serie's intervals [%d,%d] ", queryStart, serieStart, serieEnd)
	}

	// if queryStart is in between points get the index of the point after
	// else get the index of the point it lies on
	ptIdx := 0
	for ptIdx < numPoints && serie.Datapoints[ptIdx].Ts < queryStart {
		ptIdx++
	}

	stIdx := 0
	stTs := serie.Datapoints[stIdx].Ts
	for ptTs := queryStart; ptTs <= serieEnd && ptIdx < numPoints; ptIdx++ {
		ptTs = serie.Datapoints[ptIdx].Ts

		// increment start index only if the difference between current and start times
		// becomes greater than the windowSize (captured in shiftOffset)
		for stIdx <= ptIdx && ptTs-stTs > shiftOffset {
			stIdx++
			stTs = serie.Datapoints[stIdx].Ts
		}

		points := serie.Datapoints[stIdx:ptIdx]
		aggPoint := schema.Point{Val: math.NaN(), Ts: ptTs}

		if pointsXffCheck(points, xFilesFactor) {
			aggPoint.Val = aggFunc(points)
		}

		out = append(out, aggPoint)
	}

	return queryStart, out, nil
}

func (s *FuncMovingWindow) getWindowSeconds() (uint32, error) {

	// Discard the sign, if present, since we need to consider
	// preceding datapoints for each point on the graph
	durStr := s.windowSize
	if durStr[0] == '-' || durStr[0] == '+' {
		durStr = durStr[1:]
	}

	// Input validation and operations above check for empty or signed widowSizes
	// or for widowSize specified as points. Errors unlikely here
	shiftOffset, err := dur.ParseDuration(durStr)
	if err != nil {
		return 0, err
	}

	return shiftOffset, nil
}
<commit_msg>fix proxy issue due to missing arg in constructor<commit_after>package expr

import (
	"fmt"
	"math"
	"strings"

	"github.com/grafana/metrictank/batch"
	"github.com/grafana/metrictank/errors"
	"github.com/grafana/metrictank/schema"
	log "github.com/sirupsen/logrus"

	"github.com/grafana/metrictank/api/models"
	"github.com/grafana/metrictank/consolidation"
	"github.com/raintank/dur"
)

type FuncMovingWindow struct {
	in           GraphiteFunc
	windowSize   string
	fn           string
	xFilesFactor float64

	shiftOffset uint32
}

// NewMovingWindowConstructor takes an agg string and returns a constructor function
func NewMovingWindowConstructor(name string) func() GraphiteFunc {
	return func() GraphiteFunc {
		return &FuncMovingWindow{fn: name}
	}
}

func NewMovingWindow() GraphiteFunc {
	return &FuncMovingWindow{fn: "average", xFilesFactor: 0}
}

func (s *FuncMovingWindow) Signature() ([]Arg, []Arg) {
	if s.fn == "" {
		return []Arg{
				ArgSeriesList{val: &s.in},
				ArgString{key: "windowSize", val: &s.windowSize, validator: []Validator{IsSignedIntervalString}},
				ArgString{key: "func", opt: true, val: &s.fn, validator: []Validator{IsConsolFunc}},
				ArgFloat{key: "xFilesFactor", opt: true, val: &s.xFilesFactor, validator: []Validator{WithinZeroOneInclusiveInterval}},
			}, []Arg{
				ArgSeriesList{},
			}
	}

	return []Arg{
			ArgSeriesList{val: &s.in},
			ArgString{key: "windowSize", val: &s.windowSize, validator: []Validator{IsSignedIntervalString}},
			ArgString{key: "func", opt: true, val: &s.fn, validator: []Validator{IsConsolFunc}},
			ArgFloat{key: "xFilesFactor", opt: true, val: &s.xFilesFactor, validator: []Validator{WithinZeroOneInclusiveInterval}},
		}, []Arg{
			ArgSeriesList{},
		}
}

func (s *FuncMovingWindow) Context(context Context) Context {
	var err error
	s.shiftOffset, err = s.getWindowSeconds()
	if err != nil {
		// shouldn't happen, validated above
		log.Warnf("movingWindow: encountered error in getWindowSeconds, %s", err)
		return context
	}

	// Adjust to fetch from the shifted context.from with
	// context.to unchanged
	context.from -= s.shiftOffset
	return context
}

func (s *FuncMovingWindow) Exec(dataMap DataMap) ([]models.Series, error) {
	series, err := s.in.Exec(dataMap)
	if err != nil {
		return nil, err
	}

	aggFunc := consolidation.GetAggFunc(consolidation.FromConsolidateBy(s.fn))
	aggFuncName := fmt.Sprintf("moving%s", strings.Title(s.fn))
	formatStr := fmt.Sprintf("%s(%%s,\"%s\")", aggFuncName, s.windowSize)

	newName := func(oldName string) string {
		return fmt.Sprintf(formatStr, oldName)
	}

	outputs := make([]models.Series, 0, len(series))
	for _, serie := range series {

		from, points, err := aggregateOnWindow(serie, aggFunc, s.shiftOffset, s.xFilesFactor)
		if err != nil {
			return nil, err
		}

		serie.Target = newName(serie.Target)
		serie.QueryPatt = newName(serie.QueryPatt)
		serie.Tags = serie.CopyTagsWith(aggFuncName, s.windowSize)
		serie.QueryFrom = from
		serie.Datapoints = points

		outputs = append(outputs, serie)
	}

	dataMap.Add(Req{}, outputs...)
	return outputs, nil
}

func aggregateOnWindow(serie models.Series, aggFunc batch.AggFunc, shiftOffset uint32, xFilesFactor float64) (uint32, []schema.Point, error) {
	out := pointSlicePool.Get()

	numPoints := len(serie.Datapoints)
	serieStart, serieEnd := serie.QueryFrom, serie.QueryTo
	queryStart := uint32(serieStart + shiftOffset)

	// Sanity check to ensure that queryStart is between serieStart and
	// serieEnd for the loop below to work (this should always be the case)"
	if queryStart < serieStart || queryStart > serieEnd {
		return 0, nil, errors.NewInternalf("query start %d doesn't lie within serie's intervals [%d,%d] ", queryStart, serieStart, serieEnd)
	}

	// if queryStart is in between points get the index of the point after
	// else get the index of the point it lies on
	ptIdx := 0
	for ptIdx < numPoints && serie.Datapoints[ptIdx].Ts < queryStart {
		ptIdx++
	}

	stIdx := 0
	stTs := serie.Datapoints[stIdx].Ts
	for ptTs := queryStart; ptTs <= serieEnd && ptIdx < numPoints; ptIdx++ {
		ptTs = serie.Datapoints[ptIdx].Ts

		// increment start index only if the difference between current and start times
		// becomes greater than the windowSize (captured in shiftOffset)
		for stIdx <= ptIdx && ptTs-stTs > shiftOffset {
			stIdx++
			stTs = serie.Datapoints[stIdx].Ts
		}

		points := serie.Datapoints[stIdx:ptIdx]
		aggPoint := schema.Point{Val: math.NaN(), Ts: ptTs}

		if pointsXffCheck(points, xFilesFactor) {
			aggPoint.Val = aggFunc(points)
		}

		out = append(out, aggPoint)
	}

	return queryStart, out, nil
}

func (s *FuncMovingWindow) getWindowSeconds() (uint32, error) {

	// Discard the sign, if present, since we need to consider
	// preceding datapoints for each point on the graph
	durStr := s.windowSize
	if durStr[0] == '-' || durStr[0] == '+' {
		durStr = durStr[1:]
	}

	// Input validation and operations above check for empty or signed widowSizes
	// or for widowSize specified as points. Errors unlikely here
	shiftOffset, err := dur.ParseDuration(durStr)
	if err != nil {
		return 0, err
	}

	return shiftOffset, nil
}
<|endoftext|>


<commit_before>// Copyright 2015 Eleme Inc. All rights reserved.

// Detector is a tcp server to detect anomalies.
//
//   detector := New(cfg, db)
//   detector.Start()
//
package detector

import (
	"bufio"
	"fmt"
	"net"

	"github.com/eleme/banshee/config"
	"github.com/eleme/banshee/models"
	"github.com/eleme/banshee/storage"
	"github.com/eleme/banshee/util"
)

// Detector is a tcp server to detect anomalies.
type Detector struct {
	// Config
	cfg *config.Config
	// Storage
	db *storage.DB
	// Rules
	rules      []string
	rulesCache *util.SafeMap
	rulesNames map[string][]string
}

var logger = util.NewLogger("detector")

// Init new Detector.
func New(cfg *config.Config, db *storage.DB) *Detector {
	detector := new(Detector)
	detector.cfg = cfg
	detector.db = db
	detector.rulesCache = util.NewSafeMap()
	detector.rulesNames = map[string][]string{}
	// FIXME: rules
	return detector
}

// Start detector
func (detector *Detector) Start() {
	addr := fmt.Sprintf("0.0.0.0:%d", detector.cfg.Detector.Port)
	ln, err := net.Listen("tcp", addr)
	if err != nil {
		logger.Fatal("failed to bind tcp://%s: %v", addr, err)
	}
	logger.Info("listening on tcp://%s..", addr)
	for {
		conn, err := ln.Accept()
		if err != nil {
			logger.Fatal("failed to accept new conn: %v", err)
		}
		go detector.handle(conn)
	}
}

// Handle a connection, it will filter the mertics by rules and detect whether
// the metrics are anomalies.
func (detector *Detector) handle(conn net.Conn) {
	addr := conn.RemoteAddr()
	defer func() {
		conn.Close()
		logger.Info("conn %s disconnected", addr)
	}()
	scanner := bufio.NewScanner(conn)
	for scanner.Scan() {
		if err := scanner.Err(); err != nil {
			logger.Info("failed to read conn: %v, closing it..", err)
			break
		}
		line := scanner.Text()
		m, err := parseMetric(line)
		if err != nil {
			if len(line) > 10 {
				line = line[:10]
			}
			logger.Error("failed to parse '%s': %v, skipping..", line, err)
			continue
		}
		if detector.match(m) {
			err = detector.detect(m)
			if err != nil {
				logger.Error("failed to detect metric: %v, skipping..", err)
				continue
			}
			logger.Info("%s %.3f", m.Name, m.Score)
		}
	}
}

func (detector *Detector) match(m *models.Metric) bool {
	// FIXME
	// v, ok := detector.rulesCache.Get(m.Name)
	// b := v.(bool)
	// if b && ok {
	// 	return true
	// }

	for _, pattern := range detector.cfg.Detector.BlackList {
		matched := util.FnMatch(pattern, m.Name)
		if matched {
			return false
		}
	}
	return true // FIXME: return true tempory
	// FIXME: get rules from db
	for _, pattern := range detector.rules {
		matched := util.FnMatch(pattern, m.Name)
		if matched {
			detector.rulesCache.Set(m.Name, true)
			slice, exists := detector.rulesNames[pattern]
			if exists {
				detector.rulesNames[pattern] = append(slice, m.Name)
			} else {
				detector.rulesNames[pattern] = []string{m.Name}
			}
			return true
		}
	}
	return false
}

// Detect incoming metric with 3-sigma rule and fill the metric.Score.
func (detector *Detector) detect(m *models.Metric) error {
	// TODO
	return nil
}
<commit_msg>Add simple detector.detect implementation<commit_after>// Copyright 2015 Eleme Inc. All rights reserved.

// Detector is a tcp server to detect anomalies.
//
//   detector := New(cfg, db)
//   detector.Start()
//
package detector

import (
	"bufio"
	"fmt"
	"net"

	"github.com/eleme/banshee/algorithm"
	"github.com/eleme/banshee/config"
	"github.com/eleme/banshee/models"
	"github.com/eleme/banshee/storage"
	"github.com/eleme/banshee/util"
)

// Detector is a tcp server to detect anomalies.
type Detector struct {
	// Debug
	debug bool
	// Config
	cfg *config.Config
	// Logger
	logger *util.Logger
	// Storage
	db *storage.DB
	// Rules
	rules      []string
	rulesCache *util.SafeMap
	rulesNames map[string][]string
}

// Init new Detector.
func New(debug bool, cfg *config.Config, db *storage.DB) *Detector {
	detector := new(Detector)
	detector.debug = debug
	detector.cfg = cfg
	detector.logger = util.NewLogger("detector")
	if detector.debug {
		detector.logger.SetLevel(util.LOG_DEBUG)
	}
	detector.db = db
	detector.rulesCache = util.NewSafeMap()
	detector.rulesNames = map[string][]string{}
	// FIXME: rules
	return detector
}

// Start detector
func (detector *Detector) Start() {
	addr := fmt.Sprintf("0.0.0.0:%d", detector.cfg.Detector.Port)
	ln, err := net.Listen("tcp", addr)
	if err != nil {
		detector.logger.Fatal("failed to bind tcp://%s: %v", addr, err)
	}
	detector.logger.Info("listening on tcp://%s..", addr)
	for {
		conn, err := ln.Accept()
		if err != nil {
			detector.logger.Fatal("failed to accept new conn: %v", err)
		}
		go detector.handle(conn)
	}
}

// Handle a connection, it will filter the mertics by rules and detect whether
// the metrics are anomalies.
func (detector *Detector) handle(conn net.Conn) {
	addr := conn.RemoteAddr()
	defer func() {
		conn.Close()
		detector.logger.Info("conn %s disconnected", addr)
	}()
	scanner := bufio.NewScanner(conn)
	for scanner.Scan() {
		if err := scanner.Err(); err != nil {
			detector.logger.Info("failed to read conn: %v, closing it..", err)
			break
		}
		line := scanner.Text()
		m, err := parseMetric(line)
		if err != nil {
			if len(line) > 10 {
				line = line[:10]
			}
			detector.logger.Error("failed to parse '%s': %v, skipping..", line, err)
			continue
		}
		if detector.match(m) {
			err = detector.detect(m)
			if err != nil {
				detector.logger.Error("failed to detect metric: %v, skipping..", err)
				continue
			}
			detector.logger.Debug("detected %s => average %.3f, socre %.3f", m.Name, m.Average, m.Score)
		}
	}
}

func (detector *Detector) match(m *models.Metric) bool {
	// FIXME
	// v, ok := detector.rulesCache.Get(m.Name)
	// b := v.(bool)
	// if b && ok {
	// 	return true
	// }

	for _, pattern := range detector.cfg.Detector.BlackList {
		matched := util.FnMatch(pattern, m.Name)
		if matched {
			return false
		}
	}
	return true // FIXME: return true tempory
	// FIXME: get rules from db
	for _, pattern := range detector.rules {
		matched := util.FnMatch(pattern, m.Name)
		if matched {
			detector.rulesCache.Set(m.Name, true)
			slice, exists := detector.rulesNames[pattern]
			if exists {
				detector.rulesNames[pattern] = append(slice, m.Name)
			} else {
				detector.rulesNames[pattern] = []string{m.Name}
			}
			return true
		}
	}
	return false
}

// Detect incoming metric with 3-sigma rule and fill the metric.Score.
func (detector *Detector) detect(m *models.Metric) error {
	wf := detector.cfg.Detector.TrendFactor
	startSize := detector.cfg.Detector.StartSize
	state, err := detector.db.GetState(m)
	if err != nil && err != storage.ErrNotFound {
		return err
	}
	stateNext := &models.State{}
	if err == storage.ErrNotFound {
		m.Average = m.Value
		stateNext.Average = m.Value
		stateNext.StdDev = 0
		stateNext.Count = 1
	} else {
		m.Average = state.Average
		stateNext.Average = algorithm.Ewma(wf, state.Average, m.Value)
		stateNext.StdDev = algorithm.Ewms(wf, state.Average, stateNext.Average, state.StdDev, m.Value)
		if state.Count < startSize {
			stateNext.Count = state.Count + 1
		} else {
			stateNext.Count = state.Count
		}
	}
	if stateNext.Count >= startSize {
		m.Score = algorithm.Div3Sigma(stateNext.Average, stateNext.StdDev, m.Value)
	} else {
		m.Score = 0
	}
	err = detector.db.PutState(m, stateNext)
	return err
}
<|endoftext|>


<commit_before>package aws

import (
	"errors"
	"fmt"
	"log"
	"strings"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/service/lambda"
	"github.com/hashicorp/terraform/helper/schema"
)

func resourceAwsLambdaAlias() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsLambdaAliasCreate,
		Read:   resourceAwsLambdaAliasRead,
		Update: resourceAwsLambdaAliasUpdate,
		Delete: resourceAwsLambdaAliasDelete,

		Schema: map[string]*schema.Schema{
			"description": {
				Type:     schema.TypeString,
				Optional: true,
			},
			"function_name": {
				Type:     schema.TypeString,
				Required: true,
			},
			"function_version": {
				Type:     schema.TypeString,
				Required: true,
			},
			"name": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},
			"arn": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"routing_config": {
				Type:     schema.TypeList,
				Optional: true,
				MaxItems: 1,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"additional_version_weights": {
							Type:     schema.TypeMap,
							Optional: true,
							Elem:     &schema.Schema{Type: schema.TypeFloat},
						},
					},
				},
			},
		},
	}
}

// resourceAwsLambdaAliasCreate maps to:
// CreateAlias in the API / SDK
func resourceAwsLambdaAliasCreate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).lambdaconn

	functionName := d.Get("function_name").(string)
	aliasName := d.Get("name").(string)

	log.Printf("[DEBUG] Creating Lambda alias: alias %s for function %s", aliasName, functionName)

	params := &lambda.CreateAliasInput{
		Description:     aws.String(d.Get("description").(string)),
		FunctionName:    aws.String(functionName),
		FunctionVersion: aws.String(d.Get("function_version").(string)),
		Name:            aws.String(aliasName),
	}

	if v, ok := d.GetOk("routing_config"); ok {
		params.RoutingConfig = expandLambdaAliasRoutingConfiguration(v.([]interface{}))
	}

	aliasConfiguration, err := conn.CreateAlias(params)
	if err != nil {
		return fmt.Errorf("Error creating Lambda alias: %s", err)
	}

	d.SetId(*aliasConfiguration.AliasArn)

	return resourceAwsLambdaAliasRead(d, meta)
}

// resourceAwsLambdaAliasRead maps to:
// GetAlias in the API / SDK
func resourceAwsLambdaAliasRead(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).lambdaconn

	log.Printf("[DEBUG] Fetching Lambda alias: %s:%s", d.Get("function_name"), d.Get("name"))

	params := &lambda.GetAliasInput{
		FunctionName: aws.String(d.Get("function_name").(string)),
		Name:         aws.String(d.Get("name").(string)),
	}

	aliasConfiguration, err := conn.GetAlias(params)
	if err != nil {
		if awsErr, ok := err.(awserr.Error); ok {
			if awsErr.Code() == "ResourceNotFoundException" && strings.Contains(awsErr.Message(), "Cannot find alias arn") {
				d.SetId("")
				return nil
			}
		}
		return err
	}

	d.Set("description", aliasConfiguration.Description)
	d.Set("function_version", aliasConfiguration.FunctionVersion)
	d.Set("name", aliasConfiguration.Name)
	d.Set("arn", aliasConfiguration.AliasArn)

	if err := d.Set("routing_config", flattenLambdaAliasRoutingConfiguration(aliasConfiguration.RoutingConfig)); err != nil {
		return fmt.Errorf("error setting routing_config: %s", err)
	}

	return nil
}

// resourceAwsLambdaAliasDelete maps to:
// DeleteAlias in the API / SDK
func resourceAwsLambdaAliasDelete(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).lambdaconn

	log.Printf("[INFO] Deleting Lambda alias: %s:%s", d.Get("function_name"), d.Get("name"))

	params := &lambda.DeleteAliasInput{
		FunctionName: aws.String(d.Get("function_name").(string)),
		Name:         aws.String(d.Get("name").(string)),
	}

	_, err := conn.DeleteAlias(params)
	if err != nil {
		return fmt.Errorf("Error deleting Lambda alias: %s", err)
	}

	return nil
}

// resourceAwsLambdaAliasUpdate maps to:
// UpdateAlias in the API / SDK
func resourceAwsLambdaAliasUpdate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).lambdaconn

	log.Printf("[DEBUG] Updating Lambda alias: %s:%s", d.Get("function_name"), d.Get("name"))

	params := &lambda.UpdateAliasInput{
		Description:     aws.String(d.Get("description").(string)),
		FunctionName:    aws.String(d.Get("function_name").(string)),
		FunctionVersion: aws.String(d.Get("function_version").(string)),
		Name:            aws.String(d.Get("name").(string)),
		RoutingConfig:   &lambda.AliasRoutingConfiguration{},
	}

	if v, ok := d.GetOk("routing_config"); ok {
		routingConfigs := v.([]interface{})
		routingConfig, ok := routingConfigs[0].(map[string]interface{})
		if !ok {
			return errors.New("At least one field is expected inside routing_config")
		}

		if additionalVersionWeights, ok := routingConfig["additional_version_weights"]; ok {
			weights := readAdditionalVersionWeights(additionalVersionWeights.(map[string]interface{}))
			params.RoutingConfig.AdditionalVersionWeights = aws.Float64Map(weights)
		}
	}

	_, err := conn.UpdateAlias(params)
	if err != nil {
		return fmt.Errorf("Error updating Lambda alias: %s", err)
	}

	return nil
}

func expandLambdaAliasRoutingConfiguration(l []interface{}) *lambda.AliasRoutingConfiguration {
	aliasRoutingConfiguration := &lambda.AliasRoutingConfiguration{}

	if len(l) == 0 || l[0] == nil {
		return aliasRoutingConfiguration
	}

	m := l[0].(map[string]interface{})

	if _, ok := m["additional_version_weights"]; ok {
		aliasRoutingConfiguration.AdditionalVersionWeights = expandFloat64Map(m)
	}

	return aliasRoutingConfiguration
}

func readAdditionalVersionWeights(avw map[string]interface{}) map[string]float64 {
	weights := make(map[string]float64)
	for k, v := range avw {
		weights[k] = v.(float64)
	}

	return weights
}
<commit_msg>resource/aws_lambda_alias: Consolidate update function to also use expandLambdaAliasRoutingConfiguration and remove readAdditionalVersionWeights<commit_after>package aws

import (
	"fmt"
	"log"
	"strings"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/service/lambda"
	"github.com/hashicorp/terraform/helper/schema"
)

func resourceAwsLambdaAlias() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsLambdaAliasCreate,
		Read:   resourceAwsLambdaAliasRead,
		Update: resourceAwsLambdaAliasUpdate,
		Delete: resourceAwsLambdaAliasDelete,

		Schema: map[string]*schema.Schema{
			"description": {
				Type:     schema.TypeString,
				Optional: true,
			},
			"function_name": {
				Type:     schema.TypeString,
				Required: true,
			},
			"function_version": {
				Type:     schema.TypeString,
				Required: true,
			},
			"name": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},
			"arn": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"routing_config": {
				Type:     schema.TypeList,
				Optional: true,
				MaxItems: 1,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"additional_version_weights": {
							Type:     schema.TypeMap,
							Optional: true,
							Elem:     &schema.Schema{Type: schema.TypeFloat},
						},
					},
				},
			},
		},
	}
}

// resourceAwsLambdaAliasCreate maps to:
// CreateAlias in the API / SDK
func resourceAwsLambdaAliasCreate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).lambdaconn

	functionName := d.Get("function_name").(string)
	aliasName := d.Get("name").(string)

	log.Printf("[DEBUG] Creating Lambda alias: alias %s for function %s", aliasName, functionName)

	params := &lambda.CreateAliasInput{
		Description:     aws.String(d.Get("description").(string)),
		FunctionName:    aws.String(functionName),
		FunctionVersion: aws.String(d.Get("function_version").(string)),
		Name:            aws.String(aliasName),
		RoutingConfig:   expandLambdaAliasRoutingConfiguration(d.Get("routing_config").([]interface{})),
	}

	aliasConfiguration, err := conn.CreateAlias(params)
	if err != nil {
		return fmt.Errorf("Error creating Lambda alias: %s", err)
	}

	d.SetId(*aliasConfiguration.AliasArn)

	return resourceAwsLambdaAliasRead(d, meta)
}

// resourceAwsLambdaAliasRead maps to:
// GetAlias in the API / SDK
func resourceAwsLambdaAliasRead(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).lambdaconn

	log.Printf("[DEBUG] Fetching Lambda alias: %s:%s", d.Get("function_name"), d.Get("name"))

	params := &lambda.GetAliasInput{
		FunctionName: aws.String(d.Get("function_name").(string)),
		Name:         aws.String(d.Get("name").(string)),
	}

	aliasConfiguration, err := conn.GetAlias(params)
	if err != nil {
		if awsErr, ok := err.(awserr.Error); ok {
			if awsErr.Code() == "ResourceNotFoundException" && strings.Contains(awsErr.Message(), "Cannot find alias arn") {
				d.SetId("")
				return nil
			}
		}
		return err
	}

	d.Set("description", aliasConfiguration.Description)
	d.Set("function_version", aliasConfiguration.FunctionVersion)
	d.Set("name", aliasConfiguration.Name)
	d.Set("arn", aliasConfiguration.AliasArn)

	if err := d.Set("routing_config", flattenLambdaAliasRoutingConfiguration(aliasConfiguration.RoutingConfig)); err != nil {
		return fmt.Errorf("error setting routing_config: %s", err)
	}

	return nil
}

// resourceAwsLambdaAliasDelete maps to:
// DeleteAlias in the API / SDK
func resourceAwsLambdaAliasDelete(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).lambdaconn

	log.Printf("[INFO] Deleting Lambda alias: %s:%s", d.Get("function_name"), d.Get("name"))

	params := &lambda.DeleteAliasInput{
		FunctionName: aws.String(d.Get("function_name").(string)),
		Name:         aws.String(d.Get("name").(string)),
	}

	_, err := conn.DeleteAlias(params)
	if err != nil {
		return fmt.Errorf("Error deleting Lambda alias: %s", err)
	}

	return nil
}

// resourceAwsLambdaAliasUpdate maps to:
// UpdateAlias in the API / SDK
func resourceAwsLambdaAliasUpdate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).lambdaconn

	log.Printf("[DEBUG] Updating Lambda alias: %s:%s", d.Get("function_name"), d.Get("name"))

	params := &lambda.UpdateAliasInput{
		Description:     aws.String(d.Get("description").(string)),
		FunctionName:    aws.String(d.Get("function_name").(string)),
		FunctionVersion: aws.String(d.Get("function_version").(string)),
		Name:            aws.String(d.Get("name").(string)),
		RoutingConfig:   expandLambdaAliasRoutingConfiguration(d.Get("routing_config").([]interface{})),
	}

	_, err := conn.UpdateAlias(params)
	if err != nil {
		return fmt.Errorf("Error updating Lambda alias: %s", err)
	}

	return nil
}

func expandLambdaAliasRoutingConfiguration(l []interface{}) *lambda.AliasRoutingConfiguration {
	aliasRoutingConfiguration := &lambda.AliasRoutingConfiguration{}

	if len(l) == 0 || l[0] == nil {
		return aliasRoutingConfiguration
	}

	m := l[0].(map[string]interface{})

	if _, ok := m["additional_version_weights"]; ok {
		aliasRoutingConfiguration.AdditionalVersionWeights = expandFloat64Map(m)
	}

	return aliasRoutingConfiguration
}
<|endoftext|>