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 }