<commit_before>package testrunner

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

	"github.com/onsi/ginkgo/config"
	"github.com/onsi/ginkgo/ginkgo/testsuite"
	"github.com/onsi/ginkgo/internal/remote"
	"github.com/onsi/ginkgo/reporters/stenographer"
	"github.com/onsi/ginkgo/types"
)

type TestRunner struct {
	Suite testsuite.TestSuite

	compiled              bool
	compilationTargetPath string

	numCPU         int
	parallelStream bool
	race           bool
	cover          bool
	coverPkg       string
	tags           string
	additionalArgs []string
}

func New(suite testsuite.TestSuite, numCPU int, parallelStream bool, race bool, cover bool, coverPkg string, tags string, additionalArgs []string) *TestRunner {
	runner := &TestRunner{
		Suite:          suite,
		numCPU:         numCPU,
		parallelStream: parallelStream,
		race:           race,
		cover:          cover,
		coverPkg:       coverPkg,
		tags:           tags,
		additionalArgs: additionalArgs,
	}

	if !suite.Precompiled {
		dir, err := ioutil.TempDir("", "ginkgo")
		if err != nil {
			panic(fmt.Sprintf("coulnd't create temporary directory... might be time to rm -rf:\n%s", err.Error()))
		}
		runner.compilationTargetPath = filepath.Join(dir, suite.PackageName+".test")
	}

	return runner
}

func (t *TestRunner) Compile() error {
	return t.CompileTo(t.compilationTargetPath)
}

func (t *TestRunner) CompileTo(path string) error {
	if t.compiled {
		return nil
	}

	if t.Suite.Precompiled {
		return nil
	}

	args := []string{"test", "-c", "-i", "-o", path}
	if t.race {
		args = append(args, "-race")
	}
	if t.cover {
		args = append(args, "-cover", "-covermode=atomic")
	}
	if t.coverPkg != "" {
		args = append(args, fmt.Sprintf("-coverpkg=%s", t.coverPkg))
	}
	if t.tags != "" {
		args = append(args, fmt.Sprintf("-tags=%s", t.tags))
	}

	cmd := exec.Command("go", args...)

	cmd.Dir = t.Suite.Path

	output, err := cmd.CombinedOutput()

	if err != nil {
		fixedOutput := fixCompilationOutput(string(output), t.Suite.Path)
		if len(output) > 0 {
			return fmt.Errorf("Failed to compile %s:\n\n%s", t.Suite.PackageName, fixedOutput)
		}
		return fmt.Errorf("Failed to compile %s", t.Suite.PackageName)
	}

	t.compiled = true

	return nil
}

/*
go test -c -i spits package.test out into the cwd. there's no way to change this.

to make sure it doesn't generate conflicting .test files in the cwd, Compile() must switch the cwd to the test package.

unfortunately, this causes go test's compile output to be expressed *relative to the test package* instead of the cwd.

this makes it hard to reason about what failed, and also prevents iterm's Cmd+click from working.

fixCompilationOutput..... rewrites the output to fix the paths.

yeah......
*/
func fixCompilationOutput(output string, relToPath string) string {
	re := regexp.MustCompile(`^(\S.*\.go)\:\d+\:`)
	lines := strings.Split(output, "\n")
	for i, line := range lines {
		indices := re.FindStringSubmatchIndex(line)
		if len(indices) == 0 {
			continue
		}

		path := line[indices[2]:indices[3]]
		path = filepath.Join(relToPath, path)
		lines[i] = path + line[indices[3]:]
	}
	return strings.Join(lines, "\n")
}

func (t *TestRunner) Run() RunResult {
	if t.Suite.IsGinkgo {
		if t.numCPU > 1 {
			if t.parallelStream {
				return t.runAndStreamParallelGinkgoSuite()
			} else {
				return t.runParallelGinkgoSuite()
			}
		} else {
			return t.runSerialGinkgoSuite()
		}
	} else {
		return t.runGoTestSuite()
	}
}

func (t *TestRunner) CleanUp() {
	if t.Suite.Precompiled {
		return
	}
	os.RemoveAll(filepath.Dir(t.compilationTargetPath))
}

func (t *TestRunner) runSerialGinkgoSuite() RunResult {
	ginkgoArgs := config.BuildFlagArgs("ginkgo", config.GinkgoConfig, config.DefaultReporterConfig)
	return t.run(t.cmd(ginkgoArgs, os.Stdout, 1), nil)
}

func (t *TestRunner) runGoTestSuite() RunResult {
	return t.run(t.cmd([]string{"-test.v"}, os.Stdout, 1), nil)
}

func (t *TestRunner) runAndStreamParallelGinkgoSuite() RunResult {
	completions := make(chan RunResult)
	writers := make([]*logWriter, t.numCPU)

	server, err := remote.NewServer(t.numCPU)
	if err != nil {
		panic("Failed to start parallel spec server")
	}

	server.Start()
	defer server.Close()

	for cpu := 0; cpu < t.numCPU; cpu++ {
		config.GinkgoConfig.ParallelNode = cpu + 1
		config.GinkgoConfig.ParallelTotal = t.numCPU
		config.GinkgoConfig.SyncHost = server.Address()

		ginkgoArgs := config.BuildFlagArgs("ginkgo", config.GinkgoConfig, config.DefaultReporterConfig)

		writers[cpu] = newLogWriter(os.Stdout, cpu+1)

		cmd := t.cmd(ginkgoArgs, writers[cpu], cpu+1)

		server.RegisterAlive(cpu+1, func() bool {
			if cmd.ProcessState == nil {
				return true
			}
			return !cmd.ProcessState.Exited()
		})

		go t.run(cmd, completions)
	}

	res := PassingRunResult()

	for cpu := 0; cpu < t.numCPU; cpu++ {
		res = res.Merge(<-completions)
	}

	for _, writer := range writers {
		writer.Close()
	}

	os.Stdout.Sync()

	if t.cover {
		t.combineCoverprofiles()
	}

	return res
}

func (t *TestRunner) runParallelGinkgoSuite() RunResult {
	result := make(chan bool)
	completions := make(chan RunResult)
	writers := make([]*logWriter, t.numCPU)
	reports := make([]*bytes.Buffer, t.numCPU)

	stenographer := stenographer.New(!config.DefaultReporterConfig.NoColor)
	aggregator := remote.NewAggregator(t.numCPU, result, config.DefaultReporterConfig, stenographer)

	server, err := remote.NewServer(t.numCPU)
	if err != nil {
		panic("Failed to start parallel spec server")
	}
	server.RegisterReporters(aggregator)
	server.Start()
	defer server.Close()

	for cpu := 0; cpu < t.numCPU; cpu++ {
		config.GinkgoConfig.ParallelNode = cpu + 1
		config.GinkgoConfig.ParallelTotal = t.numCPU
		config.GinkgoConfig.SyncHost = server.Address()
		config.GinkgoConfig.StreamHost = server.Address()

		ginkgoArgs := config.BuildFlagArgs("ginkgo", config.GinkgoConfig, config.DefaultReporterConfig)

		reports[cpu] = &bytes.Buffer{}
		writers[cpu] = newLogWriter(reports[cpu], cpu+1)

		cmd := t.cmd(ginkgoArgs, writers[cpu], cpu+1)

		server.RegisterAlive(cpu+1, func() bool {
			if cmd.ProcessState == nil {
				return true
			}
			return !cmd.ProcessState.Exited()
		})

		go t.run(cmd, completions)
	}

	res := PassingRunResult()

	for cpu := 0; cpu < t.numCPU; cpu++ {
		res = res.Merge(<-completions)
	}

	//all test processes are done, at this point
	//we should be able to wait for the aggregator to tell us that it's done

	select {
	case <-result:
		fmt.Println("")
	case <-time.After(time.Second):
		//the aggregator never got back to us!  something must have gone wrong
		fmt.Println(`
	 -------------------------------------------------------------------
	|                                                                   |
	|  Ginkgo timed out waiting for all parallel nodes to report back!  |
	|                                                                   |
	 -------------------------------------------------------------------
`)

		os.Stdout.Sync()

		for _, writer := range writers {
			writer.Close()
		}

		for _, report := range reports {
			fmt.Print(report.String())
		}

		os.Stdout.Sync()
	}

	if t.cover {
		t.combineCoverprofiles()
	}

	return res
}

func (t *TestRunner) cmd(ginkgoArgs []string, stream io.Writer, node int) *exec.Cmd {
	args := []string{"--test.timeout=24h"}
	if t.cover {
		coverprofile := "--test.coverprofile=" + t.Suite.PackageName + ".coverprofile"
		if t.numCPU > 1 {
			coverprofile = fmt.Sprintf("%s.%d", coverprofile, node)
		}
		args = append(args, coverprofile)
	}

	args = append(args, ginkgoArgs...)
	args = append(args, t.additionalArgs...)

	path := t.compilationTargetPath
	if t.Suite.Precompiled {
		path, _ = filepath.Abs(filepath.Join(t.Suite.Path, fmt.Sprintf("%s.test", t.Suite.PackageName)))
	}

	cmd := exec.Command(path, args...)

	cmd.Dir = t.Suite.Path
	cmd.Stderr = stream
	cmd.Stdout = stream

	return cmd
}

func (t *TestRunner) run(cmd *exec.Cmd, completions chan RunResult) RunResult {
	var res RunResult

	defer func() {
		if completions != nil {
			completions <- res
		}
	}()

	err := cmd.Start()
	if err != nil {
		fmt.Printf("Failed to run test suite!\n\t%s", err.Error())
		return res
	}

	cmd.Wait()
	exitStatus := cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus()
	res.Passed = (exitStatus == 0) || (exitStatus == types.GINKGO_FOCUS_EXIT_CODE)
	res.HasProgrammaticFocus = (exitStatus == types.GINKGO_FOCUS_EXIT_CODE)

	return res
}

func (t *TestRunner) combineCoverprofiles() {
	profiles := []string{}
	for cpu := 1; cpu <= t.numCPU; cpu++ {
		coverFile := fmt.Sprintf("%s.coverprofile.%d", t.Suite.PackageName, cpu)
		coverFile = filepath.Join(t.Suite.Path, coverFile)
		coverProfile, err := ioutil.ReadFile(coverFile)
		os.Remove(coverFile)

		if err == nil {
			profiles = append(profiles, string(coverProfile))
		}
	}

	if len(profiles) != t.numCPU {
		return
	}

	lines := map[string]int{}
	lineOrder := []string{}
	for i, coverProfile := range profiles {
		for _, line := range strings.Split(string(coverProfile), "\n")[1:] {
			if len(line) == 0 {
				continue
			}
			components := strings.Split(line, " ")
			count, _ := strconv.Atoi(components[len(components)-1])
			prefix := strings.Join(components[0:len(components)-1], " ")
			lines[prefix] += count
			if i == 0 {
				lineOrder = append(lineOrder, prefix)
			}
		}
	}

	output := []string{"mode: atomic"}
	for _, line := range lineOrder {
		output = append(output, fmt.Sprintf("%s %d", line, lines[line]))
	}
	finalOutput := strings.Join(output, "\n")
	ioutil.WriteFile(filepath.Join(t.Suite.Path, fmt.Sprintf("%s.coverprofile", t.Suite.PackageName)), []byte(finalOutput), 0666)
}
<commit_msg>Always run coverage if coverpkg given<commit_after>package testrunner

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

	"github.com/onsi/ginkgo/config"
	"github.com/onsi/ginkgo/ginkgo/testsuite"
	"github.com/onsi/ginkgo/internal/remote"
	"github.com/onsi/ginkgo/reporters/stenographer"
	"github.com/onsi/ginkgo/types"
)

type TestRunner struct {
	Suite testsuite.TestSuite

	compiled              bool
	compilationTargetPath string

	numCPU         int
	parallelStream bool
	race           bool
	cover          bool
	coverPkg       string
	tags           string
	additionalArgs []string
}

func New(suite testsuite.TestSuite, numCPU int, parallelStream bool, race bool, cover bool, coverPkg string, tags string, additionalArgs []string) *TestRunner {
	runner := &TestRunner{
		Suite:          suite,
		numCPU:         numCPU,
		parallelStream: parallelStream,
		race:           race,
		cover:          cover,
		coverPkg:       coverPkg,
		tags:           tags,
		additionalArgs: additionalArgs,
	}

	if !suite.Precompiled {
		dir, err := ioutil.TempDir("", "ginkgo")
		if err != nil {
			panic(fmt.Sprintf("coulnd't create temporary directory... might be time to rm -rf:\n%s", err.Error()))
		}
		runner.compilationTargetPath = filepath.Join(dir, suite.PackageName+".test")
	}

	return runner
}

func (t *TestRunner) Compile() error {
	return t.CompileTo(t.compilationTargetPath)
}

func (t *TestRunner) CompileTo(path string) error {
	if t.compiled {
		return nil
	}

	if t.Suite.Precompiled {
		return nil
	}

	args := []string{"test", "-c", "-i", "-o", path}
	if t.race {
		args = append(args, "-race")
	}
	if t.cover || t.coverPkg != "" {
		args = append(args, "-cover", "-covermode=atomic")
	}
	if t.coverPkg != "" {
		args = append(args, fmt.Sprintf("-coverpkg=%s", t.coverPkg))
	}
	if t.tags != "" {
		args = append(args, fmt.Sprintf("-tags=%s", t.tags))
	}

	cmd := exec.Command("go", args...)

	cmd.Dir = t.Suite.Path

	output, err := cmd.CombinedOutput()

	if err != nil {
		fixedOutput := fixCompilationOutput(string(output), t.Suite.Path)
		if len(output) > 0 {
			return fmt.Errorf("Failed to compile %s:\n\n%s", t.Suite.PackageName, fixedOutput)
		}
		return fmt.Errorf("Failed to compile %s", t.Suite.PackageName)
	}

	t.compiled = true

	return nil
}

/*
go test -c -i spits package.test out into the cwd. there's no way to change this.

to make sure it doesn't generate conflicting .test files in the cwd, Compile() must switch the cwd to the test package.

unfortunately, this causes go test's compile output to be expressed *relative to the test package* instead of the cwd.

this makes it hard to reason about what failed, and also prevents iterm's Cmd+click from working.

fixCompilationOutput..... rewrites the output to fix the paths.

yeah......
*/
func fixCompilationOutput(output string, relToPath string) string {
	re := regexp.MustCompile(`^(\S.*\.go)\:\d+\:`)
	lines := strings.Split(output, "\n")
	for i, line := range lines {
		indices := re.FindStringSubmatchIndex(line)
		if len(indices) == 0 {
			continue
		}

		path := line[indices[2]:indices[3]]
		path = filepath.Join(relToPath, path)
		lines[i] = path + line[indices[3]:]
	}
	return strings.Join(lines, "\n")
}

func (t *TestRunner) Run() RunResult {
	if t.Suite.IsGinkgo {
		if t.numCPU > 1 {
			if t.parallelStream {
				return t.runAndStreamParallelGinkgoSuite()
			} else {
				return t.runParallelGinkgoSuite()
			}
		} else {
			return t.runSerialGinkgoSuite()
		}
	} else {
		return t.runGoTestSuite()
	}
}

func (t *TestRunner) CleanUp() {
	if t.Suite.Precompiled {
		return
	}
	os.RemoveAll(filepath.Dir(t.compilationTargetPath))
}

func (t *TestRunner) runSerialGinkgoSuite() RunResult {
	ginkgoArgs := config.BuildFlagArgs("ginkgo", config.GinkgoConfig, config.DefaultReporterConfig)
	return t.run(t.cmd(ginkgoArgs, os.Stdout, 1), nil)
}

func (t *TestRunner) runGoTestSuite() RunResult {
	return t.run(t.cmd([]string{"-test.v"}, os.Stdout, 1), nil)
}

func (t *TestRunner) runAndStreamParallelGinkgoSuite() RunResult {
	completions := make(chan RunResult)
	writers := make([]*logWriter, t.numCPU)

	server, err := remote.NewServer(t.numCPU)
	if err != nil {
		panic("Failed to start parallel spec server")
	}

	server.Start()
	defer server.Close()

	for cpu := 0; cpu < t.numCPU; cpu++ {
		config.GinkgoConfig.ParallelNode = cpu + 1
		config.GinkgoConfig.ParallelTotal = t.numCPU
		config.GinkgoConfig.SyncHost = server.Address()

		ginkgoArgs := config.BuildFlagArgs("ginkgo", config.GinkgoConfig, config.DefaultReporterConfig)

		writers[cpu] = newLogWriter(os.Stdout, cpu+1)

		cmd := t.cmd(ginkgoArgs, writers[cpu], cpu+1)

		server.RegisterAlive(cpu+1, func() bool {
			if cmd.ProcessState == nil {
				return true
			}
			return !cmd.ProcessState.Exited()
		})

		go t.run(cmd, completions)
	}

	res := PassingRunResult()

	for cpu := 0; cpu < t.numCPU; cpu++ {
		res = res.Merge(<-completions)
	}

	for _, writer := range writers {
		writer.Close()
	}

	os.Stdout.Sync()

	if t.cover {
		t.combineCoverprofiles()
	}

	return res
}

func (t *TestRunner) runParallelGinkgoSuite() RunResult {
	result := make(chan bool)
	completions := make(chan RunResult)
	writers := make([]*logWriter, t.numCPU)
	reports := make([]*bytes.Buffer, t.numCPU)

	stenographer := stenographer.New(!config.DefaultReporterConfig.NoColor)
	aggregator := remote.NewAggregator(t.numCPU, result, config.DefaultReporterConfig, stenographer)

	server, err := remote.NewServer(t.numCPU)
	if err != nil {
		panic("Failed to start parallel spec server")
	}
	server.RegisterReporters(aggregator)
	server.Start()
	defer server.Close()

	for cpu := 0; cpu < t.numCPU; cpu++ {
		config.GinkgoConfig.ParallelNode = cpu + 1
		config.GinkgoConfig.ParallelTotal = t.numCPU
		config.GinkgoConfig.SyncHost = server.Address()
		config.GinkgoConfig.StreamHost = server.Address()

		ginkgoArgs := config.BuildFlagArgs("ginkgo", config.GinkgoConfig, config.DefaultReporterConfig)

		reports[cpu] = &bytes.Buffer{}
		writers[cpu] = newLogWriter(reports[cpu], cpu+1)

		cmd := t.cmd(ginkgoArgs, writers[cpu], cpu+1)

		server.RegisterAlive(cpu+1, func() bool {
			if cmd.ProcessState == nil {
				return true
			}
			return !cmd.ProcessState.Exited()
		})

		go t.run(cmd, completions)
	}

	res := PassingRunResult()

	for cpu := 0; cpu < t.numCPU; cpu++ {
		res = res.Merge(<-completions)
	}

	//all test processes are done, at this point
	//we should be able to wait for the aggregator to tell us that it's done

	select {
	case <-result:
		fmt.Println("")
	case <-time.After(time.Second):
		//the aggregator never got back to us!  something must have gone wrong
		fmt.Println(`
	 -------------------------------------------------------------------
	|                                                                   |
	|  Ginkgo timed out waiting for all parallel nodes to report back!  |
	|                                                                   |
	 -------------------------------------------------------------------
`)

		os.Stdout.Sync()

		for _, writer := range writers {
			writer.Close()
		}

		for _, report := range reports {
			fmt.Print(report.String())
		}

		os.Stdout.Sync()
	}

	if t.cover {
		t.combineCoverprofiles()
	}

	return res
}

func (t *TestRunner) cmd(ginkgoArgs []string, stream io.Writer, node int) *exec.Cmd {
	args := []string{"--test.timeout=24h"}
	if t.cover {
		coverprofile := "--test.coverprofile=" + t.Suite.PackageName + ".coverprofile"
		if t.numCPU > 1 {
			coverprofile = fmt.Sprintf("%s.%d", coverprofile, node)
		}
		args = append(args, coverprofile)
	}

	args = append(args, ginkgoArgs...)
	args = append(args, t.additionalArgs...)

	path := t.compilationTargetPath
	if t.Suite.Precompiled {
		path, _ = filepath.Abs(filepath.Join(t.Suite.Path, fmt.Sprintf("%s.test", t.Suite.PackageName)))
	}

	cmd := exec.Command(path, args...)

	cmd.Dir = t.Suite.Path
	cmd.Stderr = stream
	cmd.Stdout = stream

	return cmd
}

func (t *TestRunner) run(cmd *exec.Cmd, completions chan RunResult) RunResult {
	var res RunResult

	defer func() {
		if completions != nil {
			completions <- res
		}
	}()

	err := cmd.Start()
	if err != nil {
		fmt.Printf("Failed to run test suite!\n\t%s", err.Error())
		return res
	}

	cmd.Wait()
	exitStatus := cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus()
	res.Passed = (exitStatus == 0) || (exitStatus == types.GINKGO_FOCUS_EXIT_CODE)
	res.HasProgrammaticFocus = (exitStatus == types.GINKGO_FOCUS_EXIT_CODE)

	return res
}

func (t *TestRunner) combineCoverprofiles() {
	profiles := []string{}
	for cpu := 1; cpu <= t.numCPU; cpu++ {
		coverFile := fmt.Sprintf("%s.coverprofile.%d", t.Suite.PackageName, cpu)
		coverFile = filepath.Join(t.Suite.Path, coverFile)
		coverProfile, err := ioutil.ReadFile(coverFile)
		os.Remove(coverFile)

		if err == nil {
			profiles = append(profiles, string(coverProfile))
		}
	}

	if len(profiles) != t.numCPU {
		return
	}

	lines := map[string]int{}
	lineOrder := []string{}
	for i, coverProfile := range profiles {
		for _, line := range strings.Split(string(coverProfile), "\n")[1:] {
			if len(line) == 0 {
				continue
			}
			components := strings.Split(line, " ")
			count, _ := strconv.Atoi(components[len(components)-1])
			prefix := strings.Join(components[0:len(components)-1], " ")
			lines[prefix] += count
			if i == 0 {
				lineOrder = append(lineOrder, prefix)
			}
		}
	}

	output := []string{"mode: atomic"}
	for _, line := range lineOrder {
		output = append(output, fmt.Sprintf("%s %d", line, lines[line]))
	}
	finalOutput := strings.Join(output, "\n")
	ioutil.WriteFile(filepath.Join(t.Suite.Path, fmt.Sprintf("%s.coverprofile", t.Suite.PackageName)), []byte(finalOutput), 0666)
}
<|endoftext|>


<commit_before>package main

import (
	"context"
	"fmt"
	"os/exec"
	"time"

	pbb "github.com/brotherlogic/buildserver/proto"
	pbfc "github.com/brotherlogic/filecopier/proto"
	pb "github.com/brotherlogic/gobuildslave/proto"
)

const (
	pendWait = time.Minute
)

func (s *Server) runTransition(ctx context.Context, job *pb.JobAssignment) {
	startState := job.State
	job.LastUpdateTime = time.Now().Unix()
	switch job.State {
	case pb.State_ACKNOWLEDGED:
		key := s.scheduleBuild(ctx, job)
		s.stateMutex.Lock()
		s.stateMap[job.Job.Name] = fmt.Sprintf("SCHED: %v @ %v", key, time.Now())
		s.stateMutex.Unlock()
		if !job.Job.Bootstrap {
			if key != "" {
				job.State = pb.State_BUILT
				job.RunningVersion = key
			} else {
				// Bootstrap this job since we don't have an initial version
				if job.Job.PartialBootstrap {
					job.Job.Bootstrap = true
				}
			}
		} else {
			job.CommandKey = key
			job.State = pb.State_BUILDING
		}
	case pb.State_BUILDING:
		s.stateMutex.Lock()
		s.stateMap[job.Job.Name] = fmt.Sprintf("BUILD(%v): %v", job.CommandKey, s.scheduler.getState(job.CommandKey))
		s.stateMutex.Unlock()
		if s.taskComplete(job.CommandKey) {
			job.State = pb.State_BUILT
		}
	case pb.State_BUILT:
		job.SubState = "Getting Output"
		output, _ := s.scheduler.getOutput(job.CommandKey)
		job.SubState = "Entering Lock"
		s.stateMutex.Lock()
		s.stateMap[job.Job.Name] = fmt.Sprintf("BUILT(%v): (%v): %v", job.CommandKey, len(output), output)
		s.stateMutex.Unlock()
		if job.Job.Bootstrap && len(output) > 0 {
			if job.BuildFail == 5 {
				job.SubState = "Sending Report"
				s.deliverCrashReport(ctx, job, output)
				job.BuildFail = 0
			}
			job.BuildFail++
			job.State = pb.State_DIED
		} else {
			job.BuildFail = 0
			job.SubState = "Scheduling Run"
			key := s.scheduleRun(job)
			job.CommandKey = key
			job.StartTime = time.Now().Unix()
			job.State = pb.State_PENDING
			if _, ok := s.pendingMap[time.Now().Weekday()]; !ok {
				s.pendingMap[time.Now().Weekday()] = make(map[string]int)
			}
			s.pendingMap[time.Now().Weekday()][job.Job.Name]++
		}
		job.SubState = "Out of case"
	case pb.State_PENDING:
		if job.Job.PartialBootstrap && job.Job.Bootstrap {
			job.Job.Bootstrap = false
		}
		s.stateMutex.Lock()
		out, _ := s.scheduler.getOutput(job.CommandKey)
		s.stateMap[job.Job.Name] = fmt.Sprintf("OUTPUT = %v", out)
		s.stateMutex.Unlock()
		if time.Now().Add(-time.Minute).Unix() > job.StartTime {
			job.State = pb.State_RUNNING
		}
	case pb.State_RUNNING:
		output, errout := s.scheduler.getOutput(job.CommandKey)
		s.stateMutex.Lock()
		s.stateMap[job.Job.Name] = fmt.Sprintf("ROUTPUT = %v, %v", output, s.scheduler.getStatus(job.CommandKey))
		job.Status = s.scheduler.getStatus(job.CommandKey)
		s.stateMutex.Unlock()
		if len(job.CommandKey) > 0 && s.taskComplete(job.CommandKey) {
			s.stateMutex.Lock()
			s.stateMap[job.Job.Name] = fmt.Sprintf("COMPLETE = (%v, %v)", job, output)
			s.stateMutex.Unlock()
			s.deliverCrashReport(ctx, job, output)
			job.State = pb.State_DIED
		}

		if s.discover != nil && s.Registry != nil {
			port, err := s.discover.discover(job.Job.Name, s.Registry.Identifier)
			if err != nil {
				if job.DiscoverCount > 30 {
					output2, errout2 := s.scheduler.getErrOutput(job.CommandKey)
					s.RaiseIssue(ctx, "Cannot Discover Running Server", fmt.Sprintf("%v on %v is not discoverable, despite running (%v) the output says %v (%v), %v, %v", job.Job.Name, s.Registry.Identifier, err, output, errout, output2, errout2), false)
				}
				job.DiscoverCount++
			} else {
				job.Port = port
				job.DiscoverCount = 0
			}
		}

		// Restart this job if we need to
		if !job.Job.Bootstrap {
			if time.Now().Sub(time.Unix(job.LastVersionPull, 0)) > time.Minute*5 {
				version, err := s.getVersion(ctx, job.Job)
				job.LastVersionPull = time.Now().Unix()

				if err == nil && version.Version != job.RunningVersion {
					s.stateMutex.Lock()
					s.stateMap[job.Job.Name] = fmt.Sprintf("VERSION_MISMATCH = %v,%v", version, job.RunningVersion)
					s.stateMutex.Unlock()
					s.scheduler.killJob(job.CommandKey)
				}
			}
		}
	case pb.State_BRINK_OF_DEATH:
		if s.version.confirm(ctx, job.Job.Name) {
			s.scheduler.killJob(job.CommandKey)
			job.State = pb.State_ACKNOWLEDGED
		}
	case pb.State_DIED:
		s.stateMutex.Lock()
		s.stateMap[job.Job.Name] = fmt.Sprintf("DIED %v", job.CommandKey)
		s.stateMutex.Unlock()
		s.scheduler.removeJob(job.CommandKey)
		job.State = pb.State_ACKNOWLEDGED
	}

	if job.State != startState {
		job.LastTransitionTime = time.Now().Unix()
	}
}

type translator interface {
	build(job *pb.Job) *exec.Cmd
	run(job *pb.Job) *exec.Cmd
}

type checker interface {
	isAlive(ctx context.Context, job *pb.JobAssignment) bool
}

func (s *Server) getVersion(ctx context.Context, job *pb.Job) (*pbb.Version, error) {
	version, err := s.builder.build(ctx, job)
	if err != nil {
		return &pbb.Version{}, err
	}

	return version, nil

}

func updateJob(err error, job *pb.JobAssignment, resp *pbfc.CopyResponse) {
	if err == nil {
		job.QueuePos = resp.IndexInQueue
	}

}

// scheduleBuild builds out the job, returning the current version
func (s *Server) scheduleBuild(ctx context.Context, job *pb.JobAssignment) string {
	if job.Job.Bootstrap {
		c := s.translator.build(job.Job)
		return s.scheduler.Schedule(&rCommand{command: c, base: job.Job.Name})
	}

	val, err := s.builder.build(ctx, job.Job)
	if err != nil {
		return ""
	}
	return val.Version
}

func (s *Server) scheduleRun(job *pb.JobAssignment) string {
	//Copy over any existing new versions
	key := s.scheduler.Schedule(&rCommand{command: exec.Command("mv", "$GOPATH/bin/"+job.GetJob().GetName()+".new", "$GOPATH/bin/"+job.GetJob().GetName()), base: job.GetJob().GetName()})
	for !s.taskComplete(key) {
		str, err := s.scheduler.getOutput(key)
		str2, err2 := s.scheduler.getErrOutput(key)
		job.SubState = "WAITING FOR COPY - " + key + " (" + s.scheduler.lastRun + ") - " + fmt.Sprintf("%v - (%v,%v) (%v,%v) with %v outstanding", time.Now(), str, err, str2, err2, len(s.scheduler.commands))
		time.Sleep(time.Second)
	}

	c := s.translator.run(job.GetJob())
	return s.scheduler.Schedule(&rCommand{command: c, base: job.GetJob().GetName()})
}

func (s *Server) taskComplete(key string) bool {
	return s.scheduler.schedulerComplete(key)
}
<commit_msg>Show status<commit_after>package main

import (
	"context"
	"fmt"
	"os/exec"
	"time"

	pbb "github.com/brotherlogic/buildserver/proto"
	pbfc "github.com/brotherlogic/filecopier/proto"
	pb "github.com/brotherlogic/gobuildslave/proto"
)

const (
	pendWait = time.Minute
)

func (s *Server) runTransition(ctx context.Context, job *pb.JobAssignment) {
	startState := job.State
	job.LastUpdateTime = time.Now().Unix()
	switch job.State {
	case pb.State_ACKNOWLEDGED:
		key := s.scheduleBuild(ctx, job)
		s.stateMutex.Lock()
		s.stateMap[job.Job.Name] = fmt.Sprintf("SCHED: %v @ %v", key, time.Now())
		s.stateMutex.Unlock()
		if !job.Job.Bootstrap {
			if key != "" {
				job.State = pb.State_BUILT
				job.RunningVersion = key
			} else {
				// Bootstrap this job since we don't have an initial version
				if job.Job.PartialBootstrap {
					job.Job.Bootstrap = true
				}
			}
		} else {
			job.CommandKey = key
			job.State = pb.State_BUILDING
		}
	case pb.State_BUILDING:
		s.stateMutex.Lock()
		s.stateMap[job.Job.Name] = fmt.Sprintf("BUILD(%v): %v", job.CommandKey, s.scheduler.getState(job.CommandKey))
		s.stateMutex.Unlock()
		if s.taskComplete(job.CommandKey) {
			job.State = pb.State_BUILT
		}
	case pb.State_BUILT:
		job.SubState = "Getting Output"
		output, _ := s.scheduler.getOutput(job.CommandKey)
		job.SubState = "Entering Lock"
		s.stateMutex.Lock()
		s.stateMap[job.Job.Name] = fmt.Sprintf("BUILT(%v): (%v): %v", job.CommandKey, len(output), output)
		s.stateMutex.Unlock()
		if job.Job.Bootstrap && len(output) > 0 {
			if job.BuildFail == 5 {
				job.SubState = "Sending Report"
				s.deliverCrashReport(ctx, job, output)
				job.BuildFail = 0
			}
			job.BuildFail++
			job.State = pb.State_DIED
		} else {
			job.BuildFail = 0
			job.SubState = "Scheduling Run"
			key := s.scheduleRun(job)
			job.CommandKey = key
			job.StartTime = time.Now().Unix()
			job.State = pb.State_PENDING
			if _, ok := s.pendingMap[time.Now().Weekday()]; !ok {
				s.pendingMap[time.Now().Weekday()] = make(map[string]int)
			}
			s.pendingMap[time.Now().Weekday()][job.Job.Name]++
		}
		job.SubState = "Out of case"
	case pb.State_PENDING:
		if job.Job.PartialBootstrap && job.Job.Bootstrap {
			job.Job.Bootstrap = false
		}
		s.stateMutex.Lock()
		out, _ := s.scheduler.getOutput(job.CommandKey)
		s.stateMap[job.Job.Name] = fmt.Sprintf("OUTPUT = %v", out)
		s.stateMutex.Unlock()
		if time.Now().Add(-time.Minute).Unix() > job.StartTime {
			job.State = pb.State_RUNNING
		}
	case pb.State_RUNNING:
		output, errout := s.scheduler.getOutput(job.CommandKey)
		s.stateMutex.Lock()
		s.stateMap[job.Job.Name] = fmt.Sprintf("ROUTPUT = %v, %v", output, s.scheduler.getStatus(job.CommandKey))
		job.Status = s.scheduler.getStatus(job.CommandKey)
		s.stateMutex.Unlock()
		if len(job.CommandKey) > 0 && s.taskComplete(job.CommandKey) {
			s.stateMutex.Lock()
			s.stateMap[job.Job.Name] = fmt.Sprintf("COMPLETE = (%v, %v)", job, output)
			s.stateMutex.Unlock()
			s.deliverCrashReport(ctx, job, output)
			job.State = pb.State_DIED
		}

		if s.discover != nil && s.Registry != nil {
			port, err := s.discover.discover(job.Job.Name, s.Registry.Identifier)
			if err != nil {
				if job.DiscoverCount > 30 {
					output2, errout2 := s.scheduler.getErrOutput(job.CommandKey)
					s.RaiseIssue(ctx, "Cannot Discover Running Server", fmt.Sprintf("%v on %v is not discoverable, despite running (%v) the output says %v (%v), %v, %v", job.Job.Name, s.Registry.Identifier, err, output, errout, output2, errout2), false)
				}
				job.DiscoverCount++
			} else {
				job.Port = port
				job.DiscoverCount = 0
			}
		}

		// Restart this job if we need to
		if !job.Job.Bootstrap {
			if time.Now().Sub(time.Unix(job.LastVersionPull, 0)) > time.Minute*5 {
				version, err := s.getVersion(ctx, job.Job)
				job.LastVersionPull = time.Now().Unix()

				if err == nil && version.Version != job.RunningVersion {
					s.stateMutex.Lock()
					s.stateMap[job.Job.Name] = fmt.Sprintf("VERSION_MISMATCH = %v,%v", version, job.RunningVersion)
					s.stateMutex.Unlock()
					s.scheduler.killJob(job.CommandKey)
				}
			}
		}
	case pb.State_BRINK_OF_DEATH:
		if s.version.confirm(ctx, job.Job.Name) {
			s.scheduler.killJob(job.CommandKey)
			job.State = pb.State_ACKNOWLEDGED
		}
	case pb.State_DIED:
		s.stateMutex.Lock()
		s.stateMap[job.Job.Name] = fmt.Sprintf("DIED %v", job.CommandKey)
		s.stateMutex.Unlock()
		s.scheduler.removeJob(job.CommandKey)
		job.State = pb.State_ACKNOWLEDGED
	}

	if job.State != startState {
		job.LastTransitionTime = time.Now().Unix()
	}
}

type translator interface {
	build(job *pb.Job) *exec.Cmd
	run(job *pb.Job) *exec.Cmd
}

type checker interface {
	isAlive(ctx context.Context, job *pb.JobAssignment) bool
}

func (s *Server) getVersion(ctx context.Context, job *pb.Job) (*pbb.Version, error) {
	version, err := s.builder.build(ctx, job)
	if err != nil {
		return &pbb.Version{}, err
	}

	return version, nil

}

func updateJob(err error, job *pb.JobAssignment, resp *pbfc.CopyResponse) {
	if err == nil {
		job.QueuePos = resp.IndexInQueue
	}

}

// scheduleBuild builds out the job, returning the current version
func (s *Server) scheduleBuild(ctx context.Context, job *pb.JobAssignment) string {
	if job.Job.Bootstrap {
		c := s.translator.build(job.Job)
		return s.scheduler.Schedule(&rCommand{command: c, base: job.Job.Name})
	}

	val, err := s.builder.build(ctx, job.Job)
	if err != nil {
		return ""
	}
	return val.Version
}

func (s *Server) scheduleRun(job *pb.JobAssignment) string {
	//Copy over any existing new versions
	key := s.scheduler.Schedule(&rCommand{command: exec.Command("mv", "$GOPATH/bin/"+job.GetJob().GetName()+".new", "$GOPATH/bin/"+job.GetJob().GetName()), base: job.GetJob().GetName()})
	for !s.taskComplete(key) {
		str, err := s.scheduler.getOutput(key)
		str2, err2 := s.scheduler.getErrOutput(key)
		job.SubState = "WAITING FOR COPY - " + key + " (" + s.scheduler.lastRun + ") - " + fmt.Sprintf("%v - (%v,%v) (%v,%v) with %v outstanding and %v", time.Now(), str, err, str2, err2, len(s.scheduler.commands), s.scheduler.getStatus(key))
		time.Sleep(time.Second)
	}

	c := s.translator.run(job.GetJob())
	return s.scheduler.Schedule(&rCommand{command: c, base: job.GetJob().GetName()})
}

func (s *Server) taskComplete(key string) bool {
	return s.scheduler.schedulerComplete(key)
}
<|endoftext|>


<commit_before>package main

import (
	"context"
	"fmt"
	"os/exec"
	"time"

	pbb "github.com/brotherlogic/buildserver/proto"
	pbfc "github.com/brotherlogic/filecopier/proto"
	pb "github.com/brotherlogic/gobuildslave/proto"
)

const (
	pendWait = time.Minute
)

func (s *Server) runTransition(ctx context.Context, job *pb.JobAssignment) {
	startState := job.State
	job.LastUpdateTime = time.Now().Unix()
	switch job.State {
	case pb.State_ACKNOWLEDGED:
		key := s.scheduleBuild(ctx, job)
		s.stateMutex.Lock()
		s.stateMap[job.Job.Name] = fmt.Sprintf("SCHED: %v @ %v", key, time.Now())
		s.stateMutex.Unlock()
		if !job.Job.Bootstrap {
			if key != "" {
				job.State = pb.State_BUILT
				job.RunningVersion = key
			} else {
				// Bootstrap this job since we don't have an initial version
				if job.Job.PartialBootstrap {
					job.Job.Bootstrap = true
				}
			}
		} else {
			job.CommandKey = key
			job.State = pb.State_BUILDING
		}
	case pb.State_BUILDING:
		s.stateMutex.Lock()
		s.stateMap[job.Job.Name] = fmt.Sprintf("BUILD(%v): %v", job.CommandKey, s.scheduler.getState(job.CommandKey))
		s.stateMutex.Unlock()
		if s.taskComplete(job.CommandKey) {
			job.State = pb.State_BUILT
		}
	case pb.State_BUILT:
		job.SubState = "Getting Output"
		output, _ := s.scheduler.getOutput(job.CommandKey)
		job.SubState = "Entering Lock"
		s.stateMutex.Lock()
		s.stateMap[job.Job.Name] = fmt.Sprintf("BUILT(%v): (%v): %v", job.CommandKey, len(output), output)
		s.stateMutex.Unlock()
		if job.Job.Bootstrap && len(output) > 0 {
			if job.BuildFail == 5 {
				job.SubState = "Sending Report"
				s.deliverCrashReport(ctx, job, output)
				job.BuildFail = 0
			}
			job.BuildFail++
			job.State = pb.State_DIED
		} else {
			job.BuildFail = 0
			job.SubState = "Scheduling Run"
			key := s.scheduleRun(job)
			job.CommandKey = key
			job.StartTime = time.Now().Unix()
			job.State = pb.State_PENDING
			if _, ok := s.pendingMap[time.Now().Weekday()]; !ok {
				s.pendingMap[time.Now().Weekday()] = make(map[string]int)
			}
			s.pendingMap[time.Now().Weekday()][job.Job.Name]++
		}
		job.SubState = "Out of case"
	case pb.State_PENDING:
		if job.Job.PartialBootstrap && job.Job.Bootstrap {
			job.Job.Bootstrap = false
		}
		s.stateMutex.Lock()
		out, _ := s.scheduler.getOutput(job.CommandKey)
		s.stateMap[job.Job.Name] = fmt.Sprintf("OUTPUT = %v", out)
		s.stateMutex.Unlock()
		if time.Now().Add(-time.Minute).Unix() > job.StartTime {
			job.State = pb.State_RUNNING
		}
	case pb.State_RUNNING:
		output, errout := s.scheduler.getOutput(job.CommandKey)
		s.stateMutex.Lock()
		s.stateMap[job.Job.Name] = fmt.Sprintf("ROUTPUT = %v, %v", output, s.scheduler.getStatus(job.CommandKey))
		job.Status = s.scheduler.getStatus(job.CommandKey)
		s.stateMutex.Unlock()
		if len(job.CommandKey) > 0 && s.taskComplete(job.CommandKey) {
			s.stateMutex.Lock()
			s.stateMap[job.Job.Name] = fmt.Sprintf("COMPLETE = (%v, %v)", job, output)
			s.stateMutex.Unlock()
			s.deliverCrashReport(ctx, job, output)
			job.State = pb.State_DIED
		}

		if s.discover != nil && s.Registry != nil {
			port, err := s.discover.discover(job.Job.Name, s.Registry.Identifier)
			if err != nil {
				if job.DiscoverCount > 30 {
					output2, errout2 := s.scheduler.getErrOutput(job.CommandKey)
					s.RaiseIssue(ctx, "Cannot Discover Running Server", fmt.Sprintf("%v on %v is not discoverable, despite running (%v) the output says %v (%v), %v, %v", job.Job.Name, s.Registry.Identifier, err, output, errout, output2, errout2), false)
				}
				job.DiscoverCount++
			} else {
				job.Port = port
				job.DiscoverCount = 0
			}
		}

		// Restart this job if we need to
		if !job.Job.Bootstrap {
			if time.Now().Sub(time.Unix(job.LastVersionPull, 0)) > time.Minute*5 {
				version, err := s.getVersion(ctx, job.Job)
				job.LastVersionPull = time.Now().Unix()

				if err == nil && version.Version != job.RunningVersion {
					s.stateMutex.Lock()
					s.stateMap[job.Job.Name] = fmt.Sprintf("VERSION_MISMATCH = %v,%v", version, job.RunningVersion)
					s.stateMutex.Unlock()
					s.scheduler.killJob(job.CommandKey)
				}
			}
		}
	case pb.State_BRINK_OF_DEATH:
		if s.version.confirm(ctx, job.Job.Name) {
			s.scheduler.killJob(job.CommandKey)
			job.State = pb.State_ACKNOWLEDGED
		}
	case pb.State_DIED:
		s.stateMutex.Lock()
		s.stateMap[job.Job.Name] = fmt.Sprintf("DIED %v", job.CommandKey)
		s.stateMutex.Unlock()
		s.scheduler.removeJob(job.CommandKey)
		job.State = pb.State_ACKNOWLEDGED
	}

	if job.State != startState {
		job.LastTransitionTime = time.Now().Unix()
	}
}

type translator interface {
	build(job *pb.Job) *exec.Cmd
	run(job *pb.Job) *exec.Cmd
}

type checker interface {
	isAlive(ctx context.Context, job *pb.JobAssignment) bool
}

func (s *Server) getVersion(ctx context.Context, job *pb.Job) (*pbb.Version, error) {
	version, err := s.builder.build(ctx, job)
	if err != nil {
		return &pbb.Version{}, err
	}

	return version, nil

}

func updateJob(err error, job *pb.JobAssignment, resp *pbfc.CopyResponse) {
	if err == nil {
		job.QueuePos = resp.IndexInQueue
	}

}

// scheduleBuild builds out the job, returning the current version
func (s *Server) scheduleBuild(ctx context.Context, job *pb.JobAssignment) string {
	if job.Job.Bootstrap {
		c := s.translator.build(job.Job)
		return s.scheduler.Schedule(&rCommand{command: c, base: job.Job.Name})
	}

	val, err := s.builder.build(ctx, job.Job)
	if err != nil {
		return ""
	}
	return val.Version
}

func (s *Server) scheduleRun(job *pb.JobAssignment) string {
	//Copy over any existing new versions
	key := s.scheduler.Schedule(&rCommand{command: exec.Command("mv", "$GOPATH/bin/"+job.GetJob().GetName()+".new", "$GOPATH/bin/"+job.GetJob().GetName()), base: job.GetJob().GetName()})
	for !s.taskComplete(key) {
		job.SubState = "WAITING FOR COPY - " + key + " (" + s.scheduler.lastRun + ") - " + fmt.Sprintf("%v", time.Now())
		time.Sleep(time.Second)
	}

	c := s.translator.run(job.GetJob())
	return s.scheduler.Schedule(&rCommand{command: c, base: job.GetJob().GetName()})
}

func (s *Server) taskComplete(key string) bool {
	return s.scheduler.schedulerComplete(key)
}
<commit_msg>More details<commit_after>package main

import (
	"context"
	"fmt"
	"os/exec"
	"time"

	pbb "github.com/brotherlogic/buildserver/proto"
	pbfc "github.com/brotherlogic/filecopier/proto"
	pb "github.com/brotherlogic/gobuildslave/proto"
)

const (
	pendWait = time.Minute
)

func (s *Server) runTransition(ctx context.Context, job *pb.JobAssignment) {
	startState := job.State
	job.LastUpdateTime = time.Now().Unix()
	switch job.State {
	case pb.State_ACKNOWLEDGED:
		key := s.scheduleBuild(ctx, job)
		s.stateMutex.Lock()
		s.stateMap[job.Job.Name] = fmt.Sprintf("SCHED: %v @ %v", key, time.Now())
		s.stateMutex.Unlock()
		if !job.Job.Bootstrap {
			if key != "" {
				job.State = pb.State_BUILT
				job.RunningVersion = key
			} else {
				// Bootstrap this job since we don't have an initial version
				if job.Job.PartialBootstrap {
					job.Job.Bootstrap = true
				}
			}
		} else {
			job.CommandKey = key
			job.State = pb.State_BUILDING
		}
	case pb.State_BUILDING:
		s.stateMutex.Lock()
		s.stateMap[job.Job.Name] = fmt.Sprintf("BUILD(%v): %v", job.CommandKey, s.scheduler.getState(job.CommandKey))
		s.stateMutex.Unlock()
		if s.taskComplete(job.CommandKey) {
			job.State = pb.State_BUILT
		}
	case pb.State_BUILT:
		job.SubState = "Getting Output"
		output, _ := s.scheduler.getOutput(job.CommandKey)
		job.SubState = "Entering Lock"
		s.stateMutex.Lock()
		s.stateMap[job.Job.Name] = fmt.Sprintf("BUILT(%v): (%v): %v", job.CommandKey, len(output), output)
		s.stateMutex.Unlock()
		if job.Job.Bootstrap && len(output) > 0 {
			if job.BuildFail == 5 {
				job.SubState = "Sending Report"
				s.deliverCrashReport(ctx, job, output)
				job.BuildFail = 0
			}
			job.BuildFail++
			job.State = pb.State_DIED
		} else {
			job.BuildFail = 0
			job.SubState = "Scheduling Run"
			key := s.scheduleRun(job)
			job.CommandKey = key
			job.StartTime = time.Now().Unix()
			job.State = pb.State_PENDING
			if _, ok := s.pendingMap[time.Now().Weekday()]; !ok {
				s.pendingMap[time.Now().Weekday()] = make(map[string]int)
			}
			s.pendingMap[time.Now().Weekday()][job.Job.Name]++
		}
		job.SubState = "Out of case"
	case pb.State_PENDING:
		if job.Job.PartialBootstrap && job.Job.Bootstrap {
			job.Job.Bootstrap = false
		}
		s.stateMutex.Lock()
		out, _ := s.scheduler.getOutput(job.CommandKey)
		s.stateMap[job.Job.Name] = fmt.Sprintf("OUTPUT = %v", out)
		s.stateMutex.Unlock()
		if time.Now().Add(-time.Minute).Unix() > job.StartTime {
			job.State = pb.State_RUNNING
		}
	case pb.State_RUNNING:
		output, errout := s.scheduler.getOutput(job.CommandKey)
		s.stateMutex.Lock()
		s.stateMap[job.Job.Name] = fmt.Sprintf("ROUTPUT = %v, %v", output, s.scheduler.getStatus(job.CommandKey))
		job.Status = s.scheduler.getStatus(job.CommandKey)
		s.stateMutex.Unlock()
		if len(job.CommandKey) > 0 && s.taskComplete(job.CommandKey) {
			s.stateMutex.Lock()
			s.stateMap[job.Job.Name] = fmt.Sprintf("COMPLETE = (%v, %v)", job, output)
			s.stateMutex.Unlock()
			s.deliverCrashReport(ctx, job, output)
			job.State = pb.State_DIED
		}

		if s.discover != nil && s.Registry != nil {
			port, err := s.discover.discover(job.Job.Name, s.Registry.Identifier)
			if err != nil {
				if job.DiscoverCount > 30 {
					output2, errout2 := s.scheduler.getErrOutput(job.CommandKey)
					s.RaiseIssue(ctx, "Cannot Discover Running Server", fmt.Sprintf("%v on %v is not discoverable, despite running (%v) the output says %v (%v), %v, %v", job.Job.Name, s.Registry.Identifier, err, output, errout, output2, errout2), false)
				}
				job.DiscoverCount++
			} else {
				job.Port = port
				job.DiscoverCount = 0
			}
		}

		// Restart this job if we need to
		if !job.Job.Bootstrap {
			if time.Now().Sub(time.Unix(job.LastVersionPull, 0)) > time.Minute*5 {
				version, err := s.getVersion(ctx, job.Job)
				job.LastVersionPull = time.Now().Unix()

				if err == nil && version.Version != job.RunningVersion {
					s.stateMutex.Lock()
					s.stateMap[job.Job.Name] = fmt.Sprintf("VERSION_MISMATCH = %v,%v", version, job.RunningVersion)
					s.stateMutex.Unlock()
					s.scheduler.killJob(job.CommandKey)
				}
			}
		}
	case pb.State_BRINK_OF_DEATH:
		if s.version.confirm(ctx, job.Job.Name) {
			s.scheduler.killJob(job.CommandKey)
			job.State = pb.State_ACKNOWLEDGED
		}
	case pb.State_DIED:
		s.stateMutex.Lock()
		s.stateMap[job.Job.Name] = fmt.Sprintf("DIED %v", job.CommandKey)
		s.stateMutex.Unlock()
		s.scheduler.removeJob(job.CommandKey)
		job.State = pb.State_ACKNOWLEDGED
	}

	if job.State != startState {
		job.LastTransitionTime = time.Now().Unix()
	}
}

type translator interface {
	build(job *pb.Job) *exec.Cmd
	run(job *pb.Job) *exec.Cmd
}

type checker interface {
	isAlive(ctx context.Context, job *pb.JobAssignment) bool
}

func (s *Server) getVersion(ctx context.Context, job *pb.Job) (*pbb.Version, error) {
	version, err := s.builder.build(ctx, job)
	if err != nil {
		return &pbb.Version{}, err
	}

	return version, nil

}

func updateJob(err error, job *pb.JobAssignment, resp *pbfc.CopyResponse) {
	if err == nil {
		job.QueuePos = resp.IndexInQueue
	}

}

// scheduleBuild builds out the job, returning the current version
func (s *Server) scheduleBuild(ctx context.Context, job *pb.JobAssignment) string {
	if job.Job.Bootstrap {
		c := s.translator.build(job.Job)
		return s.scheduler.Schedule(&rCommand{command: c, base: job.Job.Name})
	}

	val, err := s.builder.build(ctx, job.Job)
	if err != nil {
		return ""
	}
	return val.Version
}

func (s *Server) scheduleRun(job *pb.JobAssignment) string {
	//Copy over any existing new versions
	key := s.scheduler.Schedule(&rCommand{command: exec.Command("mv", "$GOPATH/bin/"+job.GetJob().GetName()+".new", "$GOPATH/bin/"+job.GetJob().GetName()), base: job.GetJob().GetName()})
	for !s.taskComplete(key) {
		str, err := s.scheduler.getOutput(key)
		str2, err2 := s.scheduler.getErrOutput(key)
		job.SubState = "WAITING FOR COPY - " + key + " (" + s.scheduler.lastRun + ") - " + fmt.Sprintf("%v - (%v,%v) (%v,%v)", time.Now(), str, err, str2, err2)
		time.Sleep(time.Second)
	}

	c := s.translator.run(job.GetJob())
	return s.scheduler.Schedule(&rCommand{command: c, base: job.GetJob().GetName()})
}

func (s *Server) taskComplete(key string) bool {
	return s.scheduler.schedulerComplete(key)
}
<|endoftext|>


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

package site

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"path"
	"strings"
	"time"

	"golang.org/x/website/go.dev/cmd/internal/tmplfunc"
	"gopkg.in/yaml.v3"
)

// A page is a single web page.
// It corresponds to some .md file in the content tree.
type page struct {
	id     string // page ID (url path excluding site.BaseURL and trailing slash)
	file   string // .md file for page
	data   []byte // page data (markdown)
	html   []byte // rendered page (HTML)
	params tPage  // parameters passed to templates
}

// A tPage is the template form of the page, the data passed to rendering templates.
type tPage map[string]interface{}

// loadPage loads the site's page from the given file.
// It returns the page but also adds the page to site.pages and site.pagesByID.
func (site *Site) loadPage(file string) (*page, error) {
	id := strings.TrimPrefix(file, "_content/")
	if id == "index.md" {
		id = ""
	} else if strings.HasSuffix(id, "/index.md") {
		id = strings.TrimSuffix(id, "/index.md")
	} else {
		id = strings.TrimSuffix(id, ".md")
	}

	p := site.newPage(id)
	p.file = file

	urlPath := "/" + p.id
	if strings.HasSuffix(p.file, "/index.md") && p.id != "" {
		urlPath += "/"
	}

	// Load content, including leading yaml.
	data, err := ioutil.ReadFile(site.file(file))
	if err != nil {
		return nil, err
	}
	if bytes.HasPrefix(data, []byte("---\n")) {
		i := bytes.Index(data, []byte("\n---\n"))
		if i < 0 {
			if bytes.HasSuffix(data, []byte("\n---")) {
				i = len(data) - 4
			}
		}
		if i >= 0 {
			meta := data[4 : i+1]
			err := yaml.Unmarshal(meta, p.params)
			if err != nil {
				return nil, fmt.Errorf("load %s: %v", file, err)
			}

			// Drop YAML but insert the right number of newlines to keep line numbers correct in template errors.
			nl := 0
			for _, c := range data[:i+4] {
				if c == '\n' {
					nl++
				}
			}
			i += 4
			for ; nl > 0; nl-- {
				i--
				data[i] = '\n'
			}
			data = data[i:]
		}
	}
	p.data = data

	// Default linkTitle to title
	if _, ok := p.params["linkTitle"]; !ok {
		p.params["linkTitle"] = p.params["title"]
	}

	// Parse date to Date.
	// Note that YAML parser may have done it for us (!)
	p.params["Date"] = time.Time{}
	if d, ok := p.params["date"].(string); ok {
		t, err := parseDate(d)
		if err != nil {
			return nil, err
		}
		p.params["Date"] = t
	} else if d, ok := p.params["date"].(time.Time); ok {
		p.params["Date"] = d
	}

	// Path, Dir, URL
	p.params["Path"] = urlPath
	p.params["Dir"] = path.Dir(urlPath)
	p.params["URL"] = strings.TrimRight(site.URL, "/") + urlPath

	// Parent
	if p.id != "" {
		parent := path.Dir("/" + p.id)
		if parent != "/" {
			parent += "/"
		}
		p.params["Parent"] = parent
	}

	// Section
	section := "/"
	if i := strings.Index(p.id, "/"); i >= 0 {
		section = "/" + p.id[:i+1]
	} else if strings.HasSuffix(p.file, "/index.md") {
		section = "/" + p.id + "/"
	}
	p.params["Section"] = section

	return p, nil
}

// renderHTML renders the HTML for the page, leaving it in p.html.
func (site *Site) renderHTML(p *page) error {
	content, err := site.markdownTemplateToHTML(string(p.data), p)
	if err != nil {
		return err
	}
	p.params["Content"] = content

	// Load base template.
	base, err := ioutil.ReadFile(site.file("_templates/layouts/site.tmpl"))
	if err != nil {
		return err
	}
	t := site.clone().New("_templates/layouts/site.tmpl")
	if err := tmplfunc.Parse(t, string(base)); err != nil {
		return err
	}

	// Load page-specific layout template.
	layout, _ := p.params["layout"].(string)
	if layout == "" {
		layout = "default"
	}
	data, err := ioutil.ReadFile(site.file("_templates/layouts/" + layout + ".tmpl"))
	if err != nil {
		return err
	}
	if err := tmplfunc.Parse(t.New(layout), string(data)); err != nil {
		return err
	}

	var buf bytes.Buffer
	if err := t.Execute(&buf, p.params); err != nil {
		return err
	}
	p.html = buf.Bytes()
	return nil
}

var dateFormats = []string{
	"2006-01-02",
	time.RFC3339,
}

func parseDate(d string) (time.Time, error) {
	for _, f := range dateFormats {
		if tt, err := time.Parse(f, d); err == nil {
			return tt, nil
		}
	}
	return time.Time{}, fmt.Errorf("invalid date: %s", d)
}
<commit_msg>go.dev/cmd/internal/site: fix tests on Windows<commit_after>// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package site

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"path"
	"path/filepath"
	"strings"
	"time"

	"golang.org/x/website/go.dev/cmd/internal/tmplfunc"
	"gopkg.in/yaml.v3"
)

// A page is a single web page.
// It corresponds to some .md file in the content tree.
type page struct {
	id     string // page ID (url path excluding site.BaseURL and trailing slash)
	file   string // .md file for page
	data   []byte // page data (markdown)
	html   []byte // rendered page (HTML)
	params tPage  // parameters passed to templates
}

// A tPage is the template form of the page, the data passed to rendering templates.
type tPage map[string]interface{}

// loadPage loads the site's page from the given file.
// It returns the page but also adds the page to site.pages and site.pagesByID.
func (site *Site) loadPage(file string) (*page, error) {
	file = filepath.ToSlash(file)
	id := strings.TrimPrefix(file, "_content/")
	if id == "index.md" {
		id = ""
	} else if strings.HasSuffix(id, "/index.md") {
		id = strings.TrimSuffix(id, "/index.md")
	} else {
		id = strings.TrimSuffix(id, ".md")
	}

	p := site.newPage(id)
	p.file = file

	urlPath := "/" + p.id
	if strings.HasSuffix(p.file, "/index.md") && p.id != "" {
		urlPath += "/"
	}

	// Load content, including leading yaml.
	data, err := ioutil.ReadFile(site.file(file))
	if err != nil {
		return nil, err
	}
	if bytes.HasPrefix(data, []byte("---\n")) {
		i := bytes.Index(data, []byte("\n---\n"))
		if i < 0 {
			if bytes.HasSuffix(data, []byte("\n---")) {
				i = len(data) - 4
			}
		}
		if i >= 0 {
			meta := data[4 : i+1]
			err := yaml.Unmarshal(meta, p.params)
			if err != nil {
				return nil, fmt.Errorf("load %s: %v", file, err)
			}

			// Drop YAML but insert the right number of newlines to keep line numbers correct in template errors.
			nl := 0
			for _, c := range data[:i+4] {
				if c == '\n' {
					nl++
				}
			}
			i += 4
			for ; nl > 0; nl-- {
				i--
				data[i] = '\n'
			}
			data = data[i:]
		}
	}
	p.data = data

	// Default linkTitle to title
	if _, ok := p.params["linkTitle"]; !ok {
		p.params["linkTitle"] = p.params["title"]
	}

	// Parse date to Date.
	// Note that YAML parser may have done it for us (!)
	p.params["Date"] = time.Time{}
	if d, ok := p.params["date"].(string); ok {
		t, err := parseDate(d)
		if err != nil {
			return nil, err
		}
		p.params["Date"] = t
	} else if d, ok := p.params["date"].(time.Time); ok {
		p.params["Date"] = d
	}

	// Path, Dir, URL
	p.params["Path"] = urlPath
	p.params["Dir"] = path.Dir(urlPath)
	p.params["URL"] = strings.TrimRight(site.URL, "/") + urlPath

	// Parent
	if p.id != "" {
		parent := path.Dir("/" + p.id)
		if parent != "/" {
			parent += "/"
		}
		p.params["Parent"] = parent
	}

	// Section
	section := "/"
	if i := strings.Index(p.id, "/"); i >= 0 {
		section = "/" + p.id[:i+1]
	} else if strings.HasSuffix(p.file, "/index.md") {
		section = "/" + p.id + "/"
	}
	p.params["Section"] = section

	return p, nil
}

// renderHTML renders the HTML for the page, leaving it in p.html.
func (site *Site) renderHTML(p *page) error {
	content, err := site.markdownTemplateToHTML(string(p.data), p)
	if err != nil {
		return err
	}
	p.params["Content"] = content

	// Load base template.
	base, err := ioutil.ReadFile(site.file("_templates/layouts/site.tmpl"))
	if err != nil {
		return err
	}
	t := site.clone().New("_templates/layouts/site.tmpl")
	if err := tmplfunc.Parse(t, string(base)); err != nil {
		return err
	}

	// Load page-specific layout template.
	layout, _ := p.params["layout"].(string)
	if layout == "" {
		layout = "default"
	}
	data, err := ioutil.ReadFile(site.file("_templates/layouts/" + layout + ".tmpl"))
	if err != nil {
		return err
	}
	if err := tmplfunc.Parse(t.New(layout), string(data)); err != nil {
		return err
	}

	var buf bytes.Buffer
	if err := t.Execute(&buf, p.params); err != nil {
		return err
	}
	p.html = buf.Bytes()
	return nil
}

var dateFormats = []string{
	"2006-01-02",
	time.RFC3339,
}

func parseDate(d string) (time.Time, error) {
	for _, f := range dateFormats {
		if tt, err := time.Parse(f, d); err == nil {
			return tt, nil
		}
	}
	return time.Time{}, fmt.Errorf("invalid date: %s", d)
}
<|endoftext|>


<commit_before>/*
Copyright 2021 The Vitess 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.
*/
// +build gofuzz

package planbuilder

import (
	"vitess.io/vitess/go/vt/key"
	topodatapb "vitess.io/vitess/go/vt/proto/topodata"
	"vitess.io/vitess/go/vt/sqlparser"
	"vitess.io/vitess/go/vt/vtgate/semantics"
	"vitess.io/vitess/go/vt/vtgate/vindexes"
)

var _ semantics.SchemaInformation = (*fakeFuzzSI)(nil)

type fakeFuzzSI struct {
	tables map[string]*vindexes.Table
}

// Helper func:
func (s *fakeFuzzSI) FindTableOrVindex(tablename sqlparser.TableName) (*vindexes.Table, vindexes.Vindex, string, topodatapb.TabletType, key.Destination, error) {
	return s.tables[sqlparser.String(tablename)], nil, "", 0, nil, nil
}

// FuzzAnalyse implements the fuzzer
func FuzzAnalyse(data []byte) int {
	tree, err := sqlparser.Parse(string(data))
	if err != nil {
		return -1
	}
	semTable, err := semantics.Analyse(tree, &fakeFuzzSI{})
	if err != nil {
		return 0
	}
	_, _ = createQGFromSelect(tree.(*sqlparser.Select), semTable)
	return 1
}
<commit_msg>fix compilation error<commit_after>/*
Copyright 2021 The Vitess 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.
*/
// +build gofuzz

package planbuilder

import (
	"vitess.io/vitess/go/vt/key"
	topodatapb "vitess.io/vitess/go/vt/proto/topodata"
	"vitess.io/vitess/go/vt/sqlparser"
	"vitess.io/vitess/go/vt/vtgate/semantics"
	"vitess.io/vitess/go/vt/vtgate/vindexes"
)

var _ semantics.SchemaInformation = (*fakeFuzzSI)(nil)

type fakeFuzzSI struct {
	tables map[string]*vindexes.Table
}

// FindTableOrVindex is a helper func
func (s *fakeFuzzSI) FindTableOrVindex(tablename sqlparser.TableName) (*vindexes.Table, vindexes.Vindex, string, topodatapb.TabletType, key.Destination, error) {
	return s.tables[sqlparser.String(tablename)], nil, "", 0, nil, nil
}

// FuzzAnalyse implements the fuzzer
func FuzzAnalyse(data []byte) int {
	tree, err := sqlparser.Parse(string(data))
	if err != nil {
		return -1
	}
	semTable, err := semantics.Analyze(tree, "", &fakeFuzzSI{})
	if err != nil {
		return 0
	}
	_, _ = createQGFromSelect(tree.(*sqlparser.Select), semTable)
	return 1
}
<|endoftext|>


<commit_before>package logbuf

import (
	"bufio"
	"fmt"
	"io"
	"net/http"
	"os"
	"path"
	"sort"
	"strconv"
	"strings"
	"time"
)

func (lb *LogBuffer) addHttpHandlers() {
	http.HandleFunc("/logs", lb.httpListHandler)
	http.HandleFunc("/logs/dump", lb.httpDumpHandler)
	http.HandleFunc("/logs/showLast", lb.httpShowLastHandler)
}

func (lb *LogBuffer) httpListHandler(w http.ResponseWriter, req *http.Request) {
	if lb.logDir == "" {
		return
	}
	writer := bufio.NewWriter(w)
	defer writer.Flush()
	file, err := os.Open(lb.logDir)
	if err != nil {
		fmt.Fprintln(writer, err)
		return
	}
	names, err := file.Readdirnames(-1)
	file.Close()
	if err != nil {
		fmt.Fprintln(writer, err)
		return
	}
	tmpNames := make([]string, 0, len(names))
	for _, name := range names {
		if strings.Index(name, ":") >= 0 {
			tmpNames = append(tmpNames, name)

		}
	}
	names = tmpNames
	sort.Strings(names)
	flags, _ := parseQuery(req.URL.RawQuery)
	recentFirstString := ""
	_, recentFirst := flags["recentFirst"]
	if recentFirst {
		recentFirstString = "&recentFirst"
		reverseStrings(names)
	}
	if _, ok := flags["text"]; ok {
		for _, name := range names {
			fmt.Fprintln(writer, name)
		}
		return
	}
	fmt.Fprintln(writer, "")
	fmt.Fprint(writer, "Logs: ")
	if recentFirst {
		fmt.Fprintf(writer, "showing recent first ")
		fmt.Fprintln(writer, `<a href="logs">show recent last</a><br>`)
	} else {
		fmt.Fprintf(writer, "showing recent last ")
		fmt.Fprintln(writer,
			`<a href="logs?recentFirst">show recent first</a><br>`)
	}
	// TODO(rgooch): Enable when ready.
	// showRecentLinks(writer, recentFirstString)
	fmt.Fprintln(writer, "
")
	currentName := ""
	lb.rwMutex.Lock()
	if lb.file != nil {
		currentName = path.Base(lb.file.Name())
	}
	lb.rwMutex.Unlock()
	for _, name := range names {
		if name == currentName {
			fmt.Fprintf(writer,
				"%s (current)
\n",
				recentFirstString, name)
		} else {
			fmt.Fprintf(writer, "%s
\n",
				name, recentFirstString, name)
		}
	}
	fmt.Fprintln(writer, "")
}

func showRecentLinks(w io.Writer, recentFirstString string) {
	fmt.Fprintf(w, "Show last: minute\n",
		recentFirstString)
	fmt.Fprintf(w, "           10 min\n",
		recentFirstString)
	fmt.Fprintf(w, "           hour\n",
		recentFirstString)
	fmt.Fprintf(w, "           day\n",
		recentFirstString)
}

func (lb *LogBuffer) httpDumpHandler(w http.ResponseWriter, req *http.Request) {
	flags, pairs := parseQuery(req.URL.RawQuery)
	name, ok := pairs["name"]
	if !ok {
		w.Header().Set("Content-Type", "text/plain; charset=utf-8")
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	recentFirst := false
	if _, ok := flags["recentFirst"]; ok {
		recentFirst = true
	}
	if name == "latest" {
		writer := bufio.NewWriter(w)
		defer writer.Flush()
		lb.Dump(writer, "", "", recentFirst)
		return
	}
	file, err := os.Open(path.Join(lb.logDir, path.Base(path.Clean(name))))
	if err != nil {
		w.Header().Set("Content-Type", "text/plain; charset=utf-8")
		w.WriteHeader(http.StatusNotFound)
		return
	}
	defer file.Close()
	writer := bufio.NewWriter(w)
	defer writer.Flush()
	if recentFirst {
		scanner := bufio.NewScanner(file)
		lines := make([]string, 0)
		for scanner.Scan() {
			line := scanner.Text()
			if len(line) < 1 {
				continue
			}
			lines = append(lines, line)
		}
		if err = scanner.Err(); err == nil {
			reverseStrings(lines)
			for _, line := range lines {
				fmt.Fprintln(writer, line)
			}
		}
	} else {
		_, err = io.Copy(writer, bufio.NewReader(file))
	}
	if err != nil {
		fmt.Fprintln(writer, err)
	}
	return
}

func (lb *LogBuffer) httpShowLastHandler(w http.ResponseWriter,
	req *http.Request) {
	flags, _ := parseQuery(req.URL.RawQuery)
	_, recentFirst := flags["recentFirst"]
	for flag := range flags {
		length := len(flag)
		unitChar := flag[length-1]
		var unit time.Duration
		switch unitChar {
		case 's':
			unit = time.Second
		case 'm':
			unit = time.Minute
		case 'h':
			unit = time.Hour
		case 'd':
			unit = time.Hour * 24
		default:
			continue
		}
		if val, err := strconv.ParseUint(flag[:length-1], 10, 64); err != nil {
			w.Header().Set("Content-Type", "text/plain; charset=utf-8")
			w.WriteHeader(http.StatusBadRequest)
			return
		} else {
			lb.showRecent(w, time.Duration(val)*unit, recentFirst)
		}
	}
	w.Header().Set("Content-Type", "text/plain; charset=utf-8")
	w.WriteHeader(http.StatusBadRequest)
}

func (lb *LogBuffer) showRecent(w io.Writer, duration time.Duration,
	recentFirst bool) {
}

func (lb *LogBuffer) writeHtml(writer io.Writer) {
	fmt.Fprintln(writer, `<a href="logs">Logs:</a><br>`)
	fmt.Fprintln(writer, "
")
	lb.Dump(writer, "", "", false)
	fmt.Fprintln(writer, "
")
}

func parseQuery(rawQuery string) (map[string]struct{}, map[string]string) {
	flags := make(map[string]struct{})
	table := make(map[string]string)
	for _, pair := range strings.Split(rawQuery, "&") {
		splitPair := strings.Split(pair, "=")
		if len(splitPair) == 1 {
			flags[splitPair[0]] = struct{}{}
		}
		if len(splitPair) == 2 {
			table[splitPair[0]] = splitPair[1]
		}
	}
	return flags, table
}
<commit_msg>Fix bugs in lib/logbuf.httpShowLastHandler().<commit_after>package logbuf

import (
	"bufio"
	"fmt"
	"io"
	"net/http"
	"os"
	"path"
	"sort"
	"strconv"
	"strings"
	"time"
)

func (lb *LogBuffer) addHttpHandlers() {
	http.HandleFunc("/logs", lb.httpListHandler)
	http.HandleFunc("/logs/dump", lb.httpDumpHandler)
	http.HandleFunc("/logs/showLast", lb.httpShowLastHandler)
}

func (lb *LogBuffer) httpListHandler(w http.ResponseWriter, req *http.Request) {
	if lb.logDir == "" {
		return
	}
	writer := bufio.NewWriter(w)
	defer writer.Flush()
	file, err := os.Open(lb.logDir)
	if err != nil {
		fmt.Fprintln(writer, err)
		return
	}
	names, err := file.Readdirnames(-1)
	file.Close()
	if err != nil {
		fmt.Fprintln(writer, err)
		return
	}
	tmpNames := make([]string, 0, len(names))
	for _, name := range names {
		if strings.Index(name, ":") >= 0 {
			tmpNames = append(tmpNames, name)

		}
	}
	names = tmpNames
	sort.Strings(names)
	flags, _ := parseQuery(req.URL.RawQuery)
	recentFirstString := ""
	_, recentFirst := flags["recentFirst"]
	if recentFirst {
		recentFirstString = "&recentFirst"
		reverseStrings(names)
	}
	if _, ok := flags["text"]; ok {
		for _, name := range names {
			fmt.Fprintln(writer, name)
		}
		return
	}
	fmt.Fprintln(writer, "")
	fmt.Fprint(writer, "Logs: ")
	if recentFirst {
		fmt.Fprintf(writer, "showing recent first ")
		fmt.Fprintln(writer, `<a href="logs">show recent last</a><br>`)
	} else {
		fmt.Fprintf(writer, "showing recent last ")
		fmt.Fprintln(writer,
			`<a href="logs?recentFirst">show recent first</a><br>`)
	}
	// TODO(rgooch): Enable when ready.
	// showRecentLinks(writer, recentFirstString)
	fmt.Fprintln(writer, "
")
	currentName := ""
	lb.rwMutex.Lock()
	if lb.file != nil {
		currentName = path.Base(lb.file.Name())
	}
	lb.rwMutex.Unlock()
	for _, name := range names {
		if name == currentName {
			fmt.Fprintf(writer,
				"%s (current)
\n",
				recentFirstString, name)
		} else {
			fmt.Fprintf(writer, "%s
\n",
				name, recentFirstString, name)
		}
	}
	fmt.Fprintln(writer, "")
}

func showRecentLinks(w io.Writer, recentFirstString string) {
	fmt.Fprintf(w, "Show last: minute\n",
		recentFirstString)
	fmt.Fprintf(w, "           10 min\n",
		recentFirstString)
	fmt.Fprintf(w, "           hour\n",
		recentFirstString)
	fmt.Fprintf(w, "           day\n",
		recentFirstString)
}

func (lb *LogBuffer) httpDumpHandler(w http.ResponseWriter, req *http.Request) {
	flags, pairs := parseQuery(req.URL.RawQuery)
	name, ok := pairs["name"]
	if !ok {
		w.Header().Set("Content-Type", "text/plain; charset=utf-8")
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	recentFirst := false
	if _, ok := flags["recentFirst"]; ok {
		recentFirst = true
	}
	if name == "latest" {
		writer := bufio.NewWriter(w)
		defer writer.Flush()
		lb.Dump(writer, "", "", recentFirst)
		return
	}
	file, err := os.Open(path.Join(lb.logDir, path.Base(path.Clean(name))))
	if err != nil {
		w.Header().Set("Content-Type", "text/plain; charset=utf-8")
		w.WriteHeader(http.StatusNotFound)
		return
	}
	defer file.Close()
	writer := bufio.NewWriter(w)
	defer writer.Flush()
	if recentFirst {
		scanner := bufio.NewScanner(file)
		lines := make([]string, 0)
		for scanner.Scan() {
			line := scanner.Text()
			if len(line) < 1 {
				continue
			}
			lines = append(lines, line)
		}
		if err = scanner.Err(); err == nil {
			reverseStrings(lines)
			for _, line := range lines {
				fmt.Fprintln(writer, line)
			}
		}
	} else {
		_, err = io.Copy(writer, bufio.NewReader(file))
	}
	if err != nil {
		fmt.Fprintln(writer, err)
	}
	return
}

func (lb *LogBuffer) httpShowLastHandler(w http.ResponseWriter,
	req *http.Request) {
	flags, _ := parseQuery(req.URL.RawQuery)
	_, recentFirst := flags["recentFirst"]
	for flag := range flags {
		length := len(flag)
		if length < 2 {
			continue
		}
		unitChar := flag[length-1]
		var unit time.Duration
		switch unitChar {
		case 's':
			unit = time.Second
		case 'm':
			unit = time.Minute
		case 'h':
			unit = time.Hour
		case 'd':
			unit = time.Hour * 24
		default:
			continue
		}
		if val, err := strconv.ParseUint(flag[:length-1], 10, 64); err != nil {
			w.Header().Set("Content-Type", "text/plain; charset=utf-8")
			w.WriteHeader(http.StatusBadRequest)
			return
		} else {
			lb.showRecent(w, time.Duration(val)*unit, recentFirst)
			return
		}
	}
	w.Header().Set("Content-Type", "text/plain; charset=utf-8")
	w.WriteHeader(http.StatusBadRequest)
}

func (lb *LogBuffer) showRecent(w io.Writer, duration time.Duration,
	recentFirst bool) {
}

func (lb *LogBuffer) writeHtml(writer io.Writer) {
	fmt.Fprintln(writer, `<a href="logs">Logs:</a><br>`)
	fmt.Fprintln(writer, "
")
	lb.Dump(writer, "", "", false)
	fmt.Fprintln(writer, "
")
}

func parseQuery(rawQuery string) (map[string]struct{}, map[string]string) {
	flags := make(map[string]struct{})
	table := make(map[string]string)
	for _, pair := range strings.Split(rawQuery, "&") {
		splitPair := strings.Split(pair, "=")
		if len(splitPair) == 1 {
			flags[splitPair[0]] = struct{}{}
		}
		if len(splitPair) == 2 {
			table[splitPair[0]] = splitPair[1]
		}
	}
	return flags, table
}
<|endoftext|>


<commit_before>// Package query implements querying through the Salesforce data service.
// It is a non-trivial problem because:
//
// - The API response shape is weird
// - Subqueries can themselves be paginated.
//
// The first is generally manageable and nothing so out of the ordinary.
// The second however is a big problem from an API/interface perspective.
// It doesn't make sense to paginate subqueries to a caller,
// because they probably don't have the context to use the subquery.
// It's also just incredibly difficult to program the caller-based pagination of subqueries.
//
// So we choose to hide the subquery pagination as an implementation detail.
// Again, not ideal, but probably the best choice.
//
// We do, however, still support top-level query pagination.
package query

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

type result struct {
	Done           bool
	TotalSize      int
	NextRecordsUrl string
	Records        []map[string]interface{}
}

func recordsFromMapRecords(q queryer, records []map[string]interface{}) ([]Record, error) {
	res := make([]Record, len(records))
	for i, rec := range records {
		rec, err := recordFromMapRecord(q, rec)
		if err != nil {
			return nil, err
		}
		res[i] = rec
	}
	return res, nil
}

func recordFromMapRecord(q queryer, rec map[string]interface{}) (Record, error) {
	res := Record{
		Raw:    rec,
		Fields: make(map[string]interface{}, len(rec)-1),
	}
	for k, v := range rec {
		if k == "attributes" {
			attrs := v.(map[string]interface{})
			res.Attributes.Type = attrs["type"].(string)
			res.Attributes.Url = attrs["url"].(string)
		} else if vm, ok := v.(map[string]interface{}); ok && vm["records"] != nil {
			if _, isRelationList := vm["done"]; isRelationList {
				subrecs, err := recordsFromMapRecords(q, toStrIfaceMapSlice(vm["records"].([]interface{})))
				if err != nil {
					return res, err
				}
				if !vm["done"].(bool) {
					err := q.getAllPages(vm["nextRecordsUrl"].(string), func(records []Record) bool {
						subrecs = append(subrecs, records...)
						return true
					})
					if err != nil {
						return res, err
					}
				}
				res.Fields[k] = subrecs
			} else {
				rec, err := recordFromMapRecord(q, vm)
				if err != nil {
					return res, err
				}
				res.Fields[k] = rec
			}
		} else {
			res.Fields[k] = v
		}
	}
	return res, nil
}

type Record struct {
	Attributes struct {
		Type string
		Url  string
	}
	Fields map[string]interface{}
	Raw    map[string]interface{}
}

type Options struct {
	apiVersion  string
	cmd         string
	tooling     bool
	instanceUrl string
	tail        string
	querystring string
	httpGet     func(string) ([]byte, error)
}

type PageCallback func([]Record) bool

func (o Options) UrlTail() string {
	tail := o.tail
	if tail == "" {
		cmd := o.cmd
		if o.tooling {
			cmd = "tooling/" + cmd
		}
		tail = fmt.Sprintf("/services/data/%s/%s", o.apiVersion, cmd)
	}
	query := ""
	if o.querystring != "" {
		query = "?q=" + url.QueryEscape(o.querystring)
	}
	return fmt.Sprintf("%s%s", tail, query)
}

type Option func(*Options)

func All(o *Options) {
	o.cmd = "queryAll"
}

func Tooling(o *Options) {
	o.tooling = true
}

func InstanceUrl(url string) Option {
	return func(o *Options) {
		o.instanceUrl = url
	}
}

func Tail(tail string) Option {
	return func(o *Options) {
		o.tail = tail
	}
}

func ApiVersion(v string) Option {
	return func(o *Options) {
		o.apiVersion = v
	}
}

func QS(v string) Option {
	return func(o *Options) {
		o.querystring = v
	}
}

type HttpGetter func(string) ([]byte, error)

func HttpGet(f HttpGetter) Option {
	return func(o *Options) {
		o.httpGet = f
	}
}

func Query(cb PageCallback, options ...Option) error {
	opts := Options{cmd: "query"}
	for _, option := range options {
		option(&opts)
	}
	q := queryer{opts.instanceUrl, opts.httpGet}
	return q.getAllPages(opts.UrlTail(), cb)
}

type queryer struct {
	instanceUrl string
	httpGet     HttpGetter
}

func (q queryer) getAllPages(nextRecordsUrl string, cb PageCallback) error {
	done := false
	for !done {
		body, err := q.httpGet(fmt.Sprintf("%s%s", q.instanceUrl, nextRecordsUrl))
		if err != nil {
			return err
		}
		currResult := result{}
		if err := json.Unmarshal(body, &currResult); err != nil {
			return err
		}
		records, err := recordsFromMapRecords(q, currResult.Records)
		if err != nil {
			return err
		}
		getNextPage := cb(records)
		if !getNextPage {
			break
		}
		done = currResult.Done
		nextRecordsUrl = currResult.NextRecordsUrl
	}
	return nil
}

func Eager(options ...Option) ([]Record, error) {
	records := make([]Record, 0, 128)
	err := Query(func(children []Record) bool {
		records = append(records, children...)
		return true
	}, options...)
	return records, err
}

func toStrIfaceMapSlice(i2i []interface{}) []map[string]interface{} {
	res := make([]map[string]interface{}, len(i2i))
	for i, m := range i2i {
		res[i] = m.(map[string]interface{})
	}
	return res
}
<commit_msg>Fix Aggregate Queries<commit_after>// Package query implements querying through the Salesforce data service.
// It is a non-trivial problem because:
//
// - The API response shape is weird
// - Subqueries can themselves be paginated.
//
// The first is generally manageable and nothing so out of the ordinary.
// The second however is a big problem from an API/interface perspective.
// It doesn't make sense to paginate subqueries to a caller,
// because they probably don't have the context to use the subquery.
// It's also just incredibly difficult to program the caller-based pagination of subqueries.
//
// So we choose to hide the subquery pagination as an implementation detail.
// Again, not ideal, but probably the best choice.
//
// We do, however, still support top-level query pagination.
package query

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

type result struct {
	Done           bool
	TotalSize      int
	NextRecordsUrl string
	Records        []map[string]interface{}
}

func recordsFromMapRecords(q queryer, records []map[string]interface{}) ([]Record, error) {
	res := make([]Record, len(records))
	for i, rec := range records {
		rec, err := recordFromMapRecord(q, rec)
		if err != nil {
			return nil, err
		}
		res[i] = rec
	}
	return res, nil
}

func recordFromMapRecord(q queryer, rec map[string]interface{}) (Record, error) {
	res := Record{
		Raw:    rec,
		Fields: make(map[string]interface{}, len(rec)-1),
	}
	for k, v := range rec {
		if k == "attributes" {
			attrs := v.(map[string]interface{})
			res.Attributes.Type = attrs["type"].(string)
			res.Attributes.Url, _ = attrs["url"].(string)
		} else if vm, ok := v.(map[string]interface{}); ok && vm["records"] != nil {
			if _, isRelationList := vm["done"]; isRelationList {
				subrecs, err := recordsFromMapRecords(q, toStrIfaceMapSlice(vm["records"].([]interface{})))
				if err != nil {
					return res, err
				}
				if !vm["done"].(bool) {
					err := q.getAllPages(vm["nextRecordsUrl"].(string), func(records []Record) bool {
						subrecs = append(subrecs, records...)
						return true
					})
					if err != nil {
						return res, err
					}
				}
				res.Fields[k] = subrecs
			} else {
				rec, err := recordFromMapRecord(q, vm)
				if err != nil {
					return res, err
				}
				res.Fields[k] = rec
			}
		} else {
			res.Fields[k] = v
		}
	}
	return res, nil
}

type Record struct {
	Attributes struct {
		Type string
		Url  string
	}
	Fields map[string]interface{}
	Raw    map[string]interface{}
}

type Options struct {
	apiVersion  string
	cmd         string
	tooling     bool
	instanceUrl string
	tail        string
	querystring string
	httpGet     func(string) ([]byte, error)
}

type PageCallback func([]Record) bool

func (o Options) UrlTail() string {
	tail := o.tail
	if tail == "" {
		cmd := o.cmd
		if o.tooling {
			cmd = "tooling/" + cmd
		}
		tail = fmt.Sprintf("/services/data/%s/%s", o.apiVersion, cmd)
	}
	query := ""
	if o.querystring != "" {
		query = "?q=" + url.QueryEscape(o.querystring)
	}
	return fmt.Sprintf("%s%s", tail, query)
}

type Option func(*Options)

func All(o *Options) {
	o.cmd = "queryAll"
}

func Tooling(o *Options) {
	o.tooling = true
}

func InstanceUrl(url string) Option {
	return func(o *Options) {
		o.instanceUrl = url
	}
}

func Tail(tail string) Option {
	return func(o *Options) {
		o.tail = tail
	}
}

func ApiVersion(v string) Option {
	return func(o *Options) {
		o.apiVersion = v
	}
}

func QS(v string) Option {
	return func(o *Options) {
		o.querystring = v
	}
}

type HttpGetter func(string) ([]byte, error)

func HttpGet(f HttpGetter) Option {
	return func(o *Options) {
		o.httpGet = f
	}
}

func Query(cb PageCallback, options ...Option) error {
	opts := Options{cmd: "query"}
	for _, option := range options {
		option(&opts)
	}
	q := queryer{opts.instanceUrl, opts.httpGet}
	return q.getAllPages(opts.UrlTail(), cb)
}

type queryer struct {
	instanceUrl string
	httpGet     HttpGetter
}

func (q queryer) getAllPages(nextRecordsUrl string, cb PageCallback) error {
	done := false
	for !done {
		body, err := q.httpGet(fmt.Sprintf("%s%s", q.instanceUrl, nextRecordsUrl))
		if err != nil {
			return err
		}
		currResult := result{}
		if err := json.Unmarshal(body, &currResult); err != nil {
			return err
		}
		records, err := recordsFromMapRecords(q, currResult.Records)
		if err != nil {
			return err
		}
		getNextPage := cb(records)
		if !getNextPage {
			break
		}
		done = currResult.Done
		nextRecordsUrl = currResult.NextRecordsUrl
	}
	return nil
}

func Eager(options ...Option) ([]Record, error) {
	records := make([]Record, 0, 128)
	err := Query(func(children []Record) bool {
		records = append(records, children...)
		return true
	}, options...)
	return records, err
}

func toStrIfaceMapSlice(i2i []interface{}) []map[string]interface{} {
	res := make([]map[string]interface{}, len(i2i))
	for i, m := range i2i {
		res[i] = m.(map[string]interface{})
	}
	return res
}
<|endoftext|>


<commit_before>// +build !apparmor !linux

package apparmor

func IsEnabled() bool {
	return false
}

func ApplyProfile(name string) error {
	return nil
}
<commit_msg>Adding error conditions when apparmor disabled<commit_after>// +build !apparmor !linux

package apparmor

import (
	"errors"
)

var ErrApparmorNotEnabled = errors.New("apparmor: config provided but apparmor not supported")

func IsEnabled() bool {
	return false
}

func ApplyProfile(name string) error {
	if name != "" {
		return ErrApparmorNotEnabled
	}
	return nil
}
<|endoftext|>


<commit_before>package ifplugin

import (
	"github.com/ligato/cn-infra/datasync"
	"github.com/ligato/cn-infra/health/statuscheck"
	"github.com/ligato/cn-infra/health/statuscheck/model/status"

	"github.com/ligato/vpp-agent/api/models/vpp"
	interfaces "github.com/ligato/vpp-agent/api/models/vpp/interfaces"
)

// watchStatusEvents watches for resync event of interface state data.
func (p *IfPlugin) watchStatusEvents() {
	defer p.wg.Done()
	p.Log.Debug("Start watching interface state events")

	for {
		select {
		case e := <-p.resyncStatusChan:
			p.onStatusResyncEvent(e)

		case <-p.ctx.Done():
			p.Log.Debug("Stop watching interface state events")
			return
		}
	}
}

// onStatusResyncEvent is triggered during resync of interface state data
func (p *IfPlugin) onStatusResyncEvent(e datasync.ResyncEvent) {
	var wasError error
	for key, vals := range e.GetValues() {
		p.Log.Debugf("trying to delete obsolete status for key %v begin ", key)
		var keys []string
		for {
			x, stop := vals.GetNext()
			if stop {
				break
			}
			keys = append(keys, x.GetKey())
		}
		if len(keys) > 0 {
			err := p.resyncIfStateEvents(keys)
			if err != nil {
				wasError = err
			}
		}
	}
	e.Done(wasError)
}

// resyncIfStateEvents deletes obsolete operation status of network interfaces in DB.
func (p *IfPlugin) resyncIfStateEvents(keys []string) error {
	p.publishLock.Lock()
	defer p.publishLock.Unlock()

	for _, key := range keys {
		ifaceName, isIntfKey := interfaces.ModelInterface.ParseKey(key)
		if !isIntfKey {
			continue
		}

		_, found := p.intfIndex.LookupByName(ifaceName)
		if !found {
			err := p.PublishStatistics.Put(key, nil /*means delete*/)
			if err != nil {
				return err
			}
			p.Log.Debugf("Obsolete interface status for %v deleted", key)
		} else {
			p.Log.WithField("ifaceName", ifaceName).Debug("interface status is needed")
		}
	}

	return nil
}

// publishIfStateEvents goroutine is used to watch interface state notifications
// that are propagated to Messaging topic.
func (p *IfPlugin) publishIfStateEvents() {
	defer p.wg.Done()

	// store last errors to prevent repeating
	var lastPublishErr error
	var lastNotifErr error

	for {
		select {
		case ifState := <-p.ifStateChan:
			p.publishLock.Lock()
			key := interfaces.InterfaceStateKey(ifState.State.Name)

			if p.PublishStatistics != nil {
				err := p.PublishStatistics.Put(key, ifState.State)
				if err != nil {
					if lastPublishErr == nil || lastPublishErr.Error() != err.Error() {
						p.Log.Error(err)
					}
				}
				lastPublishErr = err
			}

			// Marshall data into JSON & send kafka message.
			if p.NotifyStates != nil && ifState.Type == interfaces.InterfaceNotification_UPDOWN {
				err := p.NotifyStates.Put(key, ifState.State)
				if err != nil {
					if lastNotifErr == nil || lastNotifErr.Error() != err.Error() {
						p.Log.Error(err)
					}
				}
				lastNotifErr = err
			}

			// Send interface state data to global agent status
			if p.statusCheckReg {
				p.StatusCheck.ReportStateChangeWithMeta(p.PluginName, statuscheck.OK, nil, &status.InterfaceStats_Interface{
					InternalName: ifState.State.InternalName,
					Index:        ifState.State.IfIndex,
					Status:       ifState.State.AdminStatus.String(),
					MacAddress:   ifState.State.PhysAddress,
				})
			}

			if p.PushNotification != nil &&
				ifState.Type == interfaces.InterfaceNotification_UPDOWN ||
				ifState.State.OperStatus == interfaces.InterfaceState_DELETED {
				p.PushNotification(&vpp.Notification{
					Interface: ifState,
				})
			}

			p.publishLock.Unlock()

		case <-p.ctx.Done():
			// Stop watching for state data updates.
			return
		}
	}
}
<commit_msg>Fix vpp-agent state publish nil check (vendor)<commit_after>package ifplugin

import (
	"github.com/ligato/cn-infra/datasync"
	"github.com/ligato/cn-infra/health/statuscheck"
	"github.com/ligato/cn-infra/health/statuscheck/model/status"

	"github.com/ligato/vpp-agent/api/models/vpp"
	interfaces "github.com/ligato/vpp-agent/api/models/vpp/interfaces"
)

// watchStatusEvents watches for resync event of interface state data.
func (p *IfPlugin) watchStatusEvents() {
	defer p.wg.Done()
	p.Log.Debug("Start watching interface state events")

	for {
		select {
		case e := <-p.resyncStatusChan:
			p.onStatusResyncEvent(e)

		case <-p.ctx.Done():
			p.Log.Debug("Stop watching interface state events")
			return
		}
	}
}

// onStatusResyncEvent is triggered during resync of interface state data
func (p *IfPlugin) onStatusResyncEvent(e datasync.ResyncEvent) {
	var wasError error
	for key, vals := range e.GetValues() {
		p.Log.Debugf("trying to delete obsolete status for key %v begin ", key)
		var keys []string
		for {
			x, stop := vals.GetNext()
			if stop {
				break
			}
			keys = append(keys, x.GetKey())
		}
		if len(keys) > 0 {
			err := p.resyncIfStateEvents(keys)
			if err != nil {
				wasError = err
			}
		}
	}
	e.Done(wasError)
}

// resyncIfStateEvents deletes obsolete operation status of network interfaces in DB.
func (p *IfPlugin) resyncIfStateEvents(keys []string) error {
	p.publishLock.Lock()
	defer p.publishLock.Unlock()

	for _, key := range keys {
		ifaceName, isIntfKey := interfaces.ModelInterface.ParseKey(key)
		if !isIntfKey {
			continue
		}

		_, found := p.intfIndex.LookupByName(ifaceName)
		if !found {
			err := p.PublishStatistics.Put(key, nil /*means delete*/)
			if err != nil {
				return err
			}
			p.Log.Debugf("Obsolete interface status for %v deleted", key)
		} else {
			p.Log.WithField("ifaceName", ifaceName).Debug("interface status is needed")
		}
	}

	return nil
}

// publishIfStateEvents goroutine is used to watch interface state notifications
// that are propagated to Messaging topic.
func (p *IfPlugin) publishIfStateEvents() {
	defer p.wg.Done()

	// store last errors to prevent repeating
	var lastPublishErr error
	var lastNotifErr error

	for {
		select {
		case ifState := <-p.ifStateChan:
			p.publishLock.Lock()
			key := interfaces.InterfaceStateKey(ifState.State.Name)

			if p.PublishStatistics != nil {
				err := p.PublishStatistics.Put(key, ifState.State)
				if err != nil {
					if lastPublishErr == nil || lastPublishErr.Error() != err.Error() {
						p.Log.Error(err)
					}
				}
				lastPublishErr = err
			}

			// Marshall data into JSON & send kafka message.
			if p.NotifyStates != nil && ifState.Type == interfaces.InterfaceNotification_UPDOWN {
				err := p.NotifyStates.Put(key, ifState.State)
				if err != nil {
					if lastNotifErr == nil || lastNotifErr.Error() != err.Error() {
						p.Log.Error(err)
					}
				}
				lastNotifErr = err
			}

			// Send interface state data to global agent status
			if p.statusCheckReg {
				p.StatusCheck.ReportStateChangeWithMeta(p.PluginName, statuscheck.OK, nil, &status.InterfaceStats_Interface{
					InternalName: ifState.State.InternalName,
					Index:        ifState.State.IfIndex,
					Status:       ifState.State.AdminStatus.String(),
					MacAddress:   ifState.State.PhysAddress,
				})
			}

			if p.PushNotification != nil &&
				(ifState.Type == interfaces.InterfaceNotification_UPDOWN ||
					ifState.State.OperStatus == interfaces.InterfaceState_DELETED) {
				p.PushNotification(&vpp.Notification{
					Interface: ifState,
				})
			}

			p.publishLock.Unlock()

		case <-p.ctx.Done():
			// Stop watching for state data updates.
			return
		}
	}
}
<|endoftext|>


<commit_before>// Copyright (c) 2012 The gocql Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//This file will be the future home for more policies
package gocql

//RetryableQuery is an interface that represents a query or batch statement that
//exposes the correct functions for the retry policy logic to evaluate correctly.
type RetryableQuery interface {
	Attempts() int
}

// RetryPolicy is the interface that is used by gocql to determine if a query
// can be retried based on the policy provided by the using application.
// See SimpleRetryPolicy for an example of how to create a custom policy.
type RetryPolicy interface {
	Attempt(RetryableQuery) bool
}

// SimpleRetryPolicy is just a simple number of retries logic.
type SimpleRetryPolicy struct {
	NumRetries int //Number of times to retry a query
}

//Attempt tells gocql to attempt the query again.
func (s *SimpleRetryPolicy) Attempt(q RetryableQuery) bool {
	return q.Attempts() <= s.NumRetries
}
<commit_msg>Updated godocs at an attempt to improve understanding of said documentation.<commit_after>// Copyright (c) 2012 The gocql Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//This file will be the future home for more policies
package gocql

//RetryableQuery is an interface that represents a query or batch statement that
//exposes the correct functions for the retry policy logic to evaluate correctly.
type RetryableQuery interface {
	Attempts() int
}

// RetryPolicy interace is used by gocql to determine if a query can be attempted
// again after a retryable error has been received. The interface allows gocql
// users to implement their own logic to determine if a query can be attempted
// again.
// See SimpleRetryPolicy as an example of implementing the RetryPolicy interface.
type RetryPolicy interface {
	Attempt(RetryableQuery) bool
}

// SimpleRetryPolicy has simple logic for attempting a query a fixed number of times.
type SimpleRetryPolicy struct {
	NumRetries int //Number of times to retry a query
}

// Attempt tells gocql to attempt the query again based on query.Attemps being less
// than the NumRetries defined in the policy.
func (s *SimpleRetryPolicy) Attempt(q RetryableQuery) bool {
	return q.Attempts() <= s.NumRetries
}
<|endoftext|>