Skip to content

Commit 6c3a37d

Browse files
committed
tests/lib/fakestore/store: add synchronization, add debug reset action
Signed-off-by: Maciej Borzecki <maciej.borzecki@canonical.com>
1 parent 7db3281 commit 6c3a37d

2 files changed

Lines changed: 97 additions & 12 deletions

File tree

tests/lib/fakestore/store/store.go

Lines changed: 58 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import (
3535
"regexp"
3636
"strconv"
3737
"strings"
38+
"sync"
3839
"time"
3940

4041
"github.com/snapcore/snapd/asserts"
@@ -67,6 +68,8 @@ type snapCachedInfo struct {
6768

6869
// Store is our snappy software store implementation
6970
type Store struct {
71+
lock sync.Mutex
72+
7073
url string
7174
blobDir string
7275
assertDir string
@@ -80,6 +83,9 @@ type Store struct {
8083

8184
snapsCache map[string]snapCachedInfo
8285

86+
// endpoint -> quota value, note this is stateful, i.e. the quota is counted
87+
// for all requests to a given endpoint and after exceeding it, all
88+
// subsequent requests will fail until it is reset though a request
8389
killAfter map[string]int64
8490
}
8591

@@ -397,12 +403,16 @@ type debugResultJSON struct {
397403

398404
func (s *Store) debugEndpoint(w http.ResponseWriter, req *http.Request) {
399405
if req.Method == http.MethodGet {
400-
res := debugResultJSON{
401-
KillAfter: s.killAfter,
402-
}
403-
out, err := json.MarshalIndent(res, "", " ")
406+
out, err := func() ([]byte, error) {
407+
s.lock.Lock()
408+
defer s.lock.Unlock()
409+
res := debugResultJSON{
410+
KillAfter: s.killAfter,
411+
}
412+
return json.Marshal(res)
413+
}()
404414
if err != nil {
405-
http.Error(w, fmt.Sprintf("cannot marshal: %v: %v", res, err), 500)
415+
http.Error(w, fmt.Sprintf("cannot marshal: %v", err), 500)
406416
return
407417
}
408418
w.Write(out)
@@ -421,21 +431,46 @@ func (s *Store) debugEndpoint(w http.ResponseWriter, req *http.Request) {
421431
return
422432
}
423433

434+
var err error
424435
switch debugReq.Action {
425436
case "kill-request":
426-
s.debugActionKillDownload(debugReq)
437+
err = s.debugActionKillDownload(debugReq)
438+
case "reset":
439+
s.debugActionReset(debugReq)
427440
default:
441+
err = fmt.Errorf("unexpected debug action %q", debugReq.Action)
442+
}
443+
if err != nil {
428444
w.WriteHeader(400)
429-
fmt.Fprintf(w, "unexpected debug action %q", debugReq.Action)
445+
fmt.Fprint(w, err.Error())
430446
}
431447
}
432448

433-
func (s *Store) debugActionKillDownload(debugReq *debugRequestJSON) {
449+
func (s *Store) debugActionKillDownload(debugReq *debugRequestJSON) error {
450+
if debugReq.KillPath == "" {
451+
return fmt.Errorf("kill-path cannot be empty")
452+
}
453+
454+
if strings.HasPrefix(debugReq.KillPath, "/debug/") {
455+
return fmt.Errorf("kill-path cannot be applied to /debug/ endpoints")
456+
}
457+
458+
s.lock.Lock()
459+
defer s.lock.Unlock()
460+
434461
if debugReq.KillAfter == 0 {
435462
delete(s.killAfter, debugReq.KillPath)
436-
return
463+
} else {
464+
s.killAfter[debugReq.KillPath] = debugReq.KillAfter
437465
}
438-
s.killAfter[debugReq.KillPath] = debugReq.KillAfter
466+
return nil
467+
}
468+
469+
func (s *Store) debugActionReset(debugReq *debugRequestJSON) {
470+
s.lock.Lock()
471+
defer s.lock.Unlock()
472+
473+
s.killAfter = map[string]int64{}
439474
}
440475

441476
func logRangeHeader(handler http.HandlerFunc) http.HandlerFunc {
@@ -451,7 +486,14 @@ func logRangeHeader(handler http.HandlerFunc) http.HandlerFunc {
451486
func (s *Store) applyKillAfter(handler http.HandlerFunc) http.HandlerFunc {
452487
return func(w http.ResponseWriter, req *http.Request) {
453488
path := req.URL.Path
454-
killAfter, exists := s.killAfter[path]
489+
490+
killAfter, exists := func() (int64, bool) {
491+
s.lock.Lock()
492+
defer s.lock.Unlock()
493+
v, ok := s.killAfter[path]
494+
return v, ok
495+
}()
496+
455497
if !exists {
456498
handler(w, req)
457499
return
@@ -468,7 +510,11 @@ func (s *Store) applyKillAfter(handler http.HandlerFunc) http.HandlerFunc {
468510
}
469511

470512
// update killAfter for path after write finishes
471-
s.killAfter[path] = kaw.killAfter
513+
func() {
514+
s.lock.Lock()
515+
defer s.lock.Unlock()
516+
s.killAfter[path] = kaw.killAfter
517+
}()
472518
}
473519
}
474520

tests/lib/fakestore/store/store_test.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1393,3 +1393,42 @@ func (s *storeTestSuite) TestDebugEndpointMethodNotAllowed(c *C) {
13931393

13941394
c.Assert(resp.StatusCode, Equals, 405)
13951395
}
1396+
1397+
func (s *storeTestSuite) TestDebugActionReset(c *C) {
1398+
// Set a rule for endpoint connection interrupt
1399+
resp, err := s.StorePostJSON("/debug", []byte(`{
1400+
"action": "kill-request",
1401+
"kill-path": "/foo/bar",
1402+
"kill-after": 123
1403+
}`))
1404+
c.Assert(err, IsNil)
1405+
resp.Body.Close()
1406+
c.Assert(resp.StatusCode, Equals, 200)
1407+
1408+
resp, err = s.StoreGet("/debug")
1409+
c.Assert(err, IsNil)
1410+
defer resp.Body.Close()
1411+
1412+
var buf bytes.Buffer
1413+
c.Assert(resp.StatusCode, Equals, 200)
1414+
_, err = io.Copy(&buf, resp.Body)
1415+
c.Assert(err, IsNil)
1416+
c.Check(buf.String(), Equals, `{"kill-after":{"/foo/bar":123}}`)
1417+
1418+
// Clear it by setting kill-after to 0
1419+
resp, err = s.StorePostJSON("/debug", []byte(`{
1420+
"action": "reset"
1421+
}`))
1422+
c.Assert(err, IsNil)
1423+
resp.Body.Close()
1424+
c.Assert(resp.StatusCode, Equals, 200)
1425+
1426+
resp, err = s.StoreGet("/debug")
1427+
c.Assert(err, IsNil)
1428+
defer resp.Body.Close()
1429+
1430+
buf.Reset()
1431+
_, err = io.Copy(&buf, resp.Body)
1432+
c.Assert(err, IsNil)
1433+
c.Check(buf.String(), Equals, `{"kill-after":{}}`)
1434+
}

0 commit comments

Comments
 (0)