21700: Install Bundler system-wide in Rails postinst
[arvados.git] / lib / controller / localdb / container_request.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 package localdb
6
7 import (
8         "context"
9         "encoding/json"
10         "fmt"
11         "net/http"
12         "net/url"
13
14         "git.arvados.org/arvados.git/lib/dispatchcloud/scheduler"
15         "git.arvados.org/arvados.git/sdk/go/arvados"
16         "git.arvados.org/arvados.git/sdk/go/auth"
17         "git.arvados.org/arvados.git/sdk/go/httpserver"
18 )
19
20 // ContainerRequestCreate defers to railsProxy for everything except
21 // vocabulary checking.
22 func (conn *Conn) ContainerRequestCreate(ctx context.Context, opts arvados.CreateOptions) (arvados.ContainerRequest, error) {
23         conn.logActivity(ctx)
24         err := conn.checkProperties(ctx, opts.Attrs["properties"])
25         if err != nil {
26                 return arvados.ContainerRequest{}, err
27         }
28         resp, err := conn.railsProxy.ContainerRequestCreate(ctx, opts)
29         if err != nil {
30                 return resp, err
31         }
32         return resp, nil
33 }
34
35 // ContainerRequestUpdate defers to railsProxy for everything except
36 // vocabulary checking.
37 func (conn *Conn) ContainerRequestUpdate(ctx context.Context, opts arvados.UpdateOptions) (arvados.ContainerRequest, error) {
38         conn.logActivity(ctx)
39         err := conn.checkProperties(ctx, opts.Attrs["properties"])
40         if err != nil {
41                 return arvados.ContainerRequest{}, err
42         }
43         resp, err := conn.railsProxy.ContainerRequestUpdate(ctx, opts)
44         if err != nil {
45                 return resp, err
46         }
47         return resp, nil
48 }
49
50 func (conn *Conn) ContainerRequestGet(ctx context.Context, opts arvados.GetOptions) (arvados.ContainerRequest, error) {
51         conn.logActivity(ctx)
52         return conn.railsProxy.ContainerRequestGet(ctx, opts)
53 }
54
55 func (conn *Conn) ContainerRequestList(ctx context.Context, opts arvados.ListOptions) (arvados.ContainerRequestList, error) {
56         conn.logActivity(ctx)
57         return conn.railsProxy.ContainerRequestList(ctx, opts)
58 }
59
60 func (conn *Conn) ContainerRequestDelete(ctx context.Context, opts arvados.DeleteOptions) (arvados.ContainerRequest, error) {
61         conn.logActivity(ctx)
62         return conn.railsProxy.ContainerRequestDelete(ctx, opts)
63 }
64
65 func (conn *Conn) ContainerRequestContainerStatus(ctx context.Context, opts arvados.GetOptions) (arvados.ContainerStatus, error) {
66         conn.logActivity(ctx)
67         var ret arvados.ContainerStatus
68         cr, err := conn.railsProxy.ContainerRequestGet(ctx, arvados.GetOptions{UUID: opts.UUID, Select: []string{"uuid", "container_uuid", "log_uuid"}})
69         if err != nil {
70                 return ret, err
71         }
72         if cr.ContainerUUID == "" {
73                 ret.SchedulingStatus = "no container assigned"
74                 return ret, nil
75         }
76         // We use admin credentials to get the container record so we
77         // don't get an error when we're in a race with auto-retry and
78         // the container became user-unreadable since we fetched the
79         // CR above.
80         ctxRoot := auth.NewContext(ctx, &auth.Credentials{Tokens: []string{conn.cluster.SystemRootToken}})
81         ctr, err := conn.railsProxy.ContainerGet(ctxRoot, arvados.GetOptions{UUID: cr.ContainerUUID, Select: []string{"uuid", "state", "priority"}})
82         if err != nil {
83                 return ret, err
84         }
85         ret.UUID = ctr.UUID
86         ret.State = ctr.State
87         if ctr.State != arvados.ContainerStateQueued && ctr.State != arvados.ContainerStateLocked {
88                 // Scheduling status is not a thing once the container
89                 // is in running state.
90                 return ret, nil
91         }
92         var lastErr error
93         for dispatchurl := range conn.cluster.Services.DispatchCloud.InternalURLs {
94                 baseurl := url.URL(dispatchurl)
95                 apiurl, err := baseurl.Parse("/arvados/v1/dispatch/container?container_uuid=" + cr.ContainerUUID)
96                 if err != nil {
97                         lastErr = err
98                         continue
99                 }
100                 req, err := http.NewRequestWithContext(ctx, http.MethodGet, apiurl.String(), nil)
101                 if err != nil {
102                         lastErr = err
103                         continue
104                 }
105                 req.Header.Set("Authorization", "Bearer "+conn.cluster.ManagementToken)
106                 resp, err := http.DefaultClient.Do(req)
107                 if err != nil {
108                         lastErr = fmt.Errorf("error getting status from dispatcher: %w", err)
109                         continue
110                 }
111                 if resp.StatusCode == http.StatusNotFound {
112                         continue
113                 } else if resp.StatusCode != http.StatusOK {
114                         lastErr = fmt.Errorf("error getting status from dispatcher: %s", resp.Status)
115                         continue
116                 }
117                 var qent scheduler.QueueEnt
118                 err = json.NewDecoder(resp.Body).Decode(&qent)
119                 if err != nil {
120                         lastErr = err
121                         continue
122                 }
123                 ret.State = qent.Container.State // Prefer dispatcher's view of state if not equal to ctr.State
124                 ret.SchedulingStatus = qent.SchedulingStatus
125                 return ret, nil
126         }
127         if lastErr != nil {
128                 // If we got a non-nil error from a dispatchcloud
129                 // service, and the container state suggests
130                 // dispatchcloud should know about it, then we return
131                 // an error so the client knows to retry.
132                 return ret, httpserver.ErrorWithStatus(lastErr, http.StatusBadGateway)
133         }
134         // All running dispatchcloud services confirm they don't have
135         // this container (the dispatcher hasn't yet noticed it
136         // appearing in the queue) or there are no dispatchcloud
137         // services configured. Either way, all we can say is that
138         // it's queued.
139         if ctr.State == arvados.ContainerStateQueued && ctr.Priority < 1 {
140                 // If it hasn't been picked up by a dispatcher
141                 // already, it won't be -- it's just on hold.
142                 // Scheduling status does not apply.
143                 return ret, nil
144         }
145         ret.SchedulingStatus = "waiting for dispatch"
146         return ret, nil
147 }