Skip to content

Commit 9d858f8

Browse files
ZeyadYasserbboozzoo
authored andcommitted
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 8743714 commit 9d858f8

1 file changed

Lines changed: 91 additions & 64 deletions

File tree

tests/lib/fakestore/store/store.go

Lines changed: 91 additions & 64 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
}
@@ -433,6 +472,7 @@ func (s *Store) debugEndpoint(w http.ResponseWriter, req *http.Request) {
433472
http.Error(w, fmt.Sprintf("cannot marshal: %v", err), 500)
434473
return
435474
}
475+
w.WriteHeader(200)
436476
w.Write(out)
437477
return
438478
}
@@ -461,6 +501,8 @@ func (s *Store) debugEndpoint(w http.ResponseWriter, req *http.Request) {
461501
if err != nil {
462502
w.WriteHeader(400)
463503
fmt.Fprint(w, err.Error())
504+
} else {
505+
w.WriteHeader(200)
464506
}
465507
}
466508

@@ -484,25 +526,15 @@ func (s *Store) debugActionKillDownload(debugReq *debugRequestJSON) error {
484526
return nil
485527
}
486528

487-
func (s *Store) debugActionReset(debugReq *debugRequestJSON) {
529+
func (s *Store) debugActionReset(_ *debugRequestJSON) {
488530
s.lock.Lock()
489531
defer s.lock.Unlock()
490532

491533
s.killAfter = map[string]int64{}
492534
}
493535

494-
func logRangeHeader(handler http.HandlerFunc) http.HandlerFunc {
495-
return func(w http.ResponseWriter, req *http.Request) {
496-
path := req.URL.Path
497-
if len(req.Header["Range"]) > 0 {
498-
logger.Noticef(`requested range for %s is %v`, path, req.Header["Range"])
499-
}
500-
handler(w, req)
501-
}
502-
}
503-
504-
func (s *Store) applyKillAfter(handler http.HandlerFunc) http.HandlerFunc {
505-
return func(w http.ResponseWriter, req *http.Request) {
536+
func (s *Store) applyKillAfter(next http.Handler) http.Handler {
537+
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
506538
path := req.URL.Path
507539

508540
exists := func() bool {
@@ -512,42 +544,40 @@ func (s *Store) applyKillAfter(handler http.HandlerFunc) http.HandlerFunc {
512544
return ok
513545
}()
514546

515-
if !exists {
516-
handler(w, req)
517-
return
518-
}
547+
if exists {
548+
kaw := &killAfterWriter{
549+
ResponseWriter: w,
550+
path: path,
551+
consumeQuota: func(want int) int {
552+
s.lock.Lock()
553+
defer s.lock.Unlock()
554+
555+
v, ok := s.killAfter[path]
556+
if !ok {
557+
// no quota set
558+
return want
559+
}
560+
561+
left := int(v)
562+
563+
var got int
564+
if want > left {
565+
got = left
566+
left = 0
567+
} else {
568+
got = want
569+
left -= want
570+
}
571+
s.killAfter[path] = int64(left)
572+
573+
return got
574+
},
575+
}
519576

520-
kaw := &killAfterWriter{
521-
ResponseWriter: w,
522-
path: path,
523-
consumeQuota: func(want int) int {
524-
s.lock.Lock()
525-
defer s.lock.Unlock()
526-
527-
v, ok := s.killAfter[path]
528-
if !ok {
529-
// no quota set
530-
return want
531-
}
532-
533-
left := int(v)
534-
535-
var got int
536-
if want > left {
537-
got = left
538-
left = 0
539-
} else {
540-
got = want
541-
left -= want
542-
}
543-
s.killAfter[path] = int64(left)
544-
545-
return got
546-
},
577+
w = kaw
547578
}
548-
handler(kaw, req)
549-
550-
}
579+
next.ServeHTTP(w, req)
580+
})
551581
}
552582

553583
func (s *Store) searchEndpoint(w http.ResponseWriter, req *http.Request) {
@@ -634,10 +664,7 @@ func (s *Store) repairsEndpoint(w http.ResponseWriter, req *http.Request) {
634664
}
635665

636666
func (s *Store) detailsEndpoint(w http.ResponseWriter, req *http.Request) {
637-
pkg := strings.TrimPrefix(req.URL.Path, "/api/v1/snaps/details/")
638-
if pkg == req.URL.Path {
639-
panic("how?")
640-
}
667+
pkg := mux.Vars(req)["name"]
641668

642669
bs, err := s.collectAssertions()
643670
if err != nil {

0 commit comments

Comments
 (0)