<commit_before>package domain_test

import (
	"errors"
	"net"
	"net/http"
	"time"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"

	"github.com/cloudfoundry-incubator/switchboard/domain"
	"github.com/cloudfoundry-incubator/switchboard/domain/fakes"
	"github.com/pivotal-golang/lager"
	"github.com/pivotal-golang/lager/lagertest"
)

var _ = Describe("Cluster", func() {
	var backends *fakes.FakeBackends
	var logger lager.Logger
	var cluster domain.Cluster
	var fakeArpManager *fakes.FakeArpManager
	healthcheckTimeout := time.Second

	BeforeEach(func() {
		fakeArpManager = nil
		backends = &fakes.FakeBackends{}
	})

	JustBeforeEach(func() {
		logger = lagertest.NewTestLogger("Cluster test")
		cluster = domain.NewCluster(backends, healthcheckTimeout, logger, fakeArpManager)
	})

	Describe("Monitor", func() {
		var backend1, backend2, backend3 *fakes.FakeBackend
		var urlGetter *fakes.FakeUrlGetter
		var healthyResponse = &http.Response{
			Body:       &fakes.FakeReadWriteCloser{},
			StatusCode: http.StatusOK,
		}

		BeforeEach(func() {
			backend1 = &fakes.FakeBackend{}
			backend1.AsJSONReturns(domain.BackendJSON{Host: "10.10.1.2"})
			backend1.HealthcheckUrlReturns("backend1")

			backend2 = &fakes.FakeBackend{}
			backend2.AsJSONReturns(domain.BackendJSON{Host: "10.10.2.2"})
			backend2.HealthcheckUrlReturns("backend2")

			backend3 = &fakes.FakeBackend{}
			backend3.AsJSONReturns(domain.BackendJSON{Host: "10.10.3.2"})
			backend3.HealthcheckUrlReturns("backend3")

			backends.AllStub = func() <-chan domain.Backend {
				c := make(chan domain.Backend)
				go func() {
					c <- backend1
					c <- backend2
					c <- backend3
					close(c)
				}()
				return c
			}

			urlGetter = &fakes.FakeUrlGetter{}
			urlGetter := urlGetter
			domain.UrlGetterProvider = func(time.Duration) domain.UrlGetter {
				return urlGetter
			}

			urlGetter.GetReturns(healthyResponse, nil)
		})

		AfterEach(func() {
			domain.UrlGetterProvider = domain.HttpUrlGetterProvider
		})

		It("notices when each backend stays healthy", func(done Done) {
			defer close(done)

			stopMonitoring := cluster.Monitor()
			defer close(stopMonitoring)

			Eventually(backends.SetHealthyCallCount, 2*time.Second).Should(BeNumerically(">=", 3))
			Expect(backends.SetHealthyArgsForCall(0)).To(Equal(backend1))
			Expect(backends.SetHealthyArgsForCall(1)).To(Equal(backend2))
			Expect(backends.SetHealthyArgsForCall(2)).To(Equal(backend3))
		}, 5)

		It("notices when a healthy backend becomes unhealthy", func(done Done) {
			defer close(done)

			unhealthyResponse := &http.Response{
				Body:       &fakes.FakeReadWriteCloser{},
				StatusCode: http.StatusInternalServerError,
			}

			urlGetter.GetStub = func(url string) (*http.Response, error) {
				if url == "backend2" {
					return unhealthyResponse, nil
				} else {
					return healthyResponse, nil
				}
			}

			stopMonitoring := cluster.Monitor()
			defer close(stopMonitoring)

			Eventually(backends.SetHealthyCallCount, 2*time.Second).Should(BeNumerically(">=", 2))
			Expect(backends.SetHealthyArgsForCall(0)).To(Equal(backend1))
			Expect(backends.SetHealthyArgsForCall(1)).To(Equal(backend3))

			Expect(backends.SetUnhealthyCallCount()).To(BeNumerically(">=", 1))
			Expect(backends.SetUnhealthyArgsForCall(0)).To(Equal(backend2))
		}, 5)

		It("notices when a healthy backend becomes unresponsive", func(done Done) {
			defer close(done)

			urlGetter.GetStub = func(url string) (*http.Response, error) {
				if url == "backend2" {
					return nil, errors.New("some error")
				} else {
					return healthyResponse, nil
				}
			}

			stopMonitoring := cluster.Monitor()
			defer close(stopMonitoring)

			Eventually(backends.SetHealthyCallCount, 2*time.Second).Should(BeNumerically(">=", 2))
			Expect(backends.SetHealthyArgsForCall(0)).Should(Equal(backend1))
			Expect(backends.SetHealthyArgsForCall(1)).Should(Equal(backend3))

			Consistently(func() int {
				return backends.SetUnhealthyCallCount()
			}, 2*time.Second).Should(BeNumerically(">=", 1))
			Expect(backends.SetUnhealthyArgsForCall(0)).Should(Equal(backend2))
		}, 5)

		It("notices when an unhealthy backend becomes healthy", func(done Done) {
			defer close(done)

			unhealthyResponse := &http.Response{
				Body:       &fakes.FakeReadWriteCloser{},
				StatusCode: http.StatusInternalServerError,
			}

			isUnhealthy := true
			urlGetter.GetStub = func(url string) (*http.Response, error) {
				if url == "backend2" && isUnhealthy {
					isUnhealthy = false
					return unhealthyResponse, nil
				} else {
					return healthyResponse, nil
				}
			}

			stopMonitoring := cluster.Monitor()
			defer close(stopMonitoring)

			initialHealthyBackendCount := 2
			initialUnhealthyBackendCount := 1
			finalHealthyBackendCount := 3
			Eventually(backends.SetHealthyCallCount, 2*time.Second).Should(BeNumerically(">=", initialHealthyBackendCount+finalHealthyBackendCount))
			Expect(backends.SetUnhealthyCallCount()).To(Equal(initialUnhealthyBackendCount))
		}, 5)

		Context("when a backend is healthy", func() {

			BeforeEach(func() {
				fakeArpManager = &fakes.FakeArpManager{}
			})

			It("does not clears arp cache after ArpFlushInterval has elapsed", func() {
				stopMonitoring := cluster.Monitor()
				defer close(stopMonitoring)

				Consistently(fakeArpManager.ClearCacheCallCount, healthcheckTimeout*2).Should(BeZero())
			})
		})

		Context("when a backend is unhealthy", func() {

			BeforeEach(func() {
				fakeArpManager = &fakes.FakeArpManager{}
				unhealthyResponse := &http.Response{
					Body:       &fakes.FakeReadWriteCloser{},
					StatusCode: http.StatusInternalServerError,
				}

				urlGetter.GetStub = func(url string) (*http.Response, error) {
					if url == "backend2" {
						return unhealthyResponse, nil
					} else {
						return healthyResponse, nil
					}
				}
			})

			Context("and the IP is in the ARP cache", func() {

				BeforeEach(func() {
					fakeArpManager.IsCachedStub = func(ip string) bool {
						if ip == backend2.AsJSON().Host {
							return true
						} else {
							return false
						}
					}
				})

				It("clears the arp cache after ArpFlushInterval has elapsed", func() {

					stopMonitoring := cluster.Monitor()
					defer close(stopMonitoring)

					Expect(fakeArpManager.ClearCacheCallCount()).To(BeZero())
					Eventually(fakeArpManager.ClearCacheCallCount, healthcheckTimeout*3).Should(BeNumerically(">=", 1), "Expected arpManager.ClearCache to be called at least once")
					Expect(fakeArpManager.ClearCacheArgsForCall(0)).To(Equal(backend2.AsJSON().Host))
				})
			})

			Context("and the IP is not in the ARP cache", func() {

				BeforeEach(func() {
					fakeArpManager.IsCachedReturns(false)
				})

				It("does not clear arp cache", func() {

					stopMonitoring := cluster.Monitor()
					defer close(stopMonitoring)

					Consistently(fakeArpManager.ClearCacheCallCount, healthcheckTimeout*2).Should(BeZero())
				})
			})
		})
	})
	Describe("RouteToBackend", func() {
		var clientConn net.Conn

		BeforeEach(func() {
			clientConn = &fakes.FakeConn{}
		})

		It("bridges the client connection to the active backend", func() {
			activeBackend := &fakes.FakeBackend{}
			backends.ActiveReturns(activeBackend)

			err := cluster.RouteToBackend(clientConn)

			Expect(err).ShouldNot(HaveOccurred())
			Expect(activeBackend.BridgeCallCount()).To(Equal(1))
			Expect(activeBackend.BridgeArgsForCall(0)).To(Equal(clientConn))
		})

		It("returns an error if there is no active backend", func() {
			backends.ActiveReturns(nil)

			err := cluster.RouteToBackend(clientConn)

			Expect(err).Should(HaveOccurred())
		})
	})
})
<commit_msg>Do not rely on ordering of backends in tests<commit_after>package domain_test

import (
	"errors"
	"net"
	"net/http"
	"time"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"

	"github.com/cloudfoundry-incubator/switchboard/domain"
	"github.com/cloudfoundry-incubator/switchboard/domain/fakes"
	"github.com/pivotal-golang/lager"
	"github.com/pivotal-golang/lager/lagertest"
)

var _ = Describe("Cluster", func() {
	var backends *fakes.FakeBackends
	var logger lager.Logger
	var cluster domain.Cluster
	var fakeArpManager *fakes.FakeArpManager
	healthcheckTimeout := time.Second

	BeforeEach(func() {
		fakeArpManager = nil
		backends = &fakes.FakeBackends{}
	})

	JustBeforeEach(func() {
		logger = lagertest.NewTestLogger("Cluster test")
		cluster = domain.NewCluster(backends, healthcheckTimeout, logger, fakeArpManager)
	})

	Describe("Monitor", func() {
		var backend1, backend2, backend3 *fakes.FakeBackend
		var urlGetter *fakes.FakeUrlGetter
		var healthyResponse = &http.Response{
			Body:       &fakes.FakeReadWriteCloser{},
			StatusCode: http.StatusOK,
		}

		BeforeEach(func() {
			backend1 = &fakes.FakeBackend{}
			backend1.AsJSONReturns(domain.BackendJSON{Host: "10.10.1.2"})
			backend1.HealthcheckUrlReturns("backend1")

			backend2 = &fakes.FakeBackend{}
			backend2.AsJSONReturns(domain.BackendJSON{Host: "10.10.2.2"})
			backend2.HealthcheckUrlReturns("backend2")

			backend3 = &fakes.FakeBackend{}
			backend3.AsJSONReturns(domain.BackendJSON{Host: "10.10.3.2"})
			backend3.HealthcheckUrlReturns("backend3")

			backends.AllStub = func() <-chan domain.Backend {
				c := make(chan domain.Backend)
				go func() {
					c <- backend1
					c <- backend2
					c <- backend3
					close(c)
				}()
				return c
			}

			urlGetter = &fakes.FakeUrlGetter{}
			urlGetter := urlGetter
			domain.UrlGetterProvider = func(time.Duration) domain.UrlGetter {
				return urlGetter
			}

			urlGetter.GetReturns(healthyResponse, nil)
		})

		AfterEach(func() {
			domain.UrlGetterProvider = domain.HttpUrlGetterProvider
		})

		It("notices when each backend stays healthy", func(done Done) {
			defer close(done)

			stopMonitoring := cluster.Monitor()
			defer close(stopMonitoring)

			Eventually(backends.SetHealthyCallCount, 2*time.Second).Should(BeNumerically(">=", 3))

			Expect(backends.SetHealthyCallCount()).To(Equal(3))

			healthyBackends := []domain.Backend{}
			for i := 0; i < 3; i++ {
				healthyBackends = append(healthyBackends, backends.SetHealthyArgsForCall(i))
			}

			Expect(healthyBackends).To(ConsistOf([]domain.Backend{
				backend1,
				backend2,
				backend3,
			}))
		}, 5)

		It("notices when a healthy backend becomes unhealthy", func(done Done) {
			defer close(done)

			unhealthyResponse := &http.Response{
				Body:       &fakes.FakeReadWriteCloser{},
				StatusCode: http.StatusInternalServerError,
			}

			urlGetter.GetStub = func(url string) (*http.Response, error) {
				if url == "backend2" {
					return unhealthyResponse, nil
				} else {
					return healthyResponse, nil
				}
			}

			stopMonitoring := cluster.Monitor()
			defer close(stopMonitoring)

			Eventually(backends.SetHealthyCallCount, 2*time.Second).Should(Equal(2))
			healthyBackends := []domain.Backend{}
			for i := 0; i < 2; i++ {
				healthyBackends = append(healthyBackends, backends.SetHealthyArgsForCall(i))
			}

			Expect(healthyBackends).To(ConsistOf([]domain.Backend{
				backend1,
				backend3,
			}))

			Expect(backends.SetUnhealthyCallCount()).To(BeNumerically(">=", 1))
			Expect(backends.SetUnhealthyArgsForCall(0)).To(Equal(backend2))
		}, 5)

		It("notices when a healthy backend becomes unresponsive", func(done Done) {
			defer close(done)

			urlGetter.GetStub = func(url string) (*http.Response, error) {
				if url == "backend2" {
					return nil, errors.New("some error")
				} else {
					return healthyResponse, nil
				}
			}

			stopMonitoring := cluster.Monitor()
			defer close(stopMonitoring)

			Eventually(backends.SetHealthyCallCount, 2*time.Second).Should(Equal(2))
			healthyBackends := []domain.Backend{}
			for i := 0; i < 2; i++ {
				healthyBackends = append(healthyBackends, backends.SetHealthyArgsForCall(i))
			}

			Expect(healthyBackends).To(ConsistOf([]domain.Backend{
				backend1,
				backend3,
			}))

			Consistently(func() int {
				return backends.SetUnhealthyCallCount()
			}, 2*time.Second).Should(BeNumerically(">=", 1))
			Expect(backends.SetUnhealthyArgsForCall(0)).Should(Equal(backend2))
		}, 5)

		It("notices when an unhealthy backend becomes healthy", func(done Done) {
			defer close(done)

			unhealthyResponse := &http.Response{
				Body:       &fakes.FakeReadWriteCloser{},
				StatusCode: http.StatusInternalServerError,
			}

			isUnhealthy := true
			urlGetter.GetStub = func(url string) (*http.Response, error) {
				if url == "backend2" && isUnhealthy {
					isUnhealthy = false
					return unhealthyResponse, nil
				} else {
					return healthyResponse, nil
				}
			}

			stopMonitoring := cluster.Monitor()
			defer close(stopMonitoring)

			initialHealthyBackendCount := 2
			initialUnhealthyBackendCount := 1
			finalHealthyBackendCount := 3
			Eventually(backends.SetHealthyCallCount, 2*time.Second).Should(BeNumerically(">=", initialHealthyBackendCount+finalHealthyBackendCount))
			Expect(backends.SetUnhealthyCallCount()).To(Equal(initialUnhealthyBackendCount))
		}, 5)

		Context("when a backend is healthy", func() {

			BeforeEach(func() {
				fakeArpManager = &fakes.FakeArpManager{}
			})

			It("does not clears arp cache after ArpFlushInterval has elapsed", func() {
				stopMonitoring := cluster.Monitor()
				defer close(stopMonitoring)

				Consistently(fakeArpManager.ClearCacheCallCount, healthcheckTimeout*2).Should(BeZero())
			})
		})

		Context("when a backend is unhealthy", func() {

			BeforeEach(func() {
				fakeArpManager = &fakes.FakeArpManager{}
				unhealthyResponse := &http.Response{
					Body:       &fakes.FakeReadWriteCloser{},
					StatusCode: http.StatusInternalServerError,
				}

				urlGetter.GetStub = func(url string) (*http.Response, error) {
					if url == "backend2" {
						return unhealthyResponse, nil
					} else {
						return healthyResponse, nil
					}
				}
			})

			Context("and the IP is in the ARP cache", func() {

				BeforeEach(func() {
					fakeArpManager.IsCachedStub = func(ip string) bool {
						if ip == backend2.AsJSON().Host {
							return true
						} else {
							return false
						}
					}
				})

				It("clears the arp cache after ArpFlushInterval has elapsed", func() {

					stopMonitoring := cluster.Monitor()
					defer close(stopMonitoring)

					Expect(fakeArpManager.ClearCacheCallCount()).To(BeZero())
					Eventually(fakeArpManager.ClearCacheCallCount, healthcheckTimeout*3).Should(BeNumerically(">=", 1), "Expected arpManager.ClearCache to be called at least once")
					Expect(fakeArpManager.ClearCacheArgsForCall(0)).To(Equal(backend2.AsJSON().Host))
				})
			})

			Context("and the IP is not in the ARP cache", func() {

				BeforeEach(func() {
					fakeArpManager.IsCachedReturns(false)
				})

				It("does not clear arp cache", func() {

					stopMonitoring := cluster.Monitor()
					defer close(stopMonitoring)

					Consistently(fakeArpManager.ClearCacheCallCount, healthcheckTimeout*2).Should(BeZero())
				})
			})
		})
	})
	Describe("RouteToBackend", func() {
		var clientConn net.Conn

		BeforeEach(func() {
			clientConn = &fakes.FakeConn{}
		})

		It("bridges the client connection to the active backend", func() {
			activeBackend := &fakes.FakeBackend{}
			backends.ActiveReturns(activeBackend)

			err := cluster.RouteToBackend(clientConn)

			Expect(err).ShouldNot(HaveOccurred())
			Expect(activeBackend.BridgeCallCount()).To(Equal(1))
			Expect(activeBackend.BridgeArgsForCall(0)).To(Equal(clientConn))
		})

		It("returns an error if there is no active backend", func() {
			backends.ActiveReturns(nil)

			err := cluster.RouteToBackend(clientConn)

			Expect(err).Should(HaveOccurred())
		})
	})
})
<|endoftext|>


<commit_before>package base

import "strings"

func SanitizedRepoPath(repo string) string {
	if strings.HasSuffix(repo, ".git") {
		repo = repo[:len(repo)-4]
	}
	if strings.HasPrefix(repo, "https://") {
		repo = repo[len("https://"):]
	}
	return repo
}
<commit_msg>remove extra space<commit_after>package base

import "strings"

func SanitizedRepoPath(repo string) string {
	repo = strings.TrimSpace(repo)
	if strings.HasSuffix(repo, ".git") {
		repo = repo[:len(repo)-4]
	}
	if strings.HasPrefix(repo, "https://") {
		repo = repo[len("https://"):]
	}
	return repo
}
<|endoftext|>


<commit_before>package edit

import (
	"github.com/elves/elvish/pkg/eval"
	"github.com/elves/elvish/pkg/eval/errs"
	"github.com/elves/elvish/pkg/eval/vals"
	"github.com/elves/elvish/pkg/eval/vars"
)

func initVarsAPI(ed *Editor, nb eval.NsBuilder) {
	nb.AddGoFns("<edit>:", map[string]interface{}{
		"add-var":  addVar,
		"add-vars": addVars,
	})
}

//elvdoc:fn add-var
//
// ```elvish
// edit:add-var $name $value
// ```
//
// Declares a new variable in the REPL. The new variable becomes available
// during the next REPL cycle.
//
// Equivalent to running `var $name = $value` at the REPL, but `$name` can be
// dynamic.
//
// Example:
//
// ```elvish-transcript
// ~> edit:add-var foo bar
// ~> put $foo
// ▶ bar
// ```

func addVar(fm *eval.Frame, name string, val interface{}) error {
	if !eval.IsUnqualified(name) {
		return errs.BadValue{
			What:  "name argument to edit:add-var",
			Valid: "unqualified variable name", Actual: name}
	}
	variable := eval.MakeVarFromName(name)
	err := variable.Set(val)
	if err != nil {
		return err
	}
	fm.Evaler.AddGlobal(eval.NsBuilder{name: vars.FromInit(val)}.Ns())
	return nil
}

//elvdoc:fn add-var
//
// ```elvish
// edit:add-vars $map
// ```
//
// Takes a map from strings to arbitrary values. Equivalent to calling
// `edit:add-var` for each key-value pair in the map.

func addVars(fm *eval.Frame, m vals.Map) error {
	nb := eval.NsBuilder{}
	for it := m.Iterator(); it.HasElem(); it.Next() {
		k, val := it.Elem()
		name, ok := k.(string)
		if !ok {
			return errs.BadValue{
				What:  "key of argument to edit:add-vars",
				Valid: "string", Actual: vals.Kind(k)}
		}
		if !eval.IsUnqualified(name) {
			return errs.BadValue{
				What:  "key of argument to edit:add-vars",
				Valid: "unqualified variable name", Actual: name}
		}
		variable := eval.MakeVarFromName(name)
		err := variable.Set(val)
		if err != nil {
			return err
		}
		nb[name] = variable
	}
	fm.Evaler.AddGlobal(nb.Ns())
	return nil
}
<commit_msg>Fix elvdoc for edit:add-vars.<commit_after>package edit

import (
	"github.com/elves/elvish/pkg/eval"
	"github.com/elves/elvish/pkg/eval/errs"
	"github.com/elves/elvish/pkg/eval/vals"
	"github.com/elves/elvish/pkg/eval/vars"
)

func initVarsAPI(ed *Editor, nb eval.NsBuilder) {
	nb.AddGoFns("<edit>:", map[string]interface{}{
		"add-var":  addVar,
		"add-vars": addVars,
	})
}

//elvdoc:fn add-var
//
// ```elvish
// edit:add-var $name $value
// ```
//
// Declares a new variable in the REPL. The new variable becomes available
// during the next REPL cycle.
//
// Equivalent to running `var $name = $value` at the REPL, but `$name` can be
// dynamic.
//
// Example:
//
// ```elvish-transcript
// ~> edit:add-var foo bar
// ~> put $foo
// ▶ bar
// ```

func addVar(fm *eval.Frame, name string, val interface{}) error {
	if !eval.IsUnqualified(name) {
		return errs.BadValue{
			What:  "name argument to edit:add-var",
			Valid: "unqualified variable name", Actual: name}
	}
	variable := eval.MakeVarFromName(name)
	err := variable.Set(val)
	if err != nil {
		return err
	}
	fm.Evaler.AddGlobal(eval.NsBuilder{name: vars.FromInit(val)}.Ns())
	return nil
}

//elvdoc:fn add-vars
//
// ```elvish
// edit:add-vars $map
// ```
//
// Takes a map from strings to arbitrary values. Equivalent to calling
// `edit:add-var` for each key-value pair in the map.

func addVars(fm *eval.Frame, m vals.Map) error {
	nb := eval.NsBuilder{}
	for it := m.Iterator(); it.HasElem(); it.Next() {
		k, val := it.Elem()
		name, ok := k.(string)
		if !ok {
			return errs.BadValue{
				What:  "key of argument to edit:add-vars",
				Valid: "string", Actual: vals.Kind(k)}
		}
		if !eval.IsUnqualified(name) {
			return errs.BadValue{
				What:  "key of argument to edit:add-vars",
				Valid: "unqualified variable name", Actual: name}
		}
		variable := eval.MakeVarFromName(name)
		err := variable.Set(val)
		if err != nil {
			return err
		}
		nb[name] = variable
	}
	fm.Evaler.AddGlobal(nb.Ns())
	return nil
}
<|endoftext|>


<commit_before>// Copyright 2015 PingCAP, Inc.
//
// 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,
// See the License for the specific language governing permissions and
// limitations under the License.

package kv

import (
	"math"
	"math/rand"
	"strings"
	"time"

	"github.com/juju/errors"
	"github.com/ngaut/log"
	"github.com/pingcap/tidb/terror"
)

// IsRetryableError checks if the err is a fatal error and the under going operation is worth to retry.
func IsRetryableError(err error) bool {
	if err == nil {
		return false
	}

	if terror.ErrorEqual(err, ErrRetryable) ||
		terror.ErrorEqual(err, ErrLockConflict) ||
		terror.ErrorEqual(err, ErrConditionNotMatch) ||
		// HBase exception message will tell you if you should retry or not
		strings.Contains(err.Error(), "try again later") {
		return true
	}

	return false
}

// RunInNewTxn will run the f in a new transaction environment.
func RunInNewTxn(store Storage, retryable bool, f func(txn Transaction) error) error {
	for i := 0; i < maxRetryCnt; i++ {
		txn, err := store.Begin()
		if err != nil {
			log.Errorf("RunInNewTxn error - %v", err)
			return errors.Trace(err)
		}

		err = f(txn)
		if retryable && IsRetryableError(err) {
			log.Warnf("Retry txn %v", txn)
			txn.Rollback()
			continue
		}
		if err != nil {
			return errors.Trace(err)
		}

		err = txn.Commit()
		if retryable && IsRetryableError(err) {
			log.Warnf("Retry txn %v", txn)
			txn.Rollback()
			BackOff(i)
			continue
		}
		if err != nil {
			return errors.Trace(err)
		}
		break
	}

	return nil
}

var (
	// Max retry count in RunInNewTxn
	maxRetryCnt = 100
	// retryBackOffBase is the initial duration, in microsecond, a failed transaction stays dormancy before it retries
	retryBackOffBase = 1
	// retryBackOffCap is the max amount of duration, in microsecond, a failed transaction stays dormancy before it retries
	retryBackOffCap = 100
)

// BackOff Implements exponential backoff with full jitter.
// Returns real back off time in microsecond.
// See: http://www.awsarchitectureblog.com/2015/03/backoff.html.
func BackOff(attempts int) int {
	upper := int(math.Min(float64(retryBackOffCap), float64(retryBackOffBase)*math.Pow(2.0, float64(attempts))))
	sleep := time.Duration(rand.Intn(upper)) * time.Microsecond
	time.Sleep(sleep)
	return int(sleep)
}
<commit_msg>*: add themis retry error check<commit_after>// Copyright 2015 PingCAP, Inc.
//
// 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,
// See the License for the specific language governing permissions and
// limitations under the License.

package kv

import (
	"math"
	"math/rand"
	"strings"
	"time"

	"github.com/juju/errors"
	"github.com/ngaut/log"
	"github.com/pingcap/go-themis"
	"github.com/pingcap/tidb/terror"
)

// IsRetryableError checks if the err is a fatal error and the under going operation is worth to retry.
func IsRetryableError(err error) bool {
	if err == nil {
		return false
	}

	if terror.ErrorEqual(err, ErrRetryable) ||
		terror.ErrorEqual(err, ErrLockConflict) ||
		terror.ErrorEqual(err, ErrConditionNotMatch) ||
		terror.ErrorEqual(err, themis.ErrRetryable) ||
		// HBase exception message will tell you if you should retry or not
		strings.Contains(err.Error(), "try again later") {
		return true
	}

	return false
}

// RunInNewTxn will run the f in a new transaction environment.
func RunInNewTxn(store Storage, retryable bool, f func(txn Transaction) error) error {
	for i := 0; i < maxRetryCnt; i++ {
		txn, err := store.Begin()
		if err != nil {
			log.Errorf("RunInNewTxn error - %v", err)
			return errors.Trace(err)
		}

		err = f(txn)
		if retryable && IsRetryableError(err) {
			log.Warnf("Retry txn %v", txn)
			txn.Rollback()
			continue
		}
		if err != nil {
			return errors.Trace(err)
		}

		err = txn.Commit()
		if retryable && IsRetryableError(err) {
			log.Warnf("Retry txn %v", txn)
			txn.Rollback()
			BackOff(i)
			continue
		}
		if err != nil {
			return errors.Trace(err)
		}
		break
	}

	return nil
}

var (
	// Max retry count in RunInNewTxn
	maxRetryCnt = 100
	// retryBackOffBase is the initial duration, in microsecond, a failed transaction stays dormancy before it retries
	retryBackOffBase = 1
	// retryBackOffCap is the max amount of duration, in microsecond, a failed transaction stays dormancy before it retries
	retryBackOffCap = 100
)

// BackOff Implements exponential backoff with full jitter.
// Returns real back off time in microsecond.
// See: http://www.awsarchitectureblog.com/2015/03/backoff.html.
func BackOff(attempts int) int {
	upper := int(math.Min(float64(retryBackOffCap), float64(retryBackOffBase)*math.Pow(2.0, float64(attempts))))
	sleep := time.Duration(rand.Intn(upper)) * time.Microsecond
	time.Sleep(sleep)
	return int(sleep)
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"log"
	"net"
	"net/http"
	"strings"

	"github.com/flynn/go-discover/discover"
	rpc "github.com/flynn/rpcplus/comborpc"
)

var listenAddr = flag.String("listen", ":1112", "listen address")

func main() {
	flag.Parse()
	rpc.HandleHTTP()
	l, err := net.Listen("tcp", *listenAddr)
	if err != nil {
		log.Fatal(err)
	}

	d, err := discover.NewClient()
	if err != nil {
		log.Fatal(err)
	}
	if hostPort := strings.SplitN(*listenAddr, ":", 2); hostPort[0] != "" {
		err = d.RegisterWithHost("flynn-sampi", hostPort[0], hostPort[1], nil)
	} else {
		err = d.Register("flynn-sampi", hostPort[1], nil)
	}
	if err != nil {
		log.Fatal(err)
	}

	http.Serve(l, nil)
}
<commit_msg>sampi: Use grohl for logging<commit_after>package main

import (
	"flag"
	"net"
	"net/http"
	"os"
	"strings"

	"github.com/flynn/go-discover/discover"
	rpc "github.com/flynn/rpcplus/comborpc"
	"github.com/technoweenie/grohl"
)

var listenAddr = flag.String("listen", ":1112", "listen address")

func main() {
	flag.Parse()
	g := grohl.NewContext(grohl.Data{"app": "sampi"})
	g.Log(grohl.Data{"at": "start"})
	rpc.HandleHTTP()
	l, err := net.Listen("tcp", *listenAddr)
	if err != nil {
		g.Log(grohl.Data{"at": "listen", "status": "error", "err": err})
		os.Exit(1)
	}

	d, err := discover.NewClient()
	if err != nil {
		g.Log(grohl.Data{"at": "discover_connect", "status": "error", "err": err})
		os.Exit(1)
	}
	if hostPort := strings.SplitN(*listenAddr, ":", 2); hostPort[0] != "" {
		err = d.RegisterWithHost("flynn-sampi", hostPort[0], hostPort[1], nil)
	} else {
		err = d.Register("flynn-sampi", hostPort[1], nil)
	}
	if err != nil {
		g.Log(grohl.Data{"at": "discover_registration", "status": "error", "err": err})
		os.Exit(1)
	}

	http.Serve(l, nil)
}
<|endoftext|>


<commit_before>// Copyright 2015 Hajime Hoshi
//
// 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.

// +build darwin freebsd
// +build !js
// +build !android
// +build !ios

package oto

// #cgo darwin        LDFLAGS: -framework OpenAL
// #cgo freebsd linux LDFLAGS: -lopenal
//
// #ifdef __APPLE__
// #include <OpenAL/al.h>
// #include <OpenAL/alc.h>
// #else
// #include <AL/al.h>
// #include <AL/alc.h>
// #endif
import "C"

import (
	"errors"
	"fmt"
	"runtime"
	"unsafe"
)

// As x/mobile/exp/audio/al is broken on macOS (https://github.com/golang/go/issues/15075),
// and that doesn't support FreeBSD, use OpenAL directly here.

type player struct {
	// alContext represents a pointer to ALCcontext. The type is uintptr since the value
	// can be 0x18 on macOS, which is invalid as a pointer value, and this might cause
	// GC errors.
	alContext    alContext
	alDevice     alDevice
	alDeviceName string
	alSource     C.ALuint
	sampleRate   int
	isClosed     bool
	alFormat     C.ALenum

	bufs       []C.ALuint
	tmp        []uint8
	bufferSize int
}

type alContext uintptr

func (a alContext) cALCcontext() *C.ALCcontext {
	return (*C.struct_ALCcontext_struct)(unsafe.Pointer(a))
}

type alDevice uintptr

func (a alDevice) getError() error {
	c := C.alcGetError(a.cALCDevice())
	switch c {
	case C.ALC_NO_ERROR:
		return nil
	case C.ALC_INVALID_DEVICE:
		return errors.New("OpenAL error: invalid device")
	case C.ALC_INVALID_CONTEXT:
		return errors.New("OpenAL error: invalid context")
	case C.ALC_INVALID_ENUM:
		return errors.New("OpenAL error: invalid enum")
	case C.ALC_INVALID_VALUE:
		return errors.New("OpenAL error: invalid value")
	case C.ALC_OUT_OF_MEMORY:
		return errors.New("OpenAL error: out of memory")
	default:
		return fmt.Errorf("OpenAL error: code %d", c)
	}
}

func (a alDevice) cALCDevice() *C.ALCdevice {
	return (*C.struct_ALCdevice_struct)(unsafe.Pointer(a))
}

func alFormat(channelNum, bytesPerSample int) C.ALenum {
	switch {
	case channelNum == 1 && bytesPerSample == 1:
		return C.AL_FORMAT_MONO8
	case channelNum == 1 && bytesPerSample == 2:
		return C.AL_FORMAT_MONO16
	case channelNum == 2 && bytesPerSample == 1:
		return C.AL_FORMAT_STEREO8
	case channelNum == 2 && bytesPerSample == 2:
		return C.AL_FORMAT_STEREO16
	}
	panic(fmt.Sprintf("oto: invalid channel num (%d) or bytes per sample (%d)", channelNum, bytesPerSample))
}

var numBufs = 2

func newPlayer(sampleRate, channelNum, bytesPerSample, bufferSizeInBytes int) (*player, error) {
	name := C.alGetString(C.ALC_DEFAULT_DEVICE_SPECIFIER)
	d := alDevice(unsafe.Pointer(C.alcOpenDevice((*C.ALCchar)(name))))
	if d == 0 {
		return nil, fmt.Errorf("oto: alcOpenDevice must not return null")
	}
	c := alContext(unsafe.Pointer(C.alcCreateContext((*C.struct_ALCdevice_struct)(unsafe.Pointer(d)), nil)))
	if c == 0 {
		return nil, fmt.Errorf("oto: alcCreateContext must not return null")
	}

	// Don't check getError until making the current context is done.
	// Linux might fail this check even though it succeeds (hajimehoshi/ebiten#204).
	C.alcMakeContextCurrent(c.cALCcontext())
	if err := d.getError(); err != nil {
		return nil, fmt.Errorf("oto: Activate: %v", err)
	}

	s := C.ALuint(0)
	C.alGenSources(1, &s)
	if err := d.getError(); err != nil {
		return nil, fmt.Errorf("oto: NewSource: %v", err)
	}

	p := &player{
		alContext:    c,
		alDevice:     d,
		alSource:     s,
		alDeviceName: C.GoString((*C.char)(name)),
		sampleRate:   sampleRate,
		alFormat:     alFormat(channelNum, bytesPerSample),
		bufs:         make([]C.ALuint, numBufs),
		bufferSize:   bufferSizeInBytes,
	}
	runtime.SetFinalizer(p, (*player).Close)
	C.alGenBuffers(C.ALsizei(numBufs), &p.bufs[0])
	C.alSourcePlay(p.alSource)

	if err := d.getError(); err != nil {
		return nil, fmt.Errorf("oto: Play: %v", err)
	}

	return p, nil
}

func (p *player) SetUnderrunCallback(f func()) {
	//TODO
}

func (p *player) Write(data []byte) (int, error) {
	if err := p.alDevice.getError(); err != nil {
		return 0, fmt.Errorf("oto: starting Write: %v", err)
	}
	n := min(len(data), p.bufferSize-len(p.tmp))
	p.tmp = append(p.tmp, data[:n]...)
	if len(p.tmp) < p.bufferSize {
		return n, nil
	}

	pn := C.ALint(0)
	C.alGetSourcei(p.alSource, C.AL_BUFFERS_PROCESSED, &pn)

	if pn > 0 {
		bufs := make([]C.ALuint, pn)
		C.alSourceUnqueueBuffers(p.alSource, C.ALsizei(len(bufs)), &bufs[0])
		if err := p.alDevice.getError(); err != nil {
			return 0, fmt.Errorf("oto: UnqueueBuffers: %v", err)
		}
		p.bufs = append(p.bufs, bufs...)
	}

	if len(p.bufs) == 0 {
		return n, nil
	}

	buf := p.bufs[0]
	p.bufs = p.bufs[1:]
	C.alBufferData(buf, p.alFormat, unsafe.Pointer(&p.tmp[0]), C.ALsizei(p.bufferSize), C.ALsizei(p.sampleRate))
	C.alSourceQueueBuffers(p.alSource, 1, &buf)
	if err := p.alDevice.getError(); err != nil {
		return 0, fmt.Errorf("oto: QueueBuffer: %v", err)
	}

	state := C.ALint(0)
	C.alGetSourcei(p.alSource, C.AL_SOURCE_STATE, &state)
	if state == C.AL_STOPPED || state == C.AL_INITIAL {
		C.alSourceRewind(p.alSource)
		C.alSourcePlay(p.alSource)
		if err := p.alDevice.getError(); err != nil {
			return 0, fmt.Errorf("oto: Rewind or Play: %v", err)
		}
	}

	p.tmp = nil
	return n, nil
}

func (p *player) Close() error {
	if err := p.alDevice.getError(); err != nil {
		return fmt.Errorf("oto: starting Close: %v", err)
	}
	if p.isClosed {
		return nil
	}

	n := C.ALint(0)
	C.alGetSourcei(p.alSource, C.AL_BUFFERS_QUEUED, &n)
	if 0 < n {
		bs := make([]C.ALuint, n)
		C.alSourceUnqueueBuffers(p.alSource, C.ALsizei(len(bs)), &bs[0])
		p.bufs = append(p.bufs, bs...)
	}

	C.alSourceStop(p.alSource)
	C.alDeleteSources(1, &p.alSource)
	C.alDeleteBuffers(C.ALsizei(numBufs), &p.bufs[0])
	C.alcDestroyContext(p.alContext.cALCcontext())

	if err := p.alDevice.getError(); err != nil {
		return fmt.Errorf("oto: CloseDevice: %v", err)
	}

	b := C.alcCloseDevice(p.alDevice.cALCDevice())
	if b == C.ALC_FALSE {
		return fmt.Errorf("oto: CloseDevice: %s failed to close", p.alDeviceName)
	}

	p.isClosed = true
	runtime.SetFinalizer(p, nil)
	return nil
}
<commit_msg>openal: avoid possibility of crash by index out of bounds (#24)<commit_after>// Copyright 2015 Hajime Hoshi
//
// 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.

// +build darwin freebsd
// +build !js
// +build !android
// +build !ios

package oto

// #cgo darwin        LDFLAGS: -framework OpenAL
// #cgo freebsd linux LDFLAGS: -lopenal
//
// #ifdef __APPLE__
// #include <OpenAL/al.h>
// #include <OpenAL/alc.h>
// #else
// #include <AL/al.h>
// #include <AL/alc.h>
// #endif
import "C"

import (
	"errors"
	"fmt"
	"runtime"
	"unsafe"
)

// As x/mobile/exp/audio/al is broken on macOS (https://github.com/golang/go/issues/15075),
// and that doesn't support FreeBSD, use OpenAL directly here.

type player struct {
	// alContext represents a pointer to ALCcontext. The type is uintptr since the value
	// can be 0x18 on macOS, which is invalid as a pointer value, and this might cause
	// GC errors.
	alContext    alContext
	alDevice     alDevice
	alDeviceName string
	alSource     C.ALuint
	sampleRate   int
	isClosed     bool
	alFormat     C.ALenum

	bufs       []C.ALuint
	tmp        []uint8
	bufferSize int
}

type alContext uintptr

func (a alContext) cALCcontext() *C.ALCcontext {
	return (*C.struct_ALCcontext_struct)(unsafe.Pointer(a))
}

type alDevice uintptr

func (a alDevice) getError() error {
	c := C.alcGetError(a.cALCDevice())
	switch c {
	case C.ALC_NO_ERROR:
		return nil
	case C.ALC_INVALID_DEVICE:
		return errors.New("OpenAL error: invalid device")
	case C.ALC_INVALID_CONTEXT:
		return errors.New("OpenAL error: invalid context")
	case C.ALC_INVALID_ENUM:
		return errors.New("OpenAL error: invalid enum")
	case C.ALC_INVALID_VALUE:
		return errors.New("OpenAL error: invalid value")
	case C.ALC_OUT_OF_MEMORY:
		return errors.New("OpenAL error: out of memory")
	default:
		return fmt.Errorf("OpenAL error: code %d", c)
	}
}

func (a alDevice) cALCDevice() *C.ALCdevice {
	return (*C.struct_ALCdevice_struct)(unsafe.Pointer(a))
}

func alFormat(channelNum, bytesPerSample int) C.ALenum {
	switch {
	case channelNum == 1 && bytesPerSample == 1:
		return C.AL_FORMAT_MONO8
	case channelNum == 1 && bytesPerSample == 2:
		return C.AL_FORMAT_MONO16
	case channelNum == 2 && bytesPerSample == 1:
		return C.AL_FORMAT_STEREO8
	case channelNum == 2 && bytesPerSample == 2:
		return C.AL_FORMAT_STEREO16
	}
	panic(fmt.Sprintf("oto: invalid channel num (%d) or bytes per sample (%d)", channelNum, bytesPerSample))
}

var numBufs = 2

func newPlayer(sampleRate, channelNum, bytesPerSample, bufferSizeInBytes int) (*player, error) {
	name := C.alGetString(C.ALC_DEFAULT_DEVICE_SPECIFIER)
	d := alDevice(unsafe.Pointer(C.alcOpenDevice((*C.ALCchar)(name))))
	if d == 0 {
		return nil, fmt.Errorf("oto: alcOpenDevice must not return null")
	}
	c := alContext(unsafe.Pointer(C.alcCreateContext((*C.struct_ALCdevice_struct)(unsafe.Pointer(d)), nil)))
	if c == 0 {
		return nil, fmt.Errorf("oto: alcCreateContext must not return null")
	}

	// Don't check getError until making the current context is done.
	// Linux might fail this check even though it succeeds (hajimehoshi/ebiten#204).
	C.alcMakeContextCurrent(c.cALCcontext())
	if err := d.getError(); err != nil {
		return nil, fmt.Errorf("oto: Activate: %v", err)
	}

	s := C.ALuint(0)
	C.alGenSources(1, &s)
	if err := d.getError(); err != nil {
		return nil, fmt.Errorf("oto: NewSource: %v", err)
	}

	p := &player{
		alContext:    c,
		alDevice:     d,
		alSource:     s,
		alDeviceName: C.GoString((*C.char)(name)),
		sampleRate:   sampleRate,
		alFormat:     alFormat(channelNum, bytesPerSample),
		bufs:         make([]C.ALuint, numBufs),
		bufferSize:   bufferSizeInBytes,
	}
	runtime.SetFinalizer(p, (*player).Close)
	C.alGenBuffers(C.ALsizei(numBufs), &p.bufs[0])
	C.alSourcePlay(p.alSource)

	if err := d.getError(); err != nil {
		return nil, fmt.Errorf("oto: Play: %v", err)
	}

	return p, nil
}

func (p *player) SetUnderrunCallback(f func()) {
	//TODO
}

func (p *player) Write(data []byte) (int, error) {
	if err := p.alDevice.getError(); err != nil {
		return 0, fmt.Errorf("oto: starting Write: %v", err)
	}
	n := min(len(data), p.bufferSize-len(p.tmp))
	p.tmp = append(p.tmp, data[:n]...)
	if len(p.tmp) < p.bufferSize {
		return n, nil
	}

	pn := C.ALint(0)
	C.alGetSourcei(p.alSource, C.AL_BUFFERS_PROCESSED, &pn)

	if pn > 0 {
		bufs := make([]C.ALuint, pn)
		C.alSourceUnqueueBuffers(p.alSource, C.ALsizei(len(bufs)), &bufs[0])
		if err := p.alDevice.getError(); err != nil {
			return 0, fmt.Errorf("oto: UnqueueBuffers: %v", err)
		}
		p.bufs = append(p.bufs, bufs...)
	}

	if len(p.bufs) == 0 {
		return n, nil
	}

	buf := p.bufs[0]
	p.bufs = p.bufs[1:]
	C.alBufferData(buf, p.alFormat, unsafe.Pointer(&p.tmp[0]), C.ALsizei(p.bufferSize), C.ALsizei(p.sampleRate))
	C.alSourceQueueBuffers(p.alSource, 1, &buf)
	if err := p.alDevice.getError(); err != nil {
		return 0, fmt.Errorf("oto: QueueBuffer: %v", err)
	}

	state := C.ALint(0)
	C.alGetSourcei(p.alSource, C.AL_SOURCE_STATE, &state)
	if state == C.AL_STOPPED || state == C.AL_INITIAL {
		C.alSourceRewind(p.alSource)
		C.alSourcePlay(p.alSource)
		if err := p.alDevice.getError(); err != nil {
			return 0, fmt.Errorf("oto: Rewind or Play: %v", err)
		}
	}

	p.tmp = nil
	return n, nil
}

func (p *player) Close() error {
	if err := p.alDevice.getError(); err != nil {
		return fmt.Errorf("oto: starting Close: %v", err)
	}
	if p.isClosed {
		return nil
	}

	n := C.ALint(0)
	C.alGetSourcei(p.alSource, C.AL_BUFFERS_QUEUED, &n)
	if 0 < n {
		bs := make([]C.ALuint, n)
		C.alSourceUnqueueBuffers(p.alSource, C.ALsizei(len(bs)), &bs[0])
		p.bufs = append(p.bufs, bs...)
	}

	C.alSourceStop(p.alSource)
	C.alDeleteSources(1, &p.alSource)
	if len(p.bufs) != 0 {
		C.alDeleteBuffers(C.ALsizei(numBufs), &p.bufs[0])
	}
	C.alcDestroyContext(p.alContext.cALCcontext())

	if err := p.alDevice.getError(); err != nil {
		return fmt.Errorf("oto: CloseDevice: %v", err)
	}

	b := C.alcCloseDevice(p.alDevice.cALCDevice())
	if b == C.ALC_FALSE {
		return fmt.Errorf("oto: CloseDevice: %s failed to close", p.alDeviceName)
	}

	p.isClosed = true
	runtime.SetFinalizer(p, nil)
	return nil
}
<|endoftext|>


<commit_before>// errchk $G $D/$F.go

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

package main

type T struct{}
type P *T

func (t *T) Meth() {}
func (t T) Meth2() {}

func main() {
	t := &T{}
	p := P(t)
	p.Meth()  // ERROR "undefined \(type P"
	p.Meth2() // ERROR "undefined \(type P"
}<commit_msg>test: match gccgo error messages for bug323.go.<commit_after>// errchk $G $D/$F.go

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

package main

type T struct{}
type P *T

func (t *T) Meth() {}
func (t T) Meth2() {}

func main() {
	t := &T{}
	p := P(t)
	p.Meth()  // ERROR "undefined"
	p.Meth2() // ERROR "undefined"
}
<|endoftext|>


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

package controller

import (
	"fmt"
	"log"
	"net/http"

	"route/middleware"
	"route/routewrapper"
	"shared"
)

func init() {
	log.Println("Init index handlers")

	routewrapper.Get("/api/sample", routewrapper.Chain(SampleAPIGET))
	routewrapper.Get("/api/sample/auth", routewrapper.Chain(SampleAPIAuthGET, middleware.DisallowAnon))
	routewrapper.Get("/api/sample/anon", routewrapper.Chain(SampleAPIAnonGET, middleware.DisallowAuth))
	if shared.Name != "" && shared.Store != nil {
		routewrapper.Post("/sample/anon", routewrapper.Chain(SampleAPIAnonGET, middleware.DisallowAuth))
	}
}

// SampleAPIGET displays the default home page.
func SampleAPIGET(w http.ResponseWriter, r *http.Request) {
	// Get session
	sess := shared.SessionInstance(r)

	msg := "{ \"message\": \"your are in GET /api/sample and all user are allowed\"}"
	fmt.Fprint(w, msg)

	if sess != nil {
		sess.Save(r, w)
	}
}

// SampleAPIAuthGET displays the default home page.
func SampleAPIAuthGET(w http.ResponseWriter, r *http.Request) {
	// Get session
	sess := shared.SessionInstance(r)

	msg := `{"message": "your are in GET /api/sample and authenticated user are allowed only"}"`
	fmt.Fprint(w, msg)

	if sess != nil {
		sess.Save(r, w)
	}
}

// SampleAPIAnonGET displays the default home page.
func SampleAPIAnonGET(w http.ResponseWriter, r *http.Request) {
	// Get session
	sess := shared.SessionInstance(r)

	msg := `{"message": "your are in GET /api/sample and anonymous user are allowed only"}"`
	fmt.Fprint(w, msg)

	if sess != nil {
		sess.Save(r, w)
	}
}
<commit_msg>remove for new base<commit_after><|endoftext|>


<commit_before>/*
 * This file is part of the KubeVirt project
 *
 * 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.
 *
 * Copyright 2018 Red Hat, Inc.
 *
 */

package admitters

import (
	"encoding/json"
	"fmt"

	"k8s.io/api/admission/v1beta1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	k8sfield "k8s.io/apimachinery/pkg/util/validation/field"

	v1 "kubevirt.io/client-go/api/v1"
	"kubevirt.io/client-go/kubecli"
	cdiclone "kubevirt.io/containerized-data-importer/pkg/clone"
	webhookutils "kubevirt.io/kubevirt/pkg/util/webhooks"
	"kubevirt.io/kubevirt/pkg/virt-api/webhooks"
	virtconfig "kubevirt.io/kubevirt/pkg/virt-config"
)

var validRunStrategies = []v1.VirtualMachineRunStrategy{v1.RunStrategyHalted, v1.RunStrategyManual, v1.RunStrategyAlways, v1.RunStrategyRerunOnFailure}

type CloneAuthFunc func(pvcNamespace, pvcName, saNamespace, saName string) (bool, string, error)

type VMsAdmitter struct {
	ClusterConfig *virtconfig.ClusterConfig
	cloneAuthFunc CloneAuthFunc
}

func NewVMsAdmitter(clusterConfig *virtconfig.ClusterConfig, client kubecli.KubevirtClient) *VMsAdmitter {
	return &VMsAdmitter{
		ClusterConfig: clusterConfig,
		cloneAuthFunc: func(pvcNamespace, pvcName, saNamespace, saName string) (bool, string, error) {
			return cdiclone.CanServiceAccountClonePVC(client, pvcNamespace, pvcName, saNamespace, saName)
		},
	}
}

func (admitter *VMsAdmitter) Admit(ar *v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {
	if !webhookutils.ValidateRequestResource(ar.Request.Resource, webhooks.VirtualMachineGroupVersionResource.Group, webhooks.VirtualMachineGroupVersionResource.Resource) {
		err := fmt.Errorf("expect resource to be '%s'", webhooks.VirtualMachineGroupVersionResource.Resource)
		return webhookutils.ToAdmissionResponseError(err)
	}

	if resp := webhookutils.ValidateSchema(v1.VirtualMachineGroupVersionKind, ar.Request.Object.Raw); resp != nil {
		return resp
	}

	raw := ar.Request.Object.Raw
	vm := v1.VirtualMachine{}

	err := json.Unmarshal(raw, &vm)
	if err != nil {
		return webhookutils.ToAdmissionResponseError(err)
	}

	causes := ValidateVirtualMachineSpec(k8sfield.NewPath("spec"), &vm.Spec, admitter.ClusterConfig)
	if len(causes) > 0 {
		return webhookutils.ToAdmissionResponse(causes)
	}

	causes, err = admitter.authorizeVirtualMachineSpec(ar.Request, &vm)
	if err != nil {
		return webhookutils.ToAdmissionResponseError(err)
	}

	if len(causes) > 0 {
		return webhookutils.ToAdmissionResponse(causes)
	}

	reviewResponse := v1beta1.AdmissionResponse{}
	reviewResponse.Allowed = true
	return &reviewResponse
}

func (admitter *VMsAdmitter) authorizeVirtualMachineSpec(ar *v1beta1.AdmissionRequest, vm *v1.VirtualMachine) ([]metav1.StatusCause, error) {
	var causes []metav1.StatusCause

	for idx, dataVolume := range vm.Spec.DataVolumeTemplates {
		pvcSource := dataVolume.Spec.Source.PVC
		if pvcSource != nil {
			sourceNamespace := pvcSource.Namespace
			if sourceNamespace == "" {
				if vm.Namespace != "" {
					sourceNamespace = vm.Namespace
				} else {
					sourceNamespace = ar.Namespace
				}
			}

			if sourceNamespace == "" || pvcSource.Name == "" {
				causes = append(causes, metav1.StatusCause{
					Type:    metav1.CauseTypeFieldValueNotFound,
					Message: fmt.Sprintf("Clone source %s/%s invalid", sourceNamespace, pvcSource.Name),
					Field:   k8sfield.NewPath("spec", "dataVolumeTemplates").Index(idx).String(),
				})
			} else {
				targetNamespace := vm.Namespace
				if targetNamespace == "" {
					targetNamespace = ar.Namespace
				}

				serviceAccount := "default"
				for _, vol := range vm.Spec.Template.Spec.Volumes {
					if vol.ServiceAccount != nil {
						serviceAccount = vol.ServiceAccount.ServiceAccountName
					}
				}

				allowed, message, err := admitter.cloneAuthFunc(sourceNamespace, pvcSource.Name, targetNamespace, serviceAccount)
				if err != nil {
					return nil, err
				}

				if !allowed {
					causes = append(causes, metav1.StatusCause{
						Type:    metav1.CauseTypeFieldValueInvalid,
						Message: "Authorization failed, message is: " + message,
						Field:   k8sfield.NewPath("spec", "dataVolumeTemplates").Index(idx).String(),
					})
				}
			}
		}
	}

	if len(causes) > 0 {
		return causes, nil
	}

	causes = validateNoModificationsDuringRename(ar, vm)

	if len(causes) > 0 {
		return causes, nil
	}

	return causes, nil
}

func ValidateVirtualMachineSpec(field *k8sfield.Path, spec *v1.VirtualMachineSpec, config *virtconfig.ClusterConfig) []metav1.StatusCause {
	var causes []metav1.StatusCause

	if spec.Template == nil {
		return append(causes, metav1.StatusCause{
			Type:    metav1.CauseTypeFieldValueRequired,
			Message: fmt.Sprintf("missing virtual machine template."),
			Field:   field.Child("template").String(),
		})
	}

	causes = append(causes, ValidateVirtualMachineInstanceMetadata(field.Child("template", "metadata"), &spec.Template.ObjectMeta, config)...)
	causes = append(causes, ValidateVirtualMachineInstanceSpec(field.Child("template", "spec"), &spec.Template.Spec, config)...)

	if len(spec.DataVolumeTemplates) > 0 {

		for idx, dataVolume := range spec.DataVolumeTemplates {
			if dataVolume.Name == "" {
				causes = append(causes, metav1.StatusCause{
					Type:    metav1.CauseTypeFieldValueRequired,
					Message: fmt.Sprintf("'name' field must not be empty for DataVolumeTemplate entry %s.", field.Child("dataVolumeTemplate").Index(idx).String()),
					Field:   field.Child("dataVolumeTemplate").Index(idx).Child("name").String(),
				})
			}

			dataVolumeRefFound := false
			for _, volume := range spec.Template.Spec.Volumes {
				if volume.VolumeSource.DataVolume == nil {
					continue
				} else if volume.VolumeSource.DataVolume.Name == dataVolume.Name {
					dataVolumeRefFound = true
					break
				}
			}

			if !dataVolumeRefFound {
				causes = append(causes, metav1.StatusCause{
					Type:    metav1.CauseTypeFieldValueRequired,
					Message: fmt.Sprintf("DataVolumeTemplate entry %s must be referenced in the VMI template's 'volumes' list", field.Child("dataVolumeTemplate").Index(idx).String()),
					Field:   field.Child("dataVolumeTemplate").Index(idx).String(),
				})
			}
		}
	}

	// Validate RunStrategy
	if spec.Running != nil && spec.RunStrategy != nil {
		causes = append(causes, metav1.StatusCause{
			Type:    metav1.CauseTypeFieldValueInvalid,
			Message: fmt.Sprintf("Running and RunStrategy are mutually exclusive"),
			Field:   field.Child("running").String(),
		})
	}

	if spec.Running == nil && spec.RunStrategy == nil {
		causes = append(causes, metav1.StatusCause{
			Type:    metav1.CauseTypeFieldValueInvalid,
			Message: fmt.Sprintf("One of Running or RunStrategy must be specified"),
			Field:   field.Child("running").String(),
		})
	}

	if spec.RunStrategy != nil {
		validRunStrategy := false
		for _, strategy := range validRunStrategies {
			if *spec.RunStrategy == strategy {
				validRunStrategy = true
				break
			}
		}
		if validRunStrategy == false {
			causes = append(causes, metav1.StatusCause{
				Type:    metav1.CauseTypeFieldValueInvalid,
				Message: fmt.Sprintf("Invalid RunStrategy (%s)", *spec.RunStrategy),
				Field:   field.Child("runStrategy").String(),
			})
		}
	}

	return causes
}

func validateNoModificationsDuringRename(ar *v1beta1.AdmissionRequest, vm *v1.VirtualMachine) []metav1.StatusCause {
	var causes []metav1.StatusCause

	hasRenameReq := hasRenameRequest(vm)

	// Prevent creation of VM with rename request
	if ar.Operation == v1beta1.Create {
		if hasRenameReq {
			return append(causes, metav1.StatusCause{
				Type:    metav1.CauseTypeFieldValueInvalid,
				Message: "Creating a VM with a rename request is not allowed",
				Field:   k8sfield.NewPath("Status", "stateChangeRequests").String(),
			})
		}
	} else if ar.Operation == v1beta1.Update {
		existingVM := &v1.VirtualMachine{}
		err := json.Unmarshal(ar.OldObject.Raw, existingVM)

		if err != nil {
			return append(causes, metav1.StatusCause{
				Type:    metav1.CauseTypeUnexpectedServerResponse,
				Message: "Could not fetch old VM",
			})
		}

		existingVMHasRenameReq := hasRenameRequest(existingVM)

		if existingVMHasRenameReq {
			return append(causes, metav1.StatusCause{
				Type:    metav1.CauseTypeFieldValueInvalid,
				Message: "Modifying a VM during a rename process is not allowed",
			})
		}

		// Reject rename requests if the VM is running
		if hasRenameReq {
			runningStatus, _ := vm.RunStrategy()
			if runningStatus != v1.RunStrategyHalted {
				return append(causes, metav1.StatusCause{
					Type:    metav1.CauseTypeFieldValueInvalid,
					Message: "Cannot rename a running VM",
					Field:   k8sfield.NewPath("spec", "running").String(),
				})
			}
		}
	}

	return causes
}

func hasRenameRequest(vm *v1.VirtualMachine) bool {
	for _, req := range vm.Status.StateChangeRequests {
		if req.Action == v1.RenameRequest {
			return true
		}
	}

	return false
}
<commit_msg>Added a comment to clarify my reasoning as requested by @mhenriks<commit_after>/*
 * This file is part of the KubeVirt project
 *
 * 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.
 *
 * Copyright 2018 Red Hat, Inc.
 *
 */

package admitters

import (
	"encoding/json"
	"fmt"

	"k8s.io/api/admission/v1beta1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	k8sfield "k8s.io/apimachinery/pkg/util/validation/field"

	v1 "kubevirt.io/client-go/api/v1"
	"kubevirt.io/client-go/kubecli"
	cdiclone "kubevirt.io/containerized-data-importer/pkg/clone"
	webhookutils "kubevirt.io/kubevirt/pkg/util/webhooks"
	"kubevirt.io/kubevirt/pkg/virt-api/webhooks"
	virtconfig "kubevirt.io/kubevirt/pkg/virt-config"
)

var validRunStrategies = []v1.VirtualMachineRunStrategy{v1.RunStrategyHalted, v1.RunStrategyManual, v1.RunStrategyAlways, v1.RunStrategyRerunOnFailure}

type CloneAuthFunc func(pvcNamespace, pvcName, saNamespace, saName string) (bool, string, error)

type VMsAdmitter struct {
	ClusterConfig *virtconfig.ClusterConfig
	cloneAuthFunc CloneAuthFunc
}

func NewVMsAdmitter(clusterConfig *virtconfig.ClusterConfig, client kubecli.KubevirtClient) *VMsAdmitter {
	return &VMsAdmitter{
		ClusterConfig: clusterConfig,
		cloneAuthFunc: func(pvcNamespace, pvcName, saNamespace, saName string) (bool, string, error) {
			return cdiclone.CanServiceAccountClonePVC(client, pvcNamespace, pvcName, saNamespace, saName)
		},
	}
}

func (admitter *VMsAdmitter) Admit(ar *v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {
	if !webhookutils.ValidateRequestResource(ar.Request.Resource, webhooks.VirtualMachineGroupVersionResource.Group, webhooks.VirtualMachineGroupVersionResource.Resource) {
		err := fmt.Errorf("expect resource to be '%s'", webhooks.VirtualMachineGroupVersionResource.Resource)
		return webhookutils.ToAdmissionResponseError(err)
	}

	if resp := webhookutils.ValidateSchema(v1.VirtualMachineGroupVersionKind, ar.Request.Object.Raw); resp != nil {
		return resp
	}

	raw := ar.Request.Object.Raw
	vm := v1.VirtualMachine{}

	err := json.Unmarshal(raw, &vm)
	if err != nil {
		return webhookutils.ToAdmissionResponseError(err)
	}

	causes := ValidateVirtualMachineSpec(k8sfield.NewPath("spec"), &vm.Spec, admitter.ClusterConfig)
	if len(causes) > 0 {
		return webhookutils.ToAdmissionResponse(causes)
	}

	causes, err = admitter.authorizeVirtualMachineSpec(ar.Request, &vm)
	if err != nil {
		return webhookutils.ToAdmissionResponseError(err)
	}

	if len(causes) > 0 {
		return webhookutils.ToAdmissionResponse(causes)
	}

	reviewResponse := v1beta1.AdmissionResponse{}
	reviewResponse.Allowed = true
	return &reviewResponse
}

func (admitter *VMsAdmitter) authorizeVirtualMachineSpec(ar *v1beta1.AdmissionRequest, vm *v1.VirtualMachine) ([]metav1.StatusCause, error) {
	var causes []metav1.StatusCause

	for idx, dataVolume := range vm.Spec.DataVolumeTemplates {
		pvcSource := dataVolume.Spec.Source.PVC
		if pvcSource != nil {
			sourceNamespace := pvcSource.Namespace
			if sourceNamespace == "" {
				if vm.Namespace != "" {
					sourceNamespace = vm.Namespace
				} else {
					sourceNamespace = ar.Namespace
				}
			}

			if sourceNamespace == "" || pvcSource.Name == "" {
				causes = append(causes, metav1.StatusCause{
					Type:    metav1.CauseTypeFieldValueNotFound,
					Message: fmt.Sprintf("Clone source %s/%s invalid", sourceNamespace, pvcSource.Name),
					Field:   k8sfield.NewPath("spec", "dataVolumeTemplates").Index(idx).String(),
				})
			} else {
				targetNamespace := vm.Namespace
				if targetNamespace == "" {
					targetNamespace = ar.Namespace
				}

				serviceAccount := "default"
				for _, vol := range vm.Spec.Template.Spec.Volumes {
					if vol.ServiceAccount != nil {
						serviceAccount = vol.ServiceAccount.ServiceAccountName
					}
				}

				allowed, message, err := admitter.cloneAuthFunc(sourceNamespace, pvcSource.Name, targetNamespace, serviceAccount)
				if err != nil {
					return nil, err
				}

				if !allowed {
					causes = append(causes, metav1.StatusCause{
						Type:    metav1.CauseTypeFieldValueInvalid,
						Message: "Authorization failed, message is: " + message,
						Field:   k8sfield.NewPath("spec", "dataVolumeTemplates").Index(idx).String(),
					})
				}
			}
		}
	}

	if len(causes) > 0 {
		return causes, nil
	}

	causes = validateNoModificationsDuringRename(ar, vm)

	// Adding this statement here so the next developer won't have to take care
	// of errors when they add more functionality to this function, they will only have to
	// add their code below it
	if len(causes) > 0 {
		return causes, nil
	}

	return causes, nil
}

func ValidateVirtualMachineSpec(field *k8sfield.Path, spec *v1.VirtualMachineSpec, config *virtconfig.ClusterConfig) []metav1.StatusCause {
	var causes []metav1.StatusCause

	if spec.Template == nil {
		return append(causes, metav1.StatusCause{
			Type:    metav1.CauseTypeFieldValueRequired,
			Message: fmt.Sprintf("missing virtual machine template."),
			Field:   field.Child("template").String(),
		})
	}

	causes = append(causes, ValidateVirtualMachineInstanceMetadata(field.Child("template", "metadata"), &spec.Template.ObjectMeta, config)...)
	causes = append(causes, ValidateVirtualMachineInstanceSpec(field.Child("template", "spec"), &spec.Template.Spec, config)...)

	if len(spec.DataVolumeTemplates) > 0 {

		for idx, dataVolume := range spec.DataVolumeTemplates {
			if dataVolume.Name == "" {
				causes = append(causes, metav1.StatusCause{
					Type:    metav1.CauseTypeFieldValueRequired,
					Message: fmt.Sprintf("'name' field must not be empty for DataVolumeTemplate entry %s.", field.Child("dataVolumeTemplate").Index(idx).String()),
					Field:   field.Child("dataVolumeTemplate").Index(idx).Child("name").String(),
				})
			}

			dataVolumeRefFound := false
			for _, volume := range spec.Template.Spec.Volumes {
				if volume.VolumeSource.DataVolume == nil {
					continue
				} else if volume.VolumeSource.DataVolume.Name == dataVolume.Name {
					dataVolumeRefFound = true
					break
				}
			}

			if !dataVolumeRefFound {
				causes = append(causes, metav1.StatusCause{
					Type:    metav1.CauseTypeFieldValueRequired,
					Message: fmt.Sprintf("DataVolumeTemplate entry %s must be referenced in the VMI template's 'volumes' list", field.Child("dataVolumeTemplate").Index(idx).String()),
					Field:   field.Child("dataVolumeTemplate").Index(idx).String(),
				})
			}
		}
	}

	// Validate RunStrategy
	if spec.Running != nil && spec.RunStrategy != nil {
		causes = append(causes, metav1.StatusCause{
			Type:    metav1.CauseTypeFieldValueInvalid,
			Message: fmt.Sprintf("Running and RunStrategy are mutually exclusive"),
			Field:   field.Child("running").String(),
		})
	}

	if spec.Running == nil && spec.RunStrategy == nil {
		causes = append(causes, metav1.StatusCause{
			Type:    metav1.CauseTypeFieldValueInvalid,
			Message: fmt.Sprintf("One of Running or RunStrategy must be specified"),
			Field:   field.Child("running").String(),
		})
	}

	if spec.RunStrategy != nil {
		validRunStrategy := false
		for _, strategy := range validRunStrategies {
			if *spec.RunStrategy == strategy {
				validRunStrategy = true
				break
			}
		}
		if validRunStrategy == false {
			causes = append(causes, metav1.StatusCause{
				Type:    metav1.CauseTypeFieldValueInvalid,
				Message: fmt.Sprintf("Invalid RunStrategy (%s)", *spec.RunStrategy),
				Field:   field.Child("runStrategy").String(),
			})
		}
	}

	return causes
}

func validateNoModificationsDuringRename(ar *v1beta1.AdmissionRequest, vm *v1.VirtualMachine) []metav1.StatusCause {
	var causes []metav1.StatusCause

	hasRenameReq := hasRenameRequest(vm)

	// Prevent creation of VM with rename request
	if ar.Operation == v1beta1.Create {
		if hasRenameReq {
			return append(causes, metav1.StatusCause{
				Type:    metav1.CauseTypeFieldValueInvalid,
				Message: "Creating a VM with a rename request is not allowed",
				Field:   k8sfield.NewPath("Status", "stateChangeRequests").String(),
			})
		}
	} else if ar.Operation == v1beta1.Update {
		existingVM := &v1.VirtualMachine{}
		err := json.Unmarshal(ar.OldObject.Raw, existingVM)

		if err != nil {
			return append(causes, metav1.StatusCause{
				Type:    metav1.CauseTypeUnexpectedServerResponse,
				Message: "Could not fetch old VM",
			})
		}

		existingVMHasRenameReq := hasRenameRequest(existingVM)

		if existingVMHasRenameReq {
			return append(causes, metav1.StatusCause{
				Type:    metav1.CauseTypeFieldValueInvalid,
				Message: "Modifying a VM during a rename process is not allowed",
			})
		}

		// Reject rename requests if the VM is running
		if hasRenameReq {
			runningStatus, _ := vm.RunStrategy()
			if runningStatus != v1.RunStrategyHalted {
				return append(causes, metav1.StatusCause{
					Type:    metav1.CauseTypeFieldValueInvalid,
					Message: "Cannot rename a running VM",
					Field:   k8sfield.NewPath("spec", "running").String(),
				})
			}
		}
	}

	return causes
}

func hasRenameRequest(vm *v1.VirtualMachine) bool {
	for _, req := range vm.Status.StateChangeRequests {
		if req.Action == v1.RenameRequest {
			return true
		}
	}

	return false
}
<|endoftext|>


<commit_before>package client

import (
	"bufio"
	"crypto/tls"
	"fmt"
	"github.com/fluffle/goirc/logging"
	"github.com/fluffle/goirc/state"
	"io"
	"net"
	"strings"
	"sync"
	"time"
)

// An IRC connection is represented by this struct.
type Conn struct {
	// For preventing races on (dis)connect.
	mu sync.RWMutex

	// Contains parameters that people can tweak to change client behaviour.
	cfg *Config

	// Handlers
	intHandlers *hSet
	fgHandlers *hSet
	bgHandlers *hSet

	// State tracker for nicks and channels
	st         state.Tracker
	stRemovers []Remover

	// I/O stuff to server
	sock      net.Conn
	io        *bufio.ReadWriter
	in        chan *Line
	out       chan string
	connected bool

	// Control channel and WaitGroup for goroutines
	die chan struct{}
	wg  sync.WaitGroup

	// Internal counters for flood protection
	badness  time.Duration
	lastsent time.Time
}

// Misc knobs to tweak client behaviour go in here
type Config struct {
	// Set this to provide the Nick, Ident and Name for the client to use.
	Me *state.Nick

	// Hostname to connect to and optional connect password.
	Server, Pass string

	// Are we connecting via SSL? Do we care about certificate validity?
	SSL       bool
	SSLConfig *tls.Config

	// Replaceable function to customise the 433 handler's new nick
	NewNick func(string) string

	// Client->server ping frequency, in seconds. Defaults to 3m.
	PingFreq time.Duration

	// Set this to true to disable flood protection and false to re-enable
	Flood bool

	// Sent as the reply to a CTCP VERSION message
	Version string

	// Sent as the QUIT message.
	QuitMessage string

	// Configurable panic recovery for all handlers.
	Recover func(*Conn, *Line)

	// Split PRIVMSGs, NOTICEs and CTCPs longer than
	// SplitLen characters over multiple lines.
	SplitLen int
}

func NewConfig(nick string, args ...string) *Config {
	cfg := &Config{
		Me:       state.NewNick(nick),
		PingFreq: 3 * time.Minute,
		NewNick:  func(s string) string { return s + "_" },
		Recover:  (*Conn).LogPanic, // in dispatch.go
		SplitLen: 450,
	}
	cfg.Me.Ident = "goirc"
	if len(args) > 0 && args[0] != "" {
		cfg.Me.Ident = args[0]
	}
	cfg.Me.Name = "Powered by GoIRC"
	if len(args) > 1 && args[1] != "" {
		cfg.Me.Name = args[1]
	}
	cfg.Version = "Powered by GoIRC"
	cfg.QuitMessage = "GoBye!"
	return cfg
}

// Creates a new IRC connection object, but doesn't connect to anything so
// that you can add event handlers to it. See AddHandler() for details
func SimpleClient(nick string, args ...string) *Conn {
	conn := Client(NewConfig(nick, args...))
	return conn
}

func Client(cfg *Config) *Conn {
	if cfg == nil {
		cfg = NewConfig("__idiot__")
	}
	if cfg.Me == nil || cfg.Me.Nick == "" || cfg.Me.Ident == "" {
		cfg.Me = state.NewNick("__idiot__")
		cfg.Me.Ident = "goirc"
		cfg.Me.Name = "Powered by GoIRC"
	}
	conn := &Conn{
		cfg:         cfg,
		in:          make(chan *Line, 32),
		out:         make(chan string, 32),
		intHandlers: handlerSet(),
		fgHandlers:  handlerSet(),
		bgHandlers:  handlerSet(),
		stRemovers:  make([]Remover, 0, len(stHandlers)),
		lastsent:    time.Now(),
	}
	conn.addIntHandlers()
	conn.initialise()
	return conn
}

func (conn *Conn) Connected() bool {
	conn.mu.RLock()
	defer conn.mu.RUnlock()
	return conn.connected
}

func (conn *Conn) Config() *Config {
	return conn.cfg
}

func (conn *Conn) Me() *state.Nick {
	return conn.cfg.Me
}

func (conn *Conn) StateTracker() state.Tracker {
	return conn.st
}

func (conn *Conn) EnableStateTracking() {
	if conn.st == nil {
		n := conn.cfg.Me
		conn.st = state.NewTracker(n.Nick)
		conn.cfg.Me = conn.st.Me()
		conn.cfg.Me.Ident = n.Ident
		conn.cfg.Me.Name = n.Name
		conn.addSTHandlers()
	}
}

func (conn *Conn) DisableStateTracking() {
	if conn.st != nil {
		conn.delSTHandlers()
		conn.st.Wipe()
		conn.st = nil
	}
}

// Per-connection state initialisation.
func (conn *Conn) initialise() {
	conn.io = nil
	conn.sock = nil
	conn.die = make(chan struct{})
	if conn.st != nil {
		conn.st.Wipe()
	}
}

// Connect the IRC connection object to "host[:port]" which should be either
// a hostname or an IP address, with an optional port. To enable explicit SSL
// on the connection to the IRC server, set Conn.SSL to true before calling
// Connect(). The port will default to 6697 if ssl is enabled, and 6667
// otherwise. You can also provide an optional connect password.
func (conn *Conn) ConnectTo(host string, pass ...string) error {
	conn.cfg.Server = host
	if len(pass) > 0 {
		conn.cfg.Pass = pass[0]
	}
	return conn.Connect()
}

func (conn *Conn) Connect() error {
	conn.mu.Lock()
	defer conn.mu.Unlock()

	if conn.cfg.Server == "" {
		return fmt.Errorf("irc.Connect(): cfg.Server must be non-empty")
	}
	if conn.connected {
		return fmt.Errorf("irc.Connect(): Cannot connect to %s, already connected.", conn.cfg.Server)
	}
	if conn.cfg.SSL {
		if !hasPort(conn.cfg.Server) {
			conn.cfg.Server += ":6697"
		}
		logging.Info("irc.Connect(): Connecting to %s with SSL.", conn.cfg.Server)
		if s, err := tls.Dial("tcp", conn.cfg.Server, conn.cfg.SSLConfig); err == nil {
			conn.sock = s
		} else {
			return err
		}
	} else {
		if !hasPort(conn.cfg.Server) {
			conn.cfg.Server += ":6667"
		}
		logging.Info("irc.Connect(): Connecting to %s without SSL.", conn.cfg.Server)
		if s, err := net.Dial("tcp", conn.cfg.Server); err == nil {
			conn.sock = s
		} else {
			return err
		}
	}
	conn.connected = true
	conn.postConnect(true)
	conn.dispatch(&Line{Cmd: REGISTER})
	return nil
}

// Post-connection setup (for ease of testing)
func (conn *Conn) postConnect(start bool) {
	conn.io = bufio.NewReadWriter(
		bufio.NewReader(conn.sock),
		bufio.NewWriter(conn.sock))
	if start {
		conn.wg.Add(3)
		go conn.send()
		go conn.recv()
		go conn.runLoop()
		if conn.cfg.PingFreq > 0 {
			conn.wg.Add(1)
			go conn.ping()
		}
	}
}

// copied from http.client for great justice
func hasPort(s string) bool {
	return strings.LastIndex(s, ":") > strings.LastIndex(s, "]")
}

// goroutine to pass data from output channel to write()
func (conn *Conn) send() {
	defer conn.wg.Done()
	for {
		select {
		case line := <-conn.out:
			conn.write(line)
		case <-conn.die:
			// control channel closed, bail out
			return
		}
	}
}

// receive one \r\n terminated line from peer, parse and dispatch it
func (conn *Conn) recv() {
	for {
		s, err := conn.io.ReadString('\n')
		if err != nil {
			if err != io.EOF {
				logging.Error("irc.recv(): %s", err.Error())
			}
			// We can't defer this, because shutdown() waits for it.
			conn.wg.Done()
			conn.shutdown()
			return
		}
		s = strings.Trim(s, "\r\n")
		logging.Debug("<- %s", s)

		if line := ParseLine(s); line != nil {
			line.Time = time.Now()
			conn.in <- line
		} else {
			logging.Warn("irc.recv(): problems parsing line:\n  %s", s)
		}
	}
}

// Repeatedly pings the server every PingFreq seconds (no matter what)
func (conn *Conn) ping() {
	defer conn.wg.Done()
	tick := time.NewTicker(conn.cfg.PingFreq)
	for {
		select {
		case <-tick.C:
			conn.Ping(fmt.Sprintf("%d", time.Now().UnixNano()))
		case <-conn.die:
			// control channel closed, bail out
			tick.Stop()
			return
		}
	}
}

// goroutine to dispatch events for lines received on input channel
func (conn *Conn) runLoop() {
	defer conn.wg.Done()
	for {
		select {
		case line := <-conn.in:
			conn.dispatch(line)
		case <-conn.die:
			// control channel closed, bail out
			return
		}
	}
}

// Write a \r\n terminated line of output to the connected server,
// using Hybrid's algorithm to rate limit if conn.cfg.Flood is false.
func (conn *Conn) write(line string) {
	if !conn.cfg.Flood {
		if t := conn.rateLimit(len(line)); t != 0 {
			// sleep for the current line's time value before sending it
			logging.Info("irc.rateLimit(): Flood! Sleeping for %.2f secs.",
				t.Seconds())
			<-time.After(t)
		}
	}

	if _, err := conn.io.WriteString(line + "\r\n"); err != nil {
		logging.Error("irc.send(): %s", err.Error())
		conn.shutdown()
		return
	}
	if err := conn.io.Flush(); err != nil {
		logging.Error("irc.send(): %s", err.Error())
		conn.shutdown()
		return
	}
	logging.Debug("-> %s", line)
}

// Implement Hybrid's flood control algorithm to rate-limit outgoing lines.
func (conn *Conn) rateLimit(chars int) time.Duration {
	// Hybrid's algorithm allows for 2 seconds per line and an additional
	// 1/120 of a second per character on that line.
	linetime := 2*time.Second + time.Duration(chars)*time.Second/120
	elapsed := time.Now().Sub(conn.lastsent)
	if conn.badness += linetime - elapsed; conn.badness < 0 {
		// negative badness times are badness...
		conn.badness = 0
	}
	conn.lastsent = time.Now()
	// If we've sent more than 10 second's worth of lines according to the
	// calculation above, then we're at risk of "Excess Flood".
	if conn.badness > 10*time.Second {
		return linetime
	}
	return 0
}

func (conn *Conn) shutdown() {
	// Guard against double-call of shutdown() if we get an error in send()
	// as calling sock.Close() will cause recv() to receive EOF in readstring()
	conn.mu.Lock()
	defer conn.mu.Unlock()
	if !conn.connected {
		return
	}
	logging.Info("irc.shutdown(): Disconnected from server.")
	conn.connected = false
	conn.sock.Close()
	close(conn.die)
	conn.wg.Wait()
	// reinit datastructures ready for next connection
	conn.initialise()
	conn.dispatch(&Line{Cmd: DISCONNECTED})
}

// Dumps a load of information about the current state of the connection to a
// string for debugging state tracking and other such things.
func (conn *Conn) String() string {
	str := "GoIRC Connection\n"
	str += "----------------\n\n"
	if conn.Connected() {
		str += "Connected to " + conn.cfg.Server + "\n\n"
	} else {
		str += "Not currently connected!\n\n"
	}
	str += conn.Me().String() + "\n"
	if conn.st != nil {
		str += conn.st.String() + "\n"
	}
	return str
}
<commit_msg>Make local bind address configurable.<commit_after>package client

import (
	"bufio"
	"crypto/tls"
	"fmt"
	"github.com/fluffle/goirc/logging"
	"github.com/fluffle/goirc/state"
	"io"
	"net"
	"strings"
	"sync"
	"time"
)

// An IRC connection is represented by this struct.
type Conn struct {
	// For preventing races on (dis)connect.
	mu sync.RWMutex

	// Contains parameters that people can tweak to change client behaviour.
	cfg *Config

	// Handlers
	intHandlers *hSet
	fgHandlers *hSet
	bgHandlers *hSet

	// State tracker for nicks and channels
	st         state.Tracker
	stRemovers []Remover

	// I/O stuff to server
	dialer    *net.Dialer
	sock      net.Conn
	io        *bufio.ReadWriter
	in        chan *Line
	out       chan string
	connected bool

	// Control channel and WaitGroup for goroutines
	die chan struct{}
	wg  sync.WaitGroup

	// Internal counters for flood protection
	badness  time.Duration
	lastsent time.Time
}

// Misc knobs to tweak client behaviour go in here
type Config struct {
	// Set this to provide the Nick, Ident and Name for the client to use.
	Me *state.Nick

	// Hostname to connect to and optional connect password.
	Server, Pass string

	// Are we connecting via SSL? Do we care about certificate validity?
	SSL       bool
	SSLConfig *tls.Config

	// Local address to connect to the server.
	LocalAddr string

	// Replaceable function to customise the 433 handler's new nick
	NewNick func(string) string

	// Client->server ping frequency, in seconds. Defaults to 3m.
	PingFreq time.Duration

	// Set this to true to disable flood protection and false to re-enable
	Flood bool

	// Sent as the reply to a CTCP VERSION message
	Version string

	// Sent as the QUIT message.
	QuitMessage string

	// Configurable panic recovery for all handlers.
	Recover func(*Conn, *Line)

	// Split PRIVMSGs, NOTICEs and CTCPs longer than
	// SplitLen characters over multiple lines.
	SplitLen int
}

func NewConfig(nick string, args ...string) *Config {
	cfg := &Config{
		Me:       state.NewNick(nick),
		PingFreq: 3 * time.Minute,
		NewNick:  func(s string) string { return s + "_" },
		Recover:  (*Conn).LogPanic, // in dispatch.go
		SplitLen: 450,
	}
	cfg.Me.Ident = "goirc"
	if len(args) > 0 && args[0] != "" {
		cfg.Me.Ident = args[0]
	}
	cfg.Me.Name = "Powered by GoIRC"
	if len(args) > 1 && args[1] != "" {
		cfg.Me.Name = args[1]
	}
	cfg.Version = "Powered by GoIRC"
	cfg.QuitMessage = "GoBye!"
	return cfg
}

// Creates a new IRC connection object, but doesn't connect to anything so
// that you can add event handlers to it. See AddHandler() for details
func SimpleClient(nick string, args ...string) *Conn {
	conn := Client(NewConfig(nick, args...))
	return conn
}

func Client(cfg *Config) *Conn {
	if cfg == nil {
		cfg = NewConfig("__idiot__")
	}
	if cfg.Me == nil || cfg.Me.Nick == "" || cfg.Me.Ident == "" {
		cfg.Me = state.NewNick("__idiot__")
		cfg.Me.Ident = "goirc"
		cfg.Me.Name = "Powered by GoIRC"
	}

	dialer := new(net.Dialer)
	if cfg.LocalAddr != "" {
		if !hasPort(cfg.LocalAddr) {
			cfg.LocalAddr += ":0"
		}

		local, err := net.ResolveTCPAddr("tcp", cfg.LocalAddr)
		if err == nil {
			dialer.LocalAddr = local
		} else {
			logging.Error("irc.Client(): Cannot resolve local address %s: %s", cfg.LocalAddr, err)
		}
	}

	conn := &Conn{
		cfg:         cfg,
		dialer:      dialer,
		in:          make(chan *Line, 32),
		out:         make(chan string, 32),
		intHandlers: handlerSet(),
		fgHandlers:  handlerSet(),
		bgHandlers:  handlerSet(),
		stRemovers:  make([]Remover, 0, len(stHandlers)),
		lastsent:    time.Now(),
	}
	conn.addIntHandlers()
	conn.initialise()
	return conn
}

func (conn *Conn) Connected() bool {
	conn.mu.RLock()
	defer conn.mu.RUnlock()
	return conn.connected
}

func (conn *Conn) Config() *Config {
	return conn.cfg
}

func (conn *Conn) Me() *state.Nick {
	return conn.cfg.Me
}

func (conn *Conn) StateTracker() state.Tracker {
	return conn.st
}

func (conn *Conn) EnableStateTracking() {
	if conn.st == nil {
		n := conn.cfg.Me
		conn.st = state.NewTracker(n.Nick)
		conn.cfg.Me = conn.st.Me()
		conn.cfg.Me.Ident = n.Ident
		conn.cfg.Me.Name = n.Name
		conn.addSTHandlers()
	}
}

func (conn *Conn) DisableStateTracking() {
	if conn.st != nil {
		conn.delSTHandlers()
		conn.st.Wipe()
		conn.st = nil
	}
}

// Per-connection state initialisation.
func (conn *Conn) initialise() {
	conn.io = nil
	conn.sock = nil
	conn.die = make(chan struct{})
	if conn.st != nil {
		conn.st.Wipe()
	}
}

// Connect the IRC connection object to "host[:port]" which should be either
// a hostname or an IP address, with an optional port. To enable explicit SSL
// on the connection to the IRC server, set Conn.SSL to true before calling
// Connect(). The port will default to 6697 if ssl is enabled, and 6667
// otherwise. You can also provide an optional connect password.
func (conn *Conn) ConnectTo(host string, pass ...string) error {
	conn.cfg.Server = host
	if len(pass) > 0 {
		conn.cfg.Pass = pass[0]
	}
	return conn.Connect()
}

func (conn *Conn) Connect() error {
	conn.mu.Lock()
	defer conn.mu.Unlock()

	if conn.cfg.Server == "" {
		return fmt.Errorf("irc.Connect(): cfg.Server must be non-empty")
	}
	if conn.connected {
		return fmt.Errorf("irc.Connect(): Cannot connect to %s, already connected.", conn.cfg.Server)
	}
	if conn.cfg.SSL {
		if !hasPort(conn.cfg.Server) {
			conn.cfg.Server += ":6697"
		}
		logging.Info("irc.Connect(): Connecting to %s with SSL.", conn.cfg.Server)
		if s, err := tls.DialWithDialer(conn.dialer, "tcp", conn.cfg.Server, conn.cfg.SSLConfig); err == nil {
			conn.sock = s
		} else {
			return err
		}
	} else {
		if !hasPort(conn.cfg.Server) {
			conn.cfg.Server += ":6667"
		}
		logging.Info("irc.Connect(): Connecting to %s without SSL.", conn.cfg.Server)
		if s, err := conn.dialer.Dial("tcp", conn.cfg.Server); err == nil {
			conn.sock = s
		} else {
			return err
		}
	}
	conn.connected = true
	conn.postConnect(true)
	conn.dispatch(&Line{Cmd: REGISTER})
	return nil
}

// Post-connection setup (for ease of testing)
func (conn *Conn) postConnect(start bool) {
	conn.io = bufio.NewReadWriter(
		bufio.NewReader(conn.sock),
		bufio.NewWriter(conn.sock))
	if start {
		conn.wg.Add(3)
		go conn.send()
		go conn.recv()
		go conn.runLoop()
		if conn.cfg.PingFreq > 0 {
			conn.wg.Add(1)
			go conn.ping()
		}
	}
}

// copied from http.client for great justice
func hasPort(s string) bool {
	return strings.LastIndex(s, ":") > strings.LastIndex(s, "]")
}

// goroutine to pass data from output channel to write()
func (conn *Conn) send() {
	defer conn.wg.Done()
	for {
		select {
		case line := <-conn.out:
			conn.write(line)
		case <-conn.die:
			// control channel closed, bail out
			return
		}
	}
}

// receive one \r\n terminated line from peer, parse and dispatch it
func (conn *Conn) recv() {
	for {
		s, err := conn.io.ReadString('\n')
		if err != nil {
			if err != io.EOF {
				logging.Error("irc.recv(): %s", err.Error())
			}
			// We can't defer this, because shutdown() waits for it.
			conn.wg.Done()
			conn.shutdown()
			return
		}
		s = strings.Trim(s, "\r\n")
		logging.Debug("<- %s", s)

		if line := ParseLine(s); line != nil {
			line.Time = time.Now()
			conn.in <- line
		} else {
			logging.Warn("irc.recv(): problems parsing line:\n  %s", s)
		}
	}
}

// Repeatedly pings the server every PingFreq seconds (no matter what)
func (conn *Conn) ping() {
	defer conn.wg.Done()
	tick := time.NewTicker(conn.cfg.PingFreq)
	for {
		select {
		case <-tick.C:
			conn.Ping(fmt.Sprintf("%d", time.Now().UnixNano()))
		case <-conn.die:
			// control channel closed, bail out
			tick.Stop()
			return
		}
	}
}

// goroutine to dispatch events for lines received on input channel
func (conn *Conn) runLoop() {
	defer conn.wg.Done()
	for {
		select {
		case line := <-conn.in:
			conn.dispatch(line)
		case <-conn.die:
			// control channel closed, bail out
			return
		}
	}
}

// Write a \r\n terminated line of output to the connected server,
// using Hybrid's algorithm to rate limit if conn.cfg.Flood is false.
func (conn *Conn) write(line string) {
	if !conn.cfg.Flood {
		if t := conn.rateLimit(len(line)); t != 0 {
			// sleep for the current line's time value before sending it
			logging.Info("irc.rateLimit(): Flood! Sleeping for %.2f secs.",
				t.Seconds())
			<-time.After(t)
		}
	}

	if _, err := conn.io.WriteString(line + "\r\n"); err != nil {
		logging.Error("irc.send(): %s", err.Error())
		conn.shutdown()
		return
	}
	if err := conn.io.Flush(); err != nil {
		logging.Error("irc.send(): %s", err.Error())
		conn.shutdown()
		return
	}
	logging.Debug("-> %s", line)
}

// Implement Hybrid's flood control algorithm to rate-limit outgoing lines.
func (conn *Conn) rateLimit(chars int) time.Duration {
	// Hybrid's algorithm allows for 2 seconds per line and an additional
	// 1/120 of a second per character on that line.
	linetime := 2*time.Second + time.Duration(chars)*time.Second/120
	elapsed := time.Now().Sub(conn.lastsent)
	if conn.badness += linetime - elapsed; conn.badness < 0 {
		// negative badness times are badness...
		conn.badness = 0
	}
	conn.lastsent = time.Now()
	// If we've sent more than 10 second's worth of lines according to the
	// calculation above, then we're at risk of "Excess Flood".
	if conn.badness > 10*time.Second {
		return linetime
	}
	return 0
}

func (conn *Conn) shutdown() {
	// Guard against double-call of shutdown() if we get an error in send()
	// as calling sock.Close() will cause recv() to receive EOF in readstring()
	conn.mu.Lock()
	defer conn.mu.Unlock()
	if !conn.connected {
		return
	}
	logging.Info("irc.shutdown(): Disconnected from server.")
	conn.connected = false
	conn.sock.Close()
	close(conn.die)
	conn.wg.Wait()
	// reinit datastructures ready for next connection
	conn.initialise()
	conn.dispatch(&Line{Cmd: DISCONNECTED})
}

// Dumps a load of information about the current state of the connection to a
// string for debugging state tracking and other such things.
func (conn *Conn) String() string {
	str := "GoIRC Connection\n"
	str += "----------------\n\n"
	if conn.Connected() {
		str += "Connected to " + conn.cfg.Server + "\n\n"
	} else {
		str += "Not currently connected!\n\n"
	}
	str += conn.Me().String() + "\n"
	if conn.st != nil {
		str += conn.st.String() + "\n"
	}
	return str
}
<|endoftext|>