A node that reports file-references gets the digest and size of each file over 64 KiB that may travel so, and the content is put in the bucket declaration-files before the declaration is sent; others get every content inline. The controller alone writes the bucket and node-engines read an object by name (the four grants the node-engine's own fetch makes, no list, no write). An object goes a day after no node's latest declaration names it. The near-limit condition warns at 75 percent, and a test holds the control node's declaration, in the reference form, under half the bus's largest message.
91 lines
4.5 KiB
Go
91 lines
4.5 KiB
Go
package broker
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/declaration"
|
|
"github.com/nats-io/nats.go/jetstream"
|
|
)
|
|
|
|
// The object bucket a declaration's large files travel in (novox/hq ADR 0486, issues 1696 and 1697).
|
|
//
|
|
// **The controller alone writes it, and a node-engine reads an object by its name** (ADR 0486 §2): its account may
|
|
// neither list the bucket nor write it, and no other account has any access. A bucket of the bus's object store is a
|
|
// stream, OBJ_<bucket>, whose messages are the objects' metadata (`$O.<bucket>.M.<base64 of the name>`) and chunks
|
|
// (`$O.<bucket>.C.<nuid>`).
|
|
|
|
// DeclarationFilesBucket is the bucket's name, which mesh-sdk fixes for the node-engine and the controller alike.
|
|
const DeclarationFilesBucket = declaration.FileBucket
|
|
|
|
// DeclarationFilesStream is the stream under the bucket.
|
|
const DeclarationFilesStream = "OBJ_" + DeclarationFilesBucket
|
|
|
|
// DeclarationFilesMaxBytes is the bucket's size: the controller's objects are immutable and regenerable, and the
|
|
// largest declaration it composes holds well under a hundred megabytes of them.
|
|
const DeclarationFilesMaxBytes = 1 << 30
|
|
|
|
// DeclarationFilesKeptFor is how long an object stays after no node's latest declaration names it (ADR 0486 §3).
|
|
const DeclarationFilesKeptFor = 24 * time.Hour
|
|
|
|
// DeclarationFilesWrites is the one pattern the controller may publish to write the bucket: every object's metadata
|
|
// and chunks. The server's API for the stream is the controller's `$JS.API.>`.
|
|
const DeclarationFilesWrites = "$O." + DeclarationFilesBucket + ".>"
|
|
|
|
// DeclarationFilesReads is what a node-engine's account may publish to read an object by its name, and nothing more.
|
|
// The set is the one node-engine's own call (mesh-host internal/link FileObject) makes, for B the bucket and S its
|
|
// stream (issue 1697, notes 4 and 5):
|
|
//
|
|
// - the object's metadata, by a direct get of the last message of the one subject that names it. **Only the suffixed
|
|
// form**: the bare `$JS.API.DIRECT.GET.S` takes a sequence number and would read any message of the stream, every
|
|
// object's name and chunks.
|
|
// - the ordered consumer on that object's chunks: `$JS.API.CONSUMER.CREATE.S.<name>.$O.B.C.<nuid>`. **Not `S.>`**:
|
|
// a consumer filtered on `$O.B.M.>` would read every object's name, the list that is refused. The chunks'
|
|
// subject carries the nuid the metadata gives, which only a reader of the metadata knows.
|
|
// - the flow-control replies of that consumer, without which a large object stalls at the flow-control window.
|
|
// - the consumer's deletion, best effort: the consumer's inactivity threshold removes it if this is refused.
|
|
//
|
|
// **Never granted:** STREAM.INFO (a `subjects_filter` of `>` lists every name), STREAM.MSG.GET (any message by its
|
|
// sequence), a publish to `$O.B.>` (a write).
|
|
func DeclarationFilesReads() []string {
|
|
return []string{
|
|
"$JS.API.DIRECT.GET." + DeclarationFilesStream + ".$O." + DeclarationFilesBucket + ".M.*",
|
|
"$JS.API.CONSUMER.CREATE." + DeclarationFilesStream + ".*.$O." + DeclarationFilesBucket + ".C.*",
|
|
"$JS.API.CONSUMER.DELETE." + DeclarationFilesStream + ".*",
|
|
"$JS.FC." + DeclarationFilesStream + ".>",
|
|
}
|
|
}
|
|
|
|
// EnsureDeclarationFiles creates the bucket if it is absent and brings its options to match. The bucket exists
|
|
// whenever the bus does, so a node that is starting finds it (ADR 0486 §4). An update, never a delete.
|
|
func (j *JetStream) EnsureDeclarationFiles() error {
|
|
js, err := jetstream.New(j.conn)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
defer cancel()
|
|
return EnsureDeclarationFiles(ctx, js)
|
|
}
|
|
|
|
// EnsureDeclarationFiles is the same on a JetStream the caller holds.
|
|
func EnsureDeclarationFiles(ctx context.Context, js jetstream.JetStream) error {
|
|
if _, err := js.CreateOrUpdateObjectStore(ctx, DeclarationFilesConfig()); err != nil {
|
|
return fmt.Errorf("asserting the object bucket %s: %w", DeclarationFilesBucket, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// DeclarationFilesConfig is the bucket as the controller asserts it.
|
|
func DeclarationFilesConfig() jetstream.ObjectStoreConfig {
|
|
return jetstream.ObjectStoreConfig{
|
|
Bucket: DeclarationFilesBucket,
|
|
Description: "the contents of a declaration's files over 64 KiB, each under the hex of its sha256 digest " +
|
|
"(novox/hq ADR 0486): written by the controller alone, read by a node-engine by name, removed a day after " +
|
|
"no node's latest declaration names it",
|
|
MaxBytes: DeclarationFilesMaxBytes,
|
|
Storage: jetstream.FileStorage,
|
|
}
|
|
}
|