14287: Add debug logs.
[arvados.git] / lib / controller / router / router.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 package router
6
7 import (
8         "context"
9         "fmt"
10         "net/http"
11         "strings"
12
13         "git.curoverse.com/arvados.git/lib/controller/federation"
14         "git.curoverse.com/arvados.git/sdk/go/arvados"
15         "git.curoverse.com/arvados.git/sdk/go/auth"
16         "git.curoverse.com/arvados.git/sdk/go/ctxlog"
17         "github.com/julienschmidt/httprouter"
18         "github.com/sirupsen/logrus"
19 )
20
21 type router struct {
22         mux *httprouter.Router
23         fed federation.Interface
24 }
25
26 func New(cluster *arvados.Cluster) *router {
27         rtr := &router{
28                 mux: httprouter.New(),
29                 fed: federation.New(cluster),
30         }
31         rtr.addRoutes(cluster)
32         return rtr
33 }
34
35 func (rtr *router) addRoutes(cluster *arvados.Cluster) {
36         for _, route := range []struct {
37                 endpoint    arvados.APIEndpoint
38                 defaultOpts func() interface{}
39                 exec        func(ctx context.Context, opts interface{}) (interface{}, error)
40         }{
41                 {
42                         arvados.EndpointCollectionCreate,
43                         func() interface{} { return &arvados.CreateOptions{} },
44                         func(ctx context.Context, opts interface{}) (interface{}, error) {
45                                 return rtr.fed.CollectionCreate(ctx, *opts.(*arvados.CreateOptions))
46                         },
47                 },
48                 {
49                         arvados.EndpointCollectionUpdate,
50                         func() interface{} { return &arvados.UpdateOptions{} },
51                         func(ctx context.Context, opts interface{}) (interface{}, error) {
52                                 return rtr.fed.CollectionUpdate(ctx, *opts.(*arvados.UpdateOptions))
53                         },
54                 },
55                 {
56                         arvados.EndpointCollectionGet,
57                         func() interface{} { return &arvados.GetOptions{} },
58                         func(ctx context.Context, opts interface{}) (interface{}, error) {
59                                 return rtr.fed.CollectionGet(ctx, *opts.(*arvados.GetOptions))
60                         },
61                 },
62                 {
63                         arvados.EndpointCollectionList,
64                         func() interface{} { return &arvados.ListOptions{Limit: -1} },
65                         func(ctx context.Context, opts interface{}) (interface{}, error) {
66                                 return rtr.fed.CollectionList(ctx, *opts.(*arvados.ListOptions))
67                         },
68                 },
69                 {
70                         arvados.EndpointCollectionProvenance,
71                         func() interface{} { return &arvados.GetOptions{} },
72                         func(ctx context.Context, opts interface{}) (interface{}, error) {
73                                 return rtr.fed.CollectionProvenance(ctx, *opts.(*arvados.GetOptions))
74                         },
75                 },
76                 {
77                         arvados.EndpointCollectionUsedBy,
78                         func() interface{} { return &arvados.GetOptions{} },
79                         func(ctx context.Context, opts interface{}) (interface{}, error) {
80                                 return rtr.fed.CollectionUsedBy(ctx, *opts.(*arvados.GetOptions))
81                         },
82                 },
83                 {
84                         arvados.EndpointCollectionDelete,
85                         func() interface{} { return &arvados.DeleteOptions{} },
86                         func(ctx context.Context, opts interface{}) (interface{}, error) {
87                                 return rtr.fed.CollectionDelete(ctx, *opts.(*arvados.DeleteOptions))
88                         },
89                 },
90                 {
91                         arvados.EndpointContainerCreate,
92                         func() interface{} { return &arvados.CreateOptions{} },
93                         func(ctx context.Context, opts interface{}) (interface{}, error) {
94                                 return rtr.fed.ContainerCreate(ctx, *opts.(*arvados.CreateOptions))
95                         },
96                 },
97                 {
98                         arvados.EndpointContainerUpdate,
99                         func() interface{} { return &arvados.UpdateOptions{} },
100                         func(ctx context.Context, opts interface{}) (interface{}, error) {
101                                 return rtr.fed.ContainerUpdate(ctx, *opts.(*arvados.UpdateOptions))
102                         },
103                 },
104                 {
105                         arvados.EndpointContainerGet,
106                         func() interface{} { return &arvados.GetOptions{} },
107                         func(ctx context.Context, opts interface{}) (interface{}, error) {
108                                 return rtr.fed.ContainerGet(ctx, *opts.(*arvados.GetOptions))
109                         },
110                 },
111                 {
112                         arvados.EndpointContainerList,
113                         func() interface{} { return &arvados.ListOptions{Limit: -1} },
114                         func(ctx context.Context, opts interface{}) (interface{}, error) {
115                                 return rtr.fed.ContainerList(ctx, *opts.(*arvados.ListOptions))
116                         },
117                 },
118                 {
119                         arvados.EndpointContainerDelete,
120                         func() interface{} { return &arvados.DeleteOptions{} },
121                         func(ctx context.Context, opts interface{}) (interface{}, error) {
122                                 return rtr.fed.ContainerDelete(ctx, *opts.(*arvados.DeleteOptions))
123                         },
124                 },
125                 {
126                         arvados.EndpointContainerLock,
127                         func() interface{} {
128                                 return &arvados.GetOptions{Select: []string{"uuid", "state", "priority", "auth_uuid", "locked_by_uuid"}}
129                         },
130                         func(ctx context.Context, opts interface{}) (interface{}, error) {
131                                 return rtr.fed.ContainerLock(ctx, *opts.(*arvados.GetOptions))
132                         },
133                 },
134                 {
135                         arvados.EndpointContainerUnlock,
136                         func() interface{} {
137                                 return &arvados.GetOptions{Select: []string{"uuid", "state", "priority", "auth_uuid", "locked_by_uuid"}}
138                         },
139                         func(ctx context.Context, opts interface{}) (interface{}, error) {
140                                 return rtr.fed.ContainerUnlock(ctx, *opts.(*arvados.GetOptions))
141                         },
142                 },
143                 {
144                         arvados.EndpointSpecimenCreate,
145                         func() interface{} { return &arvados.CreateOptions{} },
146                         func(ctx context.Context, opts interface{}) (interface{}, error) {
147                                 return rtr.fed.SpecimenCreate(ctx, *opts.(*arvados.CreateOptions))
148                         },
149                 },
150                 {
151                         arvados.EndpointSpecimenUpdate,
152                         func() interface{} { return &arvados.UpdateOptions{} },
153                         func(ctx context.Context, opts interface{}) (interface{}, error) {
154                                 return rtr.fed.SpecimenUpdate(ctx, *opts.(*arvados.UpdateOptions))
155                         },
156                 },
157                 {
158                         arvados.EndpointSpecimenGet,
159                         func() interface{} { return &arvados.GetOptions{} },
160                         func(ctx context.Context, opts interface{}) (interface{}, error) {
161                                 return rtr.fed.SpecimenGet(ctx, *opts.(*arvados.GetOptions))
162                         },
163                 },
164                 {
165                         arvados.EndpointSpecimenList,
166                         func() interface{} { return &arvados.ListOptions{Limit: -1} },
167                         func(ctx context.Context, opts interface{}) (interface{}, error) {
168                                 return rtr.fed.SpecimenList(ctx, *opts.(*arvados.ListOptions))
169                         },
170                 },
171                 {
172                         arvados.EndpointSpecimenDelete,
173                         func() interface{} { return &arvados.DeleteOptions{} },
174                         func(ctx context.Context, opts interface{}) (interface{}, error) {
175                                 return rtr.fed.SpecimenDelete(ctx, *opts.(*arvados.DeleteOptions))
176                         },
177                 },
178         } {
179                 route := route
180                 methods := []string{route.endpoint.Method}
181                 if route.endpoint.Method == "PATCH" {
182                         methods = append(methods, "PUT")
183                 }
184                 for _, method := range methods {
185                         rtr.mux.HandlerFunc(method, "/"+route.endpoint.Path, func(w http.ResponseWriter, req *http.Request) {
186                                 logger := ctxlog.FromContext(req.Context())
187                                 params, err := rtr.loadRequestParams(req, route.endpoint.AttrsKey)
188                                 if err != nil {
189                                         logger.WithField("req", req).WithField("route", route).WithError(err).Debug("error loading request params")
190                                         rtr.sendError(w, err)
191                                         return
192                                 }
193                                 opts := route.defaultOpts()
194                                 err = rtr.transcode(params, opts)
195                                 if err != nil {
196                                         logger.WithField("params", params).WithError(err).Debugf("error transcoding params to %T", opts)
197                                         rtr.sendError(w, err)
198                                         return
199                                 }
200                                 respOpts, err := rtr.responseOptions(opts)
201                                 if err != nil {
202                                         logger.WithField("opts", opts).WithError(err).Debugf("error getting response options from %T", opts)
203                                         rtr.sendError(w, err)
204                                         return
205                                 }
206
207                                 creds := auth.CredentialsFromRequest(req)
208                                 if rt, _ := params["reader_tokens"].([]interface{}); len(rt) > 0 {
209                                         for _, t := range rt {
210                                                 if t, ok := t.(string); ok {
211                                                         creds.Tokens = append(creds.Tokens, t)
212                                                 }
213                                         }
214                                 }
215                                 ctx := req.Context()
216                                 ctx = context.WithValue(ctx, auth.ContextKeyCredentials, creds)
217                                 ctx = arvados.ContextWithRequestID(ctx, req.Header.Get("X-Request-Id"))
218                                 logger.WithFields(logrus.Fields{
219                                         "apiEndpoint": route.endpoint,
220                                         "apiOptsType": fmt.Sprintf("%T", opts),
221                                         "apiOpts":     opts,
222                                 }).Debug("exec")
223                                 resp, err := route.exec(ctx, opts)
224                                 if err != nil {
225                                         logger.WithError(err).Debugf("returning error type %T", err)
226                                         rtr.sendError(w, err)
227                                         return
228                                 }
229                                 rtr.sendResponse(w, resp, respOpts)
230                         })
231                 }
232         }
233 }
234
235 func (rtr *router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
236         switch strings.SplitN(strings.TrimLeft(r.URL.Path, "/"), "/", 2)[0] {
237         case "login", "logout", "auth":
238         default:
239                 w.Header().Set("Access-Control-Allow-Origin", "*")
240                 w.Header().Set("Access-Control-Allow-Methods", "GET, HEAD, PUT, POST, DELETE")
241                 w.Header().Set("Access-Control-Allow-Headers", "Authorization, Content-Type")
242                 w.Header().Set("Access-Control-Max-Age", "86486400")
243         }
244         if r.Method == "OPTIONS" {
245                 return
246         }
247         r.ParseForm()
248         if m := r.FormValue("_method"); m != "" {
249                 r2 := *r
250                 r = &r2
251                 r.Method = m
252         }
253         rtr.mux.ServeHTTP(w, r)
254 }