mirror of
https://github.com/weaveworks/scope.git
synced 2026-03-04 10:41:14 +00:00
52d695c Merge pull request #77 from kinvolk/schu/fix-relative-weave-path 77aed01 Merge pull request #73 from weaveworks/mike/sched/fix-unicode-issue 7c080f4 integration/sanity_check: disable SC1090 d6d360a integration/gce.sh: update gcloud command e8def2c provisioning/setup: fix shellcheck SC2140 cc02224 integration/config: fix weave path 9c0d6a5 Fix config_management/README.md 334708c Merge pull request #75 from kinvolk/alban/external-build-1 da2505d gce.sh: template: print creation date e676854 integration tests: fix user account 8530836 host nameing: add repo name b556c0a gce.sh: fix deletion of gce instances 2ecd1c2 integration: fix GCE --zones/--zone parameter 3e863df sched: Fix unicode encoding issues 51785b5 Use rm -f and set current dir using BASH_SOURCE. f5c6d68 Merge pull request #71 from kinvolk/schu/fix-linter-warnings 0269628 Document requirement for `lint_sh` 9a3f09e Fix linter warnings efcf9d2 Merge pull request #53 from weaveworks/2647-testing-mvp d31ea57 Weave Kube playbook now works with multiple nodes. 27868dd Add GCP firewall rule for FastDP crypto. edc8bb3 Differentiated name of dev and test playbooks, to avoid confusion. efa3df7 Moved utility Ansible Yaml to library directory. fcd2769 Add shorthands to run Ansible playbooks against Terraform-provisioned virtual machines. f7946fb Add shorthands to SSH into Terraform-provisioned virtual machines. aad5c6f Mention Terraform and Ansible in README.md. dddabf0 Add Terraform output required for templates' creation. dcc7d02 Add Ansible configuration playbooks for development environments. f86481c Add Ansible configuration playbooks for Docker, K8S and Weave-Net. efedd25 Git-ignore Ansible retry files. 765c4ca Add helper functions to setup Terraform programmatically. 801dd1d Add Terraform cloud provisioning scripts. b8017e1 Install hclfmt on CircleCI. 4815e19 Git-ignore Terraform state files. 0aaebc7 Add script to generate cartesian product of dependencies of cross-version testing. 007d90a Add script to list OS images from GCP, AWS and DO. ca65cc0 Add script to list relevant versions of Go, Docker and Kubernetes. aa66f44 Scripts now source dependencies using absolute path (previously breaking make depending on current directory). 7865e86 Add -p option to parallelise lint. 36c1835 Merge pull request #69 from weaveworks/mflag 9857568 Use mflag and mflagext package from weaveworks/common. 9799112 Quote bash variable. 10a36b3 Merge pull request #67 from weaveworks/shfmt-ignore a59884f Add support for .lintignore. 03cc598 Don't lint generated protobuf code. 2b55c2d Merge pull request #66 from weaveworks/reduce-test-timeout d4e163c Make timeout a flag 49a8609 Reduce test timeout 8fa15cb Merge pull request #63 from weaveworks/test-defaults b783528 Tweak test script so it can be run on a mca git-subtree-dir: tools git-subtree-split: 52d695cc629c524ff17eb06e2de12d78701919c9
291 lines
6.7 KiB
Go
291 lines
6.7 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"os/exec"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/mgutz/ansi"
|
|
"github.com/weaveworks/common/mflag"
|
|
)
|
|
|
|
const (
|
|
defaultSchedulerHost = "positive-cocoa-90213.appspot.com"
|
|
jsonContentType = "application/json"
|
|
)
|
|
|
|
var (
|
|
start = ansi.ColorCode("black+ub")
|
|
fail = ansi.ColorCode("red+b")
|
|
succ = ansi.ColorCode("green+b")
|
|
reset = ansi.ColorCode("reset")
|
|
|
|
schedulerHost = defaultSchedulerHost
|
|
useScheduler = false
|
|
runParallel = false
|
|
verbose = false
|
|
timeout = 180 // In seconds. Three minutes ought to be enough for any test
|
|
|
|
consoleLock = sync.Mutex{}
|
|
)
|
|
|
|
type test struct {
|
|
name string
|
|
hosts int
|
|
}
|
|
|
|
type schedule struct {
|
|
Tests []string `json:"tests"`
|
|
}
|
|
|
|
type result struct {
|
|
test
|
|
errored bool
|
|
hosts []string
|
|
}
|
|
|
|
type tests []test
|
|
|
|
func (ts tests) Len() int { return len(ts) }
|
|
func (ts tests) Swap(i, j int) { ts[i], ts[j] = ts[j], ts[i] }
|
|
func (ts tests) Less(i, j int) bool {
|
|
if ts[i].hosts != ts[j].hosts {
|
|
return ts[i].hosts < ts[j].hosts
|
|
}
|
|
return ts[i].name < ts[j].name
|
|
}
|
|
|
|
func (ts *tests) pick(available int) (test, bool) {
|
|
// pick the first test that fits in the available hosts
|
|
for i, test := range *ts {
|
|
if test.hosts <= available {
|
|
*ts = append((*ts)[:i], (*ts)[i+1:]...)
|
|
return test, true
|
|
}
|
|
}
|
|
|
|
return test{}, false
|
|
}
|
|
|
|
func (t test) run(hosts []string) bool {
|
|
consoleLock.Lock()
|
|
fmt.Printf("%s>>> Running %s on %s%s\n", start, t.name, hosts, reset)
|
|
consoleLock.Unlock()
|
|
|
|
var out bytes.Buffer
|
|
|
|
cmd := exec.Command(t.name)
|
|
cmd.Env = os.Environ()
|
|
cmd.Stdout = &out
|
|
cmd.Stderr = &out
|
|
|
|
// replace HOSTS in env
|
|
for i, env := range cmd.Env {
|
|
if strings.HasPrefix(env, "HOSTS") {
|
|
cmd.Env[i] = fmt.Sprintf("HOSTS=%s", strings.Join(hosts, " "))
|
|
break
|
|
}
|
|
}
|
|
|
|
start := time.Now()
|
|
var err error
|
|
|
|
c := make(chan error, 1)
|
|
go func() { c <- cmd.Run() }()
|
|
select {
|
|
case err = <-c:
|
|
case <-time.After(time.Duration(timeout) * time.Second):
|
|
err = fmt.Errorf("timed out")
|
|
}
|
|
|
|
duration := float64(time.Now().Sub(start)) / float64(time.Second)
|
|
|
|
consoleLock.Lock()
|
|
if err != nil {
|
|
fmt.Printf("%s>>> Test %s finished after %0.1f secs with error: %v%s\n", fail, t.name, duration, err, reset)
|
|
} else {
|
|
fmt.Printf("%s>>> Test %s finished with success after %0.1f secs%s\n", succ, t.name, duration, reset)
|
|
}
|
|
if err != nil || verbose {
|
|
fmt.Print(out.String())
|
|
fmt.Println()
|
|
}
|
|
consoleLock.Unlock()
|
|
|
|
if err != nil && useScheduler {
|
|
updateScheduler(t.name, duration)
|
|
}
|
|
|
|
return err != nil
|
|
}
|
|
|
|
func updateScheduler(test string, duration float64) {
|
|
req := &http.Request{
|
|
Method: "POST",
|
|
Host: schedulerHost,
|
|
URL: &url.URL{
|
|
Opaque: fmt.Sprintf("/record/%s/%0.2f", url.QueryEscape(test), duration),
|
|
Scheme: "http",
|
|
Host: schedulerHost,
|
|
},
|
|
Close: true,
|
|
}
|
|
if resp, err := http.DefaultClient.Do(req); err != nil {
|
|
fmt.Printf("Error updating scheduler: %v\n", err)
|
|
} else {
|
|
resp.Body.Close()
|
|
}
|
|
}
|
|
|
|
func getSchedule(tests []string) ([]string, error) {
|
|
var (
|
|
userName = os.Getenv("CIRCLE_PROJECT_USERNAME")
|
|
project = os.Getenv("CIRCLE_PROJECT_REPONAME")
|
|
buildNum = os.Getenv("CIRCLE_BUILD_NUM")
|
|
testRun = userName + "-" + project + "-integration-" + buildNum
|
|
shardCount = os.Getenv("CIRCLE_NODE_TOTAL")
|
|
shardID = os.Getenv("CIRCLE_NODE_INDEX")
|
|
requestBody = &bytes.Buffer{}
|
|
)
|
|
if err := json.NewEncoder(requestBody).Encode(schedule{tests}); err != nil {
|
|
return []string{}, err
|
|
}
|
|
url := fmt.Sprintf("http://%s/schedule/%s/%s/%s", schedulerHost, testRun, shardCount, shardID)
|
|
resp, err := http.Post(url, jsonContentType, requestBody)
|
|
if err != nil {
|
|
return []string{}, err
|
|
}
|
|
var sched schedule
|
|
if err := json.NewDecoder(resp.Body).Decode(&sched); err != nil {
|
|
return []string{}, err
|
|
}
|
|
return sched.Tests, nil
|
|
}
|
|
|
|
func getTests(testNames []string) (tests, error) {
|
|
var err error
|
|
if useScheduler {
|
|
testNames, err = getSchedule(testNames)
|
|
if err != nil {
|
|
return tests{}, err
|
|
}
|
|
}
|
|
tests := tests{}
|
|
for _, name := range testNames {
|
|
parts := strings.Split(strings.TrimSuffix(name, "_test.sh"), "_")
|
|
numHosts, err := strconv.Atoi(parts[len(parts)-1])
|
|
if err != nil {
|
|
numHosts = 1
|
|
}
|
|
tests = append(tests, test{name, numHosts})
|
|
fmt.Printf("Test %s needs %d hosts\n", name, numHosts)
|
|
}
|
|
return tests, nil
|
|
}
|
|
|
|
func summary(tests, failed tests) {
|
|
if len(failed) > 0 {
|
|
fmt.Printf("%s>>> Ran %d tests, %d failed%s\n", fail, len(tests), len(failed), reset)
|
|
for _, test := range failed {
|
|
fmt.Printf("%s>>> Fail %s%s\n", fail, test.name, reset)
|
|
}
|
|
} else {
|
|
fmt.Printf("%s>>> Ran %d tests, all succeeded%s\n", succ, len(tests), reset)
|
|
}
|
|
}
|
|
|
|
func parallel(ts tests, hosts []string) bool {
|
|
testsCopy := ts
|
|
sort.Sort(sort.Reverse(ts))
|
|
resultsChan := make(chan result)
|
|
outstanding := 0
|
|
failed := tests{}
|
|
for len(ts) > 0 || outstanding > 0 {
|
|
// While we have some free hosts, try and schedule
|
|
// a test on them
|
|
for len(hosts) > 0 {
|
|
test, ok := ts.pick(len(hosts))
|
|
if !ok {
|
|
break
|
|
}
|
|
testHosts := hosts[:test.hosts]
|
|
hosts = hosts[test.hosts:]
|
|
|
|
go func() {
|
|
errored := test.run(testHosts)
|
|
resultsChan <- result{test, errored, testHosts}
|
|
}()
|
|
outstanding++
|
|
}
|
|
|
|
// Otherwise, wait for the test to finish and return
|
|
// the hosts to the pool
|
|
result := <-resultsChan
|
|
hosts = append(hosts, result.hosts...)
|
|
outstanding--
|
|
if result.errored {
|
|
failed = append(failed, result.test)
|
|
}
|
|
}
|
|
summary(testsCopy, failed)
|
|
return len(failed) > 0
|
|
}
|
|
|
|
func sequential(ts tests, hosts []string) bool {
|
|
failed := tests{}
|
|
for _, test := range ts {
|
|
if test.run(hosts) {
|
|
failed = append(failed, test)
|
|
}
|
|
}
|
|
summary(ts, failed)
|
|
return len(failed) > 0
|
|
}
|
|
|
|
func main() {
|
|
mflag.BoolVar(&useScheduler, []string{"scheduler"}, false, "Use scheduler to distribute tests across shards")
|
|
mflag.BoolVar(&runParallel, []string{"parallel"}, false, "Run tests in parallel on hosts where possible")
|
|
mflag.BoolVar(&verbose, []string{"v"}, false, "Print output from all tests (Also enabled via DEBUG=1)")
|
|
mflag.StringVar(&schedulerHost, []string{"scheduler-host"}, defaultSchedulerHost, "Hostname of scheduler.")
|
|
mflag.IntVar(&timeout, []string{"timeout"}, 180, "Max time to run one test for, in seconds")
|
|
mflag.Parse()
|
|
|
|
if len(os.Getenv("DEBUG")) > 0 {
|
|
verbose = true
|
|
}
|
|
|
|
testArgs := mflag.Args()
|
|
tests, err := getTests(testArgs)
|
|
if err != nil {
|
|
fmt.Printf("Error parsing tests: %v (%v)\n", err, testArgs)
|
|
os.Exit(1)
|
|
}
|
|
|
|
hosts := strings.Fields(os.Getenv("HOSTS"))
|
|
maxHosts := len(hosts)
|
|
if maxHosts == 0 {
|
|
fmt.Print("No HOSTS specified.\n")
|
|
os.Exit(1)
|
|
}
|
|
|
|
var errored bool
|
|
if runParallel {
|
|
errored = parallel(tests, hosts)
|
|
} else {
|
|
errored = sequential(tests, hosts)
|
|
}
|
|
|
|
if errored {
|
|
os.Exit(1)
|
|
}
|
|
}
|