Skip to content

Commit 889e24e

Browse files
committed
tests/lib/fakestore/store: use gorilla/mux, support request kill for all endpoints, improve logging
Use gorilla/mux which we already import in daemon. Add logging middleware and improve request logging. Move kill-after to middleware and support on all endpoints. Signed-off-by: Maciej Borzecki <maciej.borzecki@canonical.com>
1 parent 6c3a37d commit 889e24e

1 file changed

Lines changed: 77 additions & 49 deletions

File tree

tests/lib/fakestore/store/store.go

Lines changed: 77 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ import (
3838
"sync"
3939
"time"
4040

41+
"github.com/gorilla/mux"
42+
4143
"github.com/snapcore/snapd/asserts"
4244
"github.com/snapcore/snapd/asserts/sysdb"
4345
"github.com/snapcore/snapd/asserts/systestkeys"
@@ -89,9 +91,39 @@ type Store struct {
8991
killAfter map[string]int64
9092
}
9193

94+
type wrappedWriter struct {
95+
http.ResponseWriter
96+
s int
97+
}
98+
99+
func (w *wrappedWriter) WriteHeader(s int) {
100+
w.s = s
101+
w.ResponseWriter.WriteHeader(s)
102+
}
103+
104+
func logit(next http.Handler) http.Handler {
105+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
106+
ww := &wrappedWriter{ResponseWriter: w}
107+
t0 := time.Now()
108+
next.ServeHTTP(ww, r)
109+
t := time.Since(t0)
110+
logger.Noticef("%s %s %s %s %d", r.RemoteAddr, r.Method, r.URL, t, ww.s)
111+
})
112+
}
113+
114+
func logRangeHeader(next http.Handler) http.Handler {
115+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
116+
if len(r.Header["Range"]) > 0 {
117+
logger.Noticef(`%s %s %s range request %v`, r.RemoteAddr, r.Method, r.URL, r.Header["Range"])
118+
}
119+
next.ServeHTTP(w, r)
120+
})
121+
}
122+
92123
// NewStore creates a new store server serving snaps from the given top directory and assertions from topDir/asserts. If assertFallback is true missing assertions are looked up in the main online store.
93124
func NewStore(topDir, addr string, assertFallback bool) *Store {
94-
mux := http.NewServeMux()
125+
r := mux.NewRouter()
126+
95127
var sto *store.Store
96128
if assertFallback {
97129
snapdenv.SetUserAgentFromVersion("unknown", nil, "fakestore")
@@ -107,7 +139,7 @@ func NewStore(topDir, addr string, assertFallback bool) *Store {
107139
url: fmt.Sprintf("http://%s", addr),
108140
srv: &http.Server{
109141
Addr: addr,
110-
Handler: mux,
142+
Handler: r,
111143
},
112144
channelRepository: &ChannelRepository{
113145
rootDir: filepath.Join(topDir, "channels"),
@@ -116,24 +148,31 @@ func NewStore(topDir, addr string, assertFallback bool) *Store {
116148
killAfter: make(map[string]int64),
117149
}
118150

119-
mux.HandleFunc("/", rootEndpoint)
120-
mux.HandleFunc("/api/v1/snaps/search", store.searchEndpoint)
121-
mux.HandleFunc("/api/v1/snaps/details/", store.detailsEndpoint)
122-
mux.HandleFunc("/api/v1/snaps/metadata", store.bulkEndpoint)
151+
r.Use(logit)
152+
r.Use(store.applyKillAfter)
153+
154+
r.HandleFunc("/", rootEndpoint)
155+
r.HandleFunc("/api/v1/snaps/search", store.searchEndpoint)
156+
r.HandleFunc("/api/v1/snaps/details/{name}", store.detailsEndpoint).Methods("GET")
157+
r.HandleFunc("/api/v1/snaps/metadata", store.bulkEndpoint).Methods("POST")
123158

124159
fileServer := http.StripPrefix("/download/", http.FileServer(http.Dir(topDir)))
125-
mux.Handle("/download/", logRangeHeader(store.applyKillAfter(fileServer.ServeHTTP)))
160+
dr := r.PathPrefix("/download/").Subrouter()
161+
dr.Use(logRangeHeader)
162+
dr.PathPrefix("/").HandlerFunc(fileServer.ServeHTTP).Methods("GET")
126163

127-
mux.HandleFunc("/api/v1/snaps/auth/nonces", store.nonceEndpoint)
128-
mux.HandleFunc("/api/v1/snaps/auth/sessions", store.sessionEndpoint)
164+
r.HandleFunc("/api/v1/snaps/auth/nonces", store.nonceEndpoint)
165+
r.HandleFunc("/api/v1/snaps/auth/sessions", store.sessionEndpoint)
129166

130167
// v2
131-
mux.HandleFunc("/v2/assertions/", store.assertionsEndpoint)
132-
mux.HandleFunc("/v2/snaps/refresh", store.snapActionEndpoint)
168+
// TODO: use path vars for assertion type
169+
r.PathPrefix("/v2/assertions/").HandlerFunc(store.assertionsEndpoint).Methods("GET")
170+
r.HandleFunc("/v2/snaps/refresh", store.snapActionEndpoint)
133171

134-
mux.HandleFunc("/v2/repairs/", store.repairsEndpoint)
172+
// TODO use path vars for brand and repair IDs
173+
r.PathPrefix("/v2/repairs/").HandlerFunc(store.repairsEndpoint).Methods("GET")
135174

136-
mux.HandleFunc("/debug", store.debugEndpoint)
175+
r.HandleFunc("/debug", store.debugEndpoint).Methods("GET", "POST")
137176

138177
return store
139178
}
@@ -415,6 +454,7 @@ func (s *Store) debugEndpoint(w http.ResponseWriter, req *http.Request) {
415454
http.Error(w, fmt.Sprintf("cannot marshal: %v", err), 500)
416455
return
417456
}
457+
w.WriteHeader(200)
418458
w.Write(out)
419459
return
420460
}
@@ -443,6 +483,8 @@ func (s *Store) debugEndpoint(w http.ResponseWriter, req *http.Request) {
443483
if err != nil {
444484
w.WriteHeader(400)
445485
fmt.Fprint(w, err.Error())
486+
} else {
487+
w.WriteHeader(200)
446488
}
447489
}
448490

@@ -466,25 +508,15 @@ func (s *Store) debugActionKillDownload(debugReq *debugRequestJSON) error {
466508
return nil
467509
}
468510

469-
func (s *Store) debugActionReset(debugReq *debugRequestJSON) {
511+
func (s *Store) debugActionReset(_ *debugRequestJSON) {
470512
s.lock.Lock()
471513
defer s.lock.Unlock()
472514

473515
s.killAfter = map[string]int64{}
474516
}
475517

476-
func logRangeHeader(handler http.HandlerFunc) http.HandlerFunc {
477-
return func(w http.ResponseWriter, req *http.Request) {
478-
path := req.URL.Path
479-
if len(req.Header["Range"]) > 0 {
480-
logger.Noticef(`requested range for %s is %v`, path, req.Header["Range"])
481-
}
482-
handler(w, req)
483-
}
484-
}
485-
486-
func (s *Store) applyKillAfter(handler http.HandlerFunc) http.HandlerFunc {
487-
return func(w http.ResponseWriter, req *http.Request) {
518+
func (s *Store) applyKillAfter(next http.Handler) http.Handler {
519+
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
488520
path := req.URL.Path
489521

490522
killAfter, exists := func() (int64, bool) {
@@ -494,28 +526,27 @@ func (s *Store) applyKillAfter(handler http.HandlerFunc) http.HandlerFunc {
494526
return v, ok
495527
}()
496528

497-
if !exists {
498-
handler(w, req)
499-
return
500-
}
529+
if exists {
530+
kaw := &killAfterWriter{
531+
ResponseWriter: w,
532+
killAfter: killAfter,
533+
}
501534

502-
kaw := &killAfterWriter{
503-
ResponseWriter: w,
504-
killAfter: killAfter,
505-
}
506-
handler(kaw, req)
535+
defer func() {
536+
if kaw.killAfter < 0 {
537+
logger.Noticef("%s was force killed, quota exceeded", path)
538+
}
507539

508-
if kaw.killAfter < 0 {
509-
logger.Noticef("%s was force killed, quota exceeded", path)
510-
}
540+
// update killAfter for path after write finishes
541+
s.lock.Lock()
542+
defer s.lock.Unlock()
543+
s.killAfter[path] = kaw.killAfter
544+
}()
511545

512-
// update killAfter for path after write finishes
513-
func() {
514-
s.lock.Lock()
515-
defer s.lock.Unlock()
516-
s.killAfter[path] = kaw.killAfter
517-
}()
518-
}
546+
w = kaw
547+
}
548+
next.ServeHTTP(w, req)
549+
})
519550
}
520551

521552
func (s *Store) searchEndpoint(w http.ResponseWriter, req *http.Request) {
@@ -602,10 +633,7 @@ func (s *Store) repairsEndpoint(w http.ResponseWriter, req *http.Request) {
602633
}
603634

604635
func (s *Store) detailsEndpoint(w http.ResponseWriter, req *http.Request) {
605-
pkg := strings.TrimPrefix(req.URL.Path, "/api/v1/snaps/details/")
606-
if pkg == req.URL.Path {
607-
panic("how?")
608-
}
636+
pkg := mux.Vars(req)["name"]
609637

610638
bs, err := s.collectAssertions()
611639
if err != nil {

0 commit comments

Comments
 (0)