This commit adds contrib/integration-cli-on-swarm/integration-cli-on-swarm.sh, which enables IT to be running in parallel, using Swarm-mode and Funker. Please refer to contrib/integration-cli-on-swarm/README.md The test takes almost 5 to 6 minutes, with 10 n1-standard-4 GCE instances. $ ./contrib/integration-cli-on-swarm/integration-cli-on-swarm.sh --push-worker-image example.gcr.io/foo/bar --replicas 30 --shuffle 2016/12/29 08:32:15 Loaded 1618 tests (30 chunks) 2016/12/29 08:32:15 Executing 30 chunks in parallel, against "integration-cli-worker" 2016/12/29 08:32:15 Executing chunk 0 (contains 54 test filters) .. 2016/12/29 08:34:34 Finished chunk 28 [1/30] with 54 test filters in 2m19.098068003s, code=0. 2016/12/29 08:34:38 Finished chunk 12 [2/30] with 54 test filters in 2m23.088569511s, code=0. 2016/12/29 08:34:48 Finished chunk 10 [3/30] with 54 test filters in 2m33.880679079s, code=0. 2016/12/29 08:34:54 Finished chunk 20 [4/30] with 54 test filters in 2m39.973747028s, code=0. 2016/12/29 08:35:11 Finished chunk 18 [5/30] with 54 test filters in 2m56.28384361s, code=0. 2016/12/29 08:35:11 Finished chunk 29 [6/30] with 52 test filters in 2m56.54047088s, code=0. 2016/12/29 08:35:15 Finished chunk 1 [7/30] with 54 test filters in 3m0.285044426s, code=0. 2016/12/29 08:35:22 Finished chunk 6 [8/30] with 54 test filters in 3m7.211775338s, code=0. 2016/12/29 08:35:24 Finished chunk 25 [9/30] with 54 test filters in 3m9.938413009s, code=0. 2016/12/29 08:35:30 Finished chunk 27 [10/30] with 54 test filters in 3m15.219834368s, code=0. 2016/12/29 08:35:36 Finished chunk 9 [11/30] with 54 test filters in 3m21.615434162s, code=0. 2016/12/29 08:35:41 Finished chunk 13 [12/30] with 54 test filters in 3m26.576907401s, code=0. 2016/12/29 08:35:45 Finished chunk 17 [13/30] with 54 test filters in 3m30.290752537s, code=0. 2016/12/29 08:35:53 Finished chunk 2 [14/30] with 54 test filters in 3m38.148423321s, code=0. 2016/12/29 08:35:55 Finished chunk 24 [15/30] with 54 test filters in 3m40.09669137s, code=0. 2016/12/29 08:35:57 Finished chunk 8 [16/30] with 54 test filters in 3m42.299945108s, code=0. 2016/12/29 08:35:57 Finished chunk 22 [17/30] with 54 test filters in 3m42.946558809s, code=0. 2016/12/29 08:35:59 Finished chunk 23 [18/30] with 54 test filters in 3m44.232557165s, code=0. 2016/12/29 08:36:02 Finished chunk 3 [19/30] with 54 test filters in 3m47.112051358s, code=0. 2016/12/29 08:36:11 Finished chunk 15 [20/30] with 54 test filters in 3m56.340656645s, code=0. 2016/12/29 08:36:11 Finished chunk 11 [21/30] with 54 test filters in 3m56.882401231s, code=0. 2016/12/29 08:36:22 Finished chunk 19 [22/30] with 54 test filters in 4m7.551093516s, code=0. 2016/12/29 08:36:23 Finished chunk 21 [23/30] with 54 test filters in 4m8.221093446s, code=0. 2016/12/29 08:36:25 Finished chunk 16 [24/30] with 54 test filters in 4m10.450451705s, code=0. 2016/12/29 08:36:27 Finished chunk 5 [25/30] with 54 test filters in 4m12.162272692s, code=0. 2016/12/29 08:36:28 Finished chunk 14 [26/30] with 54 test filters in 4m13.977801031s, code=0. 2016/12/29 08:36:29 Finished chunk 0 [27/30] with 54 test filters in 4m14.34086812s, code=0. 2016/12/29 08:36:49 Finished chunk 26 [28/30] with 54 test filters in 4m34.437085539s, code=0. 2016/12/29 08:37:14 Finished chunk 7 [29/30] with 54 test filters in 4m59.22902721s, code=0. 2016/12/29 08:37:20 Finished chunk 4 [30/30] with 54 test filters in 5m5.103469214s, code=0. 2016/12/29 08:37:20 Executed 30 chunks in 5m5.104379119s. PASS: 30, FAIL: 0. Signed-off-by: Akihiro Suda <suda.akihiro@lab.ntt.co.jp> Upstream-commit: 28797019b3f0eb288c4b201237cb992ec148dd4c Component: engine
191 lines
5.6 KiB
Go
191 lines
5.6 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/Sirupsen/logrus"
|
|
"github.com/docker/docker/api/types"
|
|
"github.com/docker/docker/api/types/filters"
|
|
"github.com/docker/docker/client"
|
|
"github.com/docker/docker/pkg/stdcopy"
|
|
)
|
|
|
|
const (
|
|
defaultStackName = "integration-cli-on-swarm"
|
|
defaultVolumeName = "integration-cli-on-swarm"
|
|
defaultMasterImageName = "integration-cli-master"
|
|
defaultWorkerImageName = "integration-cli-worker"
|
|
)
|
|
|
|
func main() {
|
|
if err := xmain(); err != nil {
|
|
logrus.Fatalf("fatal error: %v", err)
|
|
}
|
|
}
|
|
|
|
// xmain can call os.Exit()
|
|
func xmain() error {
|
|
// Should we use cobra maybe?
|
|
replicas := flag.Int("replicas", 1, "Number of worker service replica")
|
|
chunks := flag.Int("chunks", 0, "Number of test chunks executed in batch (0 == replicas)")
|
|
pushWorkerImage := flag.String("push-worker-image", "", "Push the worker image to the registry. Required for distribuetd execution. (empty == not to push)")
|
|
shuffle := flag.Bool("shuffle", false, "Shuffle the input so as to mitigate makespan nonuniformity")
|
|
// flags below are rarely used
|
|
randSeed := flag.Int64("rand-seed", int64(0), "Random seed used for shuffling (0 == curent time)")
|
|
filtersFile := flag.String("filters-file", "", "Path to optional file composed of `-check.f` filter strings")
|
|
dryRun := flag.Bool("dry-run", false, "Dry run")
|
|
flag.Parse()
|
|
if *chunks == 0 {
|
|
*chunks = *replicas
|
|
}
|
|
if *randSeed == int64(0) {
|
|
*randSeed = time.Now().UnixNano()
|
|
}
|
|
cli, err := client.NewEnvClient()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if hasStack(cli, defaultStackName) {
|
|
logrus.Infof("Removing stack %s", defaultStackName)
|
|
removeStack(cli, defaultStackName)
|
|
}
|
|
if hasVolume(cli, defaultVolumeName) {
|
|
logrus.Infof("Removing volume %s", defaultVolumeName)
|
|
removeVolume(cli, defaultVolumeName)
|
|
}
|
|
if err = ensureImages(cli, []string{defaultWorkerImageName, defaultMasterImageName}); err != nil {
|
|
return err
|
|
}
|
|
workerImageForStack := defaultWorkerImageName
|
|
if *pushWorkerImage != "" {
|
|
logrus.Infof("Pushing %s to %s", defaultWorkerImageName, *pushWorkerImage)
|
|
if err = pushImage(cli, *pushWorkerImage, defaultWorkerImageName); err != nil {
|
|
return err
|
|
}
|
|
workerImageForStack = *pushWorkerImage
|
|
}
|
|
compose, err := createCompose("", cli, composeOptions{
|
|
Replicas: *replicas,
|
|
Chunks: *chunks,
|
|
MasterImage: defaultMasterImageName,
|
|
WorkerImage: workerImageForStack,
|
|
Volume: defaultVolumeName,
|
|
Shuffle: *shuffle,
|
|
RandSeed: *randSeed,
|
|
DryRun: *dryRun,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
filters, err := filtersBytes(*filtersFile)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
logrus.Infof("Creating volume %s with input data", defaultVolumeName)
|
|
if err = createVolumeWithData(cli,
|
|
defaultVolumeName,
|
|
map[string][]byte{"/input": filters},
|
|
defaultMasterImageName); err != nil {
|
|
return err
|
|
}
|
|
logrus.Infof("Deploying stack %s from %s", defaultStackName, compose)
|
|
defer func() {
|
|
logrus.Infof("NOTE: You may want to inspect or clean up following resources:")
|
|
logrus.Infof(" - Stack: %s", defaultStackName)
|
|
logrus.Infof(" - Volume: %s", defaultVolumeName)
|
|
logrus.Infof(" - Compose file: %s", compose)
|
|
logrus.Infof(" - Master image: %s", defaultMasterImageName)
|
|
logrus.Infof(" - Worker image: %s", workerImageForStack)
|
|
}()
|
|
if err = deployStack(cli, defaultStackName, compose); err != nil {
|
|
return err
|
|
}
|
|
logrus.Infof("The log will be displayed here after some duration."+
|
|
"You can watch the live status via `docker service logs %s_worker`",
|
|
defaultStackName)
|
|
masterContainerID, err := waitForMasterUp(cli, defaultStackName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
rc, err := waitForContainerCompletion(cli, os.Stdout, os.Stderr, masterContainerID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
logrus.Infof("Exit status: %d", rc)
|
|
os.Exit(int(rc))
|
|
return nil
|
|
}
|
|
|
|
func ensureImages(cli *client.Client, images []string) error {
|
|
for _, image := range images {
|
|
_, _, err := cli.ImageInspectWithRaw(context.Background(), image)
|
|
if err != nil {
|
|
return fmt.Errorf("could not find image %s, please run `make build-integration-cli-on-swarm`: %v",
|
|
image, err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func filtersBytes(optionalFiltersFile string) ([]byte, error) {
|
|
var b []byte
|
|
if optionalFiltersFile == "" {
|
|
tests, err := enumerateTests(".")
|
|
if err != nil {
|
|
return b, err
|
|
}
|
|
b = []byte(strings.Join(tests, "\n") + "\n")
|
|
} else {
|
|
var err error
|
|
b, err = ioutil.ReadFile(optionalFiltersFile)
|
|
if err != nil {
|
|
return b, err
|
|
}
|
|
}
|
|
return b, nil
|
|
}
|
|
|
|
func waitForMasterUp(cli *client.Client, stackName string) (string, error) {
|
|
// FIXME(AkihiroSuda): it should retry until master is up, rather than pre-sleeping
|
|
time.Sleep(10 * time.Second)
|
|
|
|
fil := filters.NewArgs()
|
|
fil.Add("label", "com.docker.stack.namespace="+stackName)
|
|
// FIXME(AkihiroSuda): we should not rely on internal service naming convention
|
|
fil.Add("label", "com.docker.swarm.service.name="+stackName+"_master")
|
|
masters, err := cli.ContainerList(context.Background(), types.ContainerListOptions{
|
|
All: true,
|
|
Filters: fil,
|
|
})
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if len(masters) == 0 {
|
|
return "", fmt.Errorf("master not running in stack %s?", stackName)
|
|
}
|
|
return masters[0].ID, nil
|
|
}
|
|
|
|
func waitForContainerCompletion(cli *client.Client, stdout, stderr io.Writer, containerID string) (int64, error) {
|
|
stream, err := cli.ContainerLogs(context.Background(),
|
|
containerID,
|
|
types.ContainerLogsOptions{
|
|
ShowStdout: true,
|
|
ShowStderr: true,
|
|
Follow: true,
|
|
})
|
|
if err != nil {
|
|
return 1, err
|
|
}
|
|
stdcopy.StdCopy(stdout, stderr, stream)
|
|
stream.Close()
|
|
return cli.ContainerWait(context.Background(), containerID)
|
|
}
|