1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
7 // Dispatcher service for Crunch that runs containers locally.
20 "git.arvados.org/arvados.git/lib/cmd"
21 "git.arvados.org/arvados.git/lib/config"
22 "git.arvados.org/arvados.git/sdk/go/arvados"
23 "git.arvados.org/arvados.git/sdk/go/arvadosclient"
24 "git.arvados.org/arvados.git/sdk/go/dispatch"
25 "github.com/sirupsen/logrus"
31 runningCmds map[string]*exec.Cmd
32 runningCmdsMutex sync.Mutex
33 waitGroup sync.WaitGroup
34 crunchRunCommand *string
38 baseLogger := logrus.StandardLogger()
39 if os.Getenv("DEBUG") != "" {
40 baseLogger.SetLevel(logrus.DebugLevel)
42 baseLogger.Formatter = &logrus.JSONFormatter{
43 TimestampFormat: "2006-01-02T15:04:05.000000000Z07:00",
46 flags := flag.NewFlagSet("crunch-dispatch-local", flag.ExitOnError)
48 pollInterval := flags.Int(
51 "Interval in seconds to poll for queued containers")
53 crunchRunCommand = flags.String(
55 "/usr/bin/crunch-run",
56 "Crunch command to run container")
58 getVersion := flags.Bool(
61 "Print version information and exit.")
63 if ok, code := cmd.ParseFlags(flags, os.Args[0], os.Args[1:], "", os.Stderr); !ok {
67 // Print version information if requested
69 fmt.Printf("crunch-dispatch-local %s\n", version)
73 loader := config.NewLoader(nil, baseLogger)
74 cfg, err := loader.Load()
76 fmt.Fprintf(os.Stderr, "error loading config: %s\n", err)
79 cluster, err := cfg.GetCluster("")
81 fmt.Fprintf(os.Stderr, "config error: %s\n", err)
85 logger := baseLogger.WithField("ClusterID", cluster.ClusterID)
86 logger.Printf("crunch-dispatch-local %s started", version)
88 runningCmds = make(map[string]*exec.Cmd)
90 var client arvados.Client
91 client.APIHost = cluster.Services.Controller.ExternalURL.Host
92 client.AuthToken = cluster.SystemRootToken
93 client.Insecure = cluster.TLS.Insecure
95 if client.APIHost != "" || client.AuthToken != "" {
96 // Copy real configs into env vars so [a]
97 // MakeArvadosClient() uses them, and [b] they get
98 // propagated to crunch-run via SLURM.
99 os.Setenv("ARVADOS_API_HOST", client.APIHost)
100 os.Setenv("ARVADOS_API_TOKEN", client.AuthToken)
101 os.Setenv("ARVADOS_API_HOST_INSECURE", "")
103 os.Setenv("ARVADOS_API_HOST_INSECURE", "1")
105 os.Setenv("ARVADOS_EXTERNAL_CLIENT", "")
107 logger.Warnf("Client credentials missing from config, so falling back on environment variables (deprecated).")
110 arv, err := arvadosclient.MakeArvadosClient()
112 logger.Errorf("error making Arvados client: %v", err)
117 ctx, cancel := context.WithCancel(context.Background())
119 dispatcher := dispatch.Dispatcher{
122 RunContainer: (&LocalRun{startFunc, make(chan bool, 8), ctx, cluster}).run,
123 PollPeriod: time.Duration(*pollInterval) * time.Second,
126 err = dispatcher.Run(ctx)
132 c := make(chan os.Signal, 1)
133 signal.Notify(c, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT)
135 logger.Printf("Received %s, shutting down", sig)
140 runningCmdsMutex.Lock()
141 // Finished dispatching; interrupt any crunch jobs that are still running
142 for _, cmd := range runningCmds {
143 cmd.Process.Signal(os.Interrupt)
145 runningCmdsMutex.Unlock()
147 // Wait for all running crunch jobs to complete / terminate
151 func startFunc(container arvados.Container, cmd *exec.Cmd) error {
155 type LocalRun struct {
156 startCmd func(container arvados.Container, cmd *exec.Cmd) error
157 concurrencyLimit chan bool
159 cluster *arvados.Cluster
164 // If the container is Locked, start a new crunch-run process and wait until
165 // crunch-run completes. If the priority is set to zero, set an interrupt
166 // signal to the crunch-run process.
168 // If the container is in any other state, or is not Complete/Cancelled after
169 // crunch-run terminates, mark the container as Cancelled.
170 func (lr *LocalRun) run(dispatcher *dispatch.Dispatcher,
171 container arvados.Container,
172 status <-chan arvados.Container) error {
174 uuid := container.UUID
176 if container.State == dispatch.Locked {
179 case lr.concurrencyLimit <- true:
181 case <-lr.ctx.Done():
185 defer func() { <-lr.concurrencyLimit }()
189 // Check for state updates after possibly
190 // waiting to be ready-to-run
199 defer waitGroup.Done()
201 cmd := exec.Command(*crunchRunCommand, "--runtime-engine="+lr.cluster.Containers.RuntimeEngine, uuid)
203 cmd.Stderr = os.Stderr
204 cmd.Stdout = os.Stderr
206 dispatcher.Logger.Printf("starting container %v", uuid)
208 // Add this crunch job to the list of runningCmds only if we
209 // succeed in starting crunch-run.
211 runningCmdsMutex.Lock()
212 if err := lr.startCmd(container, cmd); err != nil {
213 runningCmdsMutex.Unlock()
214 dispatcher.Logger.Warnf("error starting %q for %s: %s", *crunchRunCommand, uuid, err)
215 dispatcher.UpdateState(uuid, dispatch.Cancelled)
217 runningCmds[uuid] = cmd
218 runningCmdsMutex.Unlock()
220 // Need to wait for crunch-run to exit
221 done := make(chan struct{})
224 if _, err := cmd.Process.Wait(); err != nil {
225 dispatcher.Logger.Warnf("error while waiting for crunch job to finish for %v: %q", uuid, err)
227 dispatcher.Logger.Debugf("sending done")
237 // Interrupt the child process if priority changes to 0
238 if (c.State == dispatch.Locked || c.State == dispatch.Running) && c.Priority == 0 {
239 dispatcher.Logger.Printf("sending SIGINT to pid %d to cancel container %v", cmd.Process.Pid, uuid)
240 cmd.Process.Signal(os.Interrupt)
246 dispatcher.Logger.Printf("finished container run for %v", uuid)
248 // Remove the crunch job from runningCmds
249 runningCmdsMutex.Lock()
250 delete(runningCmds, uuid)
251 runningCmdsMutex.Unlock()
257 // If the container is not finalized, then change it to "Cancelled".
258 err := dispatcher.Arv.Get("containers", uuid, nil, &container)
260 dispatcher.Logger.Warnf("error getting final container state: %v", err)
262 if container.State == dispatch.Locked || container.State == dispatch.Running {
263 dispatcher.Logger.Warnf("after %q process termination, container state for %v is %q; updating it to %q",
264 *crunchRunCommand, uuid, container.State, dispatch.Cancelled)
265 dispatcher.UpdateState(uuid, dispatch.Cancelled)
268 // drain any subsequent status changes
272 dispatcher.Logger.Printf("finalized container %v", uuid)