Skip to content

Commit 3bd356f

Browse files
committed
go/runtime/bundle: Use indirection when downloading bundles
1 parent 8af27bd commit 3bd356f

File tree

5 files changed

+112
-30
lines changed

5 files changed

+112
-30
lines changed

.changelog/5962.cfg.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ The following configuration options have been added:
2020
- `runtime.runtimes.config` is the runtime local configuration,
2121

2222
- `runtime.runtimes.repositories` is the list of runtime specific URLs
23-
used to fetch runtime bundles,
23+
used to fetch runtime bundle metadata,
2424

2525
- `runtime.repositories` is the list of global URLs used to fetch
26-
runtime bundles,
26+
runtime bundle metadata,
2727

2828
- `runtime.max_bundle_size` is the maximum allowed bundle size.

go/oasis-test-runner/scenario/e2e/runtime/keymanager_upgrade.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ func (sc *KmUpgradeImpl) Run(ctx context.Context, childEnv *env.Env) error {
8080
port := parsedURL.Port()
8181

8282
// Start serving bundles.
83-
server := newBundleServer(port, bundles)
83+
server := newBundleServer(port, bundles, sc.Logger)
8484
server.Start()
8585
defer server.Stop()
8686

@@ -90,7 +90,7 @@ func (sc *KmUpgradeImpl) Run(ctx context.Context, childEnv *env.Env) error {
9090
}
9191

9292
// Verify that all key manager nodes requested bundle from the server.
93-
n := len(sc.Net.Keymanagers())
93+
n := 2 * len(sc.Net.Keymanagers())
9494
if m := server.getRequestCount(); m != n {
9595
return fmt.Errorf("invalid number of bundle requests (got: %d, expected: %d)", m, n)
9696
}

go/oasis-test-runner/scenario/e2e/runtime/runtime_upgrade.go

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"sync/atomic"
1515
"time"
1616

17+
"github.com/oasisprotocol/oasis-core/go/common/logging"
1718
cmSync "github.com/oasisprotocol/oasis-core/go/common/sync"
1819
"github.com/oasisprotocol/oasis-core/go/oasis-test-runner/env"
1920
"github.com/oasisprotocol/oasis-core/go/oasis-test-runner/oasis"
@@ -87,7 +88,7 @@ func (sc *runtimeUpgradeImpl) Run(ctx context.Context, childEnv *env.Env) error
8788
port := parsedURL.Port()
8889

8990
// Start serving bundles.
90-
server := newBundleServer(port, bundles)
91+
server := newBundleServer(port, bundles, sc.Logger)
9192
server.Start()
9293
defer server.Stop()
9394

@@ -97,7 +98,7 @@ func (sc *runtimeUpgradeImpl) Run(ctx context.Context, childEnv *env.Env) error
9798
}
9899

99100
// Verify that all client and compute nodes requested bundle from the server.
100-
n := len(sc.Net.Clients()) + len(sc.Net.ComputeWorkers())
101+
n := 2 * (len(sc.Net.Clients()) + len(sc.Net.ComputeWorkers()))
101102
if m := server.getRequestCount(); m != n {
102103
return fmt.Errorf("invalid number of bundle requests (got: %d, expected: %d)", m, n)
103104
}
@@ -117,13 +118,16 @@ type bundleServer struct {
117118
bundles map[string]string
118119

119120
requestCount uint64
121+
122+
logger *logging.Logger
120123
}
121124

122-
func newBundleServer(port string, bundles map[string]string) *bundleServer {
125+
func newBundleServer(port string, bundles map[string]string, logger *logging.Logger) *bundleServer {
123126
return &bundleServer{
124127
startOne: cmSync.NewOne(),
125128
port: port,
126129
bundles: bundles,
130+
logger: logger,
127131
}
128132
}
129133

@@ -160,6 +164,30 @@ func (s *bundleServer) run(ctx context.Context) {
160164
}
161165

162166
func (s *bundleServer) handleRequest(w http.ResponseWriter, r *http.Request) {
167+
s.logger.Info("handling request",
168+
"path", r.URL.Path,
169+
)
170+
171+
if strings.HasSuffix(r.URL.Path, bundle.FileExtension) {
172+
s.handleGetBundle(w, r)
173+
} else {
174+
s.handleGetMetadata(w, r)
175+
}
176+
}
177+
178+
func (s *bundleServer) handleGetMetadata(w http.ResponseWriter, r *http.Request) {
179+
manifestHash := path.Base(r.URL.Path)
180+
content := []byte(fmt.Sprintf("http://127.0.0.1:%s/%s%s\n", s.port, manifestHash, bundle.FileExtension))
181+
182+
w.Header().Set("Content-Disposition", "attachment; filename=metadata.txt")
183+
w.Header().Set("Content-Type", "application/octet-stream")
184+
w.WriteHeader(http.StatusOK)
185+
_, _ = w.Write(content)
186+
187+
atomic.AddUint64(&s.requestCount, 1)
188+
}
189+
190+
func (s *bundleServer) handleGetBundle(w http.ResponseWriter, r *http.Request) {
163191
filename := path.Base(r.URL.Path)
164192

165193
path, ok := s.bundles[filename]
@@ -174,7 +202,8 @@ func (s *bundleServer) handleRequest(w http.ResponseWriter, r *http.Request) {
174202
return
175203
}
176204

177-
w.Header().Set("Content-Type", "text/plain")
205+
w.Header().Set("Content-Disposition", "attachment; filename=bundle.orc")
206+
w.Header().Set("Content-Type", "application/octet-stream")
178207
w.WriteHeader(http.StatusOK)
179208
_, _ = w.Write(content)
180209

go/runtime/bundle/discovery.go

Lines changed: 73 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package bundle
22

33
import (
4+
"bytes"
45
"context"
56
"errors"
67
"fmt"
@@ -29,6 +30,9 @@ const (
2930
// requestTimeout is the time limit for http client requests.
3031
requestTimeout = 10 * time.Second
3132

33+
// maxMetadataSizeBytes is the maximum allowed metadata size in bytes.
34+
maxMetadataSizeBytes = 2 * 1024 // 2 KB
35+
3236
// maxDefaultBundleSizeBytes is the maximum allowed default bundle size
3337
// in bytes.
3438
maxDefaultBundleSizeBytes = 20 * 1024 * 1024 // 20 MB
@@ -295,7 +299,7 @@ func (d *Discovery) downloadBundle(runtimeID common.Namespace, manifestHash hash
295299

296300
for _, baseURLs := range [][]string{d.runtimeBaseURLs[runtimeID], d.globalBaseURLs} {
297301
for _, baseURL := range baseURLs {
298-
if err := d.tryDownloadBundle(runtimeID, manifestHash, baseURL); err != nil {
302+
if err := d.tryDownloadBundle(manifestHash, baseURL); err != nil {
299303
errs = errors.Join(errs, err)
300304
continue
301305
}
@@ -307,39 +311,49 @@ func (d *Discovery) downloadBundle(runtimeID common.Namespace, manifestHash hash
307311
return errs
308312
}
309313

310-
func (d *Discovery) tryDownloadBundle(runtimeID common.Namespace, manifestHash hash.Hash, baseURL string) error {
311-
filename := fmt.Sprintf("%s%s", manifestHash.Hex(), FileExtension)
314+
func (d *Discovery) tryDownloadBundle(manifestHash hash.Hash, baseURL string) error {
315+
metaURL, err := url.JoinPath(baseURL, manifestHash.Hex())
316+
if err != nil {
317+
d.logger.Error("failed to construct metadata URL",
318+
"err", err,
319+
)
320+
return fmt.Errorf("failed to construct metadata URL: %w", err)
321+
}
312322

313-
d.logger.Debug("downloading bundle",
314-
"runtime_id", runtimeID,
315-
"base_url", baseURL,
316-
"filename", filename,
323+
d.logger.Debug("downloading metadata",
324+
"url", metaURL,
317325
)
318326

319-
url, err := url.JoinPath(baseURL, filename)
327+
bundleURL, err := d.fetchMetadata(metaURL)
320328
if err != nil {
321-
d.logger.Error("failed to construct URL",
329+
d.logger.Error("failed to download metadata",
322330
"err", err,
323-
"base_url", baseURL,
324-
"filename", filename,
331+
"url", metaURL,
325332
)
326-
return fmt.Errorf("failed to construct URL: %w", err)
333+
return fmt.Errorf("failed to download metadata: %w", err)
334+
}
335+
336+
bundleURL, err = validateAndNormalizeURL(bundleURL)
337+
if err != nil {
338+
return err
327339
}
328340

329-
src, err := d.fetchBundle(url)
341+
d.logger.Debug("downloading bundle",
342+
"url", bundleURL,
343+
)
344+
345+
src, err := d.fetchBundle(bundleURL)
330346
if err != nil {
331347
d.logger.Error("failed to download bundle",
332348
"err", err,
333-
"url", url,
349+
"url", metaURL,
334350
)
335351
return fmt.Errorf("failed to download bundle: %w", err)
336352
}
337353
defer os.Remove(src)
338354

339355
d.logger.Info("bundle downloaded",
340-
"runtime_id", runtimeID,
341-
"base_url", baseURL,
342-
"filename", filename,
356+
"url", bundleURL,
343357
)
344358

345359
if err := d.registry.AddBundle(src, manifestHash); err != nil {
@@ -349,6 +363,7 @@ func (d *Discovery) tryDownloadBundle(runtimeID common.Namespace, manifestHash h
349363
return fmt.Errorf("failed to add bundle: %w", err)
350364
}
351365

366+
filename := fmt.Sprintf("%s%s", manifestHash.Hex(), FileExtension)
352367
dst := filepath.Join(d.bundleDir, filename)
353368
if err = os.Rename(src, dst); err != nil {
354369
d.logger.Error("failed to move bundle",
@@ -357,9 +372,39 @@ func (d *Discovery) tryDownloadBundle(runtimeID common.Namespace, manifestHash h
357372
"dst", dst,
358373
)
359374
}
375+
376+
d.logger.Debug("bundle stored",
377+
"dst", dst,
378+
)
379+
360380
return nil
361381
}
362382

383+
func (d *Discovery) fetchMetadata(url string) (string, error) {
384+
resp, err := d.client.Get(url)
385+
if err != nil {
386+
return "", fmt.Errorf("failed to fetch metadata: %w", err)
387+
}
388+
defer resp.Body.Close()
389+
390+
if resp.StatusCode != http.StatusOK {
391+
return "", fmt.Errorf("failed to fetch metadata: invalid status code %d", resp.StatusCode)
392+
}
393+
394+
limitedReader := io.LimitedReader{
395+
R: resp.Body,
396+
N: maxMetadataSizeBytes,
397+
}
398+
399+
var buffer bytes.Buffer
400+
_, err = buffer.ReadFrom(&limitedReader)
401+
if err != nil && err != io.EOF {
402+
return "", fmt.Errorf("failed to read metadata content: %w", err)
403+
}
404+
405+
return strings.TrimSpace(buffer.String()), nil
406+
}
407+
363408
func (d *Discovery) fetchBundle(url string) (string, error) {
364409
resp, err := d.client.Get(url)
365410
if err != nil {
@@ -470,15 +515,23 @@ func (d *Discovery) copyBundle(src string) error {
470515
return nil
471516
}
472517

518+
func validateAndNormalizeURL(rawURL string) (string, error) {
519+
parsedURL, err := url.Parse(rawURL)
520+
if err != nil {
521+
return "", fmt.Errorf("invalid URL '%s': %w", rawURL, err)
522+
}
523+
return parsedURL.String(), nil
524+
}
525+
473526
func validateAndNormalizeURLs(rawURLs []string) ([]string, error) {
474527
var normalizedURLs []string
475528

476529
for _, rawURL := range rawURLs {
477-
parsedURL, err := url.Parse(rawURL)
530+
normalizedURL, err := validateAndNormalizeURL(rawURL)
478531
if err != nil {
479-
return nil, fmt.Errorf("invalid URL '%s': %w", rawURL, err)
532+
return nil, err
480533
}
481-
normalizedURLs = append(normalizedURLs, parsedURL.String())
534+
normalizedURLs = append(normalizedURLs, normalizedURL)
482535
}
483536

484537
return normalizedURLs, nil

go/runtime/config/config.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ type Config struct {
115115
// LoadBalancer is the load balancer configuration.
116116
LoadBalancer LoadBalancerConfig `yaml:"load_balancer,omitempty"`
117117

118-
// Repositories is the list of URLs used to fetch runtime bundles.
118+
// Repositories is the list of URLs used to fetch runtime bundle metadata.
119119
Repositories []string `yaml:"repositories,omitempty"`
120120

121121
// MaxBundleSize is the maximum allowed bundle size.
@@ -166,7 +166,7 @@ type RuntimeConfig struct {
166166
// Config contains runtime local configuration.
167167
Config map[string]interface{} `yaml:"config,omitempty"`
168168

169-
// Repositories is the list of URLs used to fetch runtime bundles.
169+
// Repositories is the list of URLs used to fetch runtime bundle metadata.
170170
Repositories []string `yaml:"repositories,omitempty"`
171171
}
172172

0 commit comments

Comments
 (0)