Ctrl K
ComunidadGuías…

config.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package predastore

import (
	"cmp"
	"slices"
	"time"

	"github.com/mulgadc/predastore/internal/config"
	"github.com/mulgadc/predastore/internal/gate"
	"github.com/mulgadc/predastore/internal/gate/auth"
	"github.com/mulgadc/predastore/internal/gate/handlers"
)

// The TOML file is this package's contract with operators, so every type it
// names is re-exported here. They are aliases rather than copies: the shape an
// operator writes is the shape the cluster is derived from, and a mirror would
// only be something to keep in sync. The host and node tables are named for
// the file because Host is already this package's running process.

// NodeID and HostID identify the two levels of the cluster. They are distinct
// types so that a host id cannot be passed where a node id belongs.
type (
	NodeID = config.NodeID
	HostID = config.HostID
)

// Role is the function a node performs in the cluster, as written under
// [[host.node]].
type Role = config.Role

const (
	// RoleGate serves the S3 API in front of the cluster.
	RoleGate = config.RoleGate
	// RoleBlob stores erasure-coded object shards.
	RoleBlob = config.RoleBlob
	// RoleMeta participates in Raft consensus over global state.
	RoleMeta = config.RoleMeta
)

// HostConfig is one machine, as written under [[host]]: a data directory, an
// address, a TLS identity and the nodes it runs in one process.
type HostConfig = config.Host

// NodeConfig is a logical role pinned to a host, as written under
// [[host.node]].
type NodeConfig = config.Node

// The remaining tables of the configuration file.
type (
	RS         = config.RS
	Repair     = config.Repair
	Reaper     = config.Reaper
	Compaction = config.Compaction
	S3         = config.S3
	Bucket     = config.Bucket
	AuthEntry  = config.AuthEntry
	PolicyRule = config.PolicyRule
	IAMConfig  = config.IAMConfig
)

// Config is predastore's on-disk configuration.
type Config = config.Config

// LoadConfig reads and validates a TOML configuration file.
func LoadConfig(path string) (*Config, error) {
	return config.Load(path)
}

// HostBindAddr is where a host's cluster traffic listens, and NodeBindAddr
// where a gate serves S3. Both are re-exported because an embedder settling
// host-local fields has to know which plane it is settling: the S3 address
// belongs to the gate, and putting it on the host would move raft and blob
// traffic onto the public interface with it.
func HostBindAddr(h HostConfig) string { return config.HostBindAddr(h) }

// NodeBindAddr resolves a gate's S3 listen address against its host.
func NodeBindAddr(h HostConfig, n NodeConfig) string { return config.NodeBindAddr(h, n) }

// The queries below answer what the cluster is made of. They live here rather
// than in the config package because they are inventory questions about a
// parsed file, not part of parsing it.
//
// nodesByRole sorts by id. Raft bootstraps from the meta set and treats an
// identically ordered set as idempotent across replicas, so the order is part
// of the contract rather than a tidiness.

// hostOf is the [[host]] with this id, and whether the file names one.
func hostOf(c *Config, hostID HostID) (HostConfig, bool) {
	for _, h := range c.Hosts {
		if h.ID == hostID {
			return h, true
		}
	}
	return HostConfig{}, false
}

// nodesByRole is every node with the role, wherever it runs.
func nodesByRole(c *Config, role Role) []NodeConfig {
	var out []NodeConfig
	for _, h := range c.Hosts {
		for _, n := range h.Nodes {
			if n.Role == role {
				out = append(out, n)
			}
		}
	}
	slices.SortFunc(out, func(a, b NodeConfig) int { return cmp.Compare(a.ID, b.ID) })
	return out
}

// hasRemoteNodes reports whether any node runs off hostID, which is the only
// reason a node on it opens a network socket.
func hasRemoteNodes(c *Config, hostID HostID) bool {
	return slices.ContainsFunc(c.Hosts, func(h HostConfig) bool {
		return h.ID != hostID && len(h.Nodes) > 0
	})
}

// hostNodesByRole is every node with the role that runs on this host.
func hostNodesByRole(h HostConfig, role Role) []NodeConfig {
	var out []NodeConfig
	for _, n := range h.Nodes {
		if n.Role == role {
			out = append(out, n)
		}
	}

	return out
}

// nodeIDs names a set of nodes by id, which is how every client addresses one.
func nodeIDs(nodes []NodeConfig) []NodeID {
	ids := make([]NodeID, 0, len(nodes))
	for _, n := range nodes {
		ids = append(ids, n.ID)
	}
	return ids
}

// The conversions below turn the file into each subsystem's own settings. They
// live here rather than in the config package because gate.Config names a
// NodeID and so imports config; converting there would close the cycle.

func bucketConfigs(c *Config) []handlers.BucketConfig {
	buckets := make([]handlers.BucketConfig, len(c.Buckets))
	for i, b := range c.Buckets {
		buckets[i] = handlers.BucketConfig{
			Name:      b.Name,
			Region:    b.Region,
			Public:    b.Public,
			AccountID: b.AccountID,
		}
	}
	return buckets
}

func authEntries(c *Config) []auth.Entry {
	entries := make([]auth.Entry, len(c.Auth))
	for i, a := range c.Auth {
		rules := make([]auth.PolicyRule, len(a.Policy))
		for j, p := range a.Policy {
			rules[j] = auth.PolicyRule{Bucket: p.Bucket, Actions: p.Actions}
		}
		entries[i] = auth.Entry{
			AccessKeyID:     a.AccessKeyID,
			SecretAccessKey: a.SecretAccessKey,
			AccountID:       a.AccountID,
			Policy:          rules,
		}
	}
	return entries
}

func iamConfig(c *Config) *auth.IAMConfig {
	if c.IAM == nil {
		return nil
	}
	return &auth.IAMConfig{
		NATSUrl:          c.IAM.NATSUrl,
		NATSToken:        c.IAM.NATSToken,
		MasterKeyPath:    c.IAM.MasterKeyPath,
		AccessKeysBucket: c.IAM.AccessKeysBucket,
	}
}

// gateConfig is everything the S3 frontend runs on: the slice of the file it
// reads, plus the wiring its host supplies — where it listens, the TLS
// identity it serves under, and the cluster clients it works through.
func gateConfig(
	c *Config, host HostConfig, n NodeConfig, mc gate.MetaClient, bc gate.BlobClient,
) gate.Config {
	return gate.Config{
		Region: c.Region,
		NodeID: n.ID,
		RS: gate.RS{
			Data: c.RS.Data, Parity: c.RS.Parity,
			DegradedWrites: c.RS.DegradedWritesEnabled(), HintedHandoff: c.RS.HintedHandoffEnabled(),
		},
		Buckets:     bucketConfigs(c),
		Auth:        authEntries(c),
		IAM:         iamConfig(c),
		RateLimit:   c.RateLimit,
		BlobNodeIDs: nodeIDs(nodesByRole(c, RoleBlob)),

		// Repair is scoped to the blob nodes sharing this process, so each is
		// swept by exactly one coordinator without anything having to elect one.
		LocalBlobNodeIDs: nodeIDs(hostNodesByRole(host, RoleBlob)),
		MetaNodeIDs:      nodeIDs(nodesByRole(c, RoleMeta)),
		LocalMetaNodeIDs: nodeIDs(hostNodesByRole(host, RoleMeta)),
		Repair: gate.RepairConfig{
			Enabled:  c.Repair.IsEnabled(),
			Workers:  c.Repair.Workers,
			PageSize: c.Repair.PageSize,
			Interval: time.Duration(c.Repair.IntervalSeconds) * time.Second,
		},

		// Reaper has no ownership scoping: every gate that runs repair also
		// runs it, and a tombstone racing two gates costs redundant RPCs and
		// nothing else.
		Reaper: gate.ReaperConfig{
			Enabled:  c.Reaper.IsEnabled(),
			Workers:  c.Reaper.Workers,
			PageSize: c.Reaper.PageSize,
			Interval: time.Duration(c.Reaper.IntervalSeconds) * time.Second,
		},

		Addr:        config.NodeBindAddr(host, n),
		Port:        n.Port,
		TLSCert:     host.TLSCert,
		TLSKey:      host.TLSKey,
		EnableHTTP2: c.S3.HTTP2Enabled(),
		Meta:        mc,
		Blob:        bc,
	}
}