Skip to content

Commit a02803a

Browse files
authored
feat: extend manifest integration test (#443)
* feat: extend manifest integration test * fix: download paths via tar ref * chore: lint * chore: lint * fix: panic * fix: timeout * fix: upload and download * fix: review comments * fix: lint * fix: review comments * fix: use full nodes * fix: retry * fix: uncomment
1 parent 5d28411 commit a02803a

File tree

7 files changed

+195
-40
lines changed

7 files changed

+195
-40
lines changed

config/config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ checks:
212212
max-pathname-length: 64
213213
postage-amount: 1000
214214
postage-depth: 17
215-
timeout: 5m
215+
timeout: 30m
216216
type: manifest
217217
networkavailability:
218218
options:

config/local.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ checks:
204204
max-pathname-length: 64
205205
postage-amount: 1000
206206
postage-depth: 17
207-
timeout: 5m
207+
timeout: 30m
208208
type: manifest
209209
ci-pingpong:
210210
options:

pkg/bee/api/api.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ const (
3434
swarmSocSignatureHeader = "Swarm-Soc-Signature"
3535
swarmFeedIndexHeader = "Swarm-Feed-Index"
3636
swarmFeedIndexNextHeader = "Swarm-Feed-Index-Next"
37+
swarmIndexDocumentHeader = "Swarm-Index-Document"
38+
swarmErrorDocumentHeader = "Swarm-Error-Document"
3739
)
3840

3941
var userAgent = "beekeeper/" + beekeeper.Version
@@ -340,6 +342,10 @@ type UploadOptions struct {
340342
BatchID string
341343
Direct bool
342344
ActHistoryAddress swarm.Address
345+
346+
// Dirs
347+
IndexDocument string
348+
ErrorDocument string
343349
}
344350

345351
type DownloadOptions struct {

pkg/bee/api/dirs.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,13 @@ func (s *DirsService) Upload(ctx context.Context, data io.Reader, size int64, o
3030
header.Set("swarm-collection", "True")
3131
header.Set(postageStampBatchHeader, o.BatchID)
3232

33+
if o.IndexDocument != "" {
34+
header.Set(swarmIndexDocumentHeader, o.IndexDocument)
35+
}
36+
if o.ErrorDocument != "" {
37+
header.Set(swarmErrorDocumentHeader, o.ErrorDocument)
38+
}
39+
3340
err = s.client.requestWithHeader(ctx, http.MethodPost, "/"+apiVersion+"/bzz", header, data, &resp)
3441

3542
return

pkg/check/manifest/manifest.go

Lines changed: 164 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@ import (
55
"bytes"
66
"context"
77
"encoding/hex"
8-
"errors"
98
"fmt"
109
"io"
1110
"math/rand"
1211
"time"
1312

13+
"github.com/ethersphere/bee/v2/pkg/crypto"
14+
"github.com/ethersphere/bee/v2/pkg/swarm"
1415
"github.com/ethersphere/beekeeper/pkg/bee"
1516
"github.com/ethersphere/beekeeper/pkg/bee/api"
1617
"github.com/ethersphere/beekeeper/pkg/beekeeper"
@@ -58,23 +59,38 @@ func NewCheck(logger logging.Logger) beekeeper.Action {
5859
}
5960
}
6061

61-
var errManifest = errors.New("manifest data mismatch")
62-
6362
func (c *Check) Run(ctx context.Context, cluster orchestration.Cluster, opts interface{}) (err error) {
6463
o, ok := opts.(Options)
6564
if !ok {
6665
return fmt.Errorf("invalid options type")
6766
}
6867

6968
rnd := random.PseudoGenerator(o.Seed)
69+
clients, err := cluster.ShuffledFullNodeClients(ctx, rnd)
70+
if err != nil {
71+
return fmt.Errorf("node clients shuffle: %w", err)
72+
}
7073

71-
c.logger.Infof("Seed: %d", o.Seed)
74+
if len(clients) < 2 {
75+
return fmt.Errorf("not enough nodes to run manifest check")
76+
}
77+
upClient := clients[0]
78+
downClient := clients[1]
7279

73-
overlays, err := cluster.FlattenOverlays(ctx)
80+
err = c.checkWithoutSubDirs(ctx, rnd, o, upClient, downClient)
7481
if err != nil {
75-
return err
82+
return fmt.Errorf("check without subdirs: %w", err)
7683
}
7784

85+
err = c.checkWithSubDirs(ctx, rnd, o, upClient, downClient)
86+
if err != nil {
87+
return fmt.Errorf("check with subdirs: %w", err)
88+
}
89+
90+
return nil
91+
}
92+
93+
func (c *Check) checkWithoutSubDirs(ctx context.Context, rnd *rand.Rand, o Options, upClient *bee.Client, downClient *bee.Client) error {
7894
files, err := generateFiles(rnd, o.FilesInCollection, o.MaxPathnameLength)
7995
if err != nil {
8096
return err
@@ -86,55 +102,167 @@ func (c *Check) Run(ctx context.Context, cluster orchestration.Cluster, opts int
86102
}
87103

88104
tarFile := bee.NewBufferFile("", tarReader)
89-
clients, err := cluster.NodesClients(ctx)
105+
batchID, err := upClient.GetOrCreateMutableBatch(ctx, o.PostageAmount, o.PostageDepth, o.PostageLabel)
106+
if err != nil {
107+
return fmt.Errorf("node %s: batch id %w", upClient.Name(), err)
108+
}
109+
c.logger.Infof("node %s: batch id %s", upClient.Name(), batchID)
110+
111+
if err := upClient.UploadCollection(ctx, &tarFile, api.UploadOptions{BatchID: batchID}); err != nil {
112+
return fmt.Errorf("node %d: %w", 0, err)
113+
}
114+
115+
for _, file := range files {
116+
if err := c.downloadAndVerify(ctx, downClient, tarFile.Address(), &file, bee.File{}); err != nil {
117+
return err
118+
}
119+
}
120+
return nil
121+
}
122+
123+
func (c *Check) checkWithSubDirs(ctx context.Context, rnd *rand.Rand, o Options, upClient *bee.Client, downClient *bee.Client) error {
124+
privKey, err := crypto.GenerateSecp256k1Key()
90125
if err != nil {
91126
return err
92127
}
93128

94-
sortedNodes := cluster.FullNodeNames()
95-
node := sortedNodes[0]
129+
signer := crypto.NewDefaultSigner(privKey)
130+
topic, err := crypto.LegacyKeccak256([]byte("my-website"))
131+
if err != nil {
132+
return err
133+
}
96134

97-
client := clients[node]
135+
batchID, err := upClient.GetOrCreateMutableBatch(ctx, o.PostageAmount, o.PostageDepth, o.PostageLabel)
136+
if err != nil {
137+
return fmt.Errorf("node %s: batch id %w", upClient.Name(), err)
138+
}
139+
c.logger.Infof("node %s: batch id %s", upClient.Name(), batchID)
98140

99-
batchID, err := client.GetOrCreateMutableBatch(ctx, o.PostageAmount, o.PostageDepth, o.PostageLabel)
141+
rootFeedRef, err := upClient.CreateRootFeedManifest(ctx, signer, topic, api.UploadOptions{BatchID: batchID})
100142
if err != nil {
101-
return fmt.Errorf("node %s: batch id %w", node, err)
143+
return err
102144
}
103-
c.logger.Infof("node %s: batch id %s", node, batchID)
145+
c.logger.Infof("root feed reference: %s", rootFeedRef.Reference)
146+
time.Sleep(3 * time.Second)
104147

105-
if err := client.UploadCollection(ctx, &tarFile, api.UploadOptions{BatchID: batchID}); err != nil {
106-
return fmt.Errorf("node %d: %w", 0, err)
148+
paths := []string{"index.html", "assets/styles/styles.css", "assets/styles/images/image.png", "error.html"}
149+
files, err := generateFilesWithPaths(rnd, paths, int(o.MaxPathnameLength))
150+
if err != nil {
151+
return err
107152
}
108153

109-
lastNode := sortedNodes[len(sortedNodes)-1]
110-
try := 0
154+
tarReader, err := tarFiles(files)
155+
if err != nil {
156+
return err
157+
}
158+
tarFile := bee.NewBufferFile("", tarReader)
159+
if err := upClient.UploadCollection(ctx, &tarFile, api.UploadOptions{BatchID: batchID, IndexDocument: "index.html"}); err != nil {
160+
return err
161+
}
162+
c.logger.Infof("collection uploaded: %s", tarFile.Address())
163+
time.Sleep(3 * time.Second)
111164

112-
DOWNLOAD:
113-
time.Sleep(5 * time.Second)
114-
try++
115-
if try > 5 {
116-
return errors.New("failed getting manifest files after too many retries")
165+
// push first version of website to the feed
166+
ref, err := upClient.UpdateFeedWithReference(ctx, signer, topic, 0, tarFile.Address(), api.UploadOptions{BatchID: batchID})
167+
if err != nil {
168+
return err
169+
}
170+
c.logger.Infof("feed updated: %s", ref.Reference)
171+
172+
// download root (index.html) from the feed
173+
err = c.downloadAndVerify(ctx, downClient, rootFeedRef.Reference, nil, files[0])
174+
if err != nil {
175+
return err
176+
}
177+
178+
// update website files
179+
files, err = generateFilesWithPaths(rnd, paths, int(o.MaxPathnameLength))
180+
if err != nil {
181+
return err
182+
}
183+
184+
tarReader, err = tarFiles(files)
185+
if err != nil {
186+
return err
187+
}
188+
tarFile = bee.NewBufferFile("", tarReader)
189+
if err := upClient.UploadCollection(ctx, &tarFile, api.UploadOptions{BatchID: batchID, IndexDocument: "index.html"}); err != nil {
190+
return err
117191
}
192+
c.logger.Infof("collection uploaded: %s", tarFile.Address())
193+
time.Sleep(3 * time.Second)
118194

119-
for i, file := range files {
120-
node := clients[lastNode]
195+
// push 2nd version of website to the feed
196+
ref, err = upClient.UpdateFeedWithReference(ctx, signer, topic, 1, tarFile.Address(), api.UploadOptions{BatchID: batchID})
197+
if err != nil {
198+
return err
199+
}
200+
c.logger.Infof("feed updated: %s", ref.Reference)
121201

122-
size, hash, err := node.DownloadManifestFile(ctx, tarFile.Address(), file.Name())
202+
// download updated index.html from the feed
203+
err = c.downloadAndVerify(ctx, downClient, rootFeedRef.Reference, nil, files[0])
204+
if err != nil {
205+
return err
206+
}
207+
208+
// download other paths and compare
209+
for i := 0; i < len(files); i++ {
210+
err = c.downloadAndVerify(ctx, downClient, tarFile.Address(), &files[i], files[0])
123211
if err != nil {
124-
c.logger.Infof("Node %s. Error retrieving file: %v", lastNode, err)
125-
goto DOWNLOAD
212+
return err
126213
}
214+
}
215+
return nil
216+
}
127217

128-
if !bytes.Equal(file.Hash(), hash) {
129-
c.logger.Infof("Node %s. File %d not retrieved successfully. Uploaded size: %d Downloaded size: %d Node: %s File: %s/%s", lastNode, i, file.Size(), size, overlays[lastNode].String(), tarFile.Address().String(), file.Name())
130-
return errManifest
218+
// downloadAndVerify retrieves a file from the given address using the specified client.
219+
// If the file parameter is nil, it downloads the index file in the collection.
220+
// Then it verifies the hash of the downloaded file against the expected hash.
221+
func (c *Check) downloadAndVerify(ctx context.Context, client *bee.Client, address swarm.Address, file *bee.File, indexFile bee.File) error {
222+
expectedHash := indexFile.Hash()
223+
fName := ""
224+
if file != nil {
225+
fName = file.Name()
226+
expectedHash = file.Hash()
227+
}
228+
c.logger.Infof("downloading file: %s/%s", address, fName)
229+
230+
for i := 0; i < 10; i++ {
231+
select {
232+
case <-time.After(5 * time.Second):
233+
_, hash, err := client.DownloadManifestFile(ctx, address, fName)
234+
if err != nil {
235+
c.logger.Infof("node %s: error retrieving file: %s", client.Name(), err.Error())
236+
continue
237+
}
238+
239+
c.logger.Infof("want hash: %s, got hash: %s", hex.EncodeToString(expectedHash), hex.EncodeToString(hash))
240+
if !bytes.Equal(expectedHash, hash) {
241+
c.logger.Infof("node %s: file hash does not match.", client.Name())
242+
continue
243+
}
244+
c.logger.Infof("node %s: file retrieved successfully", client.Name())
245+
return nil
246+
case <-ctx.Done():
247+
return ctx.Err()
131248
}
132-
133-
c.logger.Infof("Node %s. File %d retrieved successfully. Node: %s File: %s/%s", lastNode, i, overlays[lastNode].String(), tarFile.Address().String(), file.Name())
134-
try = 0 // reset the retry counter for the next file
135249
}
136250

137-
return nil
251+
return fmt.Errorf("failed getting manifest file after too many retries")
252+
}
253+
254+
func generateFilesWithPaths(r *rand.Rand, paths []string, maxSize int) ([]bee.File, error) {
255+
files := make([]bee.File, len(paths))
256+
for i, path := range paths {
257+
size := int64(r.Intn(maxSize)) + 1
258+
file := bee.NewRandomFile(r, path, size)
259+
err := file.CalculateHash()
260+
if err != nil {
261+
return nil, err
262+
}
263+
files[i] = file
264+
}
265+
return files, nil
138266
}
139267

140268
func generateFiles(r *rand.Rand, filesCount int, maxPathnameLength int32) ([]bee.File, error) {
@@ -171,6 +299,8 @@ func tarFiles(files []bee.File) (*bytes.Buffer, error) {
171299
var buf bytes.Buffer
172300
tw := tar.NewWriter(&buf)
173301

302+
defer tw.Close()
303+
174304
for _, file := range files {
175305
// create tar header and write it
176306
hdr := &tar.Header{
@@ -193,9 +323,5 @@ func tarFiles(files []bee.File) (*bytes.Buffer, error) {
193323
}
194324
}
195325

196-
if err := tw.Close(); err != nil {
197-
return nil, err
198-
}
199-
200326
return &buf, nil
201327
}

pkg/orchestration/cluster.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ type Cluster interface {
2323
FlattenSettlements(ctx context.Context) (settlements NodeGroupSettlements, err error)
2424
FlattenTopologies(ctx context.Context) (topologies map[string]bee.Topology, err error)
2525
FullNodeNames() (names []string)
26+
ShuffledFullNodeClients(ctx context.Context, r *rand.Rand) ([]*bee.Client, error)
2627
GlobalReplicationFactor(ctx context.Context, a swarm.Address) (grf int, err error)
2728
LightNodeNames() (names []string)
2829
Name() string

pkg/orchestration/k8s/cluster.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,21 @@ func (c *Cluster) FullNodeNames() (names []string) {
233233
return
234234
}
235235

236+
// ShuffledFullNodeClients returns a list of full node clients shuffled
237+
func (c *Cluster) ShuffledFullNodeClients(ctx context.Context, r *rand.Rand) ([]*bee.Client, error) {
238+
var res []*bee.Client
239+
for _, node := range c.Nodes() {
240+
cfg := node.Config()
241+
if cfg.FullNode && !cfg.BootnodeMode {
242+
res = append(res, node.Client())
243+
}
244+
}
245+
r.Shuffle(len(res), func(i, j int) {
246+
res[i], res[j] = res[j], res[i]
247+
})
248+
return res, nil
249+
}
250+
236251
// NodesClients returns map of node's clients in the cluster excluding stopped nodes
237252
func (c *Cluster) NodesClients(ctx context.Context) (map[string]*bee.Client, error) {
238253
clients := make(map[string]*bee.Client)

0 commit comments

Comments
 (0)