<commit_before>package scheduler

import (
	"sync"

	"github.com/pivotal-golang/lager"

	"github.com/concourse/atc"
	"github.com/concourse/atc/db"
	"github.com/concourse/atc/engine"
)

//go:generate counterfeiter . PipelineDB

type PipelineDB interface {
	CreateJobBuild(job string) (db.Build, error)
	CreateJobBuildForCandidateInputs(job string) (db.Build, bool, error)
	ScheduleBuild(buildID int, jobConfig atc.JobConfig) (bool, error)

	GetJobBuildForInputs(job string, inputs []db.BuildInput) (db.Build, error)
	GetNextPendingBuild(job string) (db.Build, error)

	GetLatestInputVersions([]atc.JobInput) ([]db.BuildInput, error)
	SaveResourceVersions(atc.ResourceConfig, []atc.Version) error
	UseInputsForBuild(buildID int, inputs []db.BuildInput) error
}

//go:generate counterfeiter . BuildsDB

type BuildsDB interface {
	GetAllStartedBuilds() ([]db.Build, error)
	ErrorBuild(buildID int, err error) error
}

//go:generate counterfeiter . BuildFactory

type BuildFactory interface {
	Create(atc.JobConfig, atc.ResourceConfigs, []db.BuildInput) (atc.Plan, error)
}

type Waiter interface {
	Wait()
}

//go:generate counterfeiter . Scanner

type Scanner interface {
	Scan(lager.Logger, string) error
}

type Scheduler struct {
	PipelineDB PipelineDB
	BuildsDB   BuildsDB
	Factory    BuildFactory
	Engine     engine.Engine
	Scanner    Scanner
}

func (s *Scheduler) BuildLatestInputs(logger lager.Logger, job atc.JobConfig, resources atc.ResourceConfigs) error {
	logger = logger.Session("build-latest")

	inputs := job.Inputs()

	if len(inputs) == 0 {
		// no inputs; no-op
		return nil
	}

	latestInputs, err := s.PipelineDB.GetLatestInputVersions(inputs)
	if err != nil {
		if err == db.ErrNoVersions {
			logger.Debug("no-input-versions-available")
			return nil
		}

		logger.Error("failed-to-get-latest-input-versions", err)
		return err
	}

	checkInputs := []db.BuildInput{}
	for _, input := range latestInputs {
		for _, ji := range inputs {
			if ji.Name == input.Name {
				if ji.Trigger {
					checkInputs = append(checkInputs, input)
				}

				break
			}
		}
	}

	if len(checkInputs) == 0 {
		logger.Debug("no-triggered-input-versions")
		return nil
	}

	existingBuild, err := s.PipelineDB.GetJobBuildForInputs(job.Name, checkInputs)
	if err == nil {
		logger.Debug("build-already-exists-for-inputs", lager.Data{
			"existing-build": existingBuild.ID,
		})

		return nil
	}

	build, created, err := s.PipelineDB.CreateJobBuildForCandidateInputs(job.Name)
	if err != nil {
		logger.Error("failed-to-create-build", err)
		return err
	}

	logger = logger.WithData(lager.Data{"build": build.ID})

	if !created {
		logger.Debug("did-not-create-build-as-it-already-is-pending")
		return nil
	}

	logger.Debug("created-build")

	createdBuild := s.scheduleAndResumePendingBuild(logger, build, job, resources)

	if createdBuild != nil {
		logger.Info("building")
		go createdBuild.Resume(logger)
	}

	return nil
}

func (s *Scheduler) TryNextPendingBuild(logger lager.Logger, job atc.JobConfig, resources atc.ResourceConfigs) Waiter {
	logger = logger.Session("try-next-pending")

	wg := new(sync.WaitGroup)

	wg.Add(1)
	go func() {
		build, err := s.PipelineDB.GetNextPendingBuild(job.Name)
		if err != nil {
			if err == db.ErrNoBuild {
				wg.Done()

				return
			}

			logger.Error("failed-to-get-next-pending-build", err)

			wg.Done()

			return
		}

		logger = logger.WithData(lager.Data{"build": build.ID})

		createdBuild := s.scheduleAndResumePendingBuild(logger, build, job, resources)

		wg.Done()

		if createdBuild != nil {
			logger.Info("building")
			createdBuild.Resume(logger)
		}
	}()

	return wg
}

func (s *Scheduler) TriggerImmediately(logger lager.Logger, job atc.JobConfig, resources atc.ResourceConfigs) (db.Build, error) {
	logger = logger.Session("trigger-immediately")

	build, err := s.PipelineDB.CreateJobBuild(job.Name)
	if err != nil {
		logger.Error("failed-to-create-build", err)
		return db.Build{}, err
	}

	logger = logger.WithData(lager.Data{"build": build.ID})

	go func() {
		createdBuild := s.scheduleAndResumePendingBuild(logger, build, job, resources)
		if createdBuild != nil {
			logger.Info("building")
			createdBuild.Resume(logger)
		}
	}()

	return build, nil
}

func (s *Scheduler) scheduleAndResumePendingBuild(logger lager.Logger, build db.Build, job atc.JobConfig, resources atc.ResourceConfigs) engine.Build {
	scheduled, err := s.PipelineDB.ScheduleBuild(build.ID, job)
	if err != nil {
		logger.Error("failed-to-schedule-build", err)
		return nil
	}

	if !scheduled {
		logger.Debug("build-could-not-be-scheduled")
		return nil
	}

	buildInputs := job.Inputs()

	for _, input := range buildInputs {
		scanLog := logger.Session("scan", lager.Data{
			"input":    input.Name,
			"resource": input.Resource,
		})

		err := s.Scanner.Scan(scanLog, input.Resource)
		if err != nil {
			scanLog.Error("failed-to-scan", err)

			err := s.BuildsDB.ErrorBuild(build.ID, err)
			if err != nil {
				logger.Error("failed-to-mark-build-as-errored", err)
			}

			return nil
		}

		scanLog.Info("done")
	}

	inputs, err := s.PipelineDB.GetLatestInputVersions(buildInputs)
	if err != nil {
		logger.Error("failed-to-get-latest-input-versions", err)
		return nil
	}

	err = s.PipelineDB.UseInputsForBuild(build.ID, inputs)
	if err != nil {
		logger.Error("failed-to-use-inputs-for-build", err)
		return nil
	}

	plan, err := s.Factory.Create(job, resources, inputs)
	if err != nil {
		logger.Error("failed-to-create-build-plan", err)
		return nil
	}

	createdBuild, err := s.Engine.CreateBuild(build, plan)
	if err != nil {
		logger.Error("failed-to-create-build", err)
		return nil
	}

	return createdBuild
}
<commit_msg>move resuming into scheduleAndResumePendingBuild<commit_after>package scheduler

import (
	"sync"

	"github.com/pivotal-golang/lager"

	"github.com/concourse/atc"
	"github.com/concourse/atc/db"
	"github.com/concourse/atc/engine"
)

//go:generate counterfeiter . PipelineDB

type PipelineDB interface {
	CreateJobBuild(job string) (db.Build, error)
	CreateJobBuildForCandidateInputs(job string) (db.Build, bool, error)
	ScheduleBuild(buildID int, jobConfig atc.JobConfig) (bool, error)

	GetJobBuildForInputs(job string, inputs []db.BuildInput) (db.Build, error)
	GetNextPendingBuild(job string) (db.Build, error)

	GetLatestInputVersions([]atc.JobInput) ([]db.BuildInput, error)
	SaveResourceVersions(atc.ResourceConfig, []atc.Version) error
	UseInputsForBuild(buildID int, inputs []db.BuildInput) error
}

//go:generate counterfeiter . BuildsDB

type BuildsDB interface {
	GetAllStartedBuilds() ([]db.Build, error)
	ErrorBuild(buildID int, err error) error
}

//go:generate counterfeiter . BuildFactory

type BuildFactory interface {
	Create(atc.JobConfig, atc.ResourceConfigs, []db.BuildInput) (atc.Plan, error)
}

type Waiter interface {
	Wait()
}

//go:generate counterfeiter . Scanner

type Scanner interface {
	Scan(lager.Logger, string) error
}

type Scheduler struct {
	PipelineDB PipelineDB
	BuildsDB   BuildsDB
	Factory    BuildFactory
	Engine     engine.Engine
	Scanner    Scanner
}

func (s *Scheduler) BuildLatestInputs(logger lager.Logger, job atc.JobConfig, resources atc.ResourceConfigs) error {
	logger = logger.Session("build-latest")

	inputs := job.Inputs()

	if len(inputs) == 0 {
		// no inputs; no-op
		return nil
	}

	latestInputs, err := s.PipelineDB.GetLatestInputVersions(inputs)
	if err != nil {
		if err == db.ErrNoVersions {
			logger.Debug("no-input-versions-available")
			return nil
		}

		logger.Error("failed-to-get-latest-input-versions", err)
		return err
	}

	checkInputs := []db.BuildInput{}
	for _, input := range latestInputs {
		for _, ji := range inputs {
			if ji.Name == input.Name {
				if ji.Trigger {
					checkInputs = append(checkInputs, input)
				}

				break
			}
		}
	}

	if len(checkInputs) == 0 {
		logger.Debug("no-triggered-input-versions")
		return nil
	}

	existingBuild, err := s.PipelineDB.GetJobBuildForInputs(job.Name, checkInputs)
	if err == nil {
		logger.Debug("build-already-exists-for-inputs", lager.Data{
			"existing-build": existingBuild.ID,
		})

		return nil
	}

	build, created, err := s.PipelineDB.CreateJobBuildForCandidateInputs(job.Name)
	if err != nil {
		logger.Error("failed-to-create-build", err)
		return err
	}

	if !created {
		logger.Debug("waiting-for-existing-build-to-determine-inputs", lager.Data{
			"existing-build": build.ID,
		})
		return nil
	}

	logger.Debug("created-build", lager.Data{"build": build.ID})

	s.scheduleAndResumePendingBuild(logger, build, job, resources)

	return nil
}

func (s *Scheduler) TryNextPendingBuild(logger lager.Logger, job atc.JobConfig, resources atc.ResourceConfigs) Waiter {
	logger = logger.Session("try-next-pending")

	wg := new(sync.WaitGroup)

	wg.Add(1)
	go func() {
		defer wg.Done()

		build, err := s.PipelineDB.GetNextPendingBuild(job.Name)
		if err != nil {
			if err == db.ErrNoBuild {
				return
			}

			logger.Error("failed-to-get-next-pending-build", err)

			return
		}

		s.scheduleAndResumePendingBuild(logger, build, job, resources)
	}()

	return wg
}

func (s *Scheduler) TriggerImmediately(logger lager.Logger, job atc.JobConfig, resources atc.ResourceConfigs) (db.Build, error) {
	logger = logger.Session("trigger-immediately")

	build, err := s.PipelineDB.CreateJobBuild(job.Name)
	if err != nil {
		logger.Error("failed-to-create-build", err)
		return db.Build{}, err
	}

	// do not block request on scanning input versions
	go s.scheduleAndResumePendingBuild(logger, build, job, resources)

	return build, nil
}

func (s *Scheduler) scheduleAndResumePendingBuild(logger lager.Logger, build db.Build, job atc.JobConfig, resources atc.ResourceConfigs) engine.Build {
	logger = logger.WithData(lager.Data{"build": build.ID})

	scheduled, err := s.PipelineDB.ScheduleBuild(build.ID, job)
	if err != nil {
		logger.Error("failed-to-schedule-build", err)
		return nil
	}

	if !scheduled {
		logger.Debug("build-could-not-be-scheduled")
		return nil
	}

	buildInputs := job.Inputs()

	for _, input := range buildInputs {
		scanLog := logger.Session("scan", lager.Data{
			"input":    input.Name,
			"resource": input.Resource,
		})

		err := s.Scanner.Scan(scanLog, input.Resource)
		if err != nil {
			scanLog.Error("failed-to-scan", err)

			err := s.BuildsDB.ErrorBuild(build.ID, err)
			if err != nil {
				logger.Error("failed-to-mark-build-as-errored", err)
			}

			return nil
		}

		scanLog.Info("done")
	}

	inputs, err := s.PipelineDB.GetLatestInputVersions(buildInputs)
	if err != nil {
		logger.Error("failed-to-get-latest-input-versions", err)
		return nil
	}

	err = s.PipelineDB.UseInputsForBuild(build.ID, inputs)
	if err != nil {
		logger.Error("failed-to-use-inputs-for-build", err)
		return nil
	}

	plan, err := s.Factory.Create(job, resources, inputs)
	if err != nil {
		logger.Error("failed-to-create-build-plan", err)
		return nil
	}

	createdBuild, err := s.Engine.CreateBuild(build, plan)
	if err != nil {
		logger.Error("failed-to-create-build", err)
		return nil
	}

	if createdBuild != nil {
		logger.Info("building")
		go createdBuild.Resume(logger)
	}

	return createdBuild
}
<|endoftext|>


<commit_before>// Copyright 2012 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// 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 auth

import (
	"errors"
	"github.com/jacobsa/aws"
	"github.com/jacobsa/aws/s3/http"
)

// A Signer knows how to create signatures suitable for inclusion in an HTTP
// request to S3.
type Signer interface {
	// Add an appropriate signature header to the supplied HTTP request.
	Sign(r *http.Request) error
}

// NewSigner creates a Signer using the supplied access key.
func NewSigner(key aws.AccessKey) (Signer, error) {
	return newSigner(stringToSign, key)
}

// newSigner is a helper used by NewSigner, split out for testability. It
// allows you to inject the function that is used to determine the string to
// sign for any given request.
func newSigner(
	sts func(*http.Request) (string, error),
	key aws.AccessKey) (Signer, error) {
	return nil, errors.New("TODO: Implement newSigner.")
}
<commit_msg>Started on newSigner.<commit_after>// Copyright 2012 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// 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 auth

import (
	"github.com/jacobsa/aws"
	"github.com/jacobsa/aws/s3/http"
)

// A Signer knows how to create signatures suitable for inclusion in an HTTP
// request to S3.
type Signer interface {
	// Add an appropriate signature header to the supplied HTTP request.
	Sign(r *http.Request) error
}

// NewSigner creates a Signer using the supplied access key.
func NewSigner(key *aws.AccessKey) (Signer, error) {
	return newSigner(stringToSign, key)
}

// newSigner is a helper used by NewSigner, split out for testability. It
// allows you to inject the function that is used to determine the string to
// sign for any given request.
func newSigner(
	sts func(*http.Request) (string, error),
	key *aws.AccessKey) (Signer, error) {
	return &signer{sts, key}
}

type signer struct {
	sts func(*http.Request) (string, error)
	key *aws.AccessKey
}
<|endoftext|>


<commit_before>package rest

import (
	"fmt"
	"os"
	"sync"
	"time"
)

// StatusMiddleware keeps track of various stats about the processed requests.
// It depends on request.Env["STATUS_CODE"] and request.Env["ELAPSED_TIME"],
// recorderMiddleware and timerMiddleware must be in the wrapped middlewares.
type StatusMiddleware struct {
	lock              sync.RWMutex
	start             time.Time
	pid               int
	responseCounts    map[string]int
	totalResponseTime time.Time
}

// MiddlewareFunc makes StatusMiddleware implement the Middleware interface.
func (mw *StatusMiddleware) MiddlewareFunc(h HandlerFunc) HandlerFunc {

	mw.start = time.Now()
	mw.pid = os.Getpid()
	mw.responseCounts = map[string]int{}
	mw.totalResponseTime = time.Time{}

	return func(w ResponseWriter, r *Request) {

		// call the handler
		h(w, r)

		statusCode := r.Env["STATUS_CODE"].(int)
		responseTime := r.Env["ELAPSED_TIME"].(*time.Duration)

		mw.lock.Lock()
		mw.responseCounts[fmt.Sprintf("%d", statusCode)]++
		mw.totalResponseTime = mw.totalResponseTime.Add(*responseTime)
		mw.lock.Unlock()
	}
}

// Status contains stats and status information. It is returned by GetStatus.
// These information can be made available as an API endpoint, see the "status"
// example to install the following status route.
// GET /.status returns something like:
//
//     {
//       "Pid": 21732,
//       "UpTime": "1m15.926272s",
//       "UpTimeSec": 75.926272,
//       "Time": "2013-03-04 08:00:27.152986 +0000 UTC",
//       "TimeUnix": 1362384027,
//       "StatusCodeCount": {
//         "200": 53,
//         "404": 11
//       },
//       "TotalCount": 64,
//       "TotalResponseTime": "16.777ms",
//       "TotalResponseTimeSec": 0.016777,
//       "AverageResponseTime": "262.14us",
//       "AverageResponseTimeSec": 0.00026214
//     }
type Status struct {
	Pid                    int
	UpTime                 string
	UpTimeSec              float64
	Time                   string
	TimeUnix               int64
	StatusCodeCount        map[string]int
	TotalCount             int
	TotalResponseTime      string
	TotalResponseTimeSec   float64
	AverageResponseTime    string
	AverageResponseTimeSec float64
}

// GetStatus computes and returns a Status object based on the request informations accumulated
// since the start of the process.
func (mw *StatusMiddleware) GetStatus() *Status {

	mw.lock.RLock()

	now := time.Now()

	uptime := now.Sub(mw.start)

	totalCount := 0
	for _, count := range mw.responseCounts {
		totalCount += count
	}

	totalResponseTime := mw.totalResponseTime.Sub(time.Time{})

	averageResponseTime := time.Duration(0)
	if totalCount > 0 {
		avgNs := int64(totalResponseTime) / int64(totalCount)
		averageResponseTime = time.Duration(avgNs)
	}

	status := &Status{
		Pid:                    mw.pid,
		UpTime:                 uptime.String(),
		UpTimeSec:              uptime.Seconds(),
		Time:                   now.String(),
		TimeUnix:               now.Unix(),
		StatusCodeCount:        mw.responseCounts,
		TotalCount:             totalCount,
		TotalResponseTime:      totalResponseTime.String(),
		TotalResponseTimeSec:   totalResponseTime.Seconds(),
		AverageResponseTime:    averageResponseTime.String(),
		AverageResponseTimeSec: averageResponseTime.Seconds(),
	}

	mw.lock.RUnlock()

	return status
}
<commit_msg>log a better error message when StatusMiddleware is missing Timer and Recorder.<commit_after>package rest

import (
	"fmt"
	"log"
	"os"
	"sync"
	"time"
)

// StatusMiddleware keeps track of various stats about the processed requests.
// It depends on request.Env["STATUS_CODE"] and request.Env["ELAPSED_TIME"],
// recorderMiddleware and timerMiddleware must be in the wrapped middlewares.
type StatusMiddleware struct {
	lock              sync.RWMutex
	start             time.Time
	pid               int
	responseCounts    map[string]int
	totalResponseTime time.Time
}

// MiddlewareFunc makes StatusMiddleware implement the Middleware interface.
func (mw *StatusMiddleware) MiddlewareFunc(h HandlerFunc) HandlerFunc {

	mw.start = time.Now()
	mw.pid = os.Getpid()
	mw.responseCounts = map[string]int{}
	mw.totalResponseTime = time.Time{}

	return func(w ResponseWriter, r *Request) {

		// call the handler
		h(w, r)

		if r.Env["STATUS_CODE"] == nil {
			log.Fatal("StatusMiddleware: Env[\"STATUS_CODE\"] is nil, " +
				"RecorderMiddleware may not be in the wrapped Middlewares.")
		}
		statusCode := r.Env["STATUS_CODE"].(int)

		if r.Env["ELAPSED_TIME"] == nil {
			log.Fatal("StatusMiddleware: Env[\"ELAPSED_TIME\"] is nil, " +
				"TimerMiddleware may not be in the wrapped Middlewares.")
		}
		responseTime := r.Env["ELAPSED_TIME"].(*time.Duration)

		mw.lock.Lock()
		mw.responseCounts[fmt.Sprintf("%d", statusCode)]++
		mw.totalResponseTime = mw.totalResponseTime.Add(*responseTime)
		mw.lock.Unlock()
	}
}

// Status contains stats and status information. It is returned by GetStatus.
// These information can be made available as an API endpoint, see the "status"
// example to install the following status route.
// GET /.status returns something like:
//
//     {
//       "Pid": 21732,
//       "UpTime": "1m15.926272s",
//       "UpTimeSec": 75.926272,
//       "Time": "2013-03-04 08:00:27.152986 +0000 UTC",
//       "TimeUnix": 1362384027,
//       "StatusCodeCount": {
//         "200": 53,
//         "404": 11
//       },
//       "TotalCount": 64,
//       "TotalResponseTime": "16.777ms",
//       "TotalResponseTimeSec": 0.016777,
//       "AverageResponseTime": "262.14us",
//       "AverageResponseTimeSec": 0.00026214
//     }
type Status struct {
	Pid                    int
	UpTime                 string
	UpTimeSec              float64
	Time                   string
	TimeUnix               int64
	StatusCodeCount        map[string]int
	TotalCount             int
	TotalResponseTime      string
	TotalResponseTimeSec   float64
	AverageResponseTime    string
	AverageResponseTimeSec float64
}

// GetStatus computes and returns a Status object based on the request informations accumulated
// since the start of the process.
func (mw *StatusMiddleware) GetStatus() *Status {

	mw.lock.RLock()

	now := time.Now()

	uptime := now.Sub(mw.start)

	totalCount := 0
	for _, count := range mw.responseCounts {
		totalCount += count
	}

	totalResponseTime := mw.totalResponseTime.Sub(time.Time{})

	averageResponseTime := time.Duration(0)
	if totalCount > 0 {
		avgNs := int64(totalResponseTime) / int64(totalCount)
		averageResponseTime = time.Duration(avgNs)
	}

	status := &Status{
		Pid:                    mw.pid,
		UpTime:                 uptime.String(),
		UpTimeSec:              uptime.Seconds(),
		Time:                   now.String(),
		TimeUnix:               now.Unix(),
		StatusCodeCount:        mw.responseCounts,
		TotalCount:             totalCount,
		TotalResponseTime:      totalResponseTime.String(),
		TotalResponseTimeSec:   totalResponseTime.Seconds(),
		AverageResponseTime:    averageResponseTime.String(),
		AverageResponseTimeSec: averageResponseTime.Seconds(),
	}

	mw.lock.RUnlock()

	return status
}
<|endoftext|>


<commit_before>package vsolver

import (
	"fmt"
	"os"
	"path"
	"testing"

	"github.com/Masterminds/semver"
)

var basicResult Result

func init() {
	sv1, _ := semver.NewVersion("1.0.0")
	basicResult = Result{
		Attempts: 1,
		Projects: []ProjectAtom{
			ProjectAtom{
				Name: "github.com/sdboyer/testrepo",
				Version: Version{
					Type:       V_Branch,
					Info:       "master",
					Underlying: "4d59fb584b15a94d7401e356d2875c472d76ef45",
				},
			},
			ProjectAtom{
				Name: "github.com/Masterminds/VCSTestRepo",
				Version: Version{
					Type:       V_Semver,
					Info:       "1.0.0",
					Underlying: "30605f6ac35fcb075ad0bfa9296f90a7d891523e",
					SemVer:     sv1,
				},
			},
		},
	}
}

func TestResultCreateVendorTree(t *testing.T) {
	r := basicResult
	r.SolveFailure = fmt.Errorf("dummy error")

	tmp := path.Join(os.TempDir(), "vsolvtest")
	os.RemoveAll(tmp)
	//fmt.Println(tmp)

	sm, err := NewSourceManager(path.Join(tmp, "cache"), path.Join(tmp, "base"), true, false, dummyAnalyzer{})
	if err != nil {
		t.Errorf("NewSourceManager errored unexpectedly: %q", err)
	}

	err = r.CreateVendorTree(path.Join(tmp, "export"), sm)
	if err == fmt.Errorf("Cannot create vendor tree from failed solution. Failure was dummy error") {
		if err == nil {
			t.Errorf("Expected error due to result having solve failure, but no error")
		} else {
			t.Errorf("Expected error due to result having solve failure, but got %s", err)
		}
	}

	r.SolveFailure = nil
	err = r.CreateVendorTree(path.Join(tmp, "export"), sm)
	if err != nil {
		t.Errorf("Unexpected error while creating vendor tree: %s", err)
	}

	// TODO add more checks
}
<commit_msg>Add CreateVendorTree benchmark<commit_after>package vsolver

import (
	"fmt"
	"go/build"
	"os"
	"path"
	"testing"

	"github.com/Masterminds/semver"
)

var basicResult Result
var kub ProjectAtom

// An analyzer that passes nothing back, but doesn't error. This expressly
// creates a situation that shouldn't be able to happen from a general solver
// perspective, so it's only useful for particular situations in tests
type passthruAnalyzer struct{}

func (passthruAnalyzer) GetInfo(ctx build.Context, p ProjectName) (ProjectInfo, error) {
	return ProjectInfo{}, nil
}

func init() {
	sv1, _ := semver.NewVersion("1.0.0")
	basicResult = Result{
		Attempts: 1,
		Projects: []ProjectAtom{
			ProjectAtom{
				Name: "github.com/sdboyer/testrepo",
				Version: Version{
					Type:       V_Branch,
					Info:       "master",
					Underlying: "4d59fb584b15a94d7401e356d2875c472d76ef45",
				},
			},
			ProjectAtom{
				Name: "github.com/Masterminds/VCSTestRepo",
				Version: Version{
					Type:       V_Semver,
					Info:       "1.0.0",
					Underlying: "30605f6ac35fcb075ad0bfa9296f90a7d891523e",
					SemVer:     sv1,
				},
			},
		},
	}

	// just in case something needs punishing, kubernetes is happy to oblige
	sv2, _ := semver.NewVersion("v1.2.2")
	kub = ProjectAtom{
		Name: "github.com/kubernetes/kubernetes",
		Version: Version{
			Type:       V_Semver,
			Info:       "v1.2.2",
			Underlying: "528f879e7d3790ea4287687ef0ab3f2a01cc2718",
			SemVer:     sv2,
		},
	}
}

func TestResultCreateVendorTree(t *testing.T) {
	r := basicResult
	r.SolveFailure = fmt.Errorf("dummy error")

	tmp := path.Join(os.TempDir(), "vsolvtest")
	os.RemoveAll(tmp)

	sm, err := NewSourceManager(path.Join(tmp, "cache"), path.Join(tmp, "base"), true, false, passthruAnalyzer{})
	if err != nil {
		t.Errorf("NewSourceManager errored unexpectedly: %q", err)
	}

	err = r.CreateVendorTree(path.Join(tmp, "export"), sm)
	if err == fmt.Errorf("Cannot create vendor tree from failed solution. Failure was dummy error") {
		if err == nil {
			t.Errorf("Expected error due to result having solve failure, but no error")
		} else {
			t.Errorf("Expected error due to result having solve failure, but got %s", err)
		}
	}

	r.SolveFailure = nil
	err = r.CreateVendorTree(path.Join(tmp, "export"), sm)
	if err != nil {
		t.Errorf("Unexpected error while creating vendor tree: %s", err)
	}

	// TODO add more checks
}

func BenchmarkCreateVendorTree(b *testing.B) {
	// We're fs-bound here, so restrict to single parallelism
	b.SetParallelism(1)

	r := basicResult
	tmp := path.Join(os.TempDir(), "vsolvtest")

	clean := true
	sm, err := NewSourceManager(path.Join(tmp, "cache"), path.Join(tmp, "base"), true, true, passthruAnalyzer{})
	if err != nil {
		b.Errorf("NewSourceManager errored unexpectedly: %q", err)
		clean = false
	}

	// Prefetch the projects before timer starts
	for _, pa := range r.Projects {
		_, err := sm.GetProjectInfo(pa)
		if err != nil {
			b.Errorf("failed getting project info during prefetch: %s", err)
			clean = false
		}
	}

	if clean {
		b.ResetTimer()
		b.StopTimer()
		exp := path.Join(tmp, "export")
		for i := 0; i < b.N; i++ {
			// Order the loop this way to make it easy to disable final cleanup, to
			// ease manual inspection
			os.RemoveAll(exp)
			b.StartTimer()
			err = r.CreateVendorTree(exp, sm)
			b.StopTimer()
			if err != nil {
				b.Errorf("unexpected error after %v iterations: %s", i, err)
				break
			}
		}
	}

	sm.Release()
	os.RemoveAll(tmp) // comment this to leave temp dir behind for inspection
}
<|endoftext|>


<commit_before>package travis

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"strconv"
	"strings"

	"github.com/google/go-github/github"
	"github.com/parkr/auto-reply/ctx"
	"github.com/parkr/auto-reply/search"
)

var (
	travisAPIBaseURL      = "https://api.travis-ci.org"
	travisAPIContentType  = "application/vnd.travis-ci.2+json"
	failingFmtBuildLabels = []string{"tests", "help-wanted"}
)

type travisBuild struct {
	JobIDs []int64 `json:"job_ids"`
}

type travisJob struct {
	State  string
	Config travisJobConfig
}

type travisJobConfig struct {
	Env string
}

func FailingFmtBuildHandler(context *ctx.Context, payload interface{}) error {
	status, ok := payload.(*github.StatusEvent)
	if !ok {
		return context.NewError("FailingFmtBuildHandler: not an status event")
	}

	if *status.State != "failure" {
		return context.NewError("FailingFmtBuildHandler: not a failure status event")
	}

	if *status.Context != "continuous-integration/travis-ci/push" {
		return context.NewError("FailingFmtBuildHandler: not a continuous-integration/travis-ci/push context")
	}

	if status.Branches != nil && len(status.Branches) > 0 && *status.Branches[0].Name != "master" {
		return context.NewError("FailingFmtBuildHandler: not a travis build on the master branch")
	}

	context.SetRepo(*status.Repo.Owner.Login, *status.Repo.Name)

	buildID, err := buildIDFromTargetURL(*status.TargetURL)
	if err != nil {
		return context.NewError("FailingFmtBuildHandler: couldn't extract build ID from %q: %+v", *status.TargetURL, err)
	}
	uri := fmt.Sprintf("/repos/%s/%s/builds/%d", context.Repo.Owner, context.Repo.Name, buildID)
	resp, err := httpGetTravis(uri)
	if err != nil {
		return context.NewError("FailingFmtBuildHandler: %+v", err)
	}

	build := struct {
		Build travisBuild `json:"build"`
	}{Build: travisBuild{}}
	err = json.NewDecoder(resp.Body).Decode(&build)
	if err != nil {
		return context.NewError("FailingFmtBuildHandler: couldn't decode build json: %+v", err)
	}
	log.Printf("FailingFmtBuildHandler: %q response: %+v %+v", uri, resp, build)

	for _, jobID := range build.Build.JobIDs {
		job := struct {
			Job travisJob `json:"job"`
		}{Job: travisJob{}}
		resp, err := httpGetTravis("/jobs/" + strconv.FormatInt(jobID, 10))
		if err != nil {
			return context.NewError("FailingFmtBuildHandler: couldn't get job info from travis: %+v", err)
		}
		err = json.NewDecoder(resp.Body).Decode(&job)
		if err != nil {
			return context.NewError("FailingFmtBuildHandler: couldn't decode job json: %+v", err)
		}
		log.Printf("FailingFmtBuildHandler: job %d response: %+v %+v", jobID, resp, job)
		if job.Job.State == "failed" && job.Job.Config.Env == "TEST_SUITE=fmt" {
			// Winner! Open an issue if there isn't already one.
			issues, err := search.GitHubIssues(context, "repo:%s/%s is:open in:title fmt build is failing")
			if err != nil {
				context.NewError("FailingFmtBuildHandler: not a travis build on the master branch")
			}
			if len(issues) > 0 {
				log.Printf("We already have an issue or issues for this failure! %s", issues[0].HTMLURL)
			} else {
				jobHTMLURL := fmt.Sprintf("https://travis-ci.org/%s/%s/jobs/%d", context.Repo.Owner, context.Repo.Name, jobID)
				issue, _, err := context.GitHub.Issues.Create(context.Repo.Owner, context.Repo.Name, &github.IssueRequest{
					Title: github.String("fmt build is failing on master"),
					Body: github.String(fmt.Sprintf(
						"Hey @jekyll/maintainers!\n\nIt looks like the fmt build in Travis is failing again: %s :frowning_face:\n\nCould someone please fix this up? Clone down the repo, run `bundle install`, then `script/fmt` to see the failures. File a PR once you're done and say \"Fixes <this issue url>\" in the description.\n\nThanks! :revolving_hearts:",
						jobHTMLURL,
					)),
					Labels: &failingFmtBuildLabels,
				})
				if err != nil {
					return context.NewError("FailingFmtBuildHandler: failed to file an issue: %+v", err)
				}
				log.Printf("Filed issue: %s", *issue.HTMLURL)
				break
			}
		}
	}

	return nil
}

func httpGetTravis(uri string) (*http.Response, error) {
	url := travisAPIBaseURL + uri
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, fmt.Errorf("couldn't create request: %+v", err)
	}
	req.Header.Add("Accept", travisAPIContentType)
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("couldn't send request to %s: %+v", url, err)
	}
	return resp, err
}

func buildIDFromTargetURL(targetURL string) (int64, error) {
	pieces := strings.Split(targetURL, "/")
	return strconv.ParseInt(pieces[len(pieces)-1], 10, 64)
}
<commit_msg>travis: construct query properly & return where necessary<commit_after>package travis

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"strconv"
	"strings"

	"github.com/google/go-github/github"
	"github.com/parkr/auto-reply/ctx"
	"github.com/parkr/auto-reply/search"
)

var (
	travisAPIBaseURL      = "https://api.travis-ci.org"
	travisAPIContentType  = "application/vnd.travis-ci.2+json"
	failingFmtBuildLabels = []string{"tests", "help-wanted"}
)

type travisBuild struct {
	JobIDs []int64 `json:"job_ids"`
}

type travisJob struct {
	State  string
	Config travisJobConfig
}

type travisJobConfig struct {
	Env string
}

func FailingFmtBuildHandler(context *ctx.Context, payload interface{}) error {
	status, ok := payload.(*github.StatusEvent)
	if !ok {
		return context.NewError("FailingFmtBuildHandler: not an status event")
	}

	if *status.State != "failure" {
		return context.NewError("FailingFmtBuildHandler: not a failure status event")
	}

	if *status.Context != "continuous-integration/travis-ci/push" {
		return context.NewError("FailingFmtBuildHandler: not a continuous-integration/travis-ci/push context")
	}

	if status.Branches != nil && len(status.Branches) > 0 && *status.Branches[0].Name != "master" {
		return context.NewError("FailingFmtBuildHandler: not a travis build on the master branch")
	}

	context.SetRepo(*status.Repo.Owner.Login, *status.Repo.Name)

	buildID, err := buildIDFromTargetURL(*status.TargetURL)
	if err != nil {
		return context.NewError("FailingFmtBuildHandler: couldn't extract build ID from %q: %+v", *status.TargetURL, err)
	}
	uri := fmt.Sprintf("/repos/%s/%s/builds/%d", context.Repo.Owner, context.Repo.Name, buildID)
	resp, err := httpGetTravis(uri)
	if err != nil {
		return context.NewError("FailingFmtBuildHandler: %+v", err)
	}

	build := struct {
		Build travisBuild `json:"build"`
	}{Build: travisBuild{}}
	err = json.NewDecoder(resp.Body).Decode(&build)
	if err != nil {
		return context.NewError("FailingFmtBuildHandler: couldn't decode build json: %+v", err)
	}
	log.Printf("FailingFmtBuildHandler: %q response: %+v %+v", uri, resp, build)

	for _, jobID := range build.Build.JobIDs {
		job := struct {
			Job travisJob `json:"job"`
		}{Job: travisJob{}}
		resp, err := httpGetTravis("/jobs/" + strconv.FormatInt(jobID, 10))
		if err != nil {
			return context.NewError("FailingFmtBuildHandler: couldn't get job info from travis: %+v", err)
		}
		err = json.NewDecoder(resp.Body).Decode(&job)
		if err != nil {
			return context.NewError("FailingFmtBuildHandler: couldn't decode job json: %+v", err)
		}
		log.Printf("FailingFmtBuildHandler: job %d response: %+v %+v", jobID, resp, job)
		if job.Job.State == "failed" && job.Job.Config.Env == "TEST_SUITE=fmt" {
			// Winner! Open an issue if there isn't already one.
			query := fmt.Sprintf(
				"repo:%s/%s is:open in:title fmt build is failing on master",
				context.Repo.Owner,
				context.Repo.Name,
			)
			issues, err := search.GitHubIssues(context, query)
			if err != nil {
				return context.NewError("FailingFmtBuildHandler: couldn't run query %q: %+v", query, err)
			}
			if len(issues) > 0 {
				log.Printf("We already have an issue or issues for this failure! %s", issues[0].HTMLURL)
			} else {
				jobHTMLURL := fmt.Sprintf("https://travis-ci.org/%s/%s/jobs/%d", context.Repo.Owner, context.Repo.Name, jobID)
				issue, _, err := context.GitHub.Issues.Create(context.Repo.Owner, context.Repo.Name, &github.IssueRequest{
					Title: github.String("fmt build is failing on master"),
					Body: github.String(fmt.Sprintf(
						"Hey @jekyll/maintainers!\n\nIt looks like the fmt build in Travis is failing again: %s :frowning_face:\n\nCould someone please fix this up? Clone down the repo, run `bundle install`, then `script/fmt` to see the failures. File a PR once you're done and say \"Fixes <this issue url>\" in the description.\n\nThanks! :revolving_hearts:",
						jobHTMLURL,
					)),
					Labels: &failingFmtBuildLabels,
				})
				if err != nil {
					return context.NewError("FailingFmtBuildHandler: failed to file an issue: %+v", err)
				}
				log.Printf("Filed issue: %s", *issue.HTMLURL)
				break
			}
		}
	}

	return nil
}

func httpGetTravis(uri string) (*http.Response, error) {
	url := travisAPIBaseURL + uri
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, fmt.Errorf("couldn't create request: %+v", err)
	}
	req.Header.Add("Accept", travisAPIContentType)
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("couldn't send request to %s: %+v", url, err)
	}
	return resp, err
}

func buildIDFromTargetURL(targetURL string) (int64, error) {
	pieces := strings.Split(targetURL, "/")
	return strconv.ParseInt(pieces[len(pieces)-1], 10, 64)
}
<|endoftext|>


<commit_before>package retry

import (
	"context"
	"time"

	"github.com/morikuni/guard"
)

func New(n int, backoff guard.Backoff) guard.Guard {
	return guard.GuardFunc(func(ctx context.Context, f func(context.Context) error) error {
		bo := backoff.Reset()

		var err error
		for i := 0; i <= n; i++ {
			if i > 0 {
				t := time.NewTimer(bo.NextInterval())
				defer t.Stop()
				select {
				case <-ctx.Done():
					return ctx.Err()
				case <-t.C:
				}
			}

			err = f(ctx)
			if err == nil {
				return nil
			}
		}
		return err
	})
}
<commit_msg>Support infinite retry<commit_after>package retry

import (
	"context"
	"time"

	"github.com/morikuni/guard"
)

var Inf int = -1

func New(n int, backoff guard.Backoff) guard.Guard {
	return guard.GuardFunc(func(ctx context.Context, f func(context.Context) error) error {
		bo := backoff.Reset()
		stop := stopper(n)

		for {
			err := f(ctx)
			if err == nil {
				return nil
			}

			if stop() {
				return err
			}

			t := time.NewTimer(bo.NextInterval())
			defer t.Stop()
			select {
			case <-ctx.Done():
				return ctx.Err()
			case <-t.C:
			}
		}
	})
}

func stopper(n int) func() bool {
	if n < -1 {
		return inf
	}
	return count(n)
}

func inf() bool {
	return false
}

func count(n int) func() bool {
	i := 0
	return func() bool {
		i++
		if i > n {
			return true
		}
		return false
	}
}
<|endoftext|>


<commit_before>// Copyright 2015 The rkt 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 main

import (
	"fmt"
	"strings"

	"github.com/coreos/rkt/Godeps/_workspace/src/github.com/spf13/cobra"
	"github.com/coreos/rkt/store"
)

const (
	defaultTimeLayout = "2006-01-02 15:04:05.999 -0700 MST"

	keyField        = "key"
	nameField       = "name"
	importTimeField = "importtime"
	latestField     = "latest"
)

var (
	// map of valid fields and related flag value
	imagesAllFields = map[string]struct{}{
		keyField:        struct{}{},
		nameField:       struct{}{},
		importTimeField: struct{}{},
		latestField:     struct{}{},
	}

	// map of valid fields and related header name
	ImagesFieldHeaderMap = map[string]string{
		keyField:        "KEY",
		nameField:       "NAME",
		importTimeField: "IMPORTTIME",
		latestField:     "LATEST",
	}

	// map of valid sort fields containing the mapping between the provided field name
	// and the related aciinfo's field name.
	ImagesFieldAciInfoMap = map[string]string{
		keyField:        "blobkey",
		nameField:       "name",
		importTimeField: "importtime",
		latestField:     "latest",
	}

	ImagesSortableFields = map[string]struct{}{
		nameField:       struct{}{},
		importTimeField: struct{}{},
	}
)

type ImagesFields []string

func (ifs *ImagesFields) Set(s string) error {
	*ifs = []string{}
	fields := strings.Split(s, ",")
	seen := map[string]struct{}{}
	for _, f := range fields {
		// accept any case
		f = strings.ToLower(f)
		_, ok := imagesAllFields[f]
		if !ok {
			return fmt.Errorf("unknown field %q", f)
		}
		if _, ok := seen[f]; ok {
			return fmt.Errorf("duplicated field %q", f)
		}
		*ifs = append(*ifs, f)
		seen[f] = struct{}{}
	}

	return nil
}

func (ifs *ImagesFields) String() string {
	return strings.Join(*ifs, ",")
}

func (ifs *ImagesFields) Type() string {
	return "imagesFields"
}

type ImagesSortFields []string

func (isf *ImagesSortFields) Set(s string) error {
	*isf = []string{}
	fields := strings.Split(s, ",")
	seen := map[string]struct{}{}
	for _, f := range fields {
		// accept any case
		f = strings.ToLower(f)
		_, ok := ImagesSortableFields[f]
		if !ok {
			return fmt.Errorf("unknown field %q", f)
		}
		if _, ok := seen[f]; ok {
			return fmt.Errorf("duplicated field %q", f)
		}
		*isf = append(*isf, f)
		seen[f] = struct{}{}
	}

	return nil
}

func (isf *ImagesSortFields) String() string {
	return strings.Join(*isf, ",")
}

func (isf *ImagesSortFields) Type() string {
	return "imagesSortFields"
}

type ImagesSortAsc bool

func (isa *ImagesSortAsc) Set(s string) error {
	switch s {
	case "asc":
		*isa = true
	case "desc":
		*isa = false
	default:
		return fmt.Errorf("wrong sort order")
	}
	return nil
}

func (isa *ImagesSortAsc) String() string {
	if *isa {
		return "asc"
	}
	return "desc"
}

func (isa *ImagesSortAsc) Type() string {
	return "imagesSortAsc"
}

var (
	cmdImageList = &cobra.Command{
		Use:   "list",
		Short: "List images in the local store",
		Run:   runWrapper(runImages),
	}
	flagImagesFields     ImagesFields
	flagImagesSortFields ImagesSortFields
	flagImagesSortAsc    ImagesSortAsc
)

func init() {
	// Set defaults
	flagImagesFields = []string{keyField, nameField, importTimeField, latestField}
	flagImagesSortFields = []string{importTimeField}
	flagImagesSortAsc = true

	cmdImage.AddCommand(cmdImageList)
	cmdImageList.Flags().Var(&flagImagesFields, "fields", `comma separated list of fields to display. Accepted values: "key", "name", "importtime", "latest"`)
	cmdImageList.Flags().Var(&flagImagesSortFields, "sort", `sort the output according to the provided comma separated list of fields. Accepted values: "name", "importtime"`)
	cmdImageList.Flags().Var(&flagImagesSortAsc, "order", `choose the sorting order if at least one sort field is provided (--sort). Accepted values: "asc", "desc"`)
	cmdImageList.Flags().BoolVar(&flagNoLegend, "no-legend", false, "suppress a legend with the list")
}

func runImages(cmd *cobra.Command, args []string) (exit int) {
	if !flagNoLegend {
		headerFields := []string{}
		for _, f := range flagImagesFields {
			headerFields = append(headerFields, ImagesFieldHeaderMap[f])
		}
		fmt.Fprintf(tabOut, "%s\n", strings.Join(headerFields, "\t"))
	}

	s, err := store.NewStore(globalFlags.Dir)
	if err != nil {
		stderr("images: cannot open store: %v\n", err)
		return 1
	}

	sortAciinfoFields := []string{}
	for _, f := range flagImagesSortFields {
		sortAciinfoFields = append(sortAciinfoFields, ImagesFieldAciInfoMap[f])
	}
	aciInfos, err := s.GetAllACIInfos(sortAciinfoFields, bool(flagImagesSortAsc))
	if err != nil {
		stderr("images: unable to get aci infos: %v", err)
		return
	}

	for _, aciInfo := range aciInfos {
		im, err := s.GetImageManifest(aciInfo.BlobKey)
		if err != nil {
			// ignore aciInfo with missing image manifest as it can be deleted in the meantime
			continue
		}
		version, ok := im.Labels.Get("version")
		for _, f := range flagImagesFields {
			switch f {
			case keyField:
				fmt.Fprintf(tabOut, "%s", aciInfo.BlobKey)
			case nameField:
				fmt.Fprintf(tabOut, "%s", aciInfo.Name)
				if ok {
					fmt.Fprintf(tabOut, ":%s", version)
				}
			case importTimeField:
				fmt.Fprintf(tabOut, "%s", aciInfo.ImportTime.Format(defaultTimeLayout))
			case latestField:
				fmt.Fprintf(tabOut, "%t", aciInfo.Latest)
			}
			fmt.Fprintf(tabOut, "\t")

		}
		fmt.Fprintf(tabOut, "\n")
	}

	tabOut.Flush()
	return 0
}
<commit_msg>rkt: Provide more readable output to rkt image list<commit_after>// Copyright 2015 The rkt 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 main

import (
	"fmt"
	"strings"

	"github.com/coreos/rkt/Godeps/_workspace/src/github.com/dustin/go-humanize"
	"github.com/coreos/rkt/Godeps/_workspace/src/github.com/spf13/cobra"
	"github.com/coreos/rkt/store"
)

const (
	defaultTimeLayout = "2006-01-02 15:04:05.999 -0700 MST"

	keyField        = "key"
	nameField       = "name"
	importTimeField = "importtime"
	latestField     = "latest"
)

var (
	// map of valid fields and related flag value
	imagesAllFields = map[string]struct{}{
		keyField:        struct{}{},
		nameField:       struct{}{},
		importTimeField: struct{}{},
		latestField:     struct{}{},
	}

	// map of valid fields and related header name
	ImagesFieldHeaderMap = map[string]string{
		keyField:        "KEY",
		nameField:       "NAME",
		importTimeField: "IMPORTTIME",
		latestField:     "LATEST",
	}

	// map of valid sort fields containing the mapping between the provided field name
	// and the related aciinfo's field name.
	ImagesFieldAciInfoMap = map[string]string{
		keyField:        "blobkey",
		nameField:       "name",
		importTimeField: "importtime",
		latestField:     "latest",
	}

	ImagesSortableFields = map[string]struct{}{
		nameField:       struct{}{},
		importTimeField: struct{}{},
	}
)

type ImagesFields []string

func (ifs *ImagesFields) Set(s string) error {
	*ifs = []string{}
	fields := strings.Split(s, ",")
	seen := map[string]struct{}{}
	for _, f := range fields {
		// accept any case
		f = strings.ToLower(f)
		_, ok := imagesAllFields[f]
		if !ok {
			return fmt.Errorf("unknown field %q", f)
		}
		if _, ok := seen[f]; ok {
			return fmt.Errorf("duplicated field %q", f)
		}
		*ifs = append(*ifs, f)
		seen[f] = struct{}{}
	}

	return nil
}

func (ifs *ImagesFields) String() string {
	return strings.Join(*ifs, ",")
}

func (ifs *ImagesFields) Type() string {
	return "imagesFields"
}

type ImagesSortFields []string

func (isf *ImagesSortFields) Set(s string) error {
	*isf = []string{}
	fields := strings.Split(s, ",")
	seen := map[string]struct{}{}
	for _, f := range fields {
		// accept any case
		f = strings.ToLower(f)
		_, ok := ImagesSortableFields[f]
		if !ok {
			return fmt.Errorf("unknown field %q", f)
		}
		if _, ok := seen[f]; ok {
			return fmt.Errorf("duplicated field %q", f)
		}
		*isf = append(*isf, f)
		seen[f] = struct{}{}
	}

	return nil
}

func (isf *ImagesSortFields) String() string {
	return strings.Join(*isf, ",")
}

func (isf *ImagesSortFields) Type() string {
	return "imagesSortFields"
}

type ImagesSortAsc bool

func (isa *ImagesSortAsc) Set(s string) error {
	switch s {
	case "asc":
		*isa = true
	case "desc":
		*isa = false
	default:
		return fmt.Errorf("wrong sort order")
	}
	return nil
}

func (isa *ImagesSortAsc) String() string {
	if *isa {
		return "asc"
	}
	return "desc"
}

func (isa *ImagesSortAsc) Type() string {
	return "imagesSortAsc"
}

var (
	cmdImageList = &cobra.Command{
		Use:   "list",
		Short: "List images in the local store",
		Run:   runWrapper(runImages),
	}
	flagImagesFields     ImagesFields
	flagImagesSortFields ImagesSortFields
	flagImagesSortAsc    ImagesSortAsc
)

func init() {
	// Set defaults
	flagImagesFields = []string{keyField, nameField, importTimeField, latestField}
	flagImagesSortFields = []string{importTimeField}
	flagImagesSortAsc = true

	cmdImage.AddCommand(cmdImageList)
	cmdImageList.Flags().Var(&flagImagesFields, "fields", `comma separated list of fields to display. Accepted values: "key", "name", "importtime", "latest"`)
	cmdImageList.Flags().Var(&flagImagesSortFields, "sort", `sort the output according to the provided comma separated list of fields. Accepted values: "name", "importtime"`)
	cmdImageList.Flags().Var(&flagImagesSortAsc, "order", `choose the sorting order if at least one sort field is provided (--sort). Accepted values: "asc", "desc"`)
	cmdImageList.Flags().BoolVar(&flagNoLegend, "no-legend", false, "suppress a legend with the list")
	cmdImageList.Flags().BoolVar(&flagFullOutput, "full", false, "Use long output format")
}

func runImages(cmd *cobra.Command, args []string) (exit int) {
	if !flagNoLegend {
		headerFields := []string{}
		for _, f := range flagImagesFields {
			headerFields = append(headerFields, ImagesFieldHeaderMap[f])
		}
		fmt.Fprintf(tabOut, "%s\n", strings.Join(headerFields, "\t"))
	}

	s, err := store.NewStore(globalFlags.Dir)
	if err != nil {
		stderr("images: cannot open store: %v\n", err)
		return 1
	}

	sortAciinfoFields := []string{}
	for _, f := range flagImagesSortFields {
		sortAciinfoFields = append(sortAciinfoFields, ImagesFieldAciInfoMap[f])
	}
	aciInfos, err := s.GetAllACIInfos(sortAciinfoFields, bool(flagImagesSortAsc))
	if err != nil {
		stderr("images: unable to get aci infos: %v", err)
		return
	}

	for _, aciInfo := range aciInfos {
		im, err := s.GetImageManifest(aciInfo.BlobKey)
		if err != nil {
			// ignore aciInfo with missing image manifest as it can be deleted in the meantime
			continue
		}
		version, ok := im.Labels.Get("version")
		for _, f := range flagImagesFields {
			switch f {
			case keyField:
				hashKey := aciInfo.BlobKey
				if !flagFullOutput {
					// The short hash form is [HASH_ALGO]-[FIRST 12 CHAR]
					// For example, sha512-123456789012
					pos := strings.Index(hashKey, "-")
					trimLength := pos + 13
					if pos > 0 && trimLength < len(hashKey) {
						hashKey = hashKey[:trimLength]
					}
				}
				fmt.Fprintf(tabOut, "%s", hashKey)
			case nameField:
				fmt.Fprintf(tabOut, "%s", aciInfo.Name)
				if ok {
					fmt.Fprintf(tabOut, ":%s", version)
				}
			case importTimeField:
				if flagFullOutput {
					fmt.Fprintf(tabOut, "%s", aciInfo.ImportTime.Format(defaultTimeLayout))
				} else {
					fmt.Fprintf(tabOut, "%s", humanize.Time(aciInfo.ImportTime))
				}
			case latestField:
				fmt.Fprintf(tabOut, "%t", aciInfo.Latest)
			}
			fmt.Fprintf(tabOut, "\t")

		}
		fmt.Fprintf(tabOut, "\n")
	}

	tabOut.Flush()
	return 0
}
<|endoftext|>


<commit_before>// Copyright 2019 tsuru 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 applog

import (
	"strings"
	"sync"
	"time"
	"unsafe"

	"github.com/pkg/errors"
	"github.com/prometheus/client_golang/prometheus"
	appTypes "github.com/tsuru/tsuru/types/app"
	"github.com/tsuru/tsuru/types/auth"
)

const (
	maxAppBufferSize = 1 * 1024 * 1024 // 1 MiB
	watchBufferSize  = 1000
	baseLogSize      = unsafe.Sizeof(appTypes.Applog{}) + unsafe.Sizeof(ringEntry{})
)

var (
	logsMemoryReceived = prometheus.NewCounterVec(prometheus.CounterOpts{
		Name: "tsuru_logs_memory_received_total",
		Help: "The number of in memory log entries received for processing.",
	}, []string{"app"})

	logsMemoryEvicted = prometheus.NewCounterVec(prometheus.CounterOpts{
		Name: "tsuru_logs_memory_evicted_total",
		Help: "The number of in memory log entries removed due to full buffer.",
	}, []string{"app"})

	logsMemoryBlockedWatch = prometheus.NewCounterVec(prometheus.CounterOpts{
		Name: "tsuru_logs_memory_watch_blocked_seconds_total",
		Help: "The total time spent blocked trying to notify watchers of new logs.",
	}, []string{"app"})

	logsMemorySize = prometheus.NewGaugeVec(prometheus.GaugeOpts{
		Name: "tsuru_logs_memory_size",
		Help: "The size in bytes for in memory log entries of a given app.",
	}, []string{"app"})

	logsMemoryLength = prometheus.NewGaugeVec(prometheus.GaugeOpts{
		Name: "tsuru_logs_memory_length",
		Help: "The number of in memory log entries for a given app.",
	}, []string{"app"})
)

func init() {
	prometheus.MustRegister(logsMemoryReceived)
	prometheus.MustRegister(logsMemoryEvicted)
	prometheus.MustRegister(logsMemoryBlockedWatch)
	prometheus.MustRegister(logsMemorySize)
	prometheus.MustRegister(logsMemoryLength)
}

type memoryLogService struct {
	bufferMap sync.Map
}

func memoryAppLogService() (appTypes.AppLogService, error) {
	return &memoryLogService{}, nil
}

func (s *memoryLogService) Enqueue(entry *appTypes.Applog) error {
	buffer := s.getAppBuffer(entry.AppName)
	buffer.add(entry)
	return nil
}

func (s *memoryLogService) Add(appName, message, source, unit string) error {
	messages := strings.Split(message, "\n")
	logs := make([]*appTypes.Applog, 0, len(messages))
	for _, msg := range messages {
		if msg != "" {
			l := &appTypes.Applog{
				Date:    time.Now().In(time.UTC),
				Message: msg,
				Source:  source,
				AppName: appName,
				Unit:    unit,
			}
			logs = append(logs, l)
		}
	}
	if len(logs) == 0 {
		return nil
	}
	for _, log := range logs {
		err := s.Enqueue(log)
		if err != nil {
			return err
		}
	}
	return nil
}

func (s *memoryLogService) List(args appTypes.ListLogArgs) ([]appTypes.Applog, error) {
	if args.AppName == "" {
		return nil, errors.New("app name required to list logs")
	}
	if args.Limit < 0 {
		return []appTypes.Applog{}, nil
	}
	buffer := s.getAppBuffer(args.AppName)
	if buffer.length == 0 {
		return []appTypes.Applog{}, nil
	}
	if args.Limit == 0 || buffer.length < args.Limit {
		args.Limit = buffer.length
	}
	logs := make([]appTypes.Applog, args.Limit)
	var count int
	for current := buffer.end; count < args.Limit; {
		if (args.Source == "" || (args.Source == current.log.Source) != args.InvertFilters) &&
			(args.Unit == "" || (args.Unit == current.log.Unit) != args.InvertFilters) {

			logs[len(logs)-count-1] = *current.log
			count++
		}
		current = current.prev
		if current == buffer.end {
			break
		}
	}
	return logs[len(logs)-count:], nil
}

func (s *memoryLogService) Watch(appName, source, unit string, t auth.Token) (appTypes.LogWatcher, error) {
	buffer := s.getAppBuffer(appName)
	watcher := &memoryWatcher{
		buffer: buffer,
		ch:     make(chan appTypes.Applog, watchBufferSize),
		source: source,
		unit:   unit,
	}
	buffer.addWatcher(watcher)
	return watcher, nil

}

func (s *memoryLogService) getAppBuffer(appName string) *appLogBuffer {
	// Use a simple Load first to avoid unnecessary allocations and the common
	// case is Load being successful.
	buffer, ok := s.bufferMap.Load(appName)
	if !ok {
		buffer, _ = s.bufferMap.LoadOrStore(appName, &appLogBuffer{
			appName:         appName,
			receivedCounter: logsMemoryReceived.WithLabelValues(appName),
			evictedCounter:  logsMemoryEvicted.WithLabelValues(appName),
			blockedCounter:  logsMemoryBlockedWatch.WithLabelValues(appName),
			sizeGauge:       logsMemorySize.WithLabelValues(appName),
			lengthGauge:     logsMemoryLength.WithLabelValues(appName),
		})
	}
	return buffer.(*appLogBuffer)
}

type ringEntry struct {
	log        *appTypes.Applog
	size       uint
	next, prev *ringEntry
}

type appLogBuffer struct {
	mu              sync.Mutex
	appName         string
	size            uint
	length          int
	start, end      *ringEntry
	watchers        []*memoryWatcher
	receivedCounter prometheus.Counter
	evictedCounter  prometheus.Counter
	blockedCounter  prometheus.Counter
	sizeGauge       prometheus.Gauge
	lengthGauge     prometheus.Gauge
}

func (b *appLogBuffer) add(entry *appTypes.Applog) {
	b.mu.Lock()
	defer b.mu.Unlock()
	b.receivedCounter.Inc()
	next := &ringEntry{
		log:  entry,
		size: entrySize(entry),
	}
	if next.size > maxAppBufferSize {
		return
	}
	if b.start == nil {
		b.start = next
		b.end = next
	}
	next.next = b.start
	next.prev = b.end
	b.start.prev = next
	b.end.next = next
	b.end = b.end.next
	b.length++
	newFullSize := b.size + next.size
	for newFullSize > maxAppBufferSize {
		newFullSize -= b.start.size
		b.start = b.start.next
		b.start.prev = b.end
		b.end.next = b.start
		b.length--
		b.evictedCounter.Inc()
	}
	b.size = newFullSize
	b.sizeGauge.Set(float64(b.size))
	b.lengthGauge.Set(float64(b.length))
	for _, w := range b.watchers {
		if w.source != "" && w.source != entry.Source {
			continue
		}
		if w.unit != "" && w.unit != entry.Unit {
			continue
		}
		select {
		case w.ch <- *entry:
		default:
			t0 := time.Now()
			w.ch <- *entry
			b.blockedCounter.Add(time.Since(t0).Seconds())
		}
	}
}

func (b *appLogBuffer) addWatcher(watcher *memoryWatcher) {
	b.mu.Lock()
	defer b.mu.Unlock()
	b.watchers = append(b.watchers, watcher)
}

func (b *appLogBuffer) removeWatcher(watcher *memoryWatcher) bool {
	b.mu.Lock()
	defer b.mu.Unlock()
	for i := range b.watchers {
		if b.watchers[i] == watcher {
			b.watchers[i] = b.watchers[len(b.watchers)-1]
			b.watchers = b.watchers[:len(b.watchers)-1]
			return true
		}
	}
	return false
}

func entrySize(entry *appTypes.Applog) uint {
	return uint(len(entry.AppName) +
		len(entry.Message) +
		len(entry.MongoID) +
		len(entry.Source) +
		len(entry.Unit) +
		int(baseLogSize))
}

type memoryWatcher struct {
	buffer *appLogBuffer
	ch     chan appTypes.Applog
	source string
	unit   string
}

func (w *memoryWatcher) Chan() <-chan appTypes.Applog {
	return w.ch
}

func (w *memoryWatcher) Close() {
	if w.buffer.removeWatcher(w) {
		close(w.ch)
	}
}
<commit_msg>applog: prevent slow watcher from freezing memory log processing<commit_after>// Copyright 2019 tsuru 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 applog

import (
	"strings"
	"sync"
	"time"
	"unsafe"

	"github.com/pkg/errors"
	"github.com/prometheus/client_golang/prometheus"
	appTypes "github.com/tsuru/tsuru/types/app"
	"github.com/tsuru/tsuru/types/auth"
)

const (
	maxAppBufferSize     = 1 * 1024 * 1024 // 1 MiB
	watchBufferSize      = 1000
	watchWarningInterval = 30 * time.Second
	baseLogSize          = unsafe.Sizeof(appTypes.Applog{}) + unsafe.Sizeof(ringEntry{})
)

var (
	logsMemoryReceived = prometheus.NewCounterVec(prometheus.CounterOpts{
		Name: "tsuru_logs_memory_received_total",
		Help: "The number of in memory log entries received for processing.",
	}, []string{"app"})

	logsMemoryEvicted = prometheus.NewCounterVec(prometheus.CounterOpts{
		Name: "tsuru_logs_memory_evicted_total",
		Help: "The number of in memory log entries removed due to full buffer.",
	}, []string{"app"})

	logsMemoryDroppedWatch = prometheus.NewCounterVec(prometheus.CounterOpts{
		Name: "tsuru_logs_memory_watch_dropped_total",
		Help: "The number of messages dropped in watchers due to a slow client.",
	}, []string{"app"})

	logsMemorySize = prometheus.NewGaugeVec(prometheus.GaugeOpts{
		Name: "tsuru_logs_memory_size",
		Help: "The size in bytes for in memory log entries of a given app.",
	}, []string{"app"})

	logsMemoryLength = prometheus.NewGaugeVec(prometheus.GaugeOpts{
		Name: "tsuru_logs_memory_length",
		Help: "The number of in memory log entries for a given app.",
	}, []string{"app"})
)

func init() {
	prometheus.MustRegister(logsMemoryReceived)
	prometheus.MustRegister(logsMemoryEvicted)
	prometheus.MustRegister(logsMemoryDroppedWatch)
	prometheus.MustRegister(logsMemorySize)
	prometheus.MustRegister(logsMemoryLength)
}

type memoryLogService struct {
	bufferMap sync.Map
}

func memoryAppLogService() (appTypes.AppLogService, error) {
	return &memoryLogService{}, nil
}

func (s *memoryLogService) Enqueue(entry *appTypes.Applog) error {
	buffer := s.getAppBuffer(entry.AppName)
	buffer.add(entry)
	return nil
}

func (s *memoryLogService) Add(appName, message, source, unit string) error {
	messages := strings.Split(message, "\n")
	logs := make([]*appTypes.Applog, 0, len(messages))
	for _, msg := range messages {
		if msg != "" {
			l := &appTypes.Applog{
				Date:    time.Now().In(time.UTC),
				Message: msg,
				Source:  source,
				AppName: appName,
				Unit:    unit,
			}
			logs = append(logs, l)
		}
	}
	if len(logs) == 0 {
		return nil
	}
	for _, log := range logs {
		err := s.Enqueue(log)
		if err != nil {
			return err
		}
	}
	return nil
}

func (s *memoryLogService) List(args appTypes.ListLogArgs) ([]appTypes.Applog, error) {
	if args.AppName == "" {
		return nil, errors.New("app name required to list logs")
	}
	if args.Limit < 0 {
		return []appTypes.Applog{}, nil
	}
	buffer := s.getAppBuffer(args.AppName)
	if buffer.length == 0 {
		return []appTypes.Applog{}, nil
	}
	if args.Limit == 0 || buffer.length < args.Limit {
		args.Limit = buffer.length
	}
	logs := make([]appTypes.Applog, args.Limit)
	var count int
	for current := buffer.end; count < args.Limit; {
		if (args.Source == "" || (args.Source == current.log.Source) != args.InvertFilters) &&
			(args.Unit == "" || (args.Unit == current.log.Unit) != args.InvertFilters) {

			logs[len(logs)-count-1] = *current.log
			count++
		}
		current = current.prev
		if current == buffer.end {
			break
		}
	}
	return logs[len(logs)-count:], nil
}

func (s *memoryLogService) Watch(appName, source, unit string, t auth.Token) (appTypes.LogWatcher, error) {
	buffer := s.getAppBuffer(appName)
	watcher := &memoryWatcher{
		buffer:     buffer,
		ch:         make(chan appTypes.Applog, watchBufferSize),
		quit:       make(chan struct{}),
		wg:         &sync.WaitGroup{},
		nextNotify: time.NewTimer(0),
		source:     source,
		unit:       unit,
	}
	buffer.addWatcher(watcher)
	return watcher, nil

}

func (s *memoryLogService) getAppBuffer(appName string) *appLogBuffer {
	// Use a simple Load first to avoid unnecessary allocations and the common
	// case is Load being successful.
	buffer, ok := s.bufferMap.Load(appName)
	if !ok {
		buffer, _ = s.bufferMap.LoadOrStore(appName, &appLogBuffer{
			appName:         appName,
			receivedCounter: logsMemoryReceived.WithLabelValues(appName),
			evictedCounter:  logsMemoryEvicted.WithLabelValues(appName),
			droppedCounter:  logsMemoryDroppedWatch.WithLabelValues(appName),
			sizeGauge:       logsMemorySize.WithLabelValues(appName),
			lengthGauge:     logsMemoryLength.WithLabelValues(appName),
		})
	}
	return buffer.(*appLogBuffer)
}

type ringEntry struct {
	log        *appTypes.Applog
	size       uint
	next, prev *ringEntry
}

type appLogBuffer struct {
	mu              sync.Mutex
	appName         string
	size            uint
	length          int
	start, end      *ringEntry
	watchers        []*memoryWatcher
	receivedCounter prometheus.Counter
	evictedCounter  prometheus.Counter
	droppedCounter  prometheus.Counter
	sizeGauge       prometheus.Gauge
	lengthGauge     prometheus.Gauge
}

func (b *appLogBuffer) add(entry *appTypes.Applog) {
	b.mu.Lock()
	defer b.mu.Unlock()
	b.receivedCounter.Inc()
	next := &ringEntry{
		log:  entry,
		size: entrySize(entry),
	}
	if next.size > maxAppBufferSize {
		return
	}
	if b.start == nil {
		b.start = next
		b.end = next
	}
	next.next = b.start
	next.prev = b.end
	b.start.prev = next
	b.end.next = next
	b.end = b.end.next
	b.length++
	newFullSize := b.size + next.size
	for newFullSize > maxAppBufferSize {
		newFullSize -= b.start.size
		b.start = b.start.next
		b.start.prev = b.end
		b.end.next = b.start
		b.length--
		b.evictedCounter.Inc()
	}
	b.size = newFullSize
	b.sizeGauge.Set(float64(b.size))
	b.lengthGauge.Set(float64(b.length))
	for _, w := range b.watchers {
		w.notify(entry, b.droppedCounter)
	}
}

func (b *appLogBuffer) addWatcher(watcher *memoryWatcher) {
	b.mu.Lock()
	defer b.mu.Unlock()
	b.watchers = append(b.watchers, watcher)
}

func (b *appLogBuffer) removeWatcher(watcher *memoryWatcher) bool {
	b.mu.Lock()
	defer b.mu.Unlock()
	for i := range b.watchers {
		if b.watchers[i] == watcher {
			b.watchers[i] = b.watchers[len(b.watchers)-1]
			b.watchers = b.watchers[:len(b.watchers)-1]
			return true
		}
	}
	return false
}

func entrySize(entry *appTypes.Applog) uint {
	return uint(len(entry.AppName) +
		len(entry.Message) +
		len(entry.MongoID) +
		len(entry.Source) +
		len(entry.Unit) +
		int(baseLogSize))
}

type memoryWatcher struct {
	buffer     *appLogBuffer
	ch         chan appTypes.Applog
	quit       chan struct{}
	wg         *sync.WaitGroup
	nextNotify *time.Timer
	source     string
	unit       string
}

func (w *memoryWatcher) notify(entry *appTypes.Applog, dropCounter prometheus.Counter) {
	if w.source != "" && w.source != entry.Source {
		return
	}
	if w.unit != "" && w.unit != entry.Unit {
		return
	}
	select {
	case w.ch <- *entry:
	default:
		dropCounter.Inc()
		select {
		case <-w.nextNotify.C:
			w.wg.Add(1)
			go func() {
				defer w.wg.Done()
				select {
				case w.ch <- slowWatcherWarning(entry.AppName):
				case <-w.quit:
				}
				w.nextNotify.Reset(watchWarningInterval)
			}()
		default:
		}
	}
}

func (w *memoryWatcher) Chan() <-chan appTypes.Applog {
	return w.ch
}

func (w *memoryWatcher) Close() {
	if w.buffer.removeWatcher(w) {
		close(w.quit)
		w.wg.Wait()
		close(w.ch)
	}
}

func slowWatcherWarning(appName string) appTypes.Applog {
	return appTypes.Applog{
		AppName: appName,
		Date:    time.Now(),
		Message: "Log messages dropped due to slow tail client or too many messages being produced.",
		Source:  "tsuru",
		Unit:    "api",
	}
}
<|endoftext|>


<commit_before>package controllers

import (
	"fmt"
	"github.com/astaxie/beego"
	"github.com/dyzdyz010/Blog/models"
	"github.com/qiniu/api/rs"
)

type AdminController struct {
	beego.Controller
}

func (this *AdminController) Prepare() {
	this.Data["PageTitle"] = models.Appconf.String("blog::title")
	this.Data["Title"] = models.Appconf.String("blog::title")
	this.Data["Subtitle"] = models.Appconf.String("blog::subtitle")
}

func checkLogin(ac *AdminController) {
	name := ac.GetSession("user")
	if name == nil {
		ac.Redirect("/admin/login", 302)
	}
}

func (this *AdminController) Dashboard() {
	checkLogin(this)

	this.TplNames = "admin/dashboard.tpl"

	this.Data["DashboardActive"] = true

	renderTemplate(this.Ctx, "views/admin/dashboard.amber", this.Data)
}

// Login

func (this *AdminController) Login() {
	this.TplNames = "admin/login.tpl"

	renderTemplate(this.Ctx, "views/admin/login.amber", this.Data)
}

func (this *AdminController) PostLogin() {
	this.TplNames = "admin/login.tpl"

	author := models.Author{}
	err := this.ParseForm(&author)
	if err != nil {
		panic(err)
	}

	a, err := models.AuthorByName(author.Name)
	if err != nil {
		this.Data["Message"] = err.Error()
	} else if models.Hash(author.Password) != a.Password {
		this.Data["Message"] = "Wrong password."
	} else {
		this.SetSession("user", author.Name)
		this.Redirect("/admin", 302)
	}

	renderTemplate(this.Ctx, "views/admin/login.amber", this.Data)
}

// Entry Operations

func (this *AdminController) Entries() {
	checkLogin(this)
	this.TplNames = "admin/entry-list.tpl"

	// fmt.Println(this.GetSession("user").(string))
	entries, err := models.EntriesByUser(this.GetSession("user").(string))
	if err != nil {
		panic(err)
	}

	this.Data["EntryActive"] = true
	this.Data["Entries"] = entries

	renderTemplate(this.Ctx, "views/admin/entry-list.amber", this.Data)
}

func (this *AdminController) Entry() {
	checkLogin(this)
	this.TplNames = "admin/entry.tpl"

	eid := this.Ctx.Input.Param(":id")
	fmt.Println(eid)
	if eid != "new" {
		entry, _ := models.EntryById(eid)
		if entry == nil {
			this.Abort("404")
			return
		} else {
			this.Data["Entry"] = entry
			this.Data["PostId"] = "update/" + entry.Id
		}
	}

	this.Data["EntryActive"] = true
	this.Data["MarkdownEnabled"] = true

	collections, err := models.CollectionsByUser(this.GetSession("user").(string))
	if err != nil {
		panic(err)
	}
	this.Data["Collections"] = collections
	fmt.Println(this.Data["Entry"])

	renderTemplate(this.Ctx, "views/admin/entry.amber", this.Data)
}

func (this *AdminController) UpdateEntry() {
	entry := models.Entry{}
	err := this.ParseForm(&entry)
	if err != nil {
		panic(err)
	}

	entry.Id = this.Ctx.Input.Param(":id")
	entry.Author = this.GetSession("user").(string)
	entry.Collection = this.Input().Get("collection")
	err = models.UpdateEntry(entry)
	if err != nil {
		panic(err)
	}

	this.TplNames = "admin/entry.tpl"
	this.Data["EntryActive"] = true
	this.Data["PostId"] = "update/" + entry.Id
	this.Data["Entry"] = entry
	this.Data["MarkdownEnabled"] = true
	collections, err := models.CollectionsByUser(this.GetSession("user").(string))
	if err != nil {
		panic(err)
	}
	this.Data["Collections"] = collections
	this.Data["Message"] = "Update Successful"

	renderTemplate(this.Ctx, "views/admin/entry.amber", this.Data)
}

func (this *AdminController) DeleteEntry() {
	this.TplNames = "admin/entry-list.tpl"

	id := this.GetString("id")
	fmt.Println("Entry Id: ", id)
	err := models.DeleteEntry(id)
	if err != nil {
		panic(err)
	}

	this.Redirect("/admin/entries", 302)
}

func (this *AdminController) NewEntry() {
	checkLogin(this)
	this.TplNames = "admin/entry.tpl"

	this.Data["PostId"] = "new"
	this.Data["EntryActive"] = true
	this.Data["MarkdownEnabled"] = true
	collections, err := models.CollectionsByUser(this.GetSession("user").(string))
	if err != nil {
		panic(err)
	}
	this.Data["Collections"] = collections

	renderTemplate(this.Ctx, "views/admin/entry.amber", this.Data)
}

func (this *AdminController) PostNewEntry() {
	this.TplNames = "admin/entry.tpl"

	entry := models.Entry{}
	err := this.ParseForm(&entry)
	if err != nil {
		panic(err)
	}
	entry.Author = this.GetSession("user").(string)
	entry.Collection = this.Input().Get("collection")
	nid, err := models.AddEntry(entry)
	if err != nil {
		this.Data["PostId"] = "new"
		this.Data["Message"] = err.Error()
	} else {
		this.Data["PostId"] = "update/" + nid
		this.Data["Message"] = "Post Successful"

		collections, err := models.CollectionsByUser(this.GetSession("user").(string))
		if err != nil {
			panic(err)
		}
		this.Data["Collections"] = collections
	}
	this.Data["Entry"] = entry
	this.Data["MarkdownEnabled"] = true
	this.Data["EntryActive"] = true

	renderTemplate(this.Ctx, "views/admin/entry.amber", this.Data)
}

// Collection Operations

func (this *AdminController) Collections() {
	checkLogin(this)
	this.TplNames = "admin/collection-list.tpl"

	collections, err := models.CollectionsByUser(this.GetSession("user").(string))
	if err != nil {
		panic(err)
	}

	this.Data["CollectionActive"] = true
	this.Data["Collections"] = collections

	renderTemplate(this.Ctx, "views/admin/collection-list.amber", this.Data)
}

func (this *AdminController) Collection() {
	fmt.Println("Collection")
	checkLogin(this)
	this.TplNames = "admin/collection.tpl"

	id := this.Ctx.Input.Param(":id")
	collection, err := models.CollectionById(id)
	if err != nil {
		panic(err)
	}

	this.Data["CollectionActive"] = true
	this.Data["Collection"] = collection
	this.Data["PostId"] = "update/" + id

	renderTemplate(this.Ctx, "views/admin/collection.amber", this.Data)
}

func (this *AdminController) UpdateCollection() {
	this.TplNames = "admin/collection.tpl"

	collection := models.Collection{}
	err := this.ParseForm(&collection)
	if err != nil {
		panic(err)
	}

	collection.Id = this.Ctx.Input.Param(":id")
	collection.Author = this.GetSession("user").(string)
	nid, err := models.UpdateCollection(collection)
	if err != nil {
		this.Data["PostId"] = "update/" + collection.Id
		this.Data["Message"] = err.Error()
	} else {
		this.Data["PostId"] = "update/" + nid
		this.Data["Message"] = "Update Successful"
	}
	this.Data["Collection"] = collection
	this.Data["CollectionActive"] = true

	renderTemplate(this.Ctx, "views/admin/collection.amber", this.Data)
}

func (this *AdminController) NewCollection() {
	fmt.Println("New Collection")
	checkLogin(this)
	this.TplNames = "admin/collection.tpl"

	this.Data["CollectionActive"] = true
	this.Data["PostId"] = "new"

	renderTemplate(this.Ctx, "views/admin/collection.amber", this.Data)
}

func (this *AdminController) DeleteCollection() {
	fmt.Println("Delete Collection")
	this.TplNames = "admin/collection-list.tpl"

	id := this.GetString("id")
	err := models.DeleteCollection(id)
	if err != nil {
		panic(err)
	}

	this.Redirect("/admin/collections", 302)
}

func (this *AdminController) CreateCollection() {
	this.TplNames = "admin/collection.tpl"

	collection := models.Collection{}
	err := this.ParseForm(&collection)
	if err != nil {
		panic(err)
	}
	fmt.Println(this.GetSession("user"))
	collection.Author = this.GetSession("user").(string)
	cid, err := models.CreateCollection(collection)
	if err != nil {
		this.Data["PostId"] = "new"
		this.Data["Message"] = err.Error()
	} else {
		this.Data["PostId"] = "update/" + cid
		this.Data["Message"] = "Post Successful"
	}
	this.Data["Collection"] = collection
	this.Data["CollectionActive"] = true

	renderTemplate(this.Ctx, "views/admin/collection.amber", this.Data)
}

func (this *AdminController) QiniuTokens() {
	bucket := models.Appconf.String("qiniu::bucket")
	putPolicy := rs.PutPolicy{
		Scope: bucket,
	}

	this.Data["json"] = putPolicy.Token(nil)
	this.ServeJson()
}
<commit_msg>no message<commit_after>package controllers

import (
	"fmt"
	"github.com/astaxie/beego"
	"github.com/dyzdyz010/Blog/models"
	"github.com/qiniu/api.v6/rs"
)

type AdminController struct {
	beego.Controller
}

func (this *AdminController) Prepare() {
	this.Data["PageTitle"] = models.Appconf.String("blog::title")
	this.Data["Title"] = models.Appconf.String("blog::title")
	this.Data["Subtitle"] = models.Appconf.String("blog::subtitle")
}

func checkLogin(ac *AdminController) {
	name := ac.GetSession("user")
	if name == nil {
		ac.Redirect("/admin/login", 302)
	}
}

func (this *AdminController) Dashboard() {
	checkLogin(this)

	this.TplNames = "admin/dashboard.tpl"

	this.Data["DashboardActive"] = true

	renderTemplate(this.Ctx, "views/admin/dashboard.amber", this.Data)
}

// Login

func (this *AdminController) Login() {
	this.TplNames = "admin/login.tpl"

	renderTemplate(this.Ctx, "views/admin/login.amber", this.Data)
}

func (this *AdminController) PostLogin() {
	this.TplNames = "admin/login.tpl"

	author := models.Author{}
	err := this.ParseForm(&author)
	if err != nil {
		panic(err)
	}

	a, err := models.AuthorByName(author.Name)
	if err != nil {
		this.Data["Message"] = err.Error()
	} else if models.Hash(author.Password) != a.Password {
		this.Data["Message"] = "Wrong password."
	} else {
		this.SetSession("user", author.Name)
		this.Redirect("/admin", 302)
	}

	renderTemplate(this.Ctx, "views/admin/login.amber", this.Data)
}

// Entry Operations

func (this *AdminController) Entries() {
	checkLogin(this)
	this.TplNames = "admin/entry-list.tpl"

	// fmt.Println(this.GetSession("user").(string))
	entries, err := models.EntriesByUser(this.GetSession("user").(string))
	if err != nil {
		panic(err)
	}

	this.Data["EntryActive"] = true
	this.Data["Entries"] = entries

	renderTemplate(this.Ctx, "views/admin/entry-list.amber", this.Data)
}

func (this *AdminController) Entry() {
	checkLogin(this)
	this.TplNames = "admin/entry.tpl"

	eid := this.Ctx.Input.Param(":id")
	fmt.Println(eid)
	if eid != "new" {
		entry, _ := models.EntryById(eid)
		if entry == nil {
			this.Abort("404")
			return
		} else {
			this.Data["Entry"] = entry
			this.Data["PostId"] = "update/" + entry.Id
		}
	}

	this.Data["EntryActive"] = true
	this.Data["MarkdownEnabled"] = true

	collections, err := models.CollectionsByUser(this.GetSession("user").(string))
	if err != nil {
		panic(err)
	}
	this.Data["Collections"] = collections
	fmt.Println(this.Data["Entry"])

	renderTemplate(this.Ctx, "views/admin/entry.amber", this.Data)
}

func (this *AdminController) UpdateEntry() {
	entry := models.Entry{}
	err := this.ParseForm(&entry)
	if err != nil {
		panic(err)
	}

	entry.Id = this.Ctx.Input.Param(":id")
	entry.Author = this.GetSession("user").(string)
	entry.Collection = this.Input().Get("collection")
	err = models.UpdateEntry(entry)
	if err != nil {
		panic(err)
	}

	this.TplNames = "admin/entry.tpl"
	this.Data["EntryActive"] = true
	this.Data["PostId"] = "update/" + entry.Id
	this.Data["Entry"] = entry
	this.Data["MarkdownEnabled"] = true
	collections, err := models.CollectionsByUser(this.GetSession("user").(string))
	if err != nil {
		panic(err)
	}
	this.Data["Collections"] = collections
	this.Data["Message"] = "Update Successful"

	renderTemplate(this.Ctx, "views/admin/entry.amber", this.Data)
}

func (this *AdminController) DeleteEntry() {
	this.TplNames = "admin/entry-list.tpl"

	id := this.GetString("id")
	fmt.Println("Entry Id: ", id)
	err := models.DeleteEntry(id)
	if err != nil {
		panic(err)
	}

	this.Redirect("/admin/entries", 302)
}

func (this *AdminController) NewEntry() {
	checkLogin(this)
	this.TplNames = "admin/entry.tpl"

	this.Data["PostId"] = "new"
	this.Data["EntryActive"] = true
	this.Data["MarkdownEnabled"] = true
	collections, err := models.CollectionsByUser(this.GetSession("user").(string))
	if err != nil {
		panic(err)
	}
	this.Data["Collections"] = collections

	renderTemplate(this.Ctx, "views/admin/entry.amber", this.Data)
}

func (this *AdminController) PostNewEntry() {
	this.TplNames = "admin/entry.tpl"

	entry := models.Entry{}
	err := this.ParseForm(&entry)
	if err != nil {
		panic(err)
	}
	entry.Author = this.GetSession("user").(string)
	entry.Collection = this.Input().Get("collection")
	nid, err := models.AddEntry(entry)
	if err != nil {
		this.Data["PostId"] = "new"
		this.Data["Message"] = err.Error()
	} else {
		this.Data["PostId"] = "update/" + nid
		this.Data["Message"] = "Post Successful"

		collections, err := models.CollectionsByUser(this.GetSession("user").(string))
		if err != nil {
			panic(err)
		}
		this.Data["Collections"] = collections
	}
	this.Data["Entry"] = entry
	this.Data["MarkdownEnabled"] = true
	this.Data["EntryActive"] = true

	renderTemplate(this.Ctx, "views/admin/entry.amber", this.Data)
}

// Collection Operations

func (this *AdminController) Collections() {
	checkLogin(this)
	this.TplNames = "admin/collection-list.tpl"

	collections, err := models.CollectionsByUser(this.GetSession("user").(string))
	if err != nil {
		panic(err)
	}

	this.Data["CollectionActive"] = true
	this.Data["Collections"] = collections

	renderTemplate(this.Ctx, "views/admin/collection-list.amber", this.Data)
}

func (this *AdminController) Collection() {
	fmt.Println("Collection")
	checkLogin(this)
	this.TplNames = "admin/collection.tpl"

	id := this.Ctx.Input.Param(":id")
	collection, err := models.CollectionById(id)
	if err != nil {
		panic(err)
	}

	this.Data["CollectionActive"] = true
	this.Data["Collection"] = collection
	this.Data["PostId"] = "update/" + id

	renderTemplate(this.Ctx, "views/admin/collection.amber", this.Data)
}

func (this *AdminController) UpdateCollection() {
	this.TplNames = "admin/collection.tpl"

	collection := models.Collection{}
	err := this.ParseForm(&collection)
	if err != nil {
		panic(err)
	}

	collection.Id = this.Ctx.Input.Param(":id")
	collection.Author = this.GetSession("user").(string)
	nid, err := models.UpdateCollection(collection)
	if err != nil {
		this.Data["PostId"] = "update/" + collection.Id
		this.Data["Message"] = err.Error()
	} else {
		this.Data["PostId"] = "update/" + nid
		this.Data["Message"] = "Update Successful"
	}
	this.Data["Collection"] = collection
	this.Data["CollectionActive"] = true

	renderTemplate(this.Ctx, "views/admin/collection.amber", this.Data)
}

func (this *AdminController) NewCollection() {
	fmt.Println("New Collection")
	checkLogin(this)
	this.TplNames = "admin/collection.tpl"

	this.Data["CollectionActive"] = true
	this.Data["PostId"] = "new"

	renderTemplate(this.Ctx, "views/admin/collection.amber", this.Data)
}

func (this *AdminController) DeleteCollection() {
	fmt.Println("Delete Collection")
	this.TplNames = "admin/collection-list.tpl"

	id := this.GetString("id")
	err := models.DeleteCollection(id)
	if err != nil {
		panic(err)
	}

	this.Redirect("/admin/collections", 302)
}

func (this *AdminController) CreateCollection() {
	this.TplNames = "admin/collection.tpl"

	collection := models.Collection{}
	err := this.ParseForm(&collection)
	if err != nil {
		panic(err)
	}
	fmt.Println(this.GetSession("user"))
	collection.Author = this.GetSession("user").(string)
	cid, err := models.CreateCollection(collection)
	if err != nil {
		this.Data["PostId"] = "new"
		this.Data["Message"] = err.Error()
	} else {
		this.Data["PostId"] = "update/" + cid
		this.Data["Message"] = "Post Successful"
	}
	this.Data["Collection"] = collection
	this.Data["CollectionActive"] = true

	renderTemplate(this.Ctx, "views/admin/collection.amber", this.Data)
}

func (this *AdminController) QiniuTokens() {
	bucket := models.Appconf.String("qiniu::bucket")
	putPolicy := rs.PutPolicy{
		Scope: bucket,
	}

	this.Data["json"] = putPolicy.Token(nil)
	this.ServeJson()
}
<|endoftext|>


<commit_before>package route

import (
	"fmt"
	"log"
	"net/url"
	"reflect"
	"sort"
	"strings"

	"github.com/fabiolb/fabio/metrics"
)

// Route maps a path prefix to one or more target URLs.
// routes can have a share value which describes the
// amount of traffic this route should get. You can specify
// that a route should get a fixed percentage of the traffic
// independent of how many instances are running.
type Route struct {
	// Host contains the host of the route.
	// not used for routing but for config generation
	// Table has a map with the host as key
	// for faster lookup and smaller search space.
	Host string

	// Path is the path prefix from a request uri
	Path string

	// Opts is the raw route options
	Opts map[string]string

	// Targets contains the list of URLs
	Targets []*Target

	// wTargets contains 100 targets distributed
	// according to their weight and ordered RR in the
	// same order as targets
	wTargets []*Target

	// total contains the total number of requests for this route.
	// Used by the RRPicker
	total uint64
}

func (r *Route) addTarget(service string, targetURL *url.URL, fixedWeight float64, tags []string) {
	if fixedWeight < 0 {
		fixedWeight = 0
	}

	// de-dup existing target
	for _, t := range r.Targets {
		if t.Service == service && t.URL.String() == targetURL.String() && t.FixedWeight == fixedWeight && reflect.DeepEqual(t.Tags, tags) {
			return
		}
	}

	name, err := metrics.TargetName(service, r.Host, r.Path, targetURL)
	if err != nil {
		log.Printf("[ERROR] Invalid metrics name: %s", err)
		name = "unknown"
	}

	t := &Target{
		Service:     service,
		Tags:        tags,
		URL:         targetURL,
		FixedWeight: fixedWeight,
		Timer:       ServiceRegistry.GetTimer(name),
		timerName:   name,
	}
	if r.Opts != nil {
		t.StripPath = r.Opts["strip"]
		t.TLSSkipVerify = r.Opts["tlsskipverify"] == "true"
		t.Host = r.Opts["host"]
	}

	r.Targets = append(r.Targets, t)
	r.weighTargets()
}

func (r *Route) filter(skip func(t *Target) bool) {
	var clone []*Target
	for _, t := range r.Targets {
		if skip(t) {
			continue
		}
		clone = append(clone, t)
	}
	r.Targets = clone
	r.weighTargets()
}

func (r *Route) setWeight(service string, weight float64, tags []string) int {
	loop := func(w float64) int {
		n := 0
		for _, t := range r.Targets {
			if service != "" && t.Service != service {
				continue
			}
			if len(tags) > 0 && !contains(t.Tags, tags) {
				continue
			}
			n++
			t.FixedWeight = w
		}
		return n
	}

	// if we have multiple matching targets
	// then we need to distribute the total
	// weight across all of them since the rule
	// states to assign only that percentage
	// of traffic to all matching routes combined.
	n := loop(0)
	w := weight / float64(n)
	loop(w)

	if n > 0 {
		r.weighTargets()
	}
	return n
}

func contains(src, dst []string) bool {
	for _, d := range dst {
		found := false
		for _, s := range src {
			if s == d {
				found = true
				break
			}
		}
		if !found {
			return false
		}
	}
	return true
}

func (r *Route) TargetConfig(t *Target, addWeight bool) string {
	s := fmt.Sprintf("route add %s %s %s", t.Service, r.Host+r.Path, t.URL)
	if addWeight {
		s += fmt.Sprintf(" weight %2.4f", t.Weight)
	} else if t.FixedWeight > 0 {
		s += fmt.Sprintf(" weight %.4f", t.FixedWeight)
	}
	if len(t.Tags) > 0 {
		s += fmt.Sprintf(" tags %q", strings.Join(t.Tags, ","))
	}
	if len(r.Opts) > 0 {
		var keys []string
		for k := range r.Opts {
			keys = append(keys, k)
		}
		sort.Strings(keys)

		var vals []string
		for _, k := range keys {
			vals = append(vals, k+"="+r.Opts[k])
		}
		s += fmt.Sprintf(" opts \"%s\"", strings.Join(vals, " "))
	}
	return s
}

// config returns the route configuration in the config language.
// with the weights specified by the user.
func (r *Route) config(addWeight bool) []string {
	var cfg []string
	for _, t := range r.Targets {
		if t.Weight <= 0 {
			continue
		}
		cfg = append(cfg, r.TargetConfig(t, addWeight))
	}
	return cfg
}

// maxSlots defines the maximum number of slots on the ring for
// weighted round-robin distribution for a single route. Consequently,
// this then defines the maximum number of separate instances that can
// serve a single route. maxSlots must be a power of ten.
const maxSlots = 1e4 // 10000

// weighTargets computes the share of traffic each target receives based
// on its weight and the weight of the other targets.
//
// Traffic is first distributed to targets with a fixed weight. If the sum of
// all fixed weights exceeds 100% then they are normalized to 100%.
//
// Targets with a dynamic weight will receive an equal share of the remaining
// traffic if there is any left.
func (r *Route) weighTargets() {
	// how big is the fixed weighted traffic?
	var nFixed int
	var sumFixed float64
	for _, t := range r.Targets {
		if t.FixedWeight > 0 {
			nFixed++
			sumFixed += t.FixedWeight
		}
	}

	// if there are no targets with fixed weight then each target simply gets
	// an equal amount of traffic
	if nFixed == 0 {
		w := 1.0 / float64(len(r.Targets))
		for _, t := range r.Targets {
			t.Weight = w
		}
		r.wTargets = r.Targets
		return
	}

	// normalize fixed weights up (sumFixed < 1) or down (sumFixed > 1)
	scale := 1.0
	if sumFixed > 1 || (nFixed == len(r.Targets) && sumFixed < 1) {
		scale = 1 / sumFixed
	}

	// compute the weight for the targets with dynamic weights
	dynamic := (1 - sumFixed) / float64(len(r.Targets)-nFixed)
	if dynamic < 0 {
		dynamic = 0
	}

	// assign the actual weight to each target
	for _, t := range r.Targets {
		if t.FixedWeight > 0 {
			t.Weight = t.FixedWeight * scale
		} else {
			t.Weight = dynamic
		}
	}

	// distribute the targets on a ring suitable for weighted round-robin
	// distribution
	//
	// This is done in two steps:
	//
	// Step one determines the necessary ring size to distribute the targets
	// according to their weight with reasonable accuracy. For example, two
	// targets with 50% weight fit in a ring of size 2 whereas two targets with
	// 10% and 90% weight require a ring of size 10.
	//
	// To keep it simple we allocate 10000 slots which provides slots to all
	// targets with at least a weight of 0.01%. In addition, we guarantee that
	// every target with a weight > 0 gets at least one slot. The case where
	// all targets get an equal share of traffic is handled earlier so this is
	// for situations with some fixed weight.
	//
	// Step two distributes the targets onto the ring spacing them out evenly
	// so that iterating over the ring performs the weighted rr distribution.
	// For example, a 50/50 distribution on a ring of size 10 should be
	// 0101010101 instead of 0000011111.
	//
	// To ensure that targets with smaller weights are properly placed we place
	// them on the ring first by sorting the targets by slot count.
	//
	// TODO(fs): I assume that this is some sort of mathematical problem
	// (coloring, optimizing, ...) but I don't know which. Happy to make this
	// more formal, if possible.
	//
	slots := make(byN, len(r.Targets))
	usedSlots := 0
	for i, t := range r.Targets {
		n := int(float64(maxSlots) * t.Weight)
		if n == 0 && t.Weight > 0 {
			n = 1
		}
		slots[i].i = i
		slots[i].n = n
		usedSlots += n
	}

	sort.Sort(slots)
	targets := make([]*Target, usedSlots)
	for _, s := range slots {
		if s.n <= 0 {
			continue
		}

		next, step := 0, usedSlots/s.n
		for k := 0; k < s.n; k++ {
			// find the next empty slot
			for targets[next] != nil {
				next = (next + 1) % usedSlots
			}

			// use slot and move to next one
			targets[next] = r.Targets[s.i]
			next = (next + step) % usedSlots
		}
	}

	r.wTargets = targets
}

type byN []struct{ i, n int }

func (r byN) Len() int           { return len(r) }
func (r byN) Swap(i, j int)      { r[i], r[j] = r[j], r[i] }
func (r byN) Less(i, j int) bool { return r[i].n < r[j].n }
<commit_msg>Fix comments<commit_after>package route

import (
	"fmt"
	"log"
	"net/url"
	"reflect"
	"sort"
	"strings"

	"github.com/fabiolb/fabio/metrics"
)

// Route maps a path prefix to one or more target URLs.
// routes can have a weight value which describes the
// amount of traffic this route should get. You can specify
// that a route should get a fixed percentage of the traffic
// independent of how many instances are running.
type Route struct {
	// Host contains the host of the route.
	// not used for routing but for config generation
	// Table has a map with the host as key
	// for faster lookup and smaller search space.
	Host string

	// Path is the path prefix from a request uri
	Path string

	// Opts is the raw route options
	Opts map[string]string

	// Targets contains the list of URLs
	Targets []*Target

	// wTargets contains targets distributed according to their weight
	wTargets []*Target

	// total contains the total number of requests for this route.
	// Used by the RRPicker
	total uint64
}

func (r *Route) addTarget(service string, targetURL *url.URL, fixedWeight float64, tags []string) {
	if fixedWeight < 0 {
		fixedWeight = 0
	}

	// de-dup existing target
	for _, t := range r.Targets {
		if t.Service == service && t.URL.String() == targetURL.String() && t.FixedWeight == fixedWeight && reflect.DeepEqual(t.Tags, tags) {
			return
		}
	}

	name, err := metrics.TargetName(service, r.Host, r.Path, targetURL)
	if err != nil {
		log.Printf("[ERROR] Invalid metrics name: %s", err)
		name = "unknown"
	}

	t := &Target{
		Service:     service,
		Tags:        tags,
		URL:         targetURL,
		FixedWeight: fixedWeight,
		Timer:       ServiceRegistry.GetTimer(name),
		timerName:   name,
	}
	if r.Opts != nil {
		t.StripPath = r.Opts["strip"]
		t.TLSSkipVerify = r.Opts["tlsskipverify"] == "true"
		t.Host = r.Opts["host"]
	}

	r.Targets = append(r.Targets, t)
	r.weighTargets()
}

func (r *Route) filter(skip func(t *Target) bool) {
	var clone []*Target
	for _, t := range r.Targets {
		if skip(t) {
			continue
		}
		clone = append(clone, t)
	}
	r.Targets = clone
	r.weighTargets()
}

func (r *Route) setWeight(service string, weight float64, tags []string) int {
	loop := func(w float64) int {
		n := 0
		for _, t := range r.Targets {
			if service != "" && t.Service != service {
				continue
			}
			if len(tags) > 0 && !contains(t.Tags, tags) {
				continue
			}
			n++
			t.FixedWeight = w
		}
		return n
	}

	// if we have multiple matching targets
	// then we need to distribute the total
	// weight across all of them since the rule
	// states to assign only that percentage
	// of traffic to all matching routes combined.
	n := loop(0)
	w := weight / float64(n)
	loop(w)

	if n > 0 {
		r.weighTargets()
	}
	return n
}

func contains(src, dst []string) bool {
	for _, d := range dst {
		found := false
		for _, s := range src {
			if s == d {
				found = true
				break
			}
		}
		if !found {
			return false
		}
	}
	return true
}

func (r *Route) TargetConfig(t *Target, addWeight bool) string {
	s := fmt.Sprintf("route add %s %s %s", t.Service, r.Host+r.Path, t.URL)
	if addWeight {
		s += fmt.Sprintf(" weight %2.4f", t.Weight)
	} else if t.FixedWeight > 0 {
		s += fmt.Sprintf(" weight %.4f", t.FixedWeight)
	}
	if len(t.Tags) > 0 {
		s += fmt.Sprintf(" tags %q", strings.Join(t.Tags, ","))
	}
	if len(r.Opts) > 0 {
		var keys []string
		for k := range r.Opts {
			keys = append(keys, k)
		}
		sort.Strings(keys)

		var vals []string
		for _, k := range keys {
			vals = append(vals, k+"="+r.Opts[k])
		}
		s += fmt.Sprintf(" opts \"%s\"", strings.Join(vals, " "))
	}
	return s
}

// config returns the route configuration in the config language.
// with the weights specified by the user.
func (r *Route) config(addWeight bool) []string {
	var cfg []string
	for _, t := range r.Targets {
		if t.Weight <= 0 {
			continue
		}
		cfg = append(cfg, r.TargetConfig(t, addWeight))
	}
	return cfg
}

// maxSlots defines the maximum number of slots on the ring for
// weighted round-robin distribution for a single route. Consequently,
// this then defines the maximum number of separate instances that can
// serve a single route. maxSlots must be a power of ten.
const maxSlots = 1e4 // 10000

// weighTargets computes the share of traffic each target receives based
// on its weight and the weight of the other targets.
//
// Traffic is first distributed to targets with a fixed weight. If the sum of
// all fixed weights exceeds 100% then they are normalized to 100%.
//
// Targets with a dynamic weight will receive an equal share of the remaining
// traffic if there is any left.
func (r *Route) weighTargets() {
	// how big is the fixed weighted traffic?
	var nFixed int
	var sumFixed float64
	for _, t := range r.Targets {
		if t.FixedWeight > 0 {
			nFixed++
			sumFixed += t.FixedWeight
		}
	}

	// if there are no targets with fixed weight then each target simply gets
	// an equal amount of traffic
	if nFixed == 0 {
		w := 1.0 / float64(len(r.Targets))
		for _, t := range r.Targets {
			t.Weight = w
		}
		r.wTargets = r.Targets
		return
	}

	// normalize fixed weights up (sumFixed < 1) or down (sumFixed > 1)
	scale := 1.0
	if sumFixed > 1 || (nFixed == len(r.Targets) && sumFixed < 1) {
		scale = 1 / sumFixed
	}

	// compute the weight for the targets with dynamic weights
	dynamic := (1 - sumFixed) / float64(len(r.Targets)-nFixed)
	if dynamic < 0 {
		dynamic = 0
	}

	// assign the actual weight to each target
	for _, t := range r.Targets {
		if t.FixedWeight > 0 {
			t.Weight = t.FixedWeight * scale
		} else {
			t.Weight = dynamic
		}
	}

	// distribute the targets on a ring suitable for weighted round-robin
	// distribution
	//
	// This is done in two steps:
	//
	// Step one determines the necessary ring size to distribute the targets
	// according to their weight with reasonable accuracy. For example, two
	// targets with 50% weight fit in a ring of size 2 whereas two targets with
	// 10% and 90% weight require a ring of size 10.
	//
	// To keep it simple we allocate 10000 slots which provides slots to all
	// targets with at least a weight of 0.01%. In addition, we guarantee that
	// every target with a weight > 0 gets at least one slot. The case where
	// all targets get an equal share of traffic is handled earlier so this is
	// for situations with some fixed weight.
	//
	// Step two distributes the targets onto the ring spacing them out evenly
	// so that iterating over the ring performs the weighted rr distribution.
	// For example, a 50/50 distribution on a ring of size 10 should be
	// 0101010101 instead of 0000011111.
	//
	// To ensure that targets with smaller weights are properly placed we place
	// them on the ring first by sorting the targets by slot count.
	//
	// TODO(fs): I assume that this is some sort of mathematical problem
	// (coloring, optimizing, ...) but I don't know which. Happy to make this
	// more formal, if possible.
	//
	slots := make(byN, len(r.Targets))
	usedSlots := 0
	for i, t := range r.Targets {
		n := int(float64(maxSlots) * t.Weight)
		if n == 0 && t.Weight > 0 {
			n = 1
		}
		slots[i].i = i
		slots[i].n = n
		usedSlots += n
	}

	sort.Sort(slots)
	targets := make([]*Target, usedSlots)
	for _, s := range slots {
		if s.n <= 0 {
			continue
		}

		next, step := 0, usedSlots/s.n
		for k := 0; k < s.n; k++ {
			// find the next empty slot
			for targets[next] != nil {
				next = (next + 1) % usedSlots
			}

			// use slot and move to next one
			targets[next] = r.Targets[s.i]
			next = (next + step) % usedSlots
		}
	}

	r.wTargets = targets
}

type byN []struct{ i, n int }

func (r byN) Len() int           { return len(r) }
func (r byN) Swap(i, j int)      { r[i], r[j] = r[j], r[i] }
func (r byN) Less(i, j int) bool { return r[i].n < r[j].n }
<|endoftext|>