(composeFile, prefix string)
| 226 | } |
| 227 | |
| 228 | func startCluster(composeFile, prefix string) error { |
| 229 | ensureGoPathLinuxBinEnvVarSet() |
| 230 | |
| 231 | if os.Getenv("GOPATH") == "" { |
| 232 | return fmt.Errorf("GOPATH environment variable is required but not set") |
| 233 | } |
| 234 | |
| 235 | if err := ensureDgraphLinuxBinary(); err != nil { |
| 236 | return err |
| 237 | } |
| 238 | // + --project-directory so relative bind-mount sources resolve against the |
| 239 | // pristine test package dir. |
| 240 | composeArgs := ComposeFileArgs(composeFile, *baseDir) |
| 241 | upArgs := append([]string{"docker", "compose", "--compatibility"}, |
| 242 | append(composeArgs, "-p", prefix, "up", "--force-recreate", "--build", "--remove-orphans", "--detach")...) |
| 243 | |
| 244 | // docker compose `up` on a shared named volume can race on initial |
| 245 | // volume population when multiple containers using the same volume |
| 246 | // start concurrently ("failed to mkdir .../_data/<entry>: file exists"). |
| 247 | // Retry once after a full `down -v` — the second attempt finds a fresh |
| 248 | // volume and succeeds. One retry is enough in practice for this class |
| 249 | // of race; anything persistent is a real configuration error. |
| 250 | const upAttempts = 3 |
| 251 | var lastErr error |
| 252 | var cmdStderr strings.Builder |
| 253 | fmt.Printf("Bringing up cluster %s for package: %s ...\n", prefix, composeFile) |
| 254 | for attempt := 1; attempt <= upAttempts; attempt++ { |
| 255 | cmdStderr.Reset() |
| 256 | cmd := command(upArgs...) |
| 257 | cmd.Stderr = &cmdStderr |
| 258 | if err := cmd.Run(); err == nil { |
| 259 | lastErr = nil |
| 260 | break |
| 261 | } else { |
| 262 | lastErr = err |
| 263 | stderr := cmdStderr.String() |
| 264 | fmt.Printf("Bring-up attempt %d/%d failed: %v\n", attempt, upAttempts, err) |
| 265 | if stderr != "" { |
| 266 | fmt.Printf("docker compose stderr:\n%s\n", stderr) |
| 267 | } |
| 268 | if attempt < upAttempts { |
| 269 | // Tear down any partial state so the retry starts from a |
| 270 | // clean volume/network baseline. Run `down -v` to remove |
| 271 | // the volume the next attempt will recreate, then sleep |
| 272 | // briefly so docker has time to release the mount points |
| 273 | // before the next `up` tries to populate a fresh volume. |
| 274 | // Without the sleep, the retry can hit the same volume- |
| 275 | // init race the first attempt did. |
| 276 | downArgs := append([]string{"docker", "compose", "--compatibility"}, |
| 277 | append(composeArgs, "-p", prefix, "down", "-v")...) |
| 278 | downCmd := command(downArgs...) |
| 279 | downCmd.Stderr = nil |
| 280 | _ = downCmd.Run() |
| 281 | time.Sleep(2 * time.Second) |
| 282 | fmt.Printf("Retrying cluster bring-up (attempt %d/%d) after `down -v`...\n", attempt+1, upAttempts) |
| 283 | } |
| 284 | } |
| 285 | } |
no test coverage detected