github.com/ssdev-go/moby@v17.12.1-ce-rc2+incompatible/hack/integration-cli-on-swarm/host/host.go (about)

     1  package main
     2  
     3  import (
     4  	"context"
     5  	"flag"
     6  	"fmt"
     7  	"io"
     8  	"io/ioutil"
     9  	"os"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/docker/docker/api/types"
    14  	"github.com/docker/docker/api/types/filters"
    15  	"github.com/docker/docker/client"
    16  	"github.com/docker/docker/pkg/stdcopy"
    17  	"github.com/sirupsen/logrus"
    18  )
    19  
    20  const (
    21  	defaultStackName       = "integration-cli-on-swarm"
    22  	defaultVolumeName      = "integration-cli-on-swarm"
    23  	defaultMasterImageName = "integration-cli-master"
    24  	defaultWorkerImageName = "integration-cli-worker"
    25  )
    26  
    27  func main() {
    28  	rc, err := xmain()
    29  	if err != nil {
    30  		logrus.Fatalf("fatal error: %v", err)
    31  	}
    32  	os.Exit(rc)
    33  }
    34  
    35  func xmain() (int, error) {
    36  	// Should we use cobra maybe?
    37  	replicas := flag.Int("replicas", 1, "Number of worker service replica")
    38  	chunks := flag.Int("chunks", 0, "Number of test chunks executed in batch (0 == replicas)")
    39  	pushWorkerImage := flag.String("push-worker-image", "", "Push the worker image to the registry. Required for distributed execution. (empty == not to push)")
    40  	shuffle := flag.Bool("shuffle", false, "Shuffle the input so as to mitigate makespan nonuniformity")
    41  	// flags below are rarely used
    42  	randSeed := flag.Int64("rand-seed", int64(0), "Random seed used for shuffling (0 == current time)")
    43  	filtersFile := flag.String("filters-file", "", "Path to optional file composed of `-check.f` filter strings")
    44  	dryRun := flag.Bool("dry-run", false, "Dry run")
    45  	keepExecutor := flag.Bool("keep-executor", false, "Do not auto-remove executor containers, which is used for running privileged programs on Swarm")
    46  	flag.Parse()
    47  	if *chunks == 0 {
    48  		*chunks = *replicas
    49  	}
    50  	if *randSeed == int64(0) {
    51  		*randSeed = time.Now().UnixNano()
    52  	}
    53  	cli, err := client.NewEnvClient()
    54  	if err != nil {
    55  		return 1, err
    56  	}
    57  	if hasStack(cli, defaultStackName) {
    58  		logrus.Infof("Removing stack %s", defaultStackName)
    59  		removeStack(cli, defaultStackName)
    60  	}
    61  	if hasVolume(cli, defaultVolumeName) {
    62  		logrus.Infof("Removing volume %s", defaultVolumeName)
    63  		removeVolume(cli, defaultVolumeName)
    64  	}
    65  	if err = ensureImages(cli, []string{defaultWorkerImageName, defaultMasterImageName}); err != nil {
    66  		return 1, err
    67  	}
    68  	workerImageForStack := defaultWorkerImageName
    69  	if *pushWorkerImage != "" {
    70  		logrus.Infof("Pushing %s to %s", defaultWorkerImageName, *pushWorkerImage)
    71  		if err = pushImage(cli, *pushWorkerImage, defaultWorkerImageName); err != nil {
    72  			return 1, err
    73  		}
    74  		workerImageForStack = *pushWorkerImage
    75  	}
    76  	compose, err := createCompose("", cli, composeOptions{
    77  		Replicas:     *replicas,
    78  		Chunks:       *chunks,
    79  		MasterImage:  defaultMasterImageName,
    80  		WorkerImage:  workerImageForStack,
    81  		Volume:       defaultVolumeName,
    82  		Shuffle:      *shuffle,
    83  		RandSeed:     *randSeed,
    84  		DryRun:       *dryRun,
    85  		KeepExecutor: *keepExecutor,
    86  	})
    87  	if err != nil {
    88  		return 1, err
    89  	}
    90  	filters, err := filtersBytes(*filtersFile)
    91  	if err != nil {
    92  		return 1, err
    93  	}
    94  	logrus.Infof("Creating volume %s with input data", defaultVolumeName)
    95  	if err = createVolumeWithData(cli,
    96  		defaultVolumeName,
    97  		map[string][]byte{"/input": filters},
    98  		defaultMasterImageName); err != nil {
    99  		return 1, err
   100  	}
   101  	logrus.Infof("Deploying stack %s from %s", defaultStackName, compose)
   102  	defer func() {
   103  		logrus.Infof("NOTE: You may want to inspect or clean up following resources:")
   104  		logrus.Infof(" - Stack: %s", defaultStackName)
   105  		logrus.Infof(" - Volume: %s", defaultVolumeName)
   106  		logrus.Infof(" - Compose file: %s", compose)
   107  		logrus.Infof(" - Master image: %s", defaultMasterImageName)
   108  		logrus.Infof(" - Worker image: %s", workerImageForStack)
   109  	}()
   110  	if err = deployStack(cli, defaultStackName, compose); err != nil {
   111  		return 1, err
   112  	}
   113  	logrus.Infof("The log will be displayed here after some duration."+
   114  		"You can watch the live status via `docker service logs %s_worker`",
   115  		defaultStackName)
   116  	masterContainerID, err := waitForMasterUp(cli, defaultStackName)
   117  	if err != nil {
   118  		return 1, err
   119  	}
   120  	rc, err := waitForContainerCompletion(cli, os.Stdout, os.Stderr, masterContainerID)
   121  	if err != nil {
   122  		return 1, err
   123  	}
   124  	logrus.Infof("Exit status: %d", rc)
   125  	return int(rc), nil
   126  }
   127  
   128  func ensureImages(cli *client.Client, images []string) error {
   129  	for _, image := range images {
   130  		_, _, err := cli.ImageInspectWithRaw(context.Background(), image)
   131  		if err != nil {
   132  			return fmt.Errorf("could not find image %s, please run `make build-integration-cli-on-swarm`: %v",
   133  				image, err)
   134  		}
   135  	}
   136  	return nil
   137  }
   138  
   139  func filtersBytes(optionalFiltersFile string) ([]byte, error) {
   140  	var b []byte
   141  	if optionalFiltersFile == "" {
   142  		tests, err := enumerateTests(".")
   143  		if err != nil {
   144  			return b, err
   145  		}
   146  		b = []byte(strings.Join(tests, "\n") + "\n")
   147  	} else {
   148  		var err error
   149  		b, err = ioutil.ReadFile(optionalFiltersFile)
   150  		if err != nil {
   151  			return b, err
   152  		}
   153  	}
   154  	return b, nil
   155  }
   156  
   157  func waitForMasterUp(cli *client.Client, stackName string) (string, error) {
   158  	// FIXME(AkihiroSuda): it should retry until master is up, rather than pre-sleeping
   159  	time.Sleep(10 * time.Second)
   160  
   161  	fil := filters.NewArgs()
   162  	fil.Add("label", "com.docker.stack.namespace="+stackName)
   163  	// FIXME(AkihiroSuda): we should not rely on internal service naming convention
   164  	fil.Add("label", "com.docker.swarm.service.name="+stackName+"_master")
   165  	masters, err := cli.ContainerList(context.Background(), types.ContainerListOptions{
   166  		All:     true,
   167  		Filters: fil,
   168  	})
   169  	if err != nil {
   170  		return "", err
   171  	}
   172  	if len(masters) == 0 {
   173  		return "", fmt.Errorf("master not running in stack %s?", stackName)
   174  	}
   175  	return masters[0].ID, nil
   176  }
   177  
   178  func waitForContainerCompletion(cli *client.Client, stdout, stderr io.Writer, containerID string) (int64, error) {
   179  	stream, err := cli.ContainerLogs(context.Background(),
   180  		containerID,
   181  		types.ContainerLogsOptions{
   182  			ShowStdout: true,
   183  			ShowStderr: true,
   184  			Follow:     true,
   185  		})
   186  	if err != nil {
   187  		return 1, err
   188  	}
   189  	stdcopy.StdCopy(stdout, stderr, stream)
   190  	stream.Close()
   191  	resultC, errC := cli.ContainerWait(context.Background(), containerID, "")
   192  	select {
   193  	case err := <-errC:
   194  		return 1, err
   195  	case result := <-resultC:
   196  		return result.StatusCode, nil
   197  	}
   198  }