-
Notifications
You must be signed in to change notification settings - Fork 15
Expand file tree
/
Copy pathconfig_opts.go
More file actions
162 lines (143 loc) · 4.9 KB
/
config_opts.go
File metadata and controls
162 lines (143 loc) · 4.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
package gorums
import (
"fmt"
"maps"
"net"
"slices"
)
// NodeListOption must be implemented by node providers. It is used by both the
// Manager (outbound) and by inboundManager (inbound) via newConfig.
type NodeListOption interface {
newConfig(nodeRegistry) (Configuration, error)
}
// nodeRegistry abstracts the node management operations required to build a Configuration.
// Implemented by Manager and inboundManager.
type nodeRegistry interface {
Nodes() []*Node
newNode(id uint32, addr string) (*Node, error)
}
// NodeAddress must be implemented by types that can be used as node addresses.
type NodeAddress interface {
Addr() string
}
// WithNodes returns a NodeListOption containing the provided mapping from
// application-specific IDs to types implementing NodeAddress.
// Node IDs must be greater than 0.
func WithNodes[T NodeAddress](nodes map[uint32]T) NodeListOption {
return nodeMap[T](nodes)
}
type nodeMap[T NodeAddress] map[uint32]T
func (nm nodeMap[T]) newConfig(registry nodeRegistry) (Configuration, error) {
if len(nm) == 0 {
return nil, fmt.Errorf("gorums: missing required node map")
}
builder := newNodeBuilder(registry, len(nm))
// Sort IDs to ensure deterministic processing order
for _, id := range slices.Sorted(maps.Keys(nm)) {
node := nm[id]
if err := builder.add(id, node.Addr()); err != nil {
return nil, err
}
}
return builder.configuration(), nil
}
// WithNodeList returns a NodeListOption for the provided list of node addresses.
// Unique Node IDs are generated sequentially starting from the maximum existing
// node ID plus one, or from 1 if no nodes exist, preventing conflicts with
// existing nodes.
func WithNodeList(addrsList []string) NodeListOption {
return nodeList(addrsList)
}
type nodeList []string
func (nl nodeList) newConfig(registry nodeRegistry) (Configuration, error) {
if len(nl) == 0 {
return nil, fmt.Errorf("gorums: missing required node addresses")
}
builder := newNodeBuilder(registry, len(nl))
nextID := builder.nextID()
for i, addr := range nl {
id := nextID + uint32(i)
if err := builder.add(id, addr); err != nil {
return nil, err
}
}
return builder.configuration(), nil
}
// nodeBuilder helps construct a Configuration while tracking addresses to prevent duplicates.
// It encapsulates the common logic shared between WithNodes and WithNodeList.
type nodeBuilder struct {
registry nodeRegistry
addrToID map[string]uint32 // normalized address -> node ID
idToNode map[uint32]*Node // existing node ID -> node
maxID uint32 // maximum existing node ID
nodes Configuration
}
// newNodeBuilder creates a new nodeBuilder initialized with existing nodes from the registry.
func newNodeBuilder(registry nodeRegistry, capacity int) *nodeBuilder {
addrToID := make(map[string]uint32, capacity)
idToNode := make(map[uint32]*Node, capacity)
maxID := uint32(0)
// Populate with existing nodes from the registry (already normalized)
for _, existingNode := range registry.Nodes() {
id := existingNode.ID()
addrToID[existingNode.Address()] = id
idToNode[id] = existingNode
maxID = max(maxID, id)
}
return &nodeBuilder{
registry: registry,
addrToID: addrToID,
idToNode: idToNode,
maxID: maxID,
nodes: make(Configuration, 0, capacity),
}
}
// add creates or reuses a node with the given ID and address.
func (b *nodeBuilder) add(id uint32, addr string) error {
if id == 0 {
return fmt.Errorf("gorums: node 0 is reserved")
}
normalizedAddr, err := normalizeAddr(addr)
if err != nil {
return fmt.Errorf("gorums: invalid address %q: %w", addr, err)
}
// If ID already exists, verify address matches
if existingNode, found := b.idToNode[id]; found {
if existingNode.Address() != normalizedAddr {
return fmt.Errorf("gorums: node %d already in use by %q", id, existingNode.Address())
}
b.nodes = append(b.nodes, existingNode)
return nil
}
// Check for duplicate address
if existingID, exists := b.addrToID[normalizedAddr]; exists {
return fmt.Errorf("gorums: address %q already in use by node %d", normalizedAddr, existingID)
}
b.addrToID[normalizedAddr] = id
node, err := b.registry.newNode(id, normalizedAddr)
if err != nil {
return err
}
b.nodes = append(b.nodes, node)
return nil
}
// configuration returns the built Configuration, sorted by ID.
func (b *nodeBuilder) configuration() Configuration {
slices.SortFunc(b.nodes, ID)
return b.nodes
}
// nextID returns the next available node ID (max existing ID + 1).
func (b *nodeBuilder) nextID() uint32 {
return b.maxID + 1
}
// normalizeAddr normalizes an address string to a canonical form using
// net.ResolveTCPAddr. This ensures consistent address comparison for
// duplicate detection. For example, "localhost:8080" and "127.0.0.1:8080"
// may resolve to the same normalized address.
func normalizeAddr(addr string) (string, error) {
tcpAddr, err := net.ResolveTCPAddr("tcp", addr)
if err != nil {
return "", err
}
return tcpAddr.String(), nil
}