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_, whose messages are the objects' metadata (`$O..M.`) and chunks // (`$O..C.`). // 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..$O.B.C.`. **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, } }