From ac1a59659b41b441aad027940c10e657b08261da Mon Sep 17 00:00:00 2001 From: asbjorn Date: Tue, 11 Mar 2025 22:35:06 +0100 Subject: [PATCH 1/7] Removed Manager, Moved Manager functionality to Configuration --- async.go | 4 +- benchmark/benchmark_gorums.pb.go | 113 +++++++--------- cmd/benchmark/main.go | 6 +- cmd/protoc-gen-gorums/dev/config.go | 48 ++++++- cmd/protoc-gen-gorums/dev/mgr.go | 67 ---------- .../gengorums/template_static.go | 117 +++++++---------- config.go | 51 ++++++-- config_opts.go | 82 +++++++----- config_test.go | 49 +++---- correctable.go | 6 +- examples/storage/client.go | 10 +- examples/storage/proto/storage_gorums.pb.go | 121 ++++++++---------- examples/storage/repl.go | 20 ++- mgr_test.go | 14 +- multicast.go | 4 +- quorumcall.go | 4 +- rpc_test.go | 29 ++--- tests/config/config_gorums.pb.go | 113 +++++++--------- tests/config/config_test.go | 21 +-- tests/correctable/correctable_gorums.pb.go | 113 +++++++--------- tests/correctable/correctable_test.go | 6 +- tests/dummy/dummy_gorums.pb.go | 113 +++++++--------- tests/metadata/metadata_gorums.pb.go | 113 +++++++--------- tests/metadata/metadata_test.go | 21 +-- tests/oneway/oneway_gorums.pb.go | 113 +++++++--------- tests/oneway/oneway_test.go | 5 +- tests/ordering/order_gorums.pb.go | 113 +++++++--------- tests/ordering/order_test.go | 7 +- tests/qf/qf_gorums.pb.go | 113 +++++++--------- tests/qf/qf_test.go | 12 +- tests/tls/tls_gorums.pb.go | 113 +++++++--------- tests/tls/tls_test.go | 7 +- tests/unresponsive/unreponsive_test.go | 8 +- tests/unresponsive/unresponsive_gorums.pb.go | 113 +++++++--------- 34 files changed, 804 insertions(+), 1045 deletions(-) diff --git a/async.go b/async.go index a383323a..bb0eeb6d 100644 --- a/async.go +++ b/async.go @@ -45,11 +45,11 @@ type asyncCallState struct { // // This function should only be used by generated code. func (c RawConfiguration) AsyncCall(ctx context.Context, d QuorumCallData) *Async { - expectedReplies := len(c) + expectedReplies := len(c.RawNodes) md := ordering.Metadata_builder{MessageID: c.getMsgID(), Method: d.Method}.Build() replyChan := make(chan response, expectedReplies) - for _, n := range c { + for _, n := range c.RawNodes { msg := d.Message if d.PerNodeArgFn != nil { msg = d.PerNodeArgFn(d.Message, n.id) diff --git a/benchmark/benchmark_gorums.pb.go b/benchmark/benchmark_gorums.pb.go index 52c21c04..f2bb3c5c 100644 --- a/benchmark/benchmark_gorums.pb.go +++ b/benchmark/benchmark_gorums.pb.go @@ -30,12 +30,30 @@ type Configuration struct { nodes []*Node } +func NewConfiguration(qspec QuorumSpec, cfg gorums.NodeListOption, opts ...gorums.ManagerOption) (c *Configuration, err error) { + if qspec == nil { + return nil, fmt.Errorf("config: QuorumSpec cannot be nil") + } + c = &Configuration{ + qspec: qspec, + } + c.RawConfiguration, err = gorums.NewRawConfiguration(cfg, opts...) + if err != nil { + return nil, err + } + c.nodes = make([]*Node, c.Size()) + for i, n := range c.RawConfiguration.RawNodes { + c.nodes[i] = &Node{n} + } + return c, nil +} + // ConfigurationFromRaw returns a new Configuration from the given raw configuration and QuorumSpec. // // This function may for example be used to "clone" a configuration but install a different QuorumSpec: // // cfg1, err := mgr.NewConfiguration(qspec1, opts...) -// cfg2 := ConfigurationFromRaw(cfg1.RawConfig, qspec2) +// cfg2 := ConfigurationFromRaw(cfg1.RawConfiguration, qspec2) func ConfigurationFromRaw(rawCfg gorums.RawConfiguration, qspec QuorumSpec) (*Configuration, error) { // return an error if the QuorumSpec interface is not empty and no implementation was provided. var test interface{} = struct{}{} @@ -48,12 +66,38 @@ func ConfigurationFromRaw(rawCfg gorums.RawConfiguration, qspec QuorumSpec) (*Co } // initialize the nodes slice newCfg.nodes = make([]*Node, newCfg.Size()) - for i, n := range rawCfg { + for i, n := range rawCfg.Nodes() { newCfg.nodes[i] = &Node{n} } return newCfg, nil } +func (c *Configuration) SubConfiguration(qspec QuorumSpec, cfg gorums.NodeListOption) (subCfg *Configuration, err error) { + if qspec == nil { + return nil, fmt.Errorf("config: QuorumSpec cannot be nil") + } + subCfg = &Configuration{ + qspec: qspec, + } + subCfg.RawConfiguration, err = c.SubRawConfiguration(cfg) + if err != nil { + return nil, err + } + subCfg.nodes = make([]*Node, subCfg.Size()) + for i, n := range subCfg.RawConfiguration.Nodes() { + subCfg.nodes[i] = &Node{n} + } + return subCfg, nil +} + +// Close closes a configuration created from the NewConfiguration method +// +// NOTE: A configuration created with ConfigurationFromRaw is closed when the original configuration is closed +// If you want the configurations to be independent you need to use NewConfiguration +func (c *Configuration) Close() error { + return c.RawConfiguration.Close() +} + // Nodes returns a slice of each available node. IDs are returned in the same // order as they were provided in the creation of the Manager. // @@ -79,71 +123,6 @@ func init() { } } -// Manager maintains a connection pool of nodes on -// which quorum calls can be performed. -type Manager struct { - *gorums.RawManager -} - -// NewManager returns a new Manager for managing connection to nodes added -// to the manager. This function accepts manager options used to configure -// various aspects of the manager. -func NewManager(opts ...gorums.ManagerOption) *Manager { - return &Manager{ - RawManager: gorums.NewRawManager(opts...), - } -} - -// NewConfiguration returns a configuration based on the provided list of nodes (required) -// and an optional quorum specification. The QuorumSpec is necessary for call types that -// must process replies. For configurations only used for unicast or multicast call types, -// a QuorumSpec is not needed. The QuorumSpec interface is also a ConfigOption. -// Nodes can be supplied using WithNodeMap or WithNodeList, or WithNodeIDs. -// A new configuration can also be created from an existing configuration, -// using the And, WithNewNodes, Except, and WithoutNodes methods. -func (m *Manager) NewConfiguration(opts ...gorums.ConfigOption) (c *Configuration, err error) { - if len(opts) < 1 || len(opts) > 2 { - return nil, fmt.Errorf("config: wrong number of options: %d", len(opts)) - } - c = &Configuration{} - for _, opt := range opts { - switch v := opt.(type) { - case gorums.NodeListOption: - c.RawConfiguration, err = gorums.NewRawConfiguration(m.RawManager, v) - if err != nil { - return nil, err - } - case QuorumSpec: - // Must be last since v may match QuorumSpec if it is interface{} - c.qspec = v - default: - return nil, fmt.Errorf("config: unknown option type: %v", v) - } - } - // return an error if the QuorumSpec interface is not empty and no implementation was provided. - var test interface{} = struct{}{} - if _, empty := test.(QuorumSpec); !empty && c.qspec == nil { - return nil, fmt.Errorf("config: missing required QuorumSpec") - } - // initialize the nodes slice - c.nodes = make([]*Node, c.Size()) - for i, n := range c.RawConfiguration { - c.nodes[i] = &Node{n} - } - return c, nil -} - -// Nodes returns a slice of available nodes on this manager. -// IDs are returned in the order they were added at creation of the manager. -func (m *Manager) Nodes() []*Node { - gorumsNodes := m.RawManager.Nodes() - nodes := make([]*Node, len(gorumsNodes)) - for i, n := range gorumsNodes { - nodes[i] = &Node{n} - } - return nodes -} - // Node encapsulates the state of a node on which a remote procedure call // can be performed. type Node struct { diff --git a/cmd/benchmark/main.go b/cmd/benchmark/main.go index 6b1456be..c8dbda0b 100644 --- a/cmd/benchmark/main.go +++ b/cmd/benchmark/main.go @@ -196,16 +196,14 @@ func main() { gorums.WithSendBufferSize(*sendBuffer), } - mgr := benchmark.NewManager(mgrOpts...) - defer mgr.Close() - qspec := &benchmark.QSpec{ QSize: options.QuorumSize, CfgSize: options.NumNodes, } - cfg, err := mgr.NewConfiguration(qspec, gorums.WithNodeList(remotes[:options.NumNodes])) + cfg, err := benchmark.NewConfiguration(qspec, gorums.WithNodeList(remotes[:options.NumNodes]), mgrOpts...) checkf("Failed to create configuration: %v", err) + defer cfg.Close() results, err := benchmark.RunBenchmarks(benchReg, options, cfg) checkf("Error running benchmarks: %v", err) diff --git a/cmd/protoc-gen-gorums/dev/config.go b/cmd/protoc-gen-gorums/dev/config.go index 99e8e86e..ca8a89f1 100644 --- a/cmd/protoc-gen-gorums/dev/config.go +++ b/cmd/protoc-gen-gorums/dev/config.go @@ -14,12 +14,30 @@ type Configuration struct { nodes []*Node } +func NewConfiguration(qspec QuorumSpec, cfg gorums.NodeListOption, opts ...gorums.ManagerOption) (c *Configuration, err error) { + if qspec == nil { + return nil, fmt.Errorf("config: QuorumSpec cannot be nil") + } + c = &Configuration{ + qspec: qspec, + } + c.RawConfiguration, err = gorums.NewRawConfiguration(cfg, opts...) + if err != nil { + return nil, err + } + c.nodes = make([]*Node, c.Size()) + for i, n := range c.RawConfiguration.RawNodes { + c.nodes[i] = &Node{n} + } + return c, nil +} + // ConfigurationFromRaw returns a new Configuration from the given raw configuration and QuorumSpec. // // This function may for example be used to "clone" a configuration but install a different QuorumSpec: // // cfg1, err := mgr.NewConfiguration(qspec1, opts...) -// cfg2 := ConfigurationFromRaw(cfg1.RawConfig, qspec2) +// cfg2 := ConfigurationFromRaw(cfg1.RawConfiguration, qspec2) func ConfigurationFromRaw(rawCfg gorums.RawConfiguration, qspec QuorumSpec) (*Configuration, error) { // return an error if the QuorumSpec interface is not empty and no implementation was provided. var test interface{} = struct{}{} @@ -32,12 +50,38 @@ func ConfigurationFromRaw(rawCfg gorums.RawConfiguration, qspec QuorumSpec) (*Co } // initialize the nodes slice newCfg.nodes = make([]*Node, newCfg.Size()) - for i, n := range rawCfg { + for i, n := range rawCfg.Nodes() { newCfg.nodes[i] = &Node{n} } return newCfg, nil } +func (c *Configuration) SubConfiguration(qspec QuorumSpec, cfg gorums.NodeListOption) (subCfg *Configuration, err error) { + if qspec == nil { + return nil, fmt.Errorf("config: QuorumSpec cannot be nil") + } + subCfg = &Configuration{ + qspec: qspec, + } + subCfg.RawConfiguration, err = c.SubRawConfiguration(cfg) + if err != nil { + return nil, err + } + subCfg.nodes = make([]*Node, subCfg.Size()) + for i, n := range subCfg.RawConfiguration.Nodes() { + subCfg.nodes[i] = &Node{n} + } + return subCfg, nil +} + +// Close closes a configuration created from the NewConfiguration method +// +// NOTE: A configuration created with ConfigurationFromRaw is closed when the original configuration is closed +// If you want the configurations to be independent you need to use NewConfiguration +func (c *Configuration) Close() error { + return c.RawConfiguration.Close() +} + // Nodes returns a slice of each available node. IDs are returned in the same // order as they were provided in the creation of the Manager. // diff --git a/cmd/protoc-gen-gorums/dev/mgr.go b/cmd/protoc-gen-gorums/dev/mgr.go index 3180a912..758c12e4 100644 --- a/cmd/protoc-gen-gorums/dev/mgr.go +++ b/cmd/protoc-gen-gorums/dev/mgr.go @@ -1,8 +1,6 @@ package dev import ( - "fmt" - "github.com/relab/gorums" "google.golang.org/grpc/encoding" ) @@ -12,68 +10,3 @@ func init() { encoding.RegisterCodec(gorums.NewCodec()) } } - -// Manager maintains a connection pool of nodes on -// which quorum calls can be performed. -type Manager struct { - *gorums.RawManager -} - -// NewManager returns a new Manager for managing connection to nodes added -// to the manager. This function accepts manager options used to configure -// various aspects of the manager. -func NewManager(opts ...gorums.ManagerOption) *Manager { - return &Manager{ - RawManager: gorums.NewRawManager(opts...), - } -} - -// NewConfiguration returns a configuration based on the provided list of nodes (required) -// and an optional quorum specification. The QuorumSpec is necessary for call types that -// must process replies. For configurations only used for unicast or multicast call types, -// a QuorumSpec is not needed. The QuorumSpec interface is also a ConfigOption. -// Nodes can be supplied using WithNodeMap or WithNodeList, or WithNodeIDs. -// A new configuration can also be created from an existing configuration, -// using the And, WithNewNodes, Except, and WithoutNodes methods. -func (m *Manager) NewConfiguration(opts ...gorums.ConfigOption) (c *Configuration, err error) { - if len(opts) < 1 || len(opts) > 2 { - return nil, fmt.Errorf("config: wrong number of options: %d", len(opts)) - } - c = &Configuration{} - for _, opt := range opts { - switch v := opt.(type) { - case gorums.NodeListOption: - c.RawConfiguration, err = gorums.NewRawConfiguration(m.RawManager, v) - if err != nil { - return nil, err - } - case QuorumSpec: - // Must be last since v may match QuorumSpec if it is interface{} - c.qspec = v - default: - return nil, fmt.Errorf("config: unknown option type: %v", v) - } - } - // return an error if the QuorumSpec interface is not empty and no implementation was provided. - var test interface{} = struct{}{} - if _, empty := test.(QuorumSpec); !empty && c.qspec == nil { - return nil, fmt.Errorf("config: missing required QuorumSpec") - } - // initialize the nodes slice - c.nodes = make([]*Node, c.Size()) - for i, n := range c.RawConfiguration { - c.nodes[i] = &Node{n} - } - return c, nil -} - -// Nodes returns a slice of available nodes on this manager. -// IDs are returned in the order they were added at creation of the manager. -func (m *Manager) Nodes() []*Node { - gorumsNodes := m.RawManager.Nodes() - nodes := make([]*Node, len(gorumsNodes)) - for i, n := range gorumsNodes { - nodes[i] = &Node{n} - } - return nodes -} diff --git a/cmd/protoc-gen-gorums/gengorums/template_static.go b/cmd/protoc-gen-gorums/gengorums/template_static.go index 0bbcd5ef..f99e0c6e 100644 --- a/cmd/protoc-gen-gorums/gengorums/template_static.go +++ b/cmd/protoc-gen-gorums/gengorums/template_static.go @@ -5,11 +5,11 @@ package gengorums // pkgIdentMap maps from package name to one of the package's identifiers. // These identifiers are used by the Gorums protoc plugin to generate import statements. -var pkgIdentMap = map[string]string{"fmt": "Errorf", "github.com/relab/gorums": "ConfigOption", "google.golang.org/grpc/encoding": "GetCodec"} +var pkgIdentMap = map[string]string{"fmt": "Errorf", "github.com/relab/gorums": "ContentSubtype", "google.golang.org/grpc/encoding": "GetCodec"} // reservedIdents holds the set of Gorums reserved identifiers. // These identifiers cannot be used to define message types in a proto file. -var reservedIdents = []string{"Configuration", "Manager", "Node", "QuorumSpec"} +var reservedIdents = []string{"Configuration", "Node", "QuorumSpec"} var staticCode = `// A Configuration represents a static set of nodes on which quorum remote // procedure calls may be invoked. @@ -19,12 +19,30 @@ type Configuration struct { nodes []*Node } +func NewConfiguration(qspec QuorumSpec, cfg gorums.NodeListOption, opts ...gorums.ManagerOption) (c *Configuration, err error) { + if qspec == nil { + return nil, fmt.Errorf("config: QuorumSpec cannot be nil") + } + c = &Configuration{ + qspec: qspec, + } + c.RawConfiguration, err = gorums.NewRawConfiguration(cfg, opts...) + if err != nil { + return nil, err + } + c.nodes = make([]*Node, c.Size()) + for i, n := range c.RawConfiguration.RawNodes { + c.nodes[i] = &Node{n} + } + return c, nil +} + // ConfigurationFromRaw returns a new Configuration from the given raw configuration and QuorumSpec. // // This function may for example be used to "clone" a configuration but install a different QuorumSpec: // // cfg1, err := mgr.NewConfiguration(qspec1, opts...) -// cfg2 := ConfigurationFromRaw(cfg1.RawConfig, qspec2) +// cfg2 := ConfigurationFromRaw(cfg1.RawConfiguration, qspec2) func ConfigurationFromRaw(rawCfg gorums.RawConfiguration, qspec QuorumSpec) (*Configuration, error) { // return an error if the QuorumSpec interface is not empty and no implementation was provided. var test interface{} = struct{}{} @@ -37,12 +55,38 @@ func ConfigurationFromRaw(rawCfg gorums.RawConfiguration, qspec QuorumSpec) (*Co } // initialize the nodes slice newCfg.nodes = make([]*Node, newCfg.Size()) - for i, n := range rawCfg { + for i, n := range rawCfg.Nodes() { newCfg.nodes[i] = &Node{n} } return newCfg, nil } +func (c *Configuration) SubConfiguration(qspec QuorumSpec, cfg gorums.NodeListOption) (subCfg *Configuration, err error) { + if qspec == nil { + return nil, fmt.Errorf("config: QuorumSpec cannot be nil") + } + subCfg = &Configuration{ + qspec: qspec, + } + subCfg.RawConfiguration, err = c.SubRawConfiguration(cfg) + if err != nil { + return nil, err + } + subCfg.nodes = make([]*Node, subCfg.Size()) + for i, n := range subCfg.RawConfiguration.Nodes() { + subCfg.nodes[i] = &Node{n} + } + return subCfg, nil +} + +// Close closes a configuration created from the NewConfiguration method +// +// NOTE: A configuration created with ConfigurationFromRaw is closed when the original configuration is closed +// If you want the configurations to be independent you need to use NewConfiguration +func (c *Configuration) Close() error { + return c.RawConfiguration.Close() +} + // Nodes returns a slice of each available node. IDs are returned in the same // order as they were provided in the creation of the Manager. // @@ -68,71 +112,6 @@ func init() { } } -// Manager maintains a connection pool of nodes on -// which quorum calls can be performed. -type Manager struct { - *gorums.RawManager -} - -// NewManager returns a new Manager for managing connection to nodes added -// to the manager. This function accepts manager options used to configure -// various aspects of the manager. -func NewManager(opts ...gorums.ManagerOption) *Manager { - return &Manager{ - RawManager: gorums.NewRawManager(opts...), - } -} - -// NewConfiguration returns a configuration based on the provided list of nodes (required) -// and an optional quorum specification. The QuorumSpec is necessary for call types that -// must process replies. For configurations only used for unicast or multicast call types, -// a QuorumSpec is not needed. The QuorumSpec interface is also a ConfigOption. -// Nodes can be supplied using WithNodeMap or WithNodeList, or WithNodeIDs. -// A new configuration can also be created from an existing configuration, -// using the And, WithNewNodes, Except, and WithoutNodes methods. -func (m *Manager) NewConfiguration(opts ...gorums.ConfigOption) (c *Configuration, err error) { - if len(opts) < 1 || len(opts) > 2 { - return nil, fmt.Errorf("config: wrong number of options: %d", len(opts)) - } - c = &Configuration{} - for _, opt := range opts { - switch v := opt.(type) { - case gorums.NodeListOption: - c.RawConfiguration, err = gorums.NewRawConfiguration(m.RawManager, v) - if err != nil { - return nil, err - } - case QuorumSpec: - // Must be last since v may match QuorumSpec if it is interface{} - c.qspec = v - default: - return nil, fmt.Errorf("config: unknown option type: %v", v) - } - } - // return an error if the QuorumSpec interface is not empty and no implementation was provided. - var test interface{} = struct{}{} - if _, empty := test.(QuorumSpec); !empty && c.qspec == nil { - return nil, fmt.Errorf("config: missing required QuorumSpec") - } - // initialize the nodes slice - c.nodes = make([]*Node, c.Size()) - for i, n := range c.RawConfiguration { - c.nodes[i] = &Node{n} - } - return c, nil -} - -// Nodes returns a slice of available nodes on this manager. -// IDs are returned in the order they were added at creation of the manager. -func (m *Manager) Nodes() []*Node { - gorumsNodes := m.RawManager.Nodes() - nodes := make([]*Node, len(gorumsNodes)) - for i, n := range gorumsNodes { - nodes[i] = &Node{n} - } - return nodes -} - // Node encapsulates the state of a node on which a remote procedure call // can be performed. type Node struct { diff --git a/config.go b/config.go index 274836be..095d53ce 100644 --- a/config.go +++ b/config.go @@ -1,6 +1,9 @@ package gorums -import "fmt" +import ( + "errors" + "fmt" +) // RawConfiguration represents a static set of nodes on which quorum calls may be invoked. // @@ -8,23 +11,43 @@ import "fmt" // // This type is intended to be used by generated code. // You should use the generated `Configuration` type instead. -type RawConfiguration []*RawNode +type RawConfiguration struct { + RawNodes []*RawNode + *RawManager +} // NewRawConfiguration returns a configuration based on the provided list of nodes. // Nodes can be supplied using WithNodeMap or WithNodeList, or WithNodeIDs. // A new configuration can also be created from an existing configuration, // using the And, WithNewNodes, Except, and WithoutNodes methods. -func NewRawConfiguration(mgr *RawManager, opt NodeListOption) (nodes RawConfiguration, err error) { - if opt == nil { - return nil, fmt.Errorf("config: missing required node list") +func NewRawConfiguration(cfg NodeListOption, opts ...ManagerOption) (nodes RawConfiguration, err error) { + if cfg == nil { + return RawConfiguration{}, fmt.Errorf("config: missing required node list") + } + mgr := NewRawManager(opts...) + return cfg.newConfig(mgr) +} + +func (c *RawConfiguration) SubRawConfiguration(cfg NodeListOption) (nodes RawConfiguration, err error) { + if cfg == nil { + return RawConfiguration{}, fmt.Errorf("config: missing required node list") + } + return cfg.newConfig(c.RawManager) +} + +// Close closes a raw configuration created from the NewRawConfiguration method +func (c *RawConfiguration) Close() error { + if c.RawManager == nil { + return errors.New("RawConfiguration.Close: RawManager is nil, cannot close a sub-configuration") } - return opt.newConfig(mgr) + c.RawManager.Close() + return nil } // NodeIDs returns a slice of this configuration's Node IDs. func (c RawConfiguration) NodeIDs() []uint32 { - ids := make([]uint32, len(c)) - for i, node := range c { + ids := make([]uint32, len(c.RawNodes)) + for i, node := range c.RawNodes { ids[i] = node.ID() } return ids @@ -34,21 +57,21 @@ func (c RawConfiguration) NodeIDs() []uint32 { // // NOTE: mutating the returned slice is not supported. func (c RawConfiguration) Nodes() []*RawNode { - return c + return c.RawNodes } // Size returns the number of nodes in this configuration. func (c RawConfiguration) Size() int { - return len(c) + return len(c.RawNodes) } // Equal returns true if configurations b and c have the same set of nodes. func (c RawConfiguration) Equal(b RawConfiguration) bool { - if len(c) != len(b) { + if len(c.RawNodes) != len(b.RawNodes) { return false } - for i := range c { - if c[i].ID() != b[i].ID() { + for i := range c.RawNodes { + if c.RawNodes[i].ID() != b.RawNodes[i].ID() { return false } } @@ -56,5 +79,5 @@ func (c RawConfiguration) Equal(b RawConfiguration) bool { } func (c RawConfiguration) getMsgID() uint64 { - return c[0].mgr.getMsgID() + return c.RawNodes[0].mgr.getMsgID() } diff --git a/config_opts.go b/config_opts.go index 8a44af80..c102ddde 100644 --- a/config_opts.go +++ b/config_opts.go @@ -17,20 +17,20 @@ type nodeIDMap struct { idMap map[string]uint32 } -func (o nodeIDMap) newConfig(mgr *RawManager) (nodes RawConfiguration, err error) { +func (o nodeIDMap) newConfig(mgr *RawManager) (cfg RawConfiguration, err error) { if len(o.idMap) == 0 { - return nil, fmt.Errorf("config: missing required node map") + return RawConfiguration{}, fmt.Errorf("config: missing required node map") } - nodes = make(RawConfiguration, 0, len(o.idMap)) + nodes := make([]*RawNode, 0, len(o.idMap)) for naddr, id := range o.idMap { node, found := mgr.Node(id) if !found { node, err = NewRawNodeWithID(naddr, id) if err != nil { - return nil, err + return RawConfiguration{}, err } if err = mgr.AddNode(node); err != nil { - return nil, err + return RawConfiguration{}, err } } nodes = append(nodes, node) @@ -38,7 +38,13 @@ func (o nodeIDMap) newConfig(mgr *RawManager) (nodes RawConfiguration, err error // Sort nodes to ensure deterministic iteration. OrderedBy(ID).Sort(mgr.nodes) OrderedBy(ID).Sort(nodes) - return nodes, nil + + cfg = RawConfiguration{ + RawNodes: nodes, + RawManager: mgr, + } + + return cfg, nil } // WithNodeMap returns a NodeListOption containing the provided @@ -51,19 +57,19 @@ type nodeList struct { addrsList []string } -func (o nodeList) newConfig(mgr *RawManager) (nodes RawConfiguration, err error) { +func (o nodeList) newConfig(mgr *RawManager) (cfg RawConfiguration, err error) { if len(o.addrsList) == 0 { - return nil, fmt.Errorf("config: missing required node addresses") + return RawConfiguration{}, fmt.Errorf("config: missing required node addresses") } - nodes = make(RawConfiguration, 0, len(o.addrsList)) + nodes := make([]*RawNode, 0, len(o.addrsList)) for _, naddr := range o.addrsList { node, err := NewRawNode(naddr) if err != nil { - return nil, err + return RawConfiguration{}, err } if n, found := mgr.Node(node.ID()); !found { if err = mgr.AddNode(node); err != nil { - return nil, err + return RawConfiguration{}, err } } else { node = n @@ -73,11 +79,17 @@ func (o nodeList) newConfig(mgr *RawManager) (nodes RawConfiguration, err error) // Sort nodes to ensure deterministic iteration. OrderedBy(ID).Sort(mgr.nodes) OrderedBy(ID).Sort(nodes) - return nodes, nil + + cfg = RawConfiguration{ + RawNodes: nodes, + RawManager: mgr, + } + + return cfg, nil } // WithNodeList returns a NodeListOption containing the provided list of node addresses. -// With this option, node IDs are generated by the Manager. +// With this option, node IDs are generated by the RawManager. func WithNodeList(addrsList []string) NodeListOption { return &nodeList{addrsList: addrsList} } @@ -86,27 +98,33 @@ type nodeIDs struct { nodeIDs []uint32 } -func (o nodeIDs) newConfig(mgr *RawManager) (nodes RawConfiguration, err error) { +func (o nodeIDs) newConfig(mgr *RawManager) (cfg RawConfiguration, err error) { if len(o.nodeIDs) == 0 { - return nil, fmt.Errorf("config: missing required node IDs") + return RawConfiguration{}, fmt.Errorf("config: missing required node IDs") } - nodes = make(RawConfiguration, 0, len(o.nodeIDs)) + nodes := make([]*RawNode, 0, len(o.nodeIDs)) for _, id := range o.nodeIDs { node, found := mgr.Node(id) if !found { // Node IDs must have been registered previously - return nil, fmt.Errorf("config: node %d not found", id) + return RawConfiguration{}, fmt.Errorf("config: node %d not found", id) } nodes = append(nodes, node) } // Sort nodes to ensure deterministic iteration. OrderedBy(ID).Sort(mgr.nodes) OrderedBy(ID).Sort(nodes) - return nodes, nil + + cfg = RawConfiguration{ + RawNodes: nodes, + RawManager: mgr, + } + + return cfg, nil } // WithNodeIDs returns a NodeListOption containing a list of node IDs. -// This assumes that the provided node IDs have already been registered with the manager. +// This assumes that the provided node IDs have already been registered with the RawManager. func WithNodeIDs(ids []uint32) NodeListOption { return &nodeIDs{nodeIDs: ids} } @@ -119,7 +137,7 @@ type addNodes struct { func (o addNodes) newConfig(mgr *RawManager) (nodes RawConfiguration, err error) { newNodes, err := o.new.newConfig(mgr) if err != nil { - return nil, err + return RawConfiguration{}, err } ac := &addConfig{old: o.old, add: newNodes} return ac.newConfig(mgr) @@ -136,10 +154,10 @@ type addConfig struct { add RawConfiguration } -func (o addConfig) newConfig(mgr *RawManager) (nodes RawConfiguration, err error) { - nodes = make(RawConfiguration, 0, len(o.old)+len(o.add)) +func (o addConfig) newConfig(mgr *RawManager) (cfg RawConfiguration, err error) { + nodes := make([]*RawNode, 0, len(o.old.RawNodes)+len(o.add.RawNodes)) m := make(map[uint32]bool) - for _, n := range append(o.old, o.add...) { + for _, n := range append(o.old.RawNodes, o.add.RawNodes...) { if !m[n.id] { m[n.id] = true nodes = append(nodes, n) @@ -148,7 +166,13 @@ func (o addConfig) newConfig(mgr *RawManager) (nodes RawConfiguration, err error // Sort nodes to ensure deterministic iteration. OrderedBy(ID).Sort(mgr.nodes) OrderedBy(ID).Sort(nodes) - return nodes, err + + cfg = RawConfiguration{ + RawNodes: nodes, + RawManager: mgr, + } + + return cfg, err } // And returns a NodeListOption that can be used to create a new configuration combining c and d. @@ -163,8 +187,8 @@ func (c RawConfiguration) WithoutNodes(ids ...uint32) NodeListOption { for _, id := range ids { rmIDs[id] = true } - keepIDs := make([]uint32, 0, len(c)) - for _, cNode := range c { + keepIDs := make([]uint32, 0, len(c.RawNodes)) + for _, cNode := range c.RawNodes { if !rmIDs[cNode.id] { keepIDs = append(keepIDs, cNode.id) } @@ -176,11 +200,11 @@ func (c RawConfiguration) WithoutNodes(ids ...uint32) NodeListOption { // from c without the nodes in rm. func (c RawConfiguration) Except(rm RawConfiguration) NodeListOption { rmIDs := make(map[uint32]bool) - for _, rmNode := range rm { + for _, rmNode := range rm.RawNodes { rmIDs[rmNode.id] = true } - keepIDs := make([]uint32, 0, len(c)) - for _, cNode := range c { + keepIDs := make([]uint32, 0, len(c.RawNodes)) + for _, cNode := range c.RawNodes { if !rmIDs[cNode.id] { keepIDs = append(keepIDs, cNode.id) } diff --git a/config_test.go b/config_test.go index 3ff99c86..4cfb063a 100644 --- a/config_test.go +++ b/config_test.go @@ -13,8 +13,7 @@ import ( func TestNewConfigurationEmptyNodeList(t *testing.T) { wantErr := errors.New("config: missing required node addresses") - mgr := gorums.NewRawManager(gorums.WithNoConnect()) - _, err := gorums.NewRawConfiguration(mgr, gorums.WithNodeList([]string{})) + _, err := gorums.NewRawConfiguration(gorums.WithNodeList([]string{})) if err == nil { t.Fatalf("Expected error, got: %v, want: %v", err, wantErr) } @@ -24,8 +23,8 @@ func TestNewConfigurationEmptyNodeList(t *testing.T) { } func TestNewConfigurationNodeList(t *testing.T) { - mgr := gorums.NewRawManager(gorums.WithNoConnect()) - cfg, err := gorums.NewRawConfiguration(mgr, gorums.WithNodeList(nodes)) + cfg, err := gorums.NewRawConfiguration(gorums.WithNodeList(nodes)) + mgr := cfg.RawManager if err != nil { t.Fatal(err) } @@ -60,8 +59,8 @@ func TestNewConfigurationNodeList(t *testing.T) { } func TestNewConfigurationNodeMap(t *testing.T) { - mgr := gorums.NewRawManager(gorums.WithNoConnect()) - cfg, err := gorums.NewRawConfiguration(mgr, gorums.WithNodeMap(nodeMap)) + cfg, err := gorums.NewRawConfiguration(gorums.WithNodeMap(nodeMap)) + mgr := cfg.RawManager if err != nil { t.Fatal(err) } @@ -84,8 +83,7 @@ func TestNewConfigurationNodeMap(t *testing.T) { } func TestNewConfigurationNodeIDs(t *testing.T) { - mgr := gorums.NewRawManager(gorums.WithNoConnect()) - c1, err := gorums.NewRawConfiguration(mgr, gorums.WithNodeList(nodes)) + c1, err := gorums.NewRawConfiguration(gorums.WithNodeList(nodes), gorums.WithNoConnect()) if err != nil { t.Fatal(err) } @@ -95,7 +93,7 @@ func TestNewConfigurationNodeIDs(t *testing.T) { // Identical configurations c1 == c2 nodeIDs := c1.NodeIDs() - c2, err := gorums.NewRawConfiguration(mgr, gorums.WithNodeIDs(nodeIDs)) + c2, err := c1.SubRawConfiguration(gorums.WithNodeIDs(nodeIDs)) if err != nil { t.Fatal(err) } @@ -107,7 +105,7 @@ func TestNewConfigurationNodeIDs(t *testing.T) { } // Configuration with one less node |c3| == |c1| - 1 - c3, err := gorums.NewRawConfiguration(mgr, gorums.WithNodeIDs(nodeIDs[:len(nodeIDs)-1])) + c3, err := c1.SubRawConfiguration(gorums.WithNodeIDs(nodeIDs[:len(nodeIDs)-1])) if err != nil { t.Fatal(err) } @@ -120,21 +118,19 @@ func TestNewConfigurationNodeIDs(t *testing.T) { } func TestNewConfigurationAnd(t *testing.T) { - mgr := gorums.NewRawManager(gorums.WithNoConnect()) - c1, err := gorums.NewRawConfiguration(mgr, gorums.WithNodeList(nodes)) + c1, err := gorums.NewRawConfiguration(gorums.WithNodeList(nodes)) if err != nil { t.Fatal(err) } c2Nodes := []string{"127.0.0.1:8080"} - c2, err := gorums.NewRawConfiguration(mgr, gorums.WithNodeList(c2Nodes)) + c2, err := c1.SubRawConfiguration(gorums.WithNodeList(c2Nodes)) if err != nil { t.Fatal(err) } // Add newNodes to c1, giving a new c3 with a total of 3+2 nodes newNodes := []string{"127.0.0.1:9083", "127.0.0.1:9084"} - c3, err := gorums.NewRawConfiguration( - mgr, + c3, err := c1.SubRawConfiguration( c1.WithNewNodes(gorums.WithNodeList(newNodes)), ) if err != nil { @@ -145,8 +141,7 @@ func TestNewConfigurationAnd(t *testing.T) { } // Combine c2 to c1, giving a new c4 with a total of 3+1 nodes - c4, err := gorums.NewRawConfiguration( - mgr, + c4, err := c1.SubRawConfiguration( c1.And(c2), ) if err != nil { @@ -159,8 +154,7 @@ func TestNewConfigurationAnd(t *testing.T) { // Combine c2 to c4, giving a new c5 with a total of 4 nodes // c4 already contains all nodes from c2 (see above): c4 = c1+c2 // c5 should essentially just be a copy of c4 (ignoring duplicates from c2) - c5, err := gorums.NewRawConfiguration( - mgr, + c5, err := c1.SubRawConfiguration( c4.And(c2), ) if err != nil { @@ -172,14 +166,12 @@ func TestNewConfigurationAnd(t *testing.T) { } func TestNewConfigurationExcept(t *testing.T) { - mgr := gorums.NewRawManager(gorums.WithNoConnect()) - c1, err := gorums.NewRawConfiguration(mgr, gorums.WithNodeList(nodes)) + c1, err := gorums.NewRawConfiguration(gorums.WithNodeList(nodes)) if err != nil { t.Fatal(err) } - c2, err := gorums.NewRawConfiguration( - mgr, - c1.WithoutNodes(c1[0].ID()), + c2, err := c1.SubRawConfiguration( + c1.WithoutNodes(c1.Nodes()[0].ID()), ) if err != nil { t.Fatal(err) @@ -189,15 +181,13 @@ func TestNewConfigurationExcept(t *testing.T) { } newNodes := []string{"127.0.0.1:9083", "127.0.0.1:9084"} - c3, err := gorums.NewRawConfiguration( - mgr, + c3, err := c1.SubRawConfiguration( c1.WithNewNodes(gorums.WithNodeList(newNodes)), ) if err != nil { t.Fatal(err) } - c4, err := gorums.NewRawConfiguration( - mgr, + c4, err := c1.SubRawConfiguration( c3.Except(c1), ) if err != nil { @@ -214,8 +204,7 @@ func TestConfigConcurrentAccess(t *testing.T) { }) defer teardown() - mgr := gorumsTestMgr() - cfg, err := mgr.NewConfiguration(gorums.WithNodeList(addrs)) + cfg, err := dummy.NewConfiguration(nil, gorums.WithNodeList(addrs), gorumsTestMgrOpts()...) if err != nil { t.Fatal(err) } diff --git a/correctable.go b/correctable.go index 309d734e..7d46c4cb 100644 --- a/correctable.go +++ b/correctable.go @@ -101,11 +101,11 @@ type correctableCallState struct { // // This method should only be used by generated code. func (c RawConfiguration) CorrectableCall(ctx context.Context, d CorrectableCallData) *Correctable { - expectedReplies := len(c) + expectedReplies := len(c.RawNodes) md := ordering.Metadata_builder{MessageID: c.getMsgID(), Method: d.Method}.Build() replyChan := make(chan response, expectedReplies) - for _, n := range c { + for _, n := range c.RawNodes { msg := d.Message if d.PerNodeArgFn != nil { msg = d.PerNodeArgFn(d.Message, n.id) @@ -140,7 +140,7 @@ func (c RawConfiguration) handleCorrectableCall(ctx context.Context, corr *Corre ) if state.data.ServerStream { - for _, n := range c { + for _, n := range c.RawNodes { defer n.channel.deleteRouter(state.md.GetMessageID()) } } diff --git a/examples/storage/client.go b/examples/storage/client.go index cde90ebf..c0e188e4 100644 --- a/examples/storage/client.go +++ b/examples/storage/client.go @@ -14,19 +14,19 @@ func runClient(addresses []string) error { log.Fatalln("No addresses provided!") } - // init gorums manager - mgr := proto.NewManager( + // create configuration containing all nodes + cfg, err := proto.NewConfiguration( + &qspec{cfgSize: len(addresses)}, + gorums.WithNodeList(addresses), gorums.WithGrpcDialOptions( grpc.WithTransportCredentials(insecure.NewCredentials()), // disable TLS ), ) - // create configuration containing all nodes - cfg, err := mgr.NewConfiguration(&qspec{cfgSize: len(addresses)}, gorums.WithNodeList(addresses)) if err != nil { log.Fatal(err) } - return Repl(mgr, cfg) + return Repl(cfg) } type qspec struct { diff --git a/examples/storage/proto/storage_gorums.pb.go b/examples/storage/proto/storage_gorums.pb.go index e581245a..a4efbb5a 100644 --- a/examples/storage/proto/storage_gorums.pb.go +++ b/examples/storage/proto/storage_gorums.pb.go @@ -30,12 +30,30 @@ type Configuration struct { nodes []*Node } +func NewConfiguration(qspec QuorumSpec, cfg gorums.NodeListOption, opts ...gorums.ManagerOption) (c *Configuration, err error) { + if qspec == nil { + return nil, fmt.Errorf("config: QuorumSpec cannot be nil") + } + c = &Configuration{ + qspec: qspec, + } + c.RawConfiguration, err = gorums.NewRawConfiguration(cfg, opts...) + if err != nil { + return nil, err + } + c.nodes = make([]*Node, c.Size()) + for i, n := range c.RawConfiguration.RawNodes { + c.nodes[i] = &Node{n} + } + return c, nil +} + // ConfigurationFromRaw returns a new Configuration from the given raw configuration and QuorumSpec. // // This function may for example be used to "clone" a configuration but install a different QuorumSpec: // // cfg1, err := mgr.NewConfiguration(qspec1, opts...) -// cfg2 := ConfigurationFromRaw(cfg1.RawConfig, qspec2) +// cfg2 := ConfigurationFromRaw(cfg1.RawConfiguration, qspec2) func ConfigurationFromRaw(rawCfg gorums.RawConfiguration, qspec QuorumSpec) (*Configuration, error) { // return an error if the QuorumSpec interface is not empty and no implementation was provided. var test interface{} = struct{}{} @@ -48,12 +66,38 @@ func ConfigurationFromRaw(rawCfg gorums.RawConfiguration, qspec QuorumSpec) (*Co } // initialize the nodes slice newCfg.nodes = make([]*Node, newCfg.Size()) - for i, n := range rawCfg { + for i, n := range rawCfg.Nodes() { newCfg.nodes[i] = &Node{n} } return newCfg, nil } +func (c *Configuration) SubConfiguration(qspec QuorumSpec, cfg gorums.NodeListOption) (subCfg *Configuration, err error) { + if qspec == nil { + return nil, fmt.Errorf("config: QuorumSpec cannot be nil") + } + subCfg = &Configuration{ + qspec: qspec, + } + subCfg.RawConfiguration, err = c.SubRawConfiguration(cfg) + if err != nil { + return nil, err + } + subCfg.nodes = make([]*Node, subCfg.Size()) + for i, n := range subCfg.RawConfiguration.Nodes() { + subCfg.nodes[i] = &Node{n} + } + return subCfg, nil +} + +// Close closes a configuration created from the NewConfiguration method +// +// NOTE: A configuration created with ConfigurationFromRaw is closed when the original configuration is closed +// If you want the configurations to be independent you need to use NewConfiguration +func (c *Configuration) Close() error { + return c.RawConfiguration.Close() +} + // Nodes returns a slice of each available node. IDs are returned in the same // order as they were provided in the creation of the Manager. // @@ -79,88 +123,23 @@ func init() { } } -// Manager maintains a connection pool of nodes on -// which quorum calls can be performed. -type Manager struct { - *gorums.RawManager -} - -// NewManager returns a new Manager for managing connection to nodes added -// to the manager. This function accepts manager options used to configure -// various aspects of the manager. -func NewManager(opts ...gorums.ManagerOption) *Manager { - return &Manager{ - RawManager: gorums.NewRawManager(opts...), - } -} - -// NewConfiguration returns a configuration based on the provided list of nodes (required) -// and an optional quorum specification. The QuorumSpec is necessary for call types that -// must process replies. For configurations only used for unicast or multicast call types, -// a QuorumSpec is not needed. The QuorumSpec interface is also a ConfigOption. -// Nodes can be supplied using WithNodeMap or WithNodeList, or WithNodeIDs. -// A new configuration can also be created from an existing configuration, -// using the And, WithNewNodes, Except, and WithoutNodes methods. -func (m *Manager) NewConfiguration(opts ...gorums.ConfigOption) (c *Configuration, err error) { - if len(opts) < 1 || len(opts) > 2 { - return nil, fmt.Errorf("config: wrong number of options: %d", len(opts)) - } - c = &Configuration{} - for _, opt := range opts { - switch v := opt.(type) { - case gorums.NodeListOption: - c.RawConfiguration, err = gorums.NewRawConfiguration(m.RawManager, v) - if err != nil { - return nil, err - } - case QuorumSpec: - // Must be last since v may match QuorumSpec if it is interface{} - c.qspec = v - default: - return nil, fmt.Errorf("config: unknown option type: %v", v) - } - } - // return an error if the QuorumSpec interface is not empty and no implementation was provided. - var test interface{} = struct{}{} - if _, empty := test.(QuorumSpec); !empty && c.qspec == nil { - return nil, fmt.Errorf("config: missing required QuorumSpec") - } - // initialize the nodes slice - c.nodes = make([]*Node, c.Size()) - for i, n := range c.RawConfiguration { - c.nodes[i] = &Node{n} - } - return c, nil -} - -// Nodes returns a slice of available nodes on this manager. -// IDs are returned in the order they were added at creation of the manager. -func (m *Manager) Nodes() []*Node { - gorumsNodes := m.RawManager.Nodes() - nodes := make([]*Node, len(gorumsNodes)) - for i, n := range gorumsNodes { - nodes[i] = &Node{n} - } - return nodes -} - // Node encapsulates the state of a node on which a remote procedure call // can be performed. type Node struct { *gorums.RawNode } -// Storage is the client-side Configuration API for the Storage Service -type StorageConfigurationClient interface { +// StorageClient is the client interface for the Storage service. +type StorageClient interface { ReadQC(ctx context.Context, in *ReadRequest) (resp *ReadResponse, err error) WriteQC(ctx context.Context, in *WriteRequest) (resp *WriteResponse, err error) WriteMulticast(ctx context.Context, in *WriteRequest, opts ...gorums.CallOption) } // enforce interface compliance -var _ StorageConfigurationClient = (*Configuration)(nil) +var _ StorageClient = (*Configuration)(nil) -// Storage is the client-side Node API for the Storage Service +// StorageNodeClient is the single node client interface for the Storage service. type StorageNodeClient interface { ReadRPC(ctx context.Context, in *ReadRequest) (resp *ReadResponse, err error) WriteRPC(ctx context.Context, in *WriteRequest) (resp *WriteResponse, err error) diff --git a/examples/storage/repl.go b/examples/storage/repl.go index a939013c..b8373380 100644 --- a/examples/storage/repl.go +++ b/examples/storage/repl.go @@ -52,14 +52,12 @@ The command performs the write quorum call on node 0 and 2 ` type repl struct { - mgr *pb.Manager cfg *pb.Configuration term *term.Terminal } -func newRepl(mgr *pb.Manager, cfg *pb.Configuration) *repl { +func newRepl(cfg *pb.Configuration) *repl { return &repl{ - mgr: mgr, cfg: cfg, term: term.NewTerminal(struct { io.Reader @@ -90,8 +88,8 @@ func (r repl) ReadLine() (string, error) { // Repl runs an interactive Read-eval-print loop, that allows users to run commands that perform // RPCs and quorum calls using the manager and configuration. -func Repl(mgr *pb.Manager, defaultCfg *pb.Configuration) error { - r := newRepl(mgr, defaultCfg) +func Repl(cfg *pb.Configuration) error { + r := newRepl(cfg) fmt.Println(help) for { @@ -131,7 +129,7 @@ func Repl(mgr *pb.Manager, defaultCfg *pb.Configuration) error { r.multicast(args[1:]) case "nodes": fmt.Println("Nodes: ") - for i, n := range mgr.Nodes() { + for i, n := range cfg.RawManager.Nodes() { fmt.Printf("%d: %s\n", i, n.Address()) } default: @@ -291,7 +289,7 @@ func (r repl) parseConfiguration(cfgStr string) (cfg *pb.Configuration) { if i := strings.Index(cfgStr, ":"); i > -1 { var start, stop int var err error - numNodes := r.mgr.Size() + numNodes := r.cfg.RawManager.Size() if i == 0 { start = 0 } else { @@ -315,10 +313,10 @@ func (r repl) parseConfiguration(cfgStr string) (cfg *pb.Configuration) { return nil } nodes := make([]string, 0) - for _, node := range r.mgr.Nodes()[start:stop] { + for _, node := range r.cfg.RawManager.Nodes()[start:stop] { nodes = append(nodes, node.Address()) } - cfg, err = r.mgr.NewConfiguration(&qspec{cfgSize: stop - start}, gorums.WithNodeList(nodes)) + cfg, err = r.cfg.SubConfiguration(&qspec{cfgSize: stop - start}, gorums.WithNodeList(nodes)) if err != nil { fmt.Printf("Failed to create configuration: %v\n", err) return nil @@ -328,7 +326,7 @@ func (r repl) parseConfiguration(cfgStr string) (cfg *pb.Configuration) { // configuration using list of indices if indices := strings.Split(cfgStr, ","); len(indices) > 0 { selectedNodes := make([]string, 0, len(indices)) - nodes := r.mgr.Nodes() + nodes := r.cfg.RawManager.Nodes() for _, index := range indices { i, err := strconv.Atoi(index) if err != nil { @@ -341,7 +339,7 @@ func (r repl) parseConfiguration(cfgStr string) (cfg *pb.Configuration) { } selectedNodes = append(selectedNodes, nodes[i].Address()) } - cfg, err := r.mgr.NewConfiguration(&qspec{cfgSize: len(selectedNodes)}, gorums.WithNodeList(selectedNodes)) + cfg, err := r.cfg.SubConfiguration(&qspec{cfgSize: len(selectedNodes)}, gorums.WithNodeList(selectedNodes)) if err != nil { fmt.Printf("Failed to create configuration: %v\n", err) return nil diff --git a/mgr_test.go b/mgr_test.go index 6939d204..eafcea48 100644 --- a/mgr_test.go +++ b/mgr_test.go @@ -30,8 +30,8 @@ func TestManagerLogging(t *testing.T) { } func TestManagerAddNode(t *testing.T) { - mgr := gorums.NewRawManager(gorums.WithNoConnect()) - _, err := gorums.NewRawConfiguration(mgr, gorums.WithNodeMap(nodeMap)) + cfg, err := gorums.NewRawConfiguration(gorums.WithNodeMap(nodeMap), gorums.WithNoConnect()) + mgr := cfg.RawManager if err != nil { t.Fatal(err) } @@ -71,17 +71,19 @@ func TestManagerAddNodeWithConn(t *testing.T) { return srv }) defer teardown() - mgr := gorums.NewRawManager( + + ctx, err := gorums.NewRawConfiguration(gorums.WithNodeList(addrs[:2]), gorums.WithGrpcDialOptions( grpc.WithTransportCredentials(insecure.NewCredentials()), ), ) - defer mgr.Close() - - _, err := gorums.NewRawConfiguration(mgr, gorums.WithNodeList(addrs[:2])) if err != nil { t.Fatal(err) } + defer ctx.Close() + + mgr := ctx.RawManager + if mgr.Size() != len(addrs)-1 { t.Errorf("mgr.Size() = %d, expected %d", mgr.Size(), len(addrs)-1) } diff --git a/multicast.go b/multicast.go index f22c6550..f7f034ac 100644 --- a/multicast.go +++ b/multicast.go @@ -17,9 +17,9 @@ func (c RawConfiguration) Multicast(ctx context.Context, d QuorumCallData, opts var replyChan chan response if !o.noSendWaiting { - replyChan = make(chan response, len(c)) + replyChan = make(chan response, c.Size()) } - for _, n := range c { + for _, n := range c.Nodes() { msg := d.Message if d.PerNodeArgFn != nil { msg = d.PerNodeArgFn(d.Message, n.id) diff --git a/quorumcall.go b/quorumcall.go index 40d6c7be..004a69ff 100644 --- a/quorumcall.go +++ b/quorumcall.go @@ -23,11 +23,11 @@ type QuorumCallData struct { // // This method should be used by generated code only. func (c RawConfiguration) QuorumCall(ctx context.Context, d QuorumCallData) (resp protoreflect.ProtoMessage, err error) { - expectedReplies := len(c) + expectedReplies := c.Size() md := ordering.Metadata_builder{MessageID: c.getMsgID(), Method: d.Method}.Build() replyChan := make(chan response, expectedReplies) - for _, n := range c { + for _, n := range c.Nodes() { msg := d.Message if d.PerNodeArgFn != nil { msg = d.PerNodeArgFn(d.Message, n.id) diff --git a/rpc_test.go b/rpc_test.go index e5b70925..4766c9a9 100644 --- a/rpc_test.go +++ b/rpc_test.go @@ -18,14 +18,12 @@ func TestRPCCallSuccess(t *testing.T) { }) defer teardown() - mgr := gorumsTestMgr() - - _, err := mgr.NewConfiguration(gorums.WithNodeList(addrs)) + cfg, err := dummy.NewConfiguration(nil, gorums.WithNodeList(addrs), gorumsTestMgrOpts()...) if err != nil { t.Fatal(err) } - node := mgr.Nodes()[0] + node := cfg.Nodes()[0] ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() response, err := node.RPCCall(ctx, gorums.CallData{ @@ -44,16 +42,15 @@ func TestRPCCallDownedNode(t *testing.T) { addrs, teardown := gorums.TestSetup(t, 1, func(_ int) gorums.ServerIface { return initServer() }) - mgr := gorumsTestMgr() - _, err := mgr.NewConfiguration(gorums.WithNodeList(addrs)) + cfg, err := dummy.NewConfiguration(nil, gorums.WithNodeList(addrs), gorumsTestMgrOpts()...) if err != nil { t.Fatal(err) } teardown() // stop all servers on purpose time.Sleep(300 * time.Millisecond) // servers are not stopped immediately - node := mgr.Nodes()[0] + node := cfg.Nodes()[0] ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() response, err := node.RPCCall(ctx, gorums.CallData{ @@ -74,14 +71,12 @@ func TestRPCCallTimedOut(t *testing.T) { }) defer teardown() - mgr := gorumsTestMgr() - - _, err := mgr.NewConfiguration(gorums.WithNodeList(addrs)) + cfg, err := dummy.NewConfiguration(nil, gorums.WithNodeList(addrs), gorumsTestMgrOpts()...) if err != nil { t.Fatal(err) } - node := mgr.Nodes()[0] + node := cfg.Nodes()[0] ctx, cancel := context.WithTimeout(context.Background(), 0*time.Second) time.Sleep(50 * time.Millisecond) defer cancel() @@ -103,13 +98,11 @@ func initServer() *gorums.Server { return srv } -func gorumsTestMgr() *dummy.Manager { - mgr := dummy.NewManager( - gorums.WithGrpcDialOptions( - grpc.WithTransportCredentials(insecure.NewCredentials()), - ), - ) - return mgr +func gorumsTestMgrOpts() []gorums.ManagerOption { + opts := []gorums.ManagerOption{gorums.WithGrpcDialOptions( + grpc.WithTransportCredentials(insecure.NewCredentials()), + )} + return opts } type testSrv struct{} diff --git a/tests/config/config_gorums.pb.go b/tests/config/config_gorums.pb.go index a173eca9..6fe9b83d 100644 --- a/tests/config/config_gorums.pb.go +++ b/tests/config/config_gorums.pb.go @@ -29,12 +29,30 @@ type Configuration struct { nodes []*Node } +func NewConfiguration(qspec QuorumSpec, cfg gorums.NodeListOption, opts ...gorums.ManagerOption) (c *Configuration, err error) { + if qspec == nil { + return nil, fmt.Errorf("config: QuorumSpec cannot be nil") + } + c = &Configuration{ + qspec: qspec, + } + c.RawConfiguration, err = gorums.NewRawConfiguration(cfg, opts...) + if err != nil { + return nil, err + } + c.nodes = make([]*Node, c.Size()) + for i, n := range c.RawConfiguration.RawNodes { + c.nodes[i] = &Node{n} + } + return c, nil +} + // ConfigurationFromRaw returns a new Configuration from the given raw configuration and QuorumSpec. // // This function may for example be used to "clone" a configuration but install a different QuorumSpec: // // cfg1, err := mgr.NewConfiguration(qspec1, opts...) -// cfg2 := ConfigurationFromRaw(cfg1.RawConfig, qspec2) +// cfg2 := ConfigurationFromRaw(cfg1.RawConfiguration, qspec2) func ConfigurationFromRaw(rawCfg gorums.RawConfiguration, qspec QuorumSpec) (*Configuration, error) { // return an error if the QuorumSpec interface is not empty and no implementation was provided. var test interface{} = struct{}{} @@ -47,12 +65,38 @@ func ConfigurationFromRaw(rawCfg gorums.RawConfiguration, qspec QuorumSpec) (*Co } // initialize the nodes slice newCfg.nodes = make([]*Node, newCfg.Size()) - for i, n := range rawCfg { + for i, n := range rawCfg.Nodes() { newCfg.nodes[i] = &Node{n} } return newCfg, nil } +func (c *Configuration) SubConfiguration(qspec QuorumSpec, cfg gorums.NodeListOption) (subCfg *Configuration, err error) { + if qspec == nil { + return nil, fmt.Errorf("config: QuorumSpec cannot be nil") + } + subCfg = &Configuration{ + qspec: qspec, + } + subCfg.RawConfiguration, err = c.SubRawConfiguration(cfg) + if err != nil { + return nil, err + } + subCfg.nodes = make([]*Node, subCfg.Size()) + for i, n := range subCfg.RawConfiguration.Nodes() { + subCfg.nodes[i] = &Node{n} + } + return subCfg, nil +} + +// Close closes a configuration created from the NewConfiguration method +// +// NOTE: A configuration created with ConfigurationFromRaw is closed when the original configuration is closed +// If you want the configurations to be independent you need to use NewConfiguration +func (c *Configuration) Close() error { + return c.RawConfiguration.Close() +} + // Nodes returns a slice of each available node. IDs are returned in the same // order as they were provided in the creation of the Manager. // @@ -78,71 +122,6 @@ func init() { } } -// Manager maintains a connection pool of nodes on -// which quorum calls can be performed. -type Manager struct { - *gorums.RawManager -} - -// NewManager returns a new Manager for managing connection to nodes added -// to the manager. This function accepts manager options used to configure -// various aspects of the manager. -func NewManager(opts ...gorums.ManagerOption) *Manager { - return &Manager{ - RawManager: gorums.NewRawManager(opts...), - } -} - -// NewConfiguration returns a configuration based on the provided list of nodes (required) -// and an optional quorum specification. The QuorumSpec is necessary for call types that -// must process replies. For configurations only used for unicast or multicast call types, -// a QuorumSpec is not needed. The QuorumSpec interface is also a ConfigOption. -// Nodes can be supplied using WithNodeMap or WithNodeList, or WithNodeIDs. -// A new configuration can also be created from an existing configuration, -// using the And, WithNewNodes, Except, and WithoutNodes methods. -func (m *Manager) NewConfiguration(opts ...gorums.ConfigOption) (c *Configuration, err error) { - if len(opts) < 1 || len(opts) > 2 { - return nil, fmt.Errorf("config: wrong number of options: %d", len(opts)) - } - c = &Configuration{} - for _, opt := range opts { - switch v := opt.(type) { - case gorums.NodeListOption: - c.RawConfiguration, err = gorums.NewRawConfiguration(m.RawManager, v) - if err != nil { - return nil, err - } - case QuorumSpec: - // Must be last since v may match QuorumSpec if it is interface{} - c.qspec = v - default: - return nil, fmt.Errorf("config: unknown option type: %v", v) - } - } - // return an error if the QuorumSpec interface is not empty and no implementation was provided. - var test interface{} = struct{}{} - if _, empty := test.(QuorumSpec); !empty && c.qspec == nil { - return nil, fmt.Errorf("config: missing required QuorumSpec") - } - // initialize the nodes slice - c.nodes = make([]*Node, c.Size()) - for i, n := range c.RawConfiguration { - c.nodes[i] = &Node{n} - } - return c, nil -} - -// Nodes returns a slice of available nodes on this manager. -// IDs are returned in the order they were added at creation of the manager. -func (m *Manager) Nodes() []*Node { - gorumsNodes := m.RawManager.Nodes() - nodes := make([]*Node, len(gorumsNodes)) - for i, n := range gorumsNodes { - nodes[i] = &Node{n} - } - return nodes -} - // Node encapsulates the state of a node on which a remote procedure call // can be performed. type Node struct { diff --git a/tests/config/config_test.go b/tests/config/config_test.go index 6478c261..5da830ae 100644 --- a/tests/config/config_test.go +++ b/tests/config/config_test.go @@ -45,7 +45,7 @@ func (q cfgQSpec) ConfigQF(_ *Request, replies map[uint32]*Response) (*Response, // setup returns a new configuration of cfgSize and a corresponding teardown function. // Calling setup multiple times will return a different configuration with different // sets of nodes. -func setup(t *testing.T, mgr *Manager, cfgSize int) (cfg *Configuration, teardown func()) { +func setup(t *testing.T, mainCfg *Configuration, cfgSize int) (cfg *Configuration, teardown func()) { t.Helper() srvs := make([]*cfgSrv, cfgSize) for i := range srvs { @@ -59,12 +59,12 @@ func setup(t *testing.T, mgr *Manager, cfgSize int) (cfg *Configuration, teardow for i := range srvs { srvs[i].name = addrs[i] } - cfg, err := mgr.NewConfiguration(newQSpec(cfgSize), gorums.WithNodeList(addrs)) + cfg, err := mainCfg.SubConfiguration(newQSpec(cfgSize), gorums.WithNodeList(addrs)) if err != nil { t.Fatal(err) } teardown = func() { - mgr.Close() + mainCfg.Close() closeServers() } return cfg, teardown @@ -84,23 +84,28 @@ func TestConfig(t *testing.T) { } } } - mgr := NewManager( + cfg, err := NewConfiguration( + &cfgQSpec{}, + gorums.WithNodeList([]string{}), gorums.WithGrpcDialOptions( grpc.WithTransportCredentials(insecure.NewCredentials()), ), ) - c1, teardown1 := setup(t, mgr, 4) + if err != nil { + t.Fatal(err) + } + c1, teardown1 := setup(t, cfg, 4) defer teardown1() fmt.Println("--- c1 ", c1.Nodes()) callRPC(c1) - c2, teardown2 := setup(t, mgr, 2) + c2, teardown2 := setup(t, cfg, 2) defer teardown2() fmt.Println("--- c2 ", c2.Nodes()) callRPC(c2) newNodeList := c1.And(c2) - c3, err := mgr.NewConfiguration( + c3, err := cfg.SubConfiguration( newQSpec(c1.Size()+c2.Size()), newNodeList, ) @@ -111,7 +116,7 @@ func TestConfig(t *testing.T) { callRPC(c3) rmNodeList := c3.Except(c1) - c4, err := mgr.NewConfiguration( + c4, err := cfg.SubConfiguration( newQSpec(c2.Size()), rmNodeList, ) diff --git a/tests/correctable/correctable_gorums.pb.go b/tests/correctable/correctable_gorums.pb.go index a918f80f..39c43802 100644 --- a/tests/correctable/correctable_gorums.pb.go +++ b/tests/correctable/correctable_gorums.pb.go @@ -31,12 +31,30 @@ type Configuration struct { nodes []*Node } +func NewConfiguration(qspec QuorumSpec, cfg gorums.NodeListOption, opts ...gorums.ManagerOption) (c *Configuration, err error) { + if qspec == nil { + return nil, fmt.Errorf("config: QuorumSpec cannot be nil") + } + c = &Configuration{ + qspec: qspec, + } + c.RawConfiguration, err = gorums.NewRawConfiguration(cfg, opts...) + if err != nil { + return nil, err + } + c.nodes = make([]*Node, c.Size()) + for i, n := range c.RawConfiguration.RawNodes { + c.nodes[i] = &Node{n} + } + return c, nil +} + // ConfigurationFromRaw returns a new Configuration from the given raw configuration and QuorumSpec. // // This function may for example be used to "clone" a configuration but install a different QuorumSpec: // // cfg1, err := mgr.NewConfiguration(qspec1, opts...) -// cfg2 := ConfigurationFromRaw(cfg1.RawConfig, qspec2) +// cfg2 := ConfigurationFromRaw(cfg1.RawConfiguration, qspec2) func ConfigurationFromRaw(rawCfg gorums.RawConfiguration, qspec QuorumSpec) (*Configuration, error) { // return an error if the QuorumSpec interface is not empty and no implementation was provided. var test interface{} = struct{}{} @@ -49,12 +67,38 @@ func ConfigurationFromRaw(rawCfg gorums.RawConfiguration, qspec QuorumSpec) (*Co } // initialize the nodes slice newCfg.nodes = make([]*Node, newCfg.Size()) - for i, n := range rawCfg { + for i, n := range rawCfg.Nodes() { newCfg.nodes[i] = &Node{n} } return newCfg, nil } +func (c *Configuration) SubConfiguration(qspec QuorumSpec, cfg gorums.NodeListOption) (subCfg *Configuration, err error) { + if qspec == nil { + return nil, fmt.Errorf("config: QuorumSpec cannot be nil") + } + subCfg = &Configuration{ + qspec: qspec, + } + subCfg.RawConfiguration, err = c.SubRawConfiguration(cfg) + if err != nil { + return nil, err + } + subCfg.nodes = make([]*Node, subCfg.Size()) + for i, n := range subCfg.RawConfiguration.Nodes() { + subCfg.nodes[i] = &Node{n} + } + return subCfg, nil +} + +// Close closes a configuration created from the NewConfiguration method +// +// NOTE: A configuration created with ConfigurationFromRaw is closed when the original configuration is closed +// If you want the configurations to be independent you need to use NewConfiguration +func (c *Configuration) Close() error { + return c.RawConfiguration.Close() +} + // Nodes returns a slice of each available node. IDs are returned in the same // order as they were provided in the creation of the Manager. // @@ -80,71 +124,6 @@ func init() { } } -// Manager maintains a connection pool of nodes on -// which quorum calls can be performed. -type Manager struct { - *gorums.RawManager -} - -// NewManager returns a new Manager for managing connection to nodes added -// to the manager. This function accepts manager options used to configure -// various aspects of the manager. -func NewManager(opts ...gorums.ManagerOption) *Manager { - return &Manager{ - RawManager: gorums.NewRawManager(opts...), - } -} - -// NewConfiguration returns a configuration based on the provided list of nodes (required) -// and an optional quorum specification. The QuorumSpec is necessary for call types that -// must process replies. For configurations only used for unicast or multicast call types, -// a QuorumSpec is not needed. The QuorumSpec interface is also a ConfigOption. -// Nodes can be supplied using WithNodeMap or WithNodeList, or WithNodeIDs. -// A new configuration can also be created from an existing configuration, -// using the And, WithNewNodes, Except, and WithoutNodes methods. -func (m *Manager) NewConfiguration(opts ...gorums.ConfigOption) (c *Configuration, err error) { - if len(opts) < 1 || len(opts) > 2 { - return nil, fmt.Errorf("config: wrong number of options: %d", len(opts)) - } - c = &Configuration{} - for _, opt := range opts { - switch v := opt.(type) { - case gorums.NodeListOption: - c.RawConfiguration, err = gorums.NewRawConfiguration(m.RawManager, v) - if err != nil { - return nil, err - } - case QuorumSpec: - // Must be last since v may match QuorumSpec if it is interface{} - c.qspec = v - default: - return nil, fmt.Errorf("config: unknown option type: %v", v) - } - } - // return an error if the QuorumSpec interface is not empty and no implementation was provided. - var test interface{} = struct{}{} - if _, empty := test.(QuorumSpec); !empty && c.qspec == nil { - return nil, fmt.Errorf("config: missing required QuorumSpec") - } - // initialize the nodes slice - c.nodes = make([]*Node, c.Size()) - for i, n := range c.RawConfiguration { - c.nodes[i] = &Node{n} - } - return c, nil -} - -// Nodes returns a slice of available nodes on this manager. -// IDs are returned in the order they were added at creation of the manager. -func (m *Manager) Nodes() []*Node { - gorumsNodes := m.RawManager.Nodes() - nodes := make([]*Node, len(gorumsNodes)) - for i, n := range gorumsNodes { - nodes[i] = &Node{n} - } - return nodes -} - // Node encapsulates the state of a node on which a remote procedure call // can be performed. type Node struct { diff --git a/tests/correctable/correctable_test.go b/tests/correctable/correctable_test.go index 62e7c7ba..f6d2d5d1 100644 --- a/tests/correctable/correctable_test.go +++ b/tests/correctable/correctable_test.go @@ -22,13 +22,13 @@ func run(t *testing.T, n int, div int, corr func(context.Context, *Configuration }) defer teardown() - mgr := NewManager( + cfg, err := NewConfiguration( + qspec{div, n}, + gorums.WithNodeList(addrs), gorums.WithGrpcDialOptions( grpc.WithTransportCredentials(insecure.NewCredentials()), ), ) - - cfg, err := mgr.NewConfiguration(qspec{div, n}, gorums.WithNodeList(addrs)) if err != nil { t.Fatal(err) } diff --git a/tests/dummy/dummy_gorums.pb.go b/tests/dummy/dummy_gorums.pb.go index a9058a01..aa0f3e9c 100644 --- a/tests/dummy/dummy_gorums.pb.go +++ b/tests/dummy/dummy_gorums.pb.go @@ -28,12 +28,30 @@ type Configuration struct { nodes []*Node } +func NewConfiguration(qspec QuorumSpec, cfg gorums.NodeListOption, opts ...gorums.ManagerOption) (c *Configuration, err error) { + if qspec == nil { + return nil, fmt.Errorf("config: QuorumSpec cannot be nil") + } + c = &Configuration{ + qspec: qspec, + } + c.RawConfiguration, err = gorums.NewRawConfiguration(cfg, opts...) + if err != nil { + return nil, err + } + c.nodes = make([]*Node, c.Size()) + for i, n := range c.RawConfiguration.RawNodes { + c.nodes[i] = &Node{n} + } + return c, nil +} + // ConfigurationFromRaw returns a new Configuration from the given raw configuration and QuorumSpec. // // This function may for example be used to "clone" a configuration but install a different QuorumSpec: // // cfg1, err := mgr.NewConfiguration(qspec1, opts...) -// cfg2 := ConfigurationFromRaw(cfg1.RawConfig, qspec2) +// cfg2 := ConfigurationFromRaw(cfg1.RawConfiguration, qspec2) func ConfigurationFromRaw(rawCfg gorums.RawConfiguration, qspec QuorumSpec) (*Configuration, error) { // return an error if the QuorumSpec interface is not empty and no implementation was provided. var test interface{} = struct{}{} @@ -46,12 +64,38 @@ func ConfigurationFromRaw(rawCfg gorums.RawConfiguration, qspec QuorumSpec) (*Co } // initialize the nodes slice newCfg.nodes = make([]*Node, newCfg.Size()) - for i, n := range rawCfg { + for i, n := range rawCfg.Nodes() { newCfg.nodes[i] = &Node{n} } return newCfg, nil } +func (c *Configuration) SubConfiguration(qspec QuorumSpec, cfg gorums.NodeListOption) (subCfg *Configuration, err error) { + if qspec == nil { + return nil, fmt.Errorf("config: QuorumSpec cannot be nil") + } + subCfg = &Configuration{ + qspec: qspec, + } + subCfg.RawConfiguration, err = c.SubRawConfiguration(cfg) + if err != nil { + return nil, err + } + subCfg.nodes = make([]*Node, subCfg.Size()) + for i, n := range subCfg.RawConfiguration.Nodes() { + subCfg.nodes[i] = &Node{n} + } + return subCfg, nil +} + +// Close closes a configuration created from the NewConfiguration method +// +// NOTE: A configuration created with ConfigurationFromRaw is closed when the original configuration is closed +// If you want the configurations to be independent you need to use NewConfiguration +func (c *Configuration) Close() error { + return c.RawConfiguration.Close() +} + // Nodes returns a slice of each available node. IDs are returned in the same // order as they were provided in the creation of the Manager. // @@ -77,71 +121,6 @@ func init() { } } -// Manager maintains a connection pool of nodes on -// which quorum calls can be performed. -type Manager struct { - *gorums.RawManager -} - -// NewManager returns a new Manager for managing connection to nodes added -// to the manager. This function accepts manager options used to configure -// various aspects of the manager. -func NewManager(opts ...gorums.ManagerOption) *Manager { - return &Manager{ - RawManager: gorums.NewRawManager(opts...), - } -} - -// NewConfiguration returns a configuration based on the provided list of nodes (required) -// and an optional quorum specification. The QuorumSpec is necessary for call types that -// must process replies. For configurations only used for unicast or multicast call types, -// a QuorumSpec is not needed. The QuorumSpec interface is also a ConfigOption. -// Nodes can be supplied using WithNodeMap or WithNodeList, or WithNodeIDs. -// A new configuration can also be created from an existing configuration, -// using the And, WithNewNodes, Except, and WithoutNodes methods. -func (m *Manager) NewConfiguration(opts ...gorums.ConfigOption) (c *Configuration, err error) { - if len(opts) < 1 || len(opts) > 2 { - return nil, fmt.Errorf("config: wrong number of options: %d", len(opts)) - } - c = &Configuration{} - for _, opt := range opts { - switch v := opt.(type) { - case gorums.NodeListOption: - c.RawConfiguration, err = gorums.NewRawConfiguration(m.RawManager, v) - if err != nil { - return nil, err - } - case QuorumSpec: - // Must be last since v may match QuorumSpec if it is interface{} - c.qspec = v - default: - return nil, fmt.Errorf("config: unknown option type: %v", v) - } - } - // return an error if the QuorumSpec interface is not empty and no implementation was provided. - var test interface{} = struct{}{} - if _, empty := test.(QuorumSpec); !empty && c.qspec == nil { - return nil, fmt.Errorf("config: missing required QuorumSpec") - } - // initialize the nodes slice - c.nodes = make([]*Node, c.Size()) - for i, n := range c.RawConfiguration { - c.nodes[i] = &Node{n} - } - return c, nil -} - -// Nodes returns a slice of available nodes on this manager. -// IDs are returned in the order they were added at creation of the manager. -func (m *Manager) Nodes() []*Node { - gorumsNodes := m.RawManager.Nodes() - nodes := make([]*Node, len(gorumsNodes)) - for i, n := range gorumsNodes { - nodes[i] = &Node{n} - } - return nodes -} - // Node encapsulates the state of a node on which a remote procedure call // can be performed. type Node struct { diff --git a/tests/metadata/metadata_gorums.pb.go b/tests/metadata/metadata_gorums.pb.go index bd775101..485a8cd0 100644 --- a/tests/metadata/metadata_gorums.pb.go +++ b/tests/metadata/metadata_gorums.pb.go @@ -29,12 +29,30 @@ type Configuration struct { nodes []*Node } +func NewConfiguration(qspec QuorumSpec, cfg gorums.NodeListOption, opts ...gorums.ManagerOption) (c *Configuration, err error) { + if qspec == nil { + return nil, fmt.Errorf("config: QuorumSpec cannot be nil") + } + c = &Configuration{ + qspec: qspec, + } + c.RawConfiguration, err = gorums.NewRawConfiguration(cfg, opts...) + if err != nil { + return nil, err + } + c.nodes = make([]*Node, c.Size()) + for i, n := range c.RawConfiguration.RawNodes { + c.nodes[i] = &Node{n} + } + return c, nil +} + // ConfigurationFromRaw returns a new Configuration from the given raw configuration and QuorumSpec. // // This function may for example be used to "clone" a configuration but install a different QuorumSpec: // // cfg1, err := mgr.NewConfiguration(qspec1, opts...) -// cfg2 := ConfigurationFromRaw(cfg1.RawConfig, qspec2) +// cfg2 := ConfigurationFromRaw(cfg1.RawConfiguration, qspec2) func ConfigurationFromRaw(rawCfg gorums.RawConfiguration, qspec QuorumSpec) (*Configuration, error) { // return an error if the QuorumSpec interface is not empty and no implementation was provided. var test interface{} = struct{}{} @@ -47,12 +65,38 @@ func ConfigurationFromRaw(rawCfg gorums.RawConfiguration, qspec QuorumSpec) (*Co } // initialize the nodes slice newCfg.nodes = make([]*Node, newCfg.Size()) - for i, n := range rawCfg { + for i, n := range rawCfg.Nodes() { newCfg.nodes[i] = &Node{n} } return newCfg, nil } +func (c *Configuration) SubConfiguration(qspec QuorumSpec, cfg gorums.NodeListOption) (subCfg *Configuration, err error) { + if qspec == nil { + return nil, fmt.Errorf("config: QuorumSpec cannot be nil") + } + subCfg = &Configuration{ + qspec: qspec, + } + subCfg.RawConfiguration, err = c.SubRawConfiguration(cfg) + if err != nil { + return nil, err + } + subCfg.nodes = make([]*Node, subCfg.Size()) + for i, n := range subCfg.RawConfiguration.Nodes() { + subCfg.nodes[i] = &Node{n} + } + return subCfg, nil +} + +// Close closes a configuration created from the NewConfiguration method +// +// NOTE: A configuration created with ConfigurationFromRaw is closed when the original configuration is closed +// If you want the configurations to be independent you need to use NewConfiguration +func (c *Configuration) Close() error { + return c.RawConfiguration.Close() +} + // Nodes returns a slice of each available node. IDs are returned in the same // order as they were provided in the creation of the Manager. // @@ -78,71 +122,6 @@ func init() { } } -// Manager maintains a connection pool of nodes on -// which quorum calls can be performed. -type Manager struct { - *gorums.RawManager -} - -// NewManager returns a new Manager for managing connection to nodes added -// to the manager. This function accepts manager options used to configure -// various aspects of the manager. -func NewManager(opts ...gorums.ManagerOption) *Manager { - return &Manager{ - RawManager: gorums.NewRawManager(opts...), - } -} - -// NewConfiguration returns a configuration based on the provided list of nodes (required) -// and an optional quorum specification. The QuorumSpec is necessary for call types that -// must process replies. For configurations only used for unicast or multicast call types, -// a QuorumSpec is not needed. The QuorumSpec interface is also a ConfigOption. -// Nodes can be supplied using WithNodeMap or WithNodeList, or WithNodeIDs. -// A new configuration can also be created from an existing configuration, -// using the And, WithNewNodes, Except, and WithoutNodes methods. -func (m *Manager) NewConfiguration(opts ...gorums.ConfigOption) (c *Configuration, err error) { - if len(opts) < 1 || len(opts) > 2 { - return nil, fmt.Errorf("config: wrong number of options: %d", len(opts)) - } - c = &Configuration{} - for _, opt := range opts { - switch v := opt.(type) { - case gorums.NodeListOption: - c.RawConfiguration, err = gorums.NewRawConfiguration(m.RawManager, v) - if err != nil { - return nil, err - } - case QuorumSpec: - // Must be last since v may match QuorumSpec if it is interface{} - c.qspec = v - default: - return nil, fmt.Errorf("config: unknown option type: %v", v) - } - } - // return an error if the QuorumSpec interface is not empty and no implementation was provided. - var test interface{} = struct{}{} - if _, empty := test.(QuorumSpec); !empty && c.qspec == nil { - return nil, fmt.Errorf("config: missing required QuorumSpec") - } - // initialize the nodes slice - c.nodes = make([]*Node, c.Size()) - for i, n := range c.RawConfiguration { - c.nodes[i] = &Node{n} - } - return c, nil -} - -// Nodes returns a slice of available nodes on this manager. -// IDs are returned in the order they were added at creation of the manager. -func (m *Manager) Nodes() []*Node { - gorumsNodes := m.RawManager.Nodes() - nodes := make([]*Node, len(gorumsNodes)) - for i, n := range gorumsNodes { - nodes[i] = &Node{n} - } - return nodes -} - // Node encapsulates the state of a node on which a remote procedure call // can be performed. type Node struct { diff --git a/tests/metadata/metadata_test.go b/tests/metadata/metadata_test.go index 0a2454ed..c9aef04e 100644 --- a/tests/metadata/metadata_test.go +++ b/tests/metadata/metadata_test.go @@ -58,18 +58,19 @@ func TestMetadata(t *testing.T) { "id": fmt.Sprint(want), }) - mgr := NewManager( + cfg, err := NewConfiguration( + nil, + gorums.WithNodeList(addrs), gorums.WithMetadata(md), gorums.WithGrpcDialOptions( grpc.WithTransportCredentials(insecure.NewCredentials()), ), ) - _, err := mgr.NewConfiguration(gorums.WithNodeList(addrs)) if err != nil { t.Fatal(err) } - node := mgr.Nodes()[0] + node := cfg.Nodes()[0] resp, err := node.IDFromMD(context.Background(), &emptypb.Empty{}) if err != nil { t.Fatalf("RPC error: %v", err) @@ -90,18 +91,19 @@ func TestPerNodeMetadata(t *testing.T) { }) } - mgr := NewManager( + cfg, err := NewConfiguration( + nil, + gorums.WithNodeList(addrs), gorums.WithPerNodeMetadata(perNodeMD), gorums.WithGrpcDialOptions( grpc.WithTransportCredentials(insecure.NewCredentials()), ), ) - _, err := mgr.NewConfiguration(gorums.WithNodeList(addrs)) if err != nil { t.Fatal(err) } - for _, node := range mgr.Nodes() { + for _, node := range cfg.Nodes() { resp, err := node.IDFromMD(context.Background(), &emptypb.Empty{}) if err != nil { t.Fatalf("RPC error: %v", err) @@ -117,17 +119,18 @@ func TestCanGetPeerInfo(t *testing.T) { addrs, teardown := gorums.TestSetup(t, 1, func(_ int) gorums.ServerIface { return initServer(t) }) defer teardown() - mgr := NewManager( + cfg, err := NewConfiguration( + nil, + gorums.WithNodeList(addrs), gorums.WithGrpcDialOptions( grpc.WithTransportCredentials(insecure.NewCredentials()), ), ) - _, err := mgr.NewConfiguration(gorums.WithNodeList(addrs)) if err != nil { t.Fatal(err) } - node := mgr.Nodes()[0] + node := cfg.Nodes()[0] ip, err := node.WhatIP(context.Background(), &emptypb.Empty{}) if err != nil { t.Fatalf("RPC error: %v", err) diff --git a/tests/oneway/oneway_gorums.pb.go b/tests/oneway/oneway_gorums.pb.go index f183a1fc..bca95608 100644 --- a/tests/oneway/oneway_gorums.pb.go +++ b/tests/oneway/oneway_gorums.pb.go @@ -29,12 +29,30 @@ type Configuration struct { nodes []*Node } +func NewConfiguration(qspec QuorumSpec, cfg gorums.NodeListOption, opts ...gorums.ManagerOption) (c *Configuration, err error) { + if qspec == nil { + return nil, fmt.Errorf("config: QuorumSpec cannot be nil") + } + c = &Configuration{ + qspec: qspec, + } + c.RawConfiguration, err = gorums.NewRawConfiguration(cfg, opts...) + if err != nil { + return nil, err + } + c.nodes = make([]*Node, c.Size()) + for i, n := range c.RawConfiguration.RawNodes { + c.nodes[i] = &Node{n} + } + return c, nil +} + // ConfigurationFromRaw returns a new Configuration from the given raw configuration and QuorumSpec. // // This function may for example be used to "clone" a configuration but install a different QuorumSpec: // // cfg1, err := mgr.NewConfiguration(qspec1, opts...) -// cfg2 := ConfigurationFromRaw(cfg1.RawConfig, qspec2) +// cfg2 := ConfigurationFromRaw(cfg1.RawConfiguration, qspec2) func ConfigurationFromRaw(rawCfg gorums.RawConfiguration, qspec QuorumSpec) (*Configuration, error) { // return an error if the QuorumSpec interface is not empty and no implementation was provided. var test interface{} = struct{}{} @@ -47,12 +65,38 @@ func ConfigurationFromRaw(rawCfg gorums.RawConfiguration, qspec QuorumSpec) (*Co } // initialize the nodes slice newCfg.nodes = make([]*Node, newCfg.Size()) - for i, n := range rawCfg { + for i, n := range rawCfg.Nodes() { newCfg.nodes[i] = &Node{n} } return newCfg, nil } +func (c *Configuration) SubConfiguration(qspec QuorumSpec, cfg gorums.NodeListOption) (subCfg *Configuration, err error) { + if qspec == nil { + return nil, fmt.Errorf("config: QuorumSpec cannot be nil") + } + subCfg = &Configuration{ + qspec: qspec, + } + subCfg.RawConfiguration, err = c.SubRawConfiguration(cfg) + if err != nil { + return nil, err + } + subCfg.nodes = make([]*Node, subCfg.Size()) + for i, n := range subCfg.RawConfiguration.Nodes() { + subCfg.nodes[i] = &Node{n} + } + return subCfg, nil +} + +// Close closes a configuration created from the NewConfiguration method +// +// NOTE: A configuration created with ConfigurationFromRaw is closed when the original configuration is closed +// If you want the configurations to be independent you need to use NewConfiguration +func (c *Configuration) Close() error { + return c.RawConfiguration.Close() +} + // Nodes returns a slice of each available node. IDs are returned in the same // order as they were provided in the creation of the Manager. // @@ -78,71 +122,6 @@ func init() { } } -// Manager maintains a connection pool of nodes on -// which quorum calls can be performed. -type Manager struct { - *gorums.RawManager -} - -// NewManager returns a new Manager for managing connection to nodes added -// to the manager. This function accepts manager options used to configure -// various aspects of the manager. -func NewManager(opts ...gorums.ManagerOption) *Manager { - return &Manager{ - RawManager: gorums.NewRawManager(opts...), - } -} - -// NewConfiguration returns a configuration based on the provided list of nodes (required) -// and an optional quorum specification. The QuorumSpec is necessary for call types that -// must process replies. For configurations only used for unicast or multicast call types, -// a QuorumSpec is not needed. The QuorumSpec interface is also a ConfigOption. -// Nodes can be supplied using WithNodeMap or WithNodeList, or WithNodeIDs. -// A new configuration can also be created from an existing configuration, -// using the And, WithNewNodes, Except, and WithoutNodes methods. -func (m *Manager) NewConfiguration(opts ...gorums.ConfigOption) (c *Configuration, err error) { - if len(opts) < 1 || len(opts) > 2 { - return nil, fmt.Errorf("config: wrong number of options: %d", len(opts)) - } - c = &Configuration{} - for _, opt := range opts { - switch v := opt.(type) { - case gorums.NodeListOption: - c.RawConfiguration, err = gorums.NewRawConfiguration(m.RawManager, v) - if err != nil { - return nil, err - } - case QuorumSpec: - // Must be last since v may match QuorumSpec if it is interface{} - c.qspec = v - default: - return nil, fmt.Errorf("config: unknown option type: %v", v) - } - } - // return an error if the QuorumSpec interface is not empty and no implementation was provided. - var test interface{} = struct{}{} - if _, empty := test.(QuorumSpec); !empty && c.qspec == nil { - return nil, fmt.Errorf("config: missing required QuorumSpec") - } - // initialize the nodes slice - c.nodes = make([]*Node, c.Size()) - for i, n := range c.RawConfiguration { - c.nodes[i] = &Node{n} - } - return c, nil -} - -// Nodes returns a slice of available nodes on this manager. -// IDs are returned in the order they were added at creation of the manager. -func (m *Manager) Nodes() []*Node { - gorumsNodes := m.RawManager.Nodes() - nodes := make([]*Node, len(gorumsNodes)) - for i, n := range gorumsNodes { - nodes[i] = &Node{n} - } - return nodes -} - // Node encapsulates the state of a node on which a remote procedure call // can be performed. type Node struct { diff --git a/tests/oneway/oneway_test.go b/tests/oneway/oneway_test.go index 0f5d88d8..6ceb1f63 100644 --- a/tests/oneway/oneway_test.go +++ b/tests/oneway/oneway_test.go @@ -61,17 +61,16 @@ func setup(t testing.TB, cfgSize int) (cfg *oneway.Configuration, srvs []*oneway nodeMap[addr] = uint32(i) } - mgr := oneway.NewManager( + cfg, err := oneway.NewConfiguration(&testQSpec{}, gorums.WithNodeMap(nodeMap), gorums.WithGrpcDialOptions( grpc.WithTransportCredentials(insecure.NewCredentials()), ), ) - cfg, err := mgr.NewConfiguration(&testQSpec{}, gorums.WithNodeMap(nodeMap)) if err != nil { t.Fatal(err) } teardown = func() { - mgr.Close() + cfg.Close() closeServers() } return cfg, srvs, teardown diff --git a/tests/ordering/order_gorums.pb.go b/tests/ordering/order_gorums.pb.go index 05cfddf4..005aa545 100644 --- a/tests/ordering/order_gorums.pb.go +++ b/tests/ordering/order_gorums.pb.go @@ -29,12 +29,30 @@ type Configuration struct { nodes []*Node } +func NewConfiguration(qspec QuorumSpec, cfg gorums.NodeListOption, opts ...gorums.ManagerOption) (c *Configuration, err error) { + if qspec == nil { + return nil, fmt.Errorf("config: QuorumSpec cannot be nil") + } + c = &Configuration{ + qspec: qspec, + } + c.RawConfiguration, err = gorums.NewRawConfiguration(cfg, opts...) + if err != nil { + return nil, err + } + c.nodes = make([]*Node, c.Size()) + for i, n := range c.RawConfiguration.RawNodes { + c.nodes[i] = &Node{n} + } + return c, nil +} + // ConfigurationFromRaw returns a new Configuration from the given raw configuration and QuorumSpec. // // This function may for example be used to "clone" a configuration but install a different QuorumSpec: // // cfg1, err := mgr.NewConfiguration(qspec1, opts...) -// cfg2 := ConfigurationFromRaw(cfg1.RawConfig, qspec2) +// cfg2 := ConfigurationFromRaw(cfg1.RawConfiguration, qspec2) func ConfigurationFromRaw(rawCfg gorums.RawConfiguration, qspec QuorumSpec) (*Configuration, error) { // return an error if the QuorumSpec interface is not empty and no implementation was provided. var test interface{} = struct{}{} @@ -47,12 +65,38 @@ func ConfigurationFromRaw(rawCfg gorums.RawConfiguration, qspec QuorumSpec) (*Co } // initialize the nodes slice newCfg.nodes = make([]*Node, newCfg.Size()) - for i, n := range rawCfg { + for i, n := range rawCfg.Nodes() { newCfg.nodes[i] = &Node{n} } return newCfg, nil } +func (c *Configuration) SubConfiguration(qspec QuorumSpec, cfg gorums.NodeListOption) (subCfg *Configuration, err error) { + if qspec == nil { + return nil, fmt.Errorf("config: QuorumSpec cannot be nil") + } + subCfg = &Configuration{ + qspec: qspec, + } + subCfg.RawConfiguration, err = c.SubRawConfiguration(cfg) + if err != nil { + return nil, err + } + subCfg.nodes = make([]*Node, subCfg.Size()) + for i, n := range subCfg.RawConfiguration.Nodes() { + subCfg.nodes[i] = &Node{n} + } + return subCfg, nil +} + +// Close closes a configuration created from the NewConfiguration method +// +// NOTE: A configuration created with ConfigurationFromRaw is closed when the original configuration is closed +// If you want the configurations to be independent you need to use NewConfiguration +func (c *Configuration) Close() error { + return c.RawConfiguration.Close() +} + // Nodes returns a slice of each available node. IDs are returned in the same // order as they were provided in the creation of the Manager. // @@ -78,71 +122,6 @@ func init() { } } -// Manager maintains a connection pool of nodes on -// which quorum calls can be performed. -type Manager struct { - *gorums.RawManager -} - -// NewManager returns a new Manager for managing connection to nodes added -// to the manager. This function accepts manager options used to configure -// various aspects of the manager. -func NewManager(opts ...gorums.ManagerOption) *Manager { - return &Manager{ - RawManager: gorums.NewRawManager(opts...), - } -} - -// NewConfiguration returns a configuration based on the provided list of nodes (required) -// and an optional quorum specification. The QuorumSpec is necessary for call types that -// must process replies. For configurations only used for unicast or multicast call types, -// a QuorumSpec is not needed. The QuorumSpec interface is also a ConfigOption. -// Nodes can be supplied using WithNodeMap or WithNodeList, or WithNodeIDs. -// A new configuration can also be created from an existing configuration, -// using the And, WithNewNodes, Except, and WithoutNodes methods. -func (m *Manager) NewConfiguration(opts ...gorums.ConfigOption) (c *Configuration, err error) { - if len(opts) < 1 || len(opts) > 2 { - return nil, fmt.Errorf("config: wrong number of options: %d", len(opts)) - } - c = &Configuration{} - for _, opt := range opts { - switch v := opt.(type) { - case gorums.NodeListOption: - c.RawConfiguration, err = gorums.NewRawConfiguration(m.RawManager, v) - if err != nil { - return nil, err - } - case QuorumSpec: - // Must be last since v may match QuorumSpec if it is interface{} - c.qspec = v - default: - return nil, fmt.Errorf("config: unknown option type: %v", v) - } - } - // return an error if the QuorumSpec interface is not empty and no implementation was provided. - var test interface{} = struct{}{} - if _, empty := test.(QuorumSpec); !empty && c.qspec == nil { - return nil, fmt.Errorf("config: missing required QuorumSpec") - } - // initialize the nodes slice - c.nodes = make([]*Node, c.Size()) - for i, n := range c.RawConfiguration { - c.nodes[i] = &Node{n} - } - return c, nil -} - -// Nodes returns a slice of available nodes on this manager. -// IDs are returned in the order they were added at creation of the manager. -func (m *Manager) Nodes() []*Node { - gorumsNodes := m.RawManager.Nodes() - nodes := make([]*Node, len(gorumsNodes)) - for i, n := range gorumsNodes { - nodes[i] = &Node{n} - } - return nodes -} - // Node encapsulates the state of a node on which a remote procedure call // can be performed. type Node struct { diff --git a/tests/ordering/order_test.go b/tests/ordering/order_test.go index e1de05bc..cbd669f3 100644 --- a/tests/ordering/order_test.go +++ b/tests/ordering/order_test.go @@ -86,17 +86,18 @@ func setup(t *testing.T, cfgSize int) (cfg *Configuration, teardown func()) { RegisterGorumsTestServer(srv, &testSrv{}) return srv }) - mgr := NewManager( + cfg, err := NewConfiguration( + &testQSpec{cfgSize}, + gorums.WithNodeList(addrs), gorums.WithGrpcDialOptions( grpc.WithTransportCredentials(insecure.NewCredentials()), ), ) - cfg, err := mgr.NewConfiguration(&testQSpec{cfgSize}, gorums.WithNodeList(addrs)) if err != nil { t.Fatal(err) } teardown = func() { - mgr.Close() + cfg.Close() closeServers() } return cfg, teardown diff --git a/tests/qf/qf_gorums.pb.go b/tests/qf/qf_gorums.pb.go index 9b137da3..ce097530 100644 --- a/tests/qf/qf_gorums.pb.go +++ b/tests/qf/qf_gorums.pb.go @@ -29,12 +29,30 @@ type Configuration struct { nodes []*Node } +func NewConfiguration(qspec QuorumSpec, cfg gorums.NodeListOption, opts ...gorums.ManagerOption) (c *Configuration, err error) { + if qspec == nil { + return nil, fmt.Errorf("config: QuorumSpec cannot be nil") + } + c = &Configuration{ + qspec: qspec, + } + c.RawConfiguration, err = gorums.NewRawConfiguration(cfg, opts...) + if err != nil { + return nil, err + } + c.nodes = make([]*Node, c.Size()) + for i, n := range c.RawConfiguration.RawNodes { + c.nodes[i] = &Node{n} + } + return c, nil +} + // ConfigurationFromRaw returns a new Configuration from the given raw configuration and QuorumSpec. // // This function may for example be used to "clone" a configuration but install a different QuorumSpec: // // cfg1, err := mgr.NewConfiguration(qspec1, opts...) -// cfg2 := ConfigurationFromRaw(cfg1.RawConfig, qspec2) +// cfg2 := ConfigurationFromRaw(cfg1.RawConfiguration, qspec2) func ConfigurationFromRaw(rawCfg gorums.RawConfiguration, qspec QuorumSpec) (*Configuration, error) { // return an error if the QuorumSpec interface is not empty and no implementation was provided. var test interface{} = struct{}{} @@ -47,12 +65,38 @@ func ConfigurationFromRaw(rawCfg gorums.RawConfiguration, qspec QuorumSpec) (*Co } // initialize the nodes slice newCfg.nodes = make([]*Node, newCfg.Size()) - for i, n := range rawCfg { + for i, n := range rawCfg.Nodes() { newCfg.nodes[i] = &Node{n} } return newCfg, nil } +func (c *Configuration) SubConfiguration(qspec QuorumSpec, cfg gorums.NodeListOption) (subCfg *Configuration, err error) { + if qspec == nil { + return nil, fmt.Errorf("config: QuorumSpec cannot be nil") + } + subCfg = &Configuration{ + qspec: qspec, + } + subCfg.RawConfiguration, err = c.SubRawConfiguration(cfg) + if err != nil { + return nil, err + } + subCfg.nodes = make([]*Node, subCfg.Size()) + for i, n := range subCfg.RawConfiguration.Nodes() { + subCfg.nodes[i] = &Node{n} + } + return subCfg, nil +} + +// Close closes a configuration created from the NewConfiguration method +// +// NOTE: A configuration created with ConfigurationFromRaw is closed when the original configuration is closed +// If you want the configurations to be independent you need to use NewConfiguration +func (c *Configuration) Close() error { + return c.RawConfiguration.Close() +} + // Nodes returns a slice of each available node. IDs are returned in the same // order as they were provided in the creation of the Manager. // @@ -78,71 +122,6 @@ func init() { } } -// Manager maintains a connection pool of nodes on -// which quorum calls can be performed. -type Manager struct { - *gorums.RawManager -} - -// NewManager returns a new Manager for managing connection to nodes added -// to the manager. This function accepts manager options used to configure -// various aspects of the manager. -func NewManager(opts ...gorums.ManagerOption) *Manager { - return &Manager{ - RawManager: gorums.NewRawManager(opts...), - } -} - -// NewConfiguration returns a configuration based on the provided list of nodes (required) -// and an optional quorum specification. The QuorumSpec is necessary for call types that -// must process replies. For configurations only used for unicast or multicast call types, -// a QuorumSpec is not needed. The QuorumSpec interface is also a ConfigOption. -// Nodes can be supplied using WithNodeMap or WithNodeList, or WithNodeIDs. -// A new configuration can also be created from an existing configuration, -// using the And, WithNewNodes, Except, and WithoutNodes methods. -func (m *Manager) NewConfiguration(opts ...gorums.ConfigOption) (c *Configuration, err error) { - if len(opts) < 1 || len(opts) > 2 { - return nil, fmt.Errorf("config: wrong number of options: %d", len(opts)) - } - c = &Configuration{} - for _, opt := range opts { - switch v := opt.(type) { - case gorums.NodeListOption: - c.RawConfiguration, err = gorums.NewRawConfiguration(m.RawManager, v) - if err != nil { - return nil, err - } - case QuorumSpec: - // Must be last since v may match QuorumSpec if it is interface{} - c.qspec = v - default: - return nil, fmt.Errorf("config: unknown option type: %v", v) - } - } - // return an error if the QuorumSpec interface is not empty and no implementation was provided. - var test interface{} = struct{}{} - if _, empty := test.(QuorumSpec); !empty && c.qspec == nil { - return nil, fmt.Errorf("config: missing required QuorumSpec") - } - // initialize the nodes slice - c.nodes = make([]*Node, c.Size()) - for i, n := range c.RawConfiguration { - c.nodes[i] = &Node{n} - } - return c, nil -} - -// Nodes returns a slice of available nodes on this manager. -// IDs are returned in the order they were added at creation of the manager. -func (m *Manager) Nodes() []*Node { - gorumsNodes := m.RawManager.Nodes() - nodes := make([]*Node, len(gorumsNodes)) - for i, n := range gorumsNodes { - nodes[i] = &Node{n} - } - return nodes -} - // Node encapsulates the state of a node on which a remote procedure call // can be performed. type Node struct { diff --git a/tests/qf/qf_test.go b/tests/qf/qf_test.go index 99c76030..8f429fb2 100644 --- a/tests/qf/qf_test.go +++ b/tests/qf/qf_test.go @@ -194,15 +194,13 @@ func BenchmarkFullStackQF(b *testing.B) { RegisterQuorumFunctionServer(srv, &testSrv{}) return srv }) - mgr := NewManager( + c, err := NewConfiguration( + &testQSpec{quorum: n / 2}, + gorums.WithNodeList([]string{"127.0.0.1:9080", "127.0.0.1:9081", "127.0.0.1:9082"}), // dummy node list; won't actually be used in test gorums.WithGrpcDialOptions( grpc.WithTransportCredentials(insecure.NewCredentials()), ), ) - c, err := mgr.NewConfiguration( - &testQSpec{quorum: n / 2}, - gorums.WithNodeList([]string{"127.0.0.1:9080", "127.0.0.1:9081", "127.0.0.1:9082"}), // dummy node list; won't actually be used in test - ) if err != nil { b.Fatal(err) } @@ -228,9 +226,9 @@ func BenchmarkFullStackQF(b *testing.B) { _ = resp.GetResult() } }) - // close manager and stop gRPC servers; + // close configuration and stop gRPC servers; // must be done for each iteration - mgr.Close() + c.Close() stop() } } diff --git a/tests/tls/tls_gorums.pb.go b/tests/tls/tls_gorums.pb.go index 11e979f1..2c385316 100644 --- a/tests/tls/tls_gorums.pb.go +++ b/tests/tls/tls_gorums.pb.go @@ -28,12 +28,30 @@ type Configuration struct { nodes []*Node } +func NewConfiguration(qspec QuorumSpec, cfg gorums.NodeListOption, opts ...gorums.ManagerOption) (c *Configuration, err error) { + if qspec == nil { + return nil, fmt.Errorf("config: QuorumSpec cannot be nil") + } + c = &Configuration{ + qspec: qspec, + } + c.RawConfiguration, err = gorums.NewRawConfiguration(cfg, opts...) + if err != nil { + return nil, err + } + c.nodes = make([]*Node, c.Size()) + for i, n := range c.RawConfiguration.RawNodes { + c.nodes[i] = &Node{n} + } + return c, nil +} + // ConfigurationFromRaw returns a new Configuration from the given raw configuration and QuorumSpec. // // This function may for example be used to "clone" a configuration but install a different QuorumSpec: // // cfg1, err := mgr.NewConfiguration(qspec1, opts...) -// cfg2 := ConfigurationFromRaw(cfg1.RawConfig, qspec2) +// cfg2 := ConfigurationFromRaw(cfg1.RawConfiguration, qspec2) func ConfigurationFromRaw(rawCfg gorums.RawConfiguration, qspec QuorumSpec) (*Configuration, error) { // return an error if the QuorumSpec interface is not empty and no implementation was provided. var test interface{} = struct{}{} @@ -46,12 +64,38 @@ func ConfigurationFromRaw(rawCfg gorums.RawConfiguration, qspec QuorumSpec) (*Co } // initialize the nodes slice newCfg.nodes = make([]*Node, newCfg.Size()) - for i, n := range rawCfg { + for i, n := range rawCfg.Nodes() { newCfg.nodes[i] = &Node{n} } return newCfg, nil } +func (c *Configuration) SubConfiguration(qspec QuorumSpec, cfg gorums.NodeListOption) (subCfg *Configuration, err error) { + if qspec == nil { + return nil, fmt.Errorf("config: QuorumSpec cannot be nil") + } + subCfg = &Configuration{ + qspec: qspec, + } + subCfg.RawConfiguration, err = c.SubRawConfiguration(cfg) + if err != nil { + return nil, err + } + subCfg.nodes = make([]*Node, subCfg.Size()) + for i, n := range subCfg.RawConfiguration.Nodes() { + subCfg.nodes[i] = &Node{n} + } + return subCfg, nil +} + +// Close closes a configuration created from the NewConfiguration method +// +// NOTE: A configuration created with ConfigurationFromRaw is closed when the original configuration is closed +// If you want the configurations to be independent you need to use NewConfiguration +func (c *Configuration) Close() error { + return c.RawConfiguration.Close() +} + // Nodes returns a slice of each available node. IDs are returned in the same // order as they were provided in the creation of the Manager. // @@ -77,71 +121,6 @@ func init() { } } -// Manager maintains a connection pool of nodes on -// which quorum calls can be performed. -type Manager struct { - *gorums.RawManager -} - -// NewManager returns a new Manager for managing connection to nodes added -// to the manager. This function accepts manager options used to configure -// various aspects of the manager. -func NewManager(opts ...gorums.ManagerOption) *Manager { - return &Manager{ - RawManager: gorums.NewRawManager(opts...), - } -} - -// NewConfiguration returns a configuration based on the provided list of nodes (required) -// and an optional quorum specification. The QuorumSpec is necessary for call types that -// must process replies. For configurations only used for unicast or multicast call types, -// a QuorumSpec is not needed. The QuorumSpec interface is also a ConfigOption. -// Nodes can be supplied using WithNodeMap or WithNodeList, or WithNodeIDs. -// A new configuration can also be created from an existing configuration, -// using the And, WithNewNodes, Except, and WithoutNodes methods. -func (m *Manager) NewConfiguration(opts ...gorums.ConfigOption) (c *Configuration, err error) { - if len(opts) < 1 || len(opts) > 2 { - return nil, fmt.Errorf("config: wrong number of options: %d", len(opts)) - } - c = &Configuration{} - for _, opt := range opts { - switch v := opt.(type) { - case gorums.NodeListOption: - c.RawConfiguration, err = gorums.NewRawConfiguration(m.RawManager, v) - if err != nil { - return nil, err - } - case QuorumSpec: - // Must be last since v may match QuorumSpec if it is interface{} - c.qspec = v - default: - return nil, fmt.Errorf("config: unknown option type: %v", v) - } - } - // return an error if the QuorumSpec interface is not empty and no implementation was provided. - var test interface{} = struct{}{} - if _, empty := test.(QuorumSpec); !empty && c.qspec == nil { - return nil, fmt.Errorf("config: missing required QuorumSpec") - } - // initialize the nodes slice - c.nodes = make([]*Node, c.Size()) - for i, n := range c.RawConfiguration { - c.nodes[i] = &Node{n} - } - return c, nil -} - -// Nodes returns a slice of available nodes on this manager. -// IDs are returned in the order they were added at creation of the manager. -func (m *Manager) Nodes() []*Node { - gorumsNodes := m.RawManager.Nodes() - nodes := make([]*Node, len(gorumsNodes)) - for i, n := range gorumsNodes { - nodes[i] = &Node{n} - } - return nodes -} - // Node encapsulates the state of a node on which a remote procedure call // can be performed. type Node struct { diff --git a/tests/tls/tls_test.go b/tests/tls/tls_test.go index 5cf8b4cc..c143c3b7 100644 --- a/tests/tls/tls_test.go +++ b/tests/tls/tls_test.go @@ -45,17 +45,18 @@ func TestTLS(t *testing.T) { }) defer teardown() - mgr := NewManager( + cfg, err := NewConfiguration( + nil, + gorums.WithNodeList(addrs), gorums.WithGrpcDialOptions( grpc.WithTransportCredentials(credentials.NewClientTLSFromCert(cp, "")), ), ) - _, err = mgr.NewConfiguration(gorums.WithNodeList(addrs)) if err != nil { t.Fatal(err) } - node := mgr.Nodes()[0] + node := cfg.Nodes()[0] resp, err := node.TestTLS(context.Background(), &Request{}) if err != nil { t.Fatalf("RPC error: %v", err) diff --git a/tests/unresponsive/unreponsive_test.go b/tests/unresponsive/unreponsive_test.go index 9c93a4a1..dec66d7a 100644 --- a/tests/unresponsive/unreponsive_test.go +++ b/tests/unresponsive/unreponsive_test.go @@ -28,18 +28,18 @@ func TestUnresponsive(t *testing.T) { }) defer teardown() - mgr := NewManager( + cfg, err := NewConfiguration( + nil, + gorums.WithNodeList(addrs), gorums.WithGrpcDialOptions( grpc.WithTransportCredentials(insecure.NewCredentials()), ), ) - _, err := mgr.NewConfiguration(gorums.WithNodeList(addrs)) if err != nil { t.Fatal(err) } - node := mgr.Nodes()[0] - + node := cfg.Nodes()[0] for i := 0; i < 1000; i++ { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond) _, err = node.TestUnresponsive(ctx, &Empty{}) diff --git a/tests/unresponsive/unresponsive_gorums.pb.go b/tests/unresponsive/unresponsive_gorums.pb.go index fa6189a1..8e638555 100644 --- a/tests/unresponsive/unresponsive_gorums.pb.go +++ b/tests/unresponsive/unresponsive_gorums.pb.go @@ -28,12 +28,30 @@ type Configuration struct { nodes []*Node } +func NewConfiguration(qspec QuorumSpec, cfg gorums.NodeListOption, opts ...gorums.ManagerOption) (c *Configuration, err error) { + if qspec == nil { + return nil, fmt.Errorf("config: QuorumSpec cannot be nil") + } + c = &Configuration{ + qspec: qspec, + } + c.RawConfiguration, err = gorums.NewRawConfiguration(cfg, opts...) + if err != nil { + return nil, err + } + c.nodes = make([]*Node, c.Size()) + for i, n := range c.RawConfiguration.RawNodes { + c.nodes[i] = &Node{n} + } + return c, nil +} + // ConfigurationFromRaw returns a new Configuration from the given raw configuration and QuorumSpec. // // This function may for example be used to "clone" a configuration but install a different QuorumSpec: // // cfg1, err := mgr.NewConfiguration(qspec1, opts...) -// cfg2 := ConfigurationFromRaw(cfg1.RawConfig, qspec2) +// cfg2 := ConfigurationFromRaw(cfg1.RawConfiguration, qspec2) func ConfigurationFromRaw(rawCfg gorums.RawConfiguration, qspec QuorumSpec) (*Configuration, error) { // return an error if the QuorumSpec interface is not empty and no implementation was provided. var test interface{} = struct{}{} @@ -46,12 +64,38 @@ func ConfigurationFromRaw(rawCfg gorums.RawConfiguration, qspec QuorumSpec) (*Co } // initialize the nodes slice newCfg.nodes = make([]*Node, newCfg.Size()) - for i, n := range rawCfg { + for i, n := range rawCfg.Nodes() { newCfg.nodes[i] = &Node{n} } return newCfg, nil } +func (c *Configuration) SubConfiguration(qspec QuorumSpec, cfg gorums.NodeListOption) (subCfg *Configuration, err error) { + if qspec == nil { + return nil, fmt.Errorf("config: QuorumSpec cannot be nil") + } + subCfg = &Configuration{ + qspec: qspec, + } + subCfg.RawConfiguration, err = c.SubRawConfiguration(cfg) + if err != nil { + return nil, err + } + subCfg.nodes = make([]*Node, subCfg.Size()) + for i, n := range subCfg.RawConfiguration.Nodes() { + subCfg.nodes[i] = &Node{n} + } + return subCfg, nil +} + +// Close closes a configuration created from the NewConfiguration method +// +// NOTE: A configuration created with ConfigurationFromRaw is closed when the original configuration is closed +// If you want the configurations to be independent you need to use NewConfiguration +func (c *Configuration) Close() error { + return c.RawConfiguration.Close() +} + // Nodes returns a slice of each available node. IDs are returned in the same // order as they were provided in the creation of the Manager. // @@ -77,71 +121,6 @@ func init() { } } -// Manager maintains a connection pool of nodes on -// which quorum calls can be performed. -type Manager struct { - *gorums.RawManager -} - -// NewManager returns a new Manager for managing connection to nodes added -// to the manager. This function accepts manager options used to configure -// various aspects of the manager. -func NewManager(opts ...gorums.ManagerOption) *Manager { - return &Manager{ - RawManager: gorums.NewRawManager(opts...), - } -} - -// NewConfiguration returns a configuration based on the provided list of nodes (required) -// and an optional quorum specification. The QuorumSpec is necessary for call types that -// must process replies. For configurations only used for unicast or multicast call types, -// a QuorumSpec is not needed. The QuorumSpec interface is also a ConfigOption. -// Nodes can be supplied using WithNodeMap or WithNodeList, or WithNodeIDs. -// A new configuration can also be created from an existing configuration, -// using the And, WithNewNodes, Except, and WithoutNodes methods. -func (m *Manager) NewConfiguration(opts ...gorums.ConfigOption) (c *Configuration, err error) { - if len(opts) < 1 || len(opts) > 2 { - return nil, fmt.Errorf("config: wrong number of options: %d", len(opts)) - } - c = &Configuration{} - for _, opt := range opts { - switch v := opt.(type) { - case gorums.NodeListOption: - c.RawConfiguration, err = gorums.NewRawConfiguration(m.RawManager, v) - if err != nil { - return nil, err - } - case QuorumSpec: - // Must be last since v may match QuorumSpec if it is interface{} - c.qspec = v - default: - return nil, fmt.Errorf("config: unknown option type: %v", v) - } - } - // return an error if the QuorumSpec interface is not empty and no implementation was provided. - var test interface{} = struct{}{} - if _, empty := test.(QuorumSpec); !empty && c.qspec == nil { - return nil, fmt.Errorf("config: missing required QuorumSpec") - } - // initialize the nodes slice - c.nodes = make([]*Node, c.Size()) - for i, n := range c.RawConfiguration { - c.nodes[i] = &Node{n} - } - return c, nil -} - -// Nodes returns a slice of available nodes on this manager. -// IDs are returned in the order they were added at creation of the manager. -func (m *Manager) Nodes() []*Node { - gorumsNodes := m.RawManager.Nodes() - nodes := make([]*Node, len(gorumsNodes)) - for i, n := range gorumsNodes { - nodes[i] = &Node{n} - } - return nodes -} - // Node encapsulates the state of a node on which a remote procedure call // can be performed. type Node struct { From 60674654832681edc0e9035d965aa931182d4d18 Mon Sep 17 00:00:00 2001 From: asbjorn Date: Tue, 11 Mar 2025 23:34:07 +0100 Subject: [PATCH 2/7] allow qspec to be nil, and fix tests --- benchmark/benchmark_gorums.pb.go | 6 ------ cmd/protoc-gen-gorums/dev/config.go | 6 ------ cmd/protoc-gen-gorums/gengorums/template_static.go | 6 ------ tests/config/config_gorums.pb.go | 6 ------ tests/config/config_test.go | 2 +- tests/correctable/correctable_gorums.pb.go | 6 ------ tests/dummy/dummy_gorums.pb.go | 6 ------ tests/metadata/metadata_gorums.pb.go | 6 ------ tests/oneway/oneway_gorums.pb.go | 6 ------ tests/oneway/oneway_test.go | 4 +++- tests/ordering/order_gorums.pb.go | 6 ------ tests/qf/qf_gorums.pb.go | 6 ------ tests/tls/tls_gorums.pb.go | 6 ------ tests/unresponsive/unresponsive_gorums.pb.go | 6 ------ 14 files changed, 4 insertions(+), 74 deletions(-) diff --git a/benchmark/benchmark_gorums.pb.go b/benchmark/benchmark_gorums.pb.go index f2bb3c5c..6df10ffc 100644 --- a/benchmark/benchmark_gorums.pb.go +++ b/benchmark/benchmark_gorums.pb.go @@ -31,9 +31,6 @@ type Configuration struct { } func NewConfiguration(qspec QuorumSpec, cfg gorums.NodeListOption, opts ...gorums.ManagerOption) (c *Configuration, err error) { - if qspec == nil { - return nil, fmt.Errorf("config: QuorumSpec cannot be nil") - } c = &Configuration{ qspec: qspec, } @@ -73,9 +70,6 @@ func ConfigurationFromRaw(rawCfg gorums.RawConfiguration, qspec QuorumSpec) (*Co } func (c *Configuration) SubConfiguration(qspec QuorumSpec, cfg gorums.NodeListOption) (subCfg *Configuration, err error) { - if qspec == nil { - return nil, fmt.Errorf("config: QuorumSpec cannot be nil") - } subCfg = &Configuration{ qspec: qspec, } diff --git a/cmd/protoc-gen-gorums/dev/config.go b/cmd/protoc-gen-gorums/dev/config.go index ca8a89f1..017681aa 100644 --- a/cmd/protoc-gen-gorums/dev/config.go +++ b/cmd/protoc-gen-gorums/dev/config.go @@ -15,9 +15,6 @@ type Configuration struct { } func NewConfiguration(qspec QuorumSpec, cfg gorums.NodeListOption, opts ...gorums.ManagerOption) (c *Configuration, err error) { - if qspec == nil { - return nil, fmt.Errorf("config: QuorumSpec cannot be nil") - } c = &Configuration{ qspec: qspec, } @@ -57,9 +54,6 @@ func ConfigurationFromRaw(rawCfg gorums.RawConfiguration, qspec QuorumSpec) (*Co } func (c *Configuration) SubConfiguration(qspec QuorumSpec, cfg gorums.NodeListOption) (subCfg *Configuration, err error) { - if qspec == nil { - return nil, fmt.Errorf("config: QuorumSpec cannot be nil") - } subCfg = &Configuration{ qspec: qspec, } diff --git a/cmd/protoc-gen-gorums/gengorums/template_static.go b/cmd/protoc-gen-gorums/gengorums/template_static.go index f99e0c6e..4c4dd414 100644 --- a/cmd/protoc-gen-gorums/gengorums/template_static.go +++ b/cmd/protoc-gen-gorums/gengorums/template_static.go @@ -20,9 +20,6 @@ type Configuration struct { } func NewConfiguration(qspec QuorumSpec, cfg gorums.NodeListOption, opts ...gorums.ManagerOption) (c *Configuration, err error) { - if qspec == nil { - return nil, fmt.Errorf("config: QuorumSpec cannot be nil") - } c = &Configuration{ qspec: qspec, } @@ -62,9 +59,6 @@ func ConfigurationFromRaw(rawCfg gorums.RawConfiguration, qspec QuorumSpec) (*Co } func (c *Configuration) SubConfiguration(qspec QuorumSpec, cfg gorums.NodeListOption) (subCfg *Configuration, err error) { - if qspec == nil { - return nil, fmt.Errorf("config: QuorumSpec cannot be nil") - } subCfg = &Configuration{ qspec: qspec, } diff --git a/tests/config/config_gorums.pb.go b/tests/config/config_gorums.pb.go index 6fe9b83d..5db6cf6f 100644 --- a/tests/config/config_gorums.pb.go +++ b/tests/config/config_gorums.pb.go @@ -30,9 +30,6 @@ type Configuration struct { } func NewConfiguration(qspec QuorumSpec, cfg gorums.NodeListOption, opts ...gorums.ManagerOption) (c *Configuration, err error) { - if qspec == nil { - return nil, fmt.Errorf("config: QuorumSpec cannot be nil") - } c = &Configuration{ qspec: qspec, } @@ -72,9 +69,6 @@ func ConfigurationFromRaw(rawCfg gorums.RawConfiguration, qspec QuorumSpec) (*Co } func (c *Configuration) SubConfiguration(qspec QuorumSpec, cfg gorums.NodeListOption) (subCfg *Configuration, err error) { - if qspec == nil { - return nil, fmt.Errorf("config: QuorumSpec cannot be nil") - } subCfg = &Configuration{ qspec: qspec, } diff --git a/tests/config/config_test.go b/tests/config/config_test.go index 5da830ae..aea24fe8 100644 --- a/tests/config/config_test.go +++ b/tests/config/config_test.go @@ -86,7 +86,7 @@ func TestConfig(t *testing.T) { } cfg, err := NewConfiguration( &cfgQSpec{}, - gorums.WithNodeList([]string{}), + gorums.RawConfiguration{}.And(gorums.RawConfiguration{}), gorums.WithGrpcDialOptions( grpc.WithTransportCredentials(insecure.NewCredentials()), ), diff --git a/tests/correctable/correctable_gorums.pb.go b/tests/correctable/correctable_gorums.pb.go index 39c43802..3dbdba04 100644 --- a/tests/correctable/correctable_gorums.pb.go +++ b/tests/correctable/correctable_gorums.pb.go @@ -32,9 +32,6 @@ type Configuration struct { } func NewConfiguration(qspec QuorumSpec, cfg gorums.NodeListOption, opts ...gorums.ManagerOption) (c *Configuration, err error) { - if qspec == nil { - return nil, fmt.Errorf("config: QuorumSpec cannot be nil") - } c = &Configuration{ qspec: qspec, } @@ -74,9 +71,6 @@ func ConfigurationFromRaw(rawCfg gorums.RawConfiguration, qspec QuorumSpec) (*Co } func (c *Configuration) SubConfiguration(qspec QuorumSpec, cfg gorums.NodeListOption) (subCfg *Configuration, err error) { - if qspec == nil { - return nil, fmt.Errorf("config: QuorumSpec cannot be nil") - } subCfg = &Configuration{ qspec: qspec, } diff --git a/tests/dummy/dummy_gorums.pb.go b/tests/dummy/dummy_gorums.pb.go index aa0f3e9c..a5401edf 100644 --- a/tests/dummy/dummy_gorums.pb.go +++ b/tests/dummy/dummy_gorums.pb.go @@ -29,9 +29,6 @@ type Configuration struct { } func NewConfiguration(qspec QuorumSpec, cfg gorums.NodeListOption, opts ...gorums.ManagerOption) (c *Configuration, err error) { - if qspec == nil { - return nil, fmt.Errorf("config: QuorumSpec cannot be nil") - } c = &Configuration{ qspec: qspec, } @@ -71,9 +68,6 @@ func ConfigurationFromRaw(rawCfg gorums.RawConfiguration, qspec QuorumSpec) (*Co } func (c *Configuration) SubConfiguration(qspec QuorumSpec, cfg gorums.NodeListOption) (subCfg *Configuration, err error) { - if qspec == nil { - return nil, fmt.Errorf("config: QuorumSpec cannot be nil") - } subCfg = &Configuration{ qspec: qspec, } diff --git a/tests/metadata/metadata_gorums.pb.go b/tests/metadata/metadata_gorums.pb.go index 485a8cd0..f96b0ea5 100644 --- a/tests/metadata/metadata_gorums.pb.go +++ b/tests/metadata/metadata_gorums.pb.go @@ -30,9 +30,6 @@ type Configuration struct { } func NewConfiguration(qspec QuorumSpec, cfg gorums.NodeListOption, opts ...gorums.ManagerOption) (c *Configuration, err error) { - if qspec == nil { - return nil, fmt.Errorf("config: QuorumSpec cannot be nil") - } c = &Configuration{ qspec: qspec, } @@ -72,9 +69,6 @@ func ConfigurationFromRaw(rawCfg gorums.RawConfiguration, qspec QuorumSpec) (*Co } func (c *Configuration) SubConfiguration(qspec QuorumSpec, cfg gorums.NodeListOption) (subCfg *Configuration, err error) { - if qspec == nil { - return nil, fmt.Errorf("config: QuorumSpec cannot be nil") - } subCfg = &Configuration{ qspec: qspec, } diff --git a/tests/oneway/oneway_gorums.pb.go b/tests/oneway/oneway_gorums.pb.go index bca95608..02954867 100644 --- a/tests/oneway/oneway_gorums.pb.go +++ b/tests/oneway/oneway_gorums.pb.go @@ -30,9 +30,6 @@ type Configuration struct { } func NewConfiguration(qspec QuorumSpec, cfg gorums.NodeListOption, opts ...gorums.ManagerOption) (c *Configuration, err error) { - if qspec == nil { - return nil, fmt.Errorf("config: QuorumSpec cannot be nil") - } c = &Configuration{ qspec: qspec, } @@ -72,9 +69,6 @@ func ConfigurationFromRaw(rawCfg gorums.RawConfiguration, qspec QuorumSpec) (*Co } func (c *Configuration) SubConfiguration(qspec QuorumSpec, cfg gorums.NodeListOption) (subCfg *Configuration, err error) { - if qspec == nil { - return nil, fmt.Errorf("config: QuorumSpec cannot be nil") - } subCfg = &Configuration{ qspec: qspec, } diff --git a/tests/oneway/oneway_test.go b/tests/oneway/oneway_test.go index 6ceb1f63..41797f3a 100644 --- a/tests/oneway/oneway_test.go +++ b/tests/oneway/oneway_test.go @@ -61,7 +61,9 @@ func setup(t testing.TB, cfgSize int) (cfg *oneway.Configuration, srvs []*oneway nodeMap[addr] = uint32(i) } - cfg, err := oneway.NewConfiguration(&testQSpec{}, gorums.WithNodeMap(nodeMap), + cfg, err := oneway.NewConfiguration( + &testQSpec{}, + gorums.WithNodeMap(nodeMap), gorums.WithGrpcDialOptions( grpc.WithTransportCredentials(insecure.NewCredentials()), ), diff --git a/tests/ordering/order_gorums.pb.go b/tests/ordering/order_gorums.pb.go index 005aa545..a006a490 100644 --- a/tests/ordering/order_gorums.pb.go +++ b/tests/ordering/order_gorums.pb.go @@ -30,9 +30,6 @@ type Configuration struct { } func NewConfiguration(qspec QuorumSpec, cfg gorums.NodeListOption, opts ...gorums.ManagerOption) (c *Configuration, err error) { - if qspec == nil { - return nil, fmt.Errorf("config: QuorumSpec cannot be nil") - } c = &Configuration{ qspec: qspec, } @@ -72,9 +69,6 @@ func ConfigurationFromRaw(rawCfg gorums.RawConfiguration, qspec QuorumSpec) (*Co } func (c *Configuration) SubConfiguration(qspec QuorumSpec, cfg gorums.NodeListOption) (subCfg *Configuration, err error) { - if qspec == nil { - return nil, fmt.Errorf("config: QuorumSpec cannot be nil") - } subCfg = &Configuration{ qspec: qspec, } diff --git a/tests/qf/qf_gorums.pb.go b/tests/qf/qf_gorums.pb.go index ce097530..8391cc5b 100644 --- a/tests/qf/qf_gorums.pb.go +++ b/tests/qf/qf_gorums.pb.go @@ -30,9 +30,6 @@ type Configuration struct { } func NewConfiguration(qspec QuorumSpec, cfg gorums.NodeListOption, opts ...gorums.ManagerOption) (c *Configuration, err error) { - if qspec == nil { - return nil, fmt.Errorf("config: QuorumSpec cannot be nil") - } c = &Configuration{ qspec: qspec, } @@ -72,9 +69,6 @@ func ConfigurationFromRaw(rawCfg gorums.RawConfiguration, qspec QuorumSpec) (*Co } func (c *Configuration) SubConfiguration(qspec QuorumSpec, cfg gorums.NodeListOption) (subCfg *Configuration, err error) { - if qspec == nil { - return nil, fmt.Errorf("config: QuorumSpec cannot be nil") - } subCfg = &Configuration{ qspec: qspec, } diff --git a/tests/tls/tls_gorums.pb.go b/tests/tls/tls_gorums.pb.go index 2c385316..6562c4e4 100644 --- a/tests/tls/tls_gorums.pb.go +++ b/tests/tls/tls_gorums.pb.go @@ -29,9 +29,6 @@ type Configuration struct { } func NewConfiguration(qspec QuorumSpec, cfg gorums.NodeListOption, opts ...gorums.ManagerOption) (c *Configuration, err error) { - if qspec == nil { - return nil, fmt.Errorf("config: QuorumSpec cannot be nil") - } c = &Configuration{ qspec: qspec, } @@ -71,9 +68,6 @@ func ConfigurationFromRaw(rawCfg gorums.RawConfiguration, qspec QuorumSpec) (*Co } func (c *Configuration) SubConfiguration(qspec QuorumSpec, cfg gorums.NodeListOption) (subCfg *Configuration, err error) { - if qspec == nil { - return nil, fmt.Errorf("config: QuorumSpec cannot be nil") - } subCfg = &Configuration{ qspec: qspec, } diff --git a/tests/unresponsive/unresponsive_gorums.pb.go b/tests/unresponsive/unresponsive_gorums.pb.go index 8e638555..b236b49b 100644 --- a/tests/unresponsive/unresponsive_gorums.pb.go +++ b/tests/unresponsive/unresponsive_gorums.pb.go @@ -29,9 +29,6 @@ type Configuration struct { } func NewConfiguration(qspec QuorumSpec, cfg gorums.NodeListOption, opts ...gorums.ManagerOption) (c *Configuration, err error) { - if qspec == nil { - return nil, fmt.Errorf("config: QuorumSpec cannot be nil") - } c = &Configuration{ qspec: qspec, } @@ -71,9 +68,6 @@ func ConfigurationFromRaw(rawCfg gorums.RawConfiguration, qspec QuorumSpec) (*Co } func (c *Configuration) SubConfiguration(qspec QuorumSpec, cfg gorums.NodeListOption) (subCfg *Configuration, err error) { - if qspec == nil { - return nil, fmt.Errorf("config: QuorumSpec cannot be nil") - } subCfg = &Configuration{ qspec: qspec, } From f3270a920759ebbfc9aeaaea0fb70deee0965a21 Mon Sep 17 00:00:00 2001 From: asbjorn Date: Wed, 12 Mar 2025 00:35:44 +0100 Subject: [PATCH 3/7] created comments for the new and modified configuration functions --- cmd/protoc-gen-gorums/dev/config.go | 17 +++++++++++++++-- config.go | 8 +++++--- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/cmd/protoc-gen-gorums/dev/config.go b/cmd/protoc-gen-gorums/dev/config.go index 017681aa..f0569b53 100644 --- a/cmd/protoc-gen-gorums/dev/config.go +++ b/cmd/protoc-gen-gorums/dev/config.go @@ -14,6 +14,13 @@ type Configuration struct { nodes []*Node } +// NewConfiguration returns a configuration based on the provided list of nodes (required) +// and an optional quorum specification. The QuorumSpec is necessary for call types that +// must process replies. For configurations only used for unicast or multicast call types, +// a QuorumSpec is not needed. +// Nodes can be supplied using WithNodeMap or WithNodeList. +// Using any other type of NodeListOption will not work. +// The ManagerOption list controls how the nodes in the configuration are created. func NewConfiguration(qspec QuorumSpec, cfg gorums.NodeListOption, opts ...gorums.ManagerOption) (c *Configuration, err error) { c = &Configuration{ qspec: qspec, @@ -29,7 +36,7 @@ func NewConfiguration(qspec QuorumSpec, cfg gorums.NodeListOption, opts ...gorum return c, nil } -// ConfigurationFromRaw returns a new Configuration from the given raw configuration and QuorumSpec. +// ConfigurationFromRaw returns a new configuration from the given raw configuration and QuorumSpec. // // This function may for example be used to "clone" a configuration but install a different QuorumSpec: // @@ -53,6 +60,11 @@ func ConfigurationFromRaw(rawCfg gorums.RawConfiguration, qspec QuorumSpec) (*Co return newCfg, nil } +// SubConfiguration allows for making a new Configuration from the ManagerOption list and +// node list of another configuration, +// Nodes can be supplied using WithNodeMap or WithNodeList, or WithNodeIDs. +// A new configuration can also be created from an existing configuration, +// using the And, WithNewNodes, Except, and WithoutNodes methods. func (c *Configuration) SubConfiguration(qspec QuorumSpec, cfg gorums.NodeListOption) (subCfg *Configuration, err error) { subCfg = &Configuration{ qspec: qspec, @@ -70,7 +82,8 @@ func (c *Configuration) SubConfiguration(qspec QuorumSpec, cfg gorums.NodeListOp // Close closes a configuration created from the NewConfiguration method // -// NOTE: A configuration created with ConfigurationFromRaw is closed when the original configuration is closed +// NOTE: A configuration created with ConfigurationFromRaw or SubConfiguration closes and +// is closed when the original configuration or any of the subconfigurations are closed. // If you want the configurations to be independent you need to use NewConfiguration func (c *Configuration) Close() error { return c.RawConfiguration.Close() diff --git a/config.go b/config.go index 095d53ce..6ce46672 100644 --- a/config.go +++ b/config.go @@ -17,9 +17,7 @@ type RawConfiguration struct { } // NewRawConfiguration returns a configuration based on the provided list of nodes. -// Nodes can be supplied using WithNodeMap or WithNodeList, or WithNodeIDs. -// A new configuration can also be created from an existing configuration, -// using the And, WithNewNodes, Except, and WithoutNodes methods. +// Nodes can be supplied using WithNodeMap or WithNodeList. func NewRawConfiguration(cfg NodeListOption, opts ...ManagerOption) (nodes RawConfiguration, err error) { if cfg == nil { return RawConfiguration{}, fmt.Errorf("config: missing required node list") @@ -28,6 +26,10 @@ func NewRawConfiguration(cfg NodeListOption, opts ...ManagerOption) (nodes RawCo return cfg.newConfig(mgr) } +// SubRawConfiguration returns a configuration from another configuration and a list of nodes. +// Nodes can be supplied using WithNodeMap or WithNodeList, or WithNodeIDs. +// A new configuration can also be created from an existing configuration, +// using the And, WithNewNodes, Except, and WithoutNodes methods. func (c *RawConfiguration) SubRawConfiguration(cfg NodeListOption) (nodes RawConfiguration, err error) { if cfg == nil { return RawConfiguration{}, fmt.Errorf("config: missing required node list") From 125b0cfb28aa058280ef808bff052a396c5c1df5 Mon Sep 17 00:00:00 2001 From: asbjorn Date: Wed, 12 Mar 2025 00:49:01 +0100 Subject: [PATCH 4/7] RawManager is never nil --- config.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/config.go b/config.go index 6ce46672..78f24f04 100644 --- a/config.go +++ b/config.go @@ -1,7 +1,6 @@ package gorums import ( - "errors" "fmt" ) @@ -39,9 +38,6 @@ func (c *RawConfiguration) SubRawConfiguration(cfg NodeListOption) (nodes RawCon // Close closes a raw configuration created from the NewRawConfiguration method func (c *RawConfiguration) Close() error { - if c.RawManager == nil { - return errors.New("RawConfiguration.Close: RawManager is nil, cannot close a sub-configuration") - } c.RawManager.Close() return nil } From abcec91232c3cbcc68c80f1d7f3327d1355f75ff Mon Sep 17 00:00:00 2001 From: asbjorn Date: Sun, 11 May 2025 22:33:48 +0200 Subject: [PATCH 5/7] support closing configurations, no tests --- config.go | 41 +++++++++++++++++++++++++++++++++-------- mgr.go | 27 +++++++++++++++++++++++++++ node.go | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 93 insertions(+), 8 deletions(-) diff --git a/config.go b/config.go index 78f24f04..e5a49fb2 100644 --- a/config.go +++ b/config.go @@ -17,31 +17,56 @@ type RawConfiguration struct { // NewRawConfiguration returns a configuration based on the provided list of nodes. // Nodes can be supplied using WithNodeMap or WithNodeList. -func NewRawConfiguration(cfg NodeListOption, opts ...ManagerOption) (nodes RawConfiguration, err error) { - if cfg == nil { +func NewRawConfiguration(nodes NodeListOption, opts ...ManagerOption) (cfg RawConfiguration, err error) { + if nodes == nil { return RawConfiguration{}, fmt.Errorf("config: missing required node list") } mgr := NewRawManager(opts...) - return cfg.newConfig(mgr) + + cfg, err = nodes.newConfig(mgr) + for _, n := range cfg.RawNodes { + n.obtain() + } + + return cfg, err } // SubRawConfiguration returns a configuration from another configuration and a list of nodes. // Nodes can be supplied using WithNodeMap or WithNodeList, or WithNodeIDs. // A new configuration can also be created from an existing configuration, // using the And, WithNewNodes, Except, and WithoutNodes methods. -func (c *RawConfiguration) SubRawConfiguration(cfg NodeListOption) (nodes RawConfiguration, err error) { - if cfg == nil { +func (c *RawConfiguration) SubRawConfiguration(nodes NodeListOption) (cfg RawConfiguration, err error) { + if nodes == nil { return RawConfiguration{}, fmt.Errorf("config: missing required node list") } - return cfg.newConfig(c.RawManager) + + cfg, err = nodes.newConfig(c.RawManager) + for _, n := range cfg.RawNodes { + n.obtain() + } + + return cfg, err } -// Close closes a raw configuration created from the NewRawConfiguration method -func (c *RawConfiguration) Close() error { +// CloseAll closes the configurations and all of its subconfigurations +func (c *RawConfiguration) CloseAll() error { c.RawManager.Close() return nil } +// Close closes the nodes which are not part of another subconfiguration +// it is only meant to be used once +func (c *RawConfiguration) Close() error { + for _, n := range c.RawNodes { + err := n.release() + if err != nil { + return err + } + } + *c = RawConfiguration{} + return nil +} + // NodeIDs returns a slice of this configuration's Node IDs. func (c RawConfiguration) NodeIDs() []uint32 { ids := make([]uint32, len(c.RawNodes)) diff --git a/mgr.go b/mgr.go index ffd15ebb..5afeda10 100644 --- a/mgr.go +++ b/mgr.go @@ -1,8 +1,10 @@ package gorums import ( + "cmp" "fmt" "log" + "slices" "sync" "sync/atomic" @@ -131,6 +133,31 @@ func (m *RawManager) AddNode(node *RawNode) error { return nil } +// remove a node from a manager, the node is closed seperately +func (m *RawManager) removeNode(node *RawNode) error { + if m.logger != nil { + m.logger.Printf("Removing node %s with id %d\n", node, node.id) + } + + m.mu.Lock() + defer m.mu.Unlock() + + delete(m.lookup, node.id) + + // assume nodes are sorted + i, found := slices.BinarySearchFunc(m.nodes, node, func(n1, n2 *RawNode) int { + return cmp.Compare(n1.id, n2.id) + }) + + if found { + // keep nodes sorted + m.nodes = append(m.nodes[:i], m.nodes[i+1:]...) + return nil + } + + return fmt.Errorf("RemodeNode: Node %d (%s) not found", node.id, node) +} + // getMsgID returns a unique message ID. func (m *RawManager) getMsgID() uint64 { return atomic.AddUint64(&m.nextMsgID, 1) diff --git a/node.go b/node.go index 9f086984..d7a0ab91 100644 --- a/node.go +++ b/node.go @@ -7,6 +7,7 @@ import ( "net" "sort" "strconv" + "sync" "time" "google.golang.org/grpc" @@ -30,6 +31,10 @@ type RawNode struct { // the default channel channel *channel + + // reference conting, use a mutex just in case + mu sync.Mutex + referenceCount uint } // NewRawNode returns a new node for the provided address. @@ -58,6 +63,34 @@ func NewRawNodeWithID(addr string, id uint32) (*RawNode, error) { }, nil } +// called when the node is added to a configuration +func (n *RawNode) obtain() { + n.mu.Lock() + defer n.mu.Unlock() + n.referenceCount++ +} + +// called when the node is removed from a configuration +// if the reference count reaches zero close the node +func (n *RawNode) release() error { + n.mu.Lock() + n.referenceCount-- + shouldClose := n.referenceCount == 0 + n.mu.Unlock() + + if shouldClose { + err := n.mgr.removeNode(n) + if err != nil { + return err + } + err = n.close() + if err != nil { + return err + } + } + return nil +} + // connect to this node and associate it with the manager. func (n *RawNode) connect(mgr *RawManager) error { n.mgr = mgr From 967e3a70ea018a55651e27860444cf862cf26e75 Mon Sep 17 00:00:00 2001 From: asbjorn Date: Sun, 11 May 2025 23:17:18 +0200 Subject: [PATCH 6/7] simple test for configuration closing --- benchmark/benchmark_gorums.pb.go | 23 ++++++---- cmd/protoc-gen-gorums/dev/config.go | 21 ++++++--- .../dev/zorums_client_gorums.pb.go | 2 +- .../dev/zorums_multicast_gorums.pb.go | 2 +- .../dev/zorums_quorumcall_gorums.pb.go | 2 +- .../dev/zorums_rpc_gorums.pb.go | 2 +- .../dev/zorums_server_gorums.pb.go | 2 +- .../dev/zorums_unicast_gorums.pb.go | 2 +- .../gengorums/template_static.go | 21 ++++++--- config.go | 10 ++++ config_test.go | 46 +++++++++++++++++++ examples/storage/proto/storage_gorums.pb.go | 23 ++++++---- tests/config/config_gorums.pb.go | 23 ++++++---- tests/correctable/correctable_gorums.pb.go | 23 ++++++---- tests/dummy/dummy_gorums.pb.go | 23 ++++++---- tests/metadata/metadata_gorums.pb.go | 23 ++++++---- tests/oneway/oneway_gorums.pb.go | 23 ++++++---- tests/ordering/order_gorums.pb.go | 23 ++++++---- tests/qf/qf_gorums.pb.go | 23 ++++++---- tests/tls/tls_gorums.pb.go | 23 ++++++---- tests/unresponsive/unresponsive_gorums.pb.go | 23 ++++++---- 21 files changed, 255 insertions(+), 108 deletions(-) diff --git a/benchmark/benchmark_gorums.pb.go b/benchmark/benchmark_gorums.pb.go index c5a8fc73..e5ca98dc 100644 --- a/benchmark/benchmark_gorums.pb.go +++ b/benchmark/benchmark_gorums.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-gorums. DO NOT EDIT. // versions: -// protoc-gen-gorums v0.9.0-devel+125b0cfb +// protoc-gen-gorums v0.9.0-devel+abcec912 // protoc v6.30.2 // source: benchmark/benchmark.proto @@ -67,17 +67,12 @@ func (c *Configuration) SubConfiguration(cfg gorums.NodeListOption) (subCfg *Con return subCfg, nil } -// Close closes a configuration created from the NewConfiguration method -// -// NOTE: A configuration created with ConfigurationFromRaw or SubConfiguration closes and -// is closed when the original configuration or any of the subconfigurations are closed. -// If you want the configurations to be independent you need to use NewConfiguration +// Close closes the nodes which aren't used by other subconfigurations func (c *Configuration) Close() error { return c.RawConfiguration.Close() } -// Nodes returns a slice of each available node. IDs are returned in the same -// order as they were provided in the creation of the Manager. +// Nodes returns a slice of the configuration nodes. Sorted by node id. // // NOTE: mutating the returned slice is not supported. func (c *Configuration) Nodes() []*Node { @@ -88,6 +83,18 @@ func (c *Configuration) Nodes() []*Node { return nodes } +// AllNodes returns a slice of each available node of all subconfigurations. Sorted by node id. +// +// NOTE: mutating the returned slice is not supported. +func (c *Configuration) AllNodes() []*Node { + rawNodes := c.RawConfiguration.AllNodes() + nodes := make([]*Node, len(rawNodes)) + for i, n := range rawNodes { + nodes[i] = &Node{n} + } + return nodes +} + // And returns a NodeListOption that can be used to create a new configuration combining c and d. func (c *Configuration) And(d *Configuration) gorums.NodeListOption { return c.RawConfiguration.And(d.RawConfiguration) diff --git a/cmd/protoc-gen-gorums/dev/config.go b/cmd/protoc-gen-gorums/dev/config.go index c0c15eaa..80d52269 100644 --- a/cmd/protoc-gen-gorums/dev/config.go +++ b/cmd/protoc-gen-gorums/dev/config.go @@ -51,17 +51,12 @@ func (c *Configuration) SubConfiguration(cfg gorums.NodeListOption) (subCfg *Con return subCfg, nil } -// Close closes a configuration created from the NewConfiguration method -// -// NOTE: A configuration created with ConfigurationFromRaw or SubConfiguration closes and -// is closed when the original configuration or any of the subconfigurations are closed. -// If you want the configurations to be independent you need to use NewConfiguration +// Close closes the nodes which aren't used by other subconfigurations func (c *Configuration) Close() error { return c.RawConfiguration.Close() } -// Nodes returns a slice of each available node. IDs are returned in the same -// order as they were provided in the creation of the Manager. +// Nodes returns a slice of the configuration nodes. Sorted by node id. // // NOTE: mutating the returned slice is not supported. func (c *Configuration) Nodes() []*Node { @@ -72,6 +67,18 @@ func (c *Configuration) Nodes() []*Node { return nodes } +// AllNodes returns a slice of each available node of all subconfigurations. Sorted by node id. +// +// NOTE: mutating the returned slice is not supported. +func (c *Configuration) AllNodes() []*Node { + rawNodes := c.RawConfiguration.AllNodes() + nodes := make([]*Node, len(rawNodes)) + for i, n := range rawNodes { + nodes[i] = &Node{n} + } + return nodes +} + // And returns a NodeListOption that can be used to create a new configuration combining c and d. func (c *Configuration) And(d *Configuration) gorums.NodeListOption { return c.RawConfiguration.And(d.RawConfiguration) diff --git a/cmd/protoc-gen-gorums/dev/zorums_client_gorums.pb.go b/cmd/protoc-gen-gorums/dev/zorums_client_gorums.pb.go index 240c1d47..6cd5fb4e 100644 --- a/cmd/protoc-gen-gorums/dev/zorums_client_gorums.pb.go +++ b/cmd/protoc-gen-gorums/dev/zorums_client_gorums.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-gorums. DO NOT EDIT. // versions: -// protoc-gen-gorums v0.9.0-devel+125b0cfb +// protoc-gen-gorums v0.9.0-devel+abcec912 // protoc v6.30.2 // source: zorums.proto diff --git a/cmd/protoc-gen-gorums/dev/zorums_multicast_gorums.pb.go b/cmd/protoc-gen-gorums/dev/zorums_multicast_gorums.pb.go index d0b464ce..808e97e2 100644 --- a/cmd/protoc-gen-gorums/dev/zorums_multicast_gorums.pb.go +++ b/cmd/protoc-gen-gorums/dev/zorums_multicast_gorums.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-gorums. DO NOT EDIT. // versions: -// protoc-gen-gorums v0.9.0-devel+125b0cfb +// protoc-gen-gorums v0.9.0-devel+abcec912 // protoc v6.30.2 // source: zorums.proto diff --git a/cmd/protoc-gen-gorums/dev/zorums_quorumcall_gorums.pb.go b/cmd/protoc-gen-gorums/dev/zorums_quorumcall_gorums.pb.go index 853bd862..55ae0677 100644 --- a/cmd/protoc-gen-gorums/dev/zorums_quorumcall_gorums.pb.go +++ b/cmd/protoc-gen-gorums/dev/zorums_quorumcall_gorums.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-gorums. DO NOT EDIT. // versions: -// protoc-gen-gorums v0.9.0-devel+125b0cfb +// protoc-gen-gorums v0.9.0-devel+abcec912 // protoc v6.30.2 // source: zorums.proto diff --git a/cmd/protoc-gen-gorums/dev/zorums_rpc_gorums.pb.go b/cmd/protoc-gen-gorums/dev/zorums_rpc_gorums.pb.go index fb1f4738..14401229 100644 --- a/cmd/protoc-gen-gorums/dev/zorums_rpc_gorums.pb.go +++ b/cmd/protoc-gen-gorums/dev/zorums_rpc_gorums.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-gorums. DO NOT EDIT. // versions: -// protoc-gen-gorums v0.9.0-devel+125b0cfb +// protoc-gen-gorums v0.9.0-devel+abcec912 // protoc v6.30.2 // source: zorums.proto diff --git a/cmd/protoc-gen-gorums/dev/zorums_server_gorums.pb.go b/cmd/protoc-gen-gorums/dev/zorums_server_gorums.pb.go index 29596945..2716a85b 100644 --- a/cmd/protoc-gen-gorums/dev/zorums_server_gorums.pb.go +++ b/cmd/protoc-gen-gorums/dev/zorums_server_gorums.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-gorums. DO NOT EDIT. // versions: -// protoc-gen-gorums v0.9.0-devel+125b0cfb +// protoc-gen-gorums v0.9.0-devel+abcec912 // protoc v6.30.2 // source: zorums.proto diff --git a/cmd/protoc-gen-gorums/dev/zorums_unicast_gorums.pb.go b/cmd/protoc-gen-gorums/dev/zorums_unicast_gorums.pb.go index 9f89aea3..e2b55da8 100644 --- a/cmd/protoc-gen-gorums/dev/zorums_unicast_gorums.pb.go +++ b/cmd/protoc-gen-gorums/dev/zorums_unicast_gorums.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-gorums. DO NOT EDIT. // versions: -// protoc-gen-gorums v0.9.0-devel+125b0cfb +// protoc-gen-gorums v0.9.0-devel+abcec912 // protoc v6.30.2 // source: zorums.proto diff --git a/cmd/protoc-gen-gorums/gengorums/template_static.go b/cmd/protoc-gen-gorums/gengorums/template_static.go index 915a7a56..13366416 100644 --- a/cmd/protoc-gen-gorums/gengorums/template_static.go +++ b/cmd/protoc-gen-gorums/gengorums/template_static.go @@ -58,17 +58,12 @@ func (c *Configuration) SubConfiguration(cfg gorums.NodeListOption) (subCfg *Con return subCfg, nil } -// Close closes a configuration created from the NewConfiguration method -// -// NOTE: A configuration created with ConfigurationFromRaw or SubConfiguration closes and -// is closed when the original configuration or any of the subconfigurations are closed. -// If you want the configurations to be independent you need to use NewConfiguration +// Close closes the nodes which aren't used by other subconfigurations func (c *Configuration) Close() error { return c.RawConfiguration.Close() } -// Nodes returns a slice of each available node. IDs are returned in the same -// order as they were provided in the creation of the Manager. +// Nodes returns a slice of the configuration nodes. Sorted by node id. // // NOTE: mutating the returned slice is not supported. func (c *Configuration) Nodes() []*Node { @@ -79,6 +74,18 @@ func (c *Configuration) Nodes() []*Node { return nodes } +// AllNodes returns a slice of each available node of all subconfigurations. Sorted by node id. +// +// NOTE: mutating the returned slice is not supported. +func (c *Configuration) AllNodes() []*Node { + rawNodes := c.RawConfiguration.AllNodes() + nodes := make([]*Node, len(rawNodes)) + for i, n := range rawNodes { + nodes[i] = &Node{n} + } + return nodes +} + // And returns a NodeListOption that can be used to create a new configuration combining c and d. func (c *Configuration) And(d *Configuration) gorums.NodeListOption { return c.RawConfiguration.And(d.RawConfiguration) diff --git a/config.go b/config.go index e5a49fb2..880b4474 100644 --- a/config.go +++ b/config.go @@ -83,6 +83,16 @@ func (c RawConfiguration) Nodes() []*RawNode { return c.RawNodes } +// AllNodes returns the nodes in this configuration and all subconfigurations. +func (c RawConfiguration) AllNodeIDs() []uint32 { + return c.RawManager.NodeIDs() +} + +// AllNodes returns the nodes in this configuration and all subconfigurations. +func (c RawConfiguration) AllNodes() []*RawNode { + return c.RawManager.Nodes() +} + // Size returns the number of nodes in this configuration. func (c RawConfiguration) Size() int { return len(c.RawNodes) diff --git a/config_test.go b/config_test.go index 1be1426a..9f4ba0c7 100644 --- a/config_test.go +++ b/config_test.go @@ -3,6 +3,7 @@ package gorums_test import ( "context" "errors" + "reflect" "sync" "testing" @@ -228,3 +229,48 @@ func TestConfigConcurrentAccess(t *testing.T) { t.Error(err) } } + +func TestConfigRelease(t *testing.T) { + addrs, teardown := gorums.TestSetup(t, 10, func(_ int) gorums.ServerIface { + return initServer() + }) + defer teardown() + + c1, err := dummy.NewConfiguration(gorums.WithNodeList(addrs[:3]), gorumsTestMgrOpts()...) + if err != nil { + t.Fatal(err) + } + + c2, err := c1.SubConfiguration(gorums.WithNodeList(addrs[2:8])) + if err != nil { + t.Fatal(err) + } + + c3, err := c1.SubConfiguration(gorums.WithNodeList(addrs[5:])) + if err != nil { + t.Fatal(err) + } + + allNodes := c2.AllNodeIDs() + + c1c2, err := c2.SubConfiguration(c1.And(c2)) + if err != nil { + t.Fatal(err) + } + targetNodes := c1c2.NodeIDs() + + c3.Close() + + remainingNodes := c2.AllNodeIDs() + + hasReleased := reflect.DeepEqual(remainingNodes, targetNodes) + hasAll := reflect.DeepEqual(remainingNodes, allNodes) + + if hasAll { + t.Fatal("c3.Close() did not close any nodes") + } + + if !hasReleased { + t.Fatal("There are not closed nodes which don't belong to any not closed configuration") + } +} diff --git a/examples/storage/proto/storage_gorums.pb.go b/examples/storage/proto/storage_gorums.pb.go index 2b664cff..91619606 100644 --- a/examples/storage/proto/storage_gorums.pb.go +++ b/examples/storage/proto/storage_gorums.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-gorums. DO NOT EDIT. // versions: -// protoc-gen-gorums v0.9.0-devel+125b0cfb +// protoc-gen-gorums v0.9.0-devel+abcec912 // protoc v6.30.2 // source: storage/proto/storage.proto @@ -67,17 +67,12 @@ func (c *Configuration) SubConfiguration(cfg gorums.NodeListOption) (subCfg *Con return subCfg, nil } -// Close closes a configuration created from the NewConfiguration method -// -// NOTE: A configuration created with ConfigurationFromRaw or SubConfiguration closes and -// is closed when the original configuration or any of the subconfigurations are closed. -// If you want the configurations to be independent you need to use NewConfiguration +// Close closes the nodes which aren't used by other subconfigurations func (c *Configuration) Close() error { return c.RawConfiguration.Close() } -// Nodes returns a slice of each available node. IDs are returned in the same -// order as they were provided in the creation of the Manager. +// Nodes returns a slice of the configuration nodes. Sorted by node id. // // NOTE: mutating the returned slice is not supported. func (c *Configuration) Nodes() []*Node { @@ -88,6 +83,18 @@ func (c *Configuration) Nodes() []*Node { return nodes } +// AllNodes returns a slice of each available node of all subconfigurations. Sorted by node id. +// +// NOTE: mutating the returned slice is not supported. +func (c *Configuration) AllNodes() []*Node { + rawNodes := c.RawConfiguration.AllNodes() + nodes := make([]*Node, len(rawNodes)) + for i, n := range rawNodes { + nodes[i] = &Node{n} + } + return nodes +} + // And returns a NodeListOption that can be used to create a new configuration combining c and d. func (c *Configuration) And(d *Configuration) gorums.NodeListOption { return c.RawConfiguration.And(d.RawConfiguration) diff --git a/tests/config/config_gorums.pb.go b/tests/config/config_gorums.pb.go index 0e621d83..6bdb69ac 100644 --- a/tests/config/config_gorums.pb.go +++ b/tests/config/config_gorums.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-gorums. DO NOT EDIT. // versions: -// protoc-gen-gorums v0.9.0-devel+125b0cfb +// protoc-gen-gorums v0.9.0-devel+abcec912 // protoc v6.30.2 // source: config/config.proto @@ -66,17 +66,12 @@ func (c *Configuration) SubConfiguration(cfg gorums.NodeListOption) (subCfg *Con return subCfg, nil } -// Close closes a configuration created from the NewConfiguration method -// -// NOTE: A configuration created with ConfigurationFromRaw or SubConfiguration closes and -// is closed when the original configuration or any of the subconfigurations are closed. -// If you want the configurations to be independent you need to use NewConfiguration +// Close closes the nodes which aren't used by other subconfigurations func (c *Configuration) Close() error { return c.RawConfiguration.Close() } -// Nodes returns a slice of each available node. IDs are returned in the same -// order as they were provided in the creation of the Manager. +// Nodes returns a slice of the configuration nodes. Sorted by node id. // // NOTE: mutating the returned slice is not supported. func (c *Configuration) Nodes() []*Node { @@ -87,6 +82,18 @@ func (c *Configuration) Nodes() []*Node { return nodes } +// AllNodes returns a slice of each available node of all subconfigurations. Sorted by node id. +// +// NOTE: mutating the returned slice is not supported. +func (c *Configuration) AllNodes() []*Node { + rawNodes := c.RawConfiguration.AllNodes() + nodes := make([]*Node, len(rawNodes)) + for i, n := range rawNodes { + nodes[i] = &Node{n} + } + return nodes +} + // And returns a NodeListOption that can be used to create a new configuration combining c and d. func (c *Configuration) And(d *Configuration) gorums.NodeListOption { return c.RawConfiguration.And(d.RawConfiguration) diff --git a/tests/correctable/correctable_gorums.pb.go b/tests/correctable/correctable_gorums.pb.go index b28199f9..89fc56a8 100644 --- a/tests/correctable/correctable_gorums.pb.go +++ b/tests/correctable/correctable_gorums.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-gorums. DO NOT EDIT. // versions: -// protoc-gen-gorums v0.9.0-devel+125b0cfb +// protoc-gen-gorums v0.9.0-devel+abcec912 // protoc v6.30.2 // source: correctable/correctable.proto @@ -68,17 +68,12 @@ func (c *Configuration) SubConfiguration(cfg gorums.NodeListOption) (subCfg *Con return subCfg, nil } -// Close closes a configuration created from the NewConfiguration method -// -// NOTE: A configuration created with ConfigurationFromRaw or SubConfiguration closes and -// is closed when the original configuration or any of the subconfigurations are closed. -// If you want the configurations to be independent you need to use NewConfiguration +// Close closes the nodes which aren't used by other subconfigurations func (c *Configuration) Close() error { return c.RawConfiguration.Close() } -// Nodes returns a slice of each available node. IDs are returned in the same -// order as they were provided in the creation of the Manager. +// Nodes returns a slice of the configuration nodes. Sorted by node id. // // NOTE: mutating the returned slice is not supported. func (c *Configuration) Nodes() []*Node { @@ -89,6 +84,18 @@ func (c *Configuration) Nodes() []*Node { return nodes } +// AllNodes returns a slice of each available node of all subconfigurations. Sorted by node id. +// +// NOTE: mutating the returned slice is not supported. +func (c *Configuration) AllNodes() []*Node { + rawNodes := c.RawConfiguration.AllNodes() + nodes := make([]*Node, len(rawNodes)) + for i, n := range rawNodes { + nodes[i] = &Node{n} + } + return nodes +} + // And returns a NodeListOption that can be used to create a new configuration combining c and d. func (c *Configuration) And(d *Configuration) gorums.NodeListOption { return c.RawConfiguration.And(d.RawConfiguration) diff --git a/tests/dummy/dummy_gorums.pb.go b/tests/dummy/dummy_gorums.pb.go index 238c0f17..0ab8032d 100644 --- a/tests/dummy/dummy_gorums.pb.go +++ b/tests/dummy/dummy_gorums.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-gorums. DO NOT EDIT. // versions: -// protoc-gen-gorums v0.9.0-devel+125b0cfb +// protoc-gen-gorums v0.9.0-devel+abcec912 // protoc v6.30.2 // source: dummy/dummy.proto @@ -66,17 +66,12 @@ func (c *Configuration) SubConfiguration(cfg gorums.NodeListOption) (subCfg *Con return subCfg, nil } -// Close closes a configuration created from the NewConfiguration method -// -// NOTE: A configuration created with ConfigurationFromRaw or SubConfiguration closes and -// is closed when the original configuration or any of the subconfigurations are closed. -// If you want the configurations to be independent you need to use NewConfiguration +// Close closes the nodes which aren't used by other subconfigurations func (c *Configuration) Close() error { return c.RawConfiguration.Close() } -// Nodes returns a slice of each available node. IDs are returned in the same -// order as they were provided in the creation of the Manager. +// Nodes returns a slice of the configuration nodes. Sorted by node id. // // NOTE: mutating the returned slice is not supported. func (c *Configuration) Nodes() []*Node { @@ -87,6 +82,18 @@ func (c *Configuration) Nodes() []*Node { return nodes } +// AllNodes returns a slice of each available node of all subconfigurations. Sorted by node id. +// +// NOTE: mutating the returned slice is not supported. +func (c *Configuration) AllNodes() []*Node { + rawNodes := c.RawConfiguration.AllNodes() + nodes := make([]*Node, len(rawNodes)) + for i, n := range rawNodes { + nodes[i] = &Node{n} + } + return nodes +} + // And returns a NodeListOption that can be used to create a new configuration combining c and d. func (c *Configuration) And(d *Configuration) gorums.NodeListOption { return c.RawConfiguration.And(d.RawConfiguration) diff --git a/tests/metadata/metadata_gorums.pb.go b/tests/metadata/metadata_gorums.pb.go index 9c71c570..c925935e 100644 --- a/tests/metadata/metadata_gorums.pb.go +++ b/tests/metadata/metadata_gorums.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-gorums. DO NOT EDIT. // versions: -// protoc-gen-gorums v0.9.0-devel+125b0cfb +// protoc-gen-gorums v0.9.0-devel+abcec912 // protoc v6.30.2 // source: metadata/metadata.proto @@ -67,17 +67,12 @@ func (c *Configuration) SubConfiguration(cfg gorums.NodeListOption) (subCfg *Con return subCfg, nil } -// Close closes a configuration created from the NewConfiguration method -// -// NOTE: A configuration created with ConfigurationFromRaw or SubConfiguration closes and -// is closed when the original configuration or any of the subconfigurations are closed. -// If you want the configurations to be independent you need to use NewConfiguration +// Close closes the nodes which aren't used by other subconfigurations func (c *Configuration) Close() error { return c.RawConfiguration.Close() } -// Nodes returns a slice of each available node. IDs are returned in the same -// order as they were provided in the creation of the Manager. +// Nodes returns a slice of the configuration nodes. Sorted by node id. // // NOTE: mutating the returned slice is not supported. func (c *Configuration) Nodes() []*Node { @@ -88,6 +83,18 @@ func (c *Configuration) Nodes() []*Node { return nodes } +// AllNodes returns a slice of each available node of all subconfigurations. Sorted by node id. +// +// NOTE: mutating the returned slice is not supported. +func (c *Configuration) AllNodes() []*Node { + rawNodes := c.RawConfiguration.AllNodes() + nodes := make([]*Node, len(rawNodes)) + for i, n := range rawNodes { + nodes[i] = &Node{n} + } + return nodes +} + // And returns a NodeListOption that can be used to create a new configuration combining c and d. func (c *Configuration) And(d *Configuration) gorums.NodeListOption { return c.RawConfiguration.And(d.RawConfiguration) diff --git a/tests/oneway/oneway_gorums.pb.go b/tests/oneway/oneway_gorums.pb.go index 5139eed6..ae2cac33 100644 --- a/tests/oneway/oneway_gorums.pb.go +++ b/tests/oneway/oneway_gorums.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-gorums. DO NOT EDIT. // versions: -// protoc-gen-gorums v0.9.0-devel+125b0cfb +// protoc-gen-gorums v0.9.0-devel+abcec912 // protoc v6.30.2 // source: oneway/oneway.proto @@ -67,17 +67,12 @@ func (c *Configuration) SubConfiguration(cfg gorums.NodeListOption) (subCfg *Con return subCfg, nil } -// Close closes a configuration created from the NewConfiguration method -// -// NOTE: A configuration created with ConfigurationFromRaw or SubConfiguration closes and -// is closed when the original configuration or any of the subconfigurations are closed. -// If you want the configurations to be independent you need to use NewConfiguration +// Close closes the nodes which aren't used by other subconfigurations func (c *Configuration) Close() error { return c.RawConfiguration.Close() } -// Nodes returns a slice of each available node. IDs are returned in the same -// order as they were provided in the creation of the Manager. +// Nodes returns a slice of the configuration nodes. Sorted by node id. // // NOTE: mutating the returned slice is not supported. func (c *Configuration) Nodes() []*Node { @@ -88,6 +83,18 @@ func (c *Configuration) Nodes() []*Node { return nodes } +// AllNodes returns a slice of each available node of all subconfigurations. Sorted by node id. +// +// NOTE: mutating the returned slice is not supported. +func (c *Configuration) AllNodes() []*Node { + rawNodes := c.RawConfiguration.AllNodes() + nodes := make([]*Node, len(rawNodes)) + for i, n := range rawNodes { + nodes[i] = &Node{n} + } + return nodes +} + // And returns a NodeListOption that can be used to create a new configuration combining c and d. func (c *Configuration) And(d *Configuration) gorums.NodeListOption { return c.RawConfiguration.And(d.RawConfiguration) diff --git a/tests/ordering/order_gorums.pb.go b/tests/ordering/order_gorums.pb.go index b52688f8..a1aedc5e 100644 --- a/tests/ordering/order_gorums.pb.go +++ b/tests/ordering/order_gorums.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-gorums. DO NOT EDIT. // versions: -// protoc-gen-gorums v0.9.0-devel+125b0cfb +// protoc-gen-gorums v0.9.0-devel+abcec912 // protoc v6.30.2 // source: ordering/order.proto @@ -66,17 +66,12 @@ func (c *Configuration) SubConfiguration(cfg gorums.NodeListOption) (subCfg *Con return subCfg, nil } -// Close closes a configuration created from the NewConfiguration method -// -// NOTE: A configuration created with ConfigurationFromRaw or SubConfiguration closes and -// is closed when the original configuration or any of the subconfigurations are closed. -// If you want the configurations to be independent you need to use NewConfiguration +// Close closes the nodes which aren't used by other subconfigurations func (c *Configuration) Close() error { return c.RawConfiguration.Close() } -// Nodes returns a slice of each available node. IDs are returned in the same -// order as they were provided in the creation of the Manager. +// Nodes returns a slice of the configuration nodes. Sorted by node id. // // NOTE: mutating the returned slice is not supported. func (c *Configuration) Nodes() []*Node { @@ -87,6 +82,18 @@ func (c *Configuration) Nodes() []*Node { return nodes } +// AllNodes returns a slice of each available node of all subconfigurations. Sorted by node id. +// +// NOTE: mutating the returned slice is not supported. +func (c *Configuration) AllNodes() []*Node { + rawNodes := c.RawConfiguration.AllNodes() + nodes := make([]*Node, len(rawNodes)) + for i, n := range rawNodes { + nodes[i] = &Node{n} + } + return nodes +} + // And returns a NodeListOption that can be used to create a new configuration combining c and d. func (c *Configuration) And(d *Configuration) gorums.NodeListOption { return c.RawConfiguration.And(d.RawConfiguration) diff --git a/tests/qf/qf_gorums.pb.go b/tests/qf/qf_gorums.pb.go index 5a87c0eb..e9954b21 100644 --- a/tests/qf/qf_gorums.pb.go +++ b/tests/qf/qf_gorums.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-gorums. DO NOT EDIT. // versions: -// protoc-gen-gorums v0.9.0-devel+125b0cfb +// protoc-gen-gorums v0.9.0-devel+abcec912 // protoc v6.30.2 // source: qf/qf.proto @@ -66,17 +66,12 @@ func (c *Configuration) SubConfiguration(cfg gorums.NodeListOption) (subCfg *Con return subCfg, nil } -// Close closes a configuration created from the NewConfiguration method -// -// NOTE: A configuration created with ConfigurationFromRaw or SubConfiguration closes and -// is closed when the original configuration or any of the subconfigurations are closed. -// If you want the configurations to be independent you need to use NewConfiguration +// Close closes the nodes which aren't used by other subconfigurations func (c *Configuration) Close() error { return c.RawConfiguration.Close() } -// Nodes returns a slice of each available node. IDs are returned in the same -// order as they were provided in the creation of the Manager. +// Nodes returns a slice of the configuration nodes. Sorted by node id. // // NOTE: mutating the returned slice is not supported. func (c *Configuration) Nodes() []*Node { @@ -87,6 +82,18 @@ func (c *Configuration) Nodes() []*Node { return nodes } +// AllNodes returns a slice of each available node of all subconfigurations. Sorted by node id. +// +// NOTE: mutating the returned slice is not supported. +func (c *Configuration) AllNodes() []*Node { + rawNodes := c.RawConfiguration.AllNodes() + nodes := make([]*Node, len(rawNodes)) + for i, n := range rawNodes { + nodes[i] = &Node{n} + } + return nodes +} + // And returns a NodeListOption that can be used to create a new configuration combining c and d. func (c *Configuration) And(d *Configuration) gorums.NodeListOption { return c.RawConfiguration.And(d.RawConfiguration) diff --git a/tests/tls/tls_gorums.pb.go b/tests/tls/tls_gorums.pb.go index ff82ac15..6f1e5a51 100644 --- a/tests/tls/tls_gorums.pb.go +++ b/tests/tls/tls_gorums.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-gorums. DO NOT EDIT. // versions: -// protoc-gen-gorums v0.9.0-devel+125b0cfb +// protoc-gen-gorums v0.9.0-devel+abcec912 // protoc v6.30.2 // source: tls/tls.proto @@ -66,17 +66,12 @@ func (c *Configuration) SubConfiguration(cfg gorums.NodeListOption) (subCfg *Con return subCfg, nil } -// Close closes a configuration created from the NewConfiguration method -// -// NOTE: A configuration created with ConfigurationFromRaw or SubConfiguration closes and -// is closed when the original configuration or any of the subconfigurations are closed. -// If you want the configurations to be independent you need to use NewConfiguration +// Close closes the nodes which aren't used by other subconfigurations func (c *Configuration) Close() error { return c.RawConfiguration.Close() } -// Nodes returns a slice of each available node. IDs are returned in the same -// order as they were provided in the creation of the Manager. +// Nodes returns a slice of the configuration nodes. Sorted by node id. // // NOTE: mutating the returned slice is not supported. func (c *Configuration) Nodes() []*Node { @@ -87,6 +82,18 @@ func (c *Configuration) Nodes() []*Node { return nodes } +// AllNodes returns a slice of each available node of all subconfigurations. Sorted by node id. +// +// NOTE: mutating the returned slice is not supported. +func (c *Configuration) AllNodes() []*Node { + rawNodes := c.RawConfiguration.AllNodes() + nodes := make([]*Node, len(rawNodes)) + for i, n := range rawNodes { + nodes[i] = &Node{n} + } + return nodes +} + // And returns a NodeListOption that can be used to create a new configuration combining c and d. func (c *Configuration) And(d *Configuration) gorums.NodeListOption { return c.RawConfiguration.And(d.RawConfiguration) diff --git a/tests/unresponsive/unresponsive_gorums.pb.go b/tests/unresponsive/unresponsive_gorums.pb.go index 774a05d0..8882dcb1 100644 --- a/tests/unresponsive/unresponsive_gorums.pb.go +++ b/tests/unresponsive/unresponsive_gorums.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-gorums. DO NOT EDIT. // versions: -// protoc-gen-gorums v0.9.0-devel+125b0cfb +// protoc-gen-gorums v0.9.0-devel+abcec912 // protoc v6.30.2 // source: unresponsive/unresponsive.proto @@ -66,17 +66,12 @@ func (c *Configuration) SubConfiguration(cfg gorums.NodeListOption) (subCfg *Con return subCfg, nil } -// Close closes a configuration created from the NewConfiguration method -// -// NOTE: A configuration created with ConfigurationFromRaw or SubConfiguration closes and -// is closed when the original configuration or any of the subconfigurations are closed. -// If you want the configurations to be independent you need to use NewConfiguration +// Close closes the nodes which aren't used by other subconfigurations func (c *Configuration) Close() error { return c.RawConfiguration.Close() } -// Nodes returns a slice of each available node. IDs are returned in the same -// order as they were provided in the creation of the Manager. +// Nodes returns a slice of the configuration nodes. Sorted by node id. // // NOTE: mutating the returned slice is not supported. func (c *Configuration) Nodes() []*Node { @@ -87,6 +82,18 @@ func (c *Configuration) Nodes() []*Node { return nodes } +// AllNodes returns a slice of each available node of all subconfigurations. Sorted by node id. +// +// NOTE: mutating the returned slice is not supported. +func (c *Configuration) AllNodes() []*Node { + rawNodes := c.RawConfiguration.AllNodes() + nodes := make([]*Node, len(rawNodes)) + for i, n := range rawNodes { + nodes[i] = &Node{n} + } + return nodes +} + // And returns a NodeListOption that can be used to create a new configuration combining c and d. func (c *Configuration) And(d *Configuration) gorums.NodeListOption { return c.RawConfiguration.And(d.RawConfiguration) From b2a01377b8448803a47d8079b9e1ccd0fee8ccae Mon Sep 17 00:00:00 2001 From: asbjorn Date: Mon, 12 May 2025 18:03:31 +0200 Subject: [PATCH 7/7] rename RawManager to manager, update manager tests to internal --- benchmark/benchmark_gorums.pb.go | 2 +- channel_test.go | 22 ++-- .../dev/zorums_client_gorums.pb.go | 2 +- .../dev/zorums_multicast_gorums.pb.go | 2 +- .../dev/zorums_quorumcall_gorums.pb.go | 2 +- .../dev/zorums_rpc_gorums.pb.go | 2 +- .../dev/zorums_server_gorums.pb.go | 2 +- .../dev/zorums_unicast_gorums.pb.go | 2 +- config.go | 12 +-- config_opts.go | 42 ++++---- config_test.go | 19 ++-- examples/storage/repl.go | 8 +- mgr.go | 31 +++--- mgr_internal_test.go | 89 +++++++++++++++ mgr_test.go | 102 ------------------ node.go | 4 +- rpc_test.go | 6 +- server_test.go => server_internal_test.go | 17 ++- tests/config/config_gorums.pb.go | 2 +- tests/correctable/correctable_gorums.pb.go | 2 +- tests/dummy/dummy_gorums.pb.go | 2 +- tests/metadata/metadata_gorums.pb.go | 2 +- tests/oneway/oneway_gorums.pb.go | 2 +- tests/ordering/order_gorums.pb.go | 2 +- tests/qf/qf_gorums.pb.go | 2 +- tests/tls/tls_gorums.pb.go | 2 +- tests/unresponsive/unresponsive_gorums.pb.go | 2 +- 27 files changed, 187 insertions(+), 197 deletions(-) create mode 100644 mgr_internal_test.go delete mode 100644 mgr_test.go rename server_test.go => server_internal_test.go (72%) diff --git a/benchmark/benchmark_gorums.pb.go b/benchmark/benchmark_gorums.pb.go index e5ca98dc..4060d82b 100644 --- a/benchmark/benchmark_gorums.pb.go +++ b/benchmark/benchmark_gorums.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-gorums. DO NOT EDIT. // versions: -// protoc-gen-gorums v0.9.0-devel+abcec912 +// protoc-gen-gorums v0.9.0-devel+967e3a70 // protoc v6.30.2 // source: benchmark/benchmark.proto diff --git a/channel_test.go b/channel_test.go index 47107208..302ebfe4 100644 --- a/channel_test.go +++ b/channel_test.go @@ -17,8 +17,8 @@ func (mockSrv) Test(_ ServerCtx, _ *mock.Request) (*mock.Response, error) { return nil, nil } -func dummyMgr() *RawManager { - return NewRawManager( +func dummyMgr() *manager { + return newManager( WithGrpcDialOptions( grpc.WithTransportCredentials(insecure.NewCredentials()), ), @@ -27,7 +27,7 @@ func dummyMgr() *RawManager { var handlerName = "mock.Server.Test" -func dummySrv() *Server { +func newDummySrv() *Server { mockSrv := &mockSrv{} srv := NewServer() srv.RegisterHandler(handlerName, func(ctx ServerCtx, in *Message, finished chan<- *Message) { @@ -66,20 +66,20 @@ func TestChannelCreation(t *testing.T) { func TestChannelSuccessfulConnection(t *testing.T) { addrs, teardown := TestSetup(t, 1, func(_ int) ServerIface { - return dummySrv() + return newDummySrv() }) defer teardown() mgr := dummyMgr() - defer mgr.Close() + defer mgr.close() node, err := NewRawNode(addrs[0]) if err != nil { t.Fatal(err) } - if err = mgr.AddNode(node); err != nil { + if err = mgr.addNode(node); err != nil { t.Fatal(err) } - if len(mgr.Nodes()) < 1 { + if len(mgr.getNodes()) < 1 { t.Fatal("the node was not added to the configuration") } if !node.channel.isConnected() { @@ -92,7 +92,7 @@ func TestChannelSuccessfulConnection(t *testing.T) { func TestChannelUnsuccessfulConnection(t *testing.T) { mgr := dummyMgr() - defer mgr.Close() + defer mgr.close() // no servers are listening on the given address node, err := NewRawNode("127.0.0.1:5000") if err != nil { @@ -100,10 +100,10 @@ func TestChannelUnsuccessfulConnection(t *testing.T) { } // the node should still be added to the configuration - if err = mgr.AddNode(node); err != nil { + if err = mgr.addNode(node); err != nil { t.Fatal(err) } - if len(mgr.Nodes()) < 1 { + if len(mgr.getNodes()) < 1 { t.Fatal("the node was not added to the configuration") } if node.conn == nil { @@ -114,7 +114,7 @@ func TestChannelUnsuccessfulConnection(t *testing.T) { func TestChannelReconnection(t *testing.T) { srvAddr := "127.0.0.1:5000" // wait to start the server - startServer, stopServer := testServerSetup(t, srvAddr, dummySrv()) + startServer, stopServer := testServerSetup(t, srvAddr, newDummySrv()) node, err := NewRawNode(srvAddr) if err != nil { t.Fatal(err) diff --git a/cmd/protoc-gen-gorums/dev/zorums_client_gorums.pb.go b/cmd/protoc-gen-gorums/dev/zorums_client_gorums.pb.go index 6cd5fb4e..ae11117a 100644 --- a/cmd/protoc-gen-gorums/dev/zorums_client_gorums.pb.go +++ b/cmd/protoc-gen-gorums/dev/zorums_client_gorums.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-gorums. DO NOT EDIT. // versions: -// protoc-gen-gorums v0.9.0-devel+abcec912 +// protoc-gen-gorums v0.9.0-devel+967e3a70 // protoc v6.30.2 // source: zorums.proto diff --git a/cmd/protoc-gen-gorums/dev/zorums_multicast_gorums.pb.go b/cmd/protoc-gen-gorums/dev/zorums_multicast_gorums.pb.go index 808e97e2..cba8e726 100644 --- a/cmd/protoc-gen-gorums/dev/zorums_multicast_gorums.pb.go +++ b/cmd/protoc-gen-gorums/dev/zorums_multicast_gorums.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-gorums. DO NOT EDIT. // versions: -// protoc-gen-gorums v0.9.0-devel+abcec912 +// protoc-gen-gorums v0.9.0-devel+967e3a70 // protoc v6.30.2 // source: zorums.proto diff --git a/cmd/protoc-gen-gorums/dev/zorums_quorumcall_gorums.pb.go b/cmd/protoc-gen-gorums/dev/zorums_quorumcall_gorums.pb.go index 55ae0677..89cf4e31 100644 --- a/cmd/protoc-gen-gorums/dev/zorums_quorumcall_gorums.pb.go +++ b/cmd/protoc-gen-gorums/dev/zorums_quorumcall_gorums.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-gorums. DO NOT EDIT. // versions: -// protoc-gen-gorums v0.9.0-devel+abcec912 +// protoc-gen-gorums v0.9.0-devel+967e3a70 // protoc v6.30.2 // source: zorums.proto diff --git a/cmd/protoc-gen-gorums/dev/zorums_rpc_gorums.pb.go b/cmd/protoc-gen-gorums/dev/zorums_rpc_gorums.pb.go index 14401229..5458b547 100644 --- a/cmd/protoc-gen-gorums/dev/zorums_rpc_gorums.pb.go +++ b/cmd/protoc-gen-gorums/dev/zorums_rpc_gorums.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-gorums. DO NOT EDIT. // versions: -// protoc-gen-gorums v0.9.0-devel+abcec912 +// protoc-gen-gorums v0.9.0-devel+967e3a70 // protoc v6.30.2 // source: zorums.proto diff --git a/cmd/protoc-gen-gorums/dev/zorums_server_gorums.pb.go b/cmd/protoc-gen-gorums/dev/zorums_server_gorums.pb.go index 2716a85b..4465ae0f 100644 --- a/cmd/protoc-gen-gorums/dev/zorums_server_gorums.pb.go +++ b/cmd/protoc-gen-gorums/dev/zorums_server_gorums.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-gorums. DO NOT EDIT. // versions: -// protoc-gen-gorums v0.9.0-devel+abcec912 +// protoc-gen-gorums v0.9.0-devel+967e3a70 // protoc v6.30.2 // source: zorums.proto diff --git a/cmd/protoc-gen-gorums/dev/zorums_unicast_gorums.pb.go b/cmd/protoc-gen-gorums/dev/zorums_unicast_gorums.pb.go index e2b55da8..e7d0b38f 100644 --- a/cmd/protoc-gen-gorums/dev/zorums_unicast_gorums.pb.go +++ b/cmd/protoc-gen-gorums/dev/zorums_unicast_gorums.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-gorums. DO NOT EDIT. // versions: -// protoc-gen-gorums v0.9.0-devel+abcec912 +// protoc-gen-gorums v0.9.0-devel+967e3a70 // protoc v6.30.2 // source: zorums.proto diff --git a/config.go b/config.go index 880b4474..ba15b4e0 100644 --- a/config.go +++ b/config.go @@ -12,7 +12,7 @@ import ( // You should use the generated `Configuration` type instead. type RawConfiguration struct { RawNodes []*RawNode - *RawManager + mgr *manager } // NewRawConfiguration returns a configuration based on the provided list of nodes. @@ -21,7 +21,7 @@ func NewRawConfiguration(nodes NodeListOption, opts ...ManagerOption) (cfg RawCo if nodes == nil { return RawConfiguration{}, fmt.Errorf("config: missing required node list") } - mgr := NewRawManager(opts...) + mgr := newManager(opts...) cfg, err = nodes.newConfig(mgr) for _, n := range cfg.RawNodes { @@ -40,7 +40,7 @@ func (c *RawConfiguration) SubRawConfiguration(nodes NodeListOption) (cfg RawCon return RawConfiguration{}, fmt.Errorf("config: missing required node list") } - cfg, err = nodes.newConfig(c.RawManager) + cfg, err = nodes.newConfig(c.mgr) for _, n := range cfg.RawNodes { n.obtain() } @@ -50,7 +50,7 @@ func (c *RawConfiguration) SubRawConfiguration(nodes NodeListOption) (cfg RawCon // CloseAll closes the configurations and all of its subconfigurations func (c *RawConfiguration) CloseAll() error { - c.RawManager.Close() + c.mgr.close() return nil } @@ -85,12 +85,12 @@ func (c RawConfiguration) Nodes() []*RawNode { // AllNodes returns the nodes in this configuration and all subconfigurations. func (c RawConfiguration) AllNodeIDs() []uint32 { - return c.RawManager.NodeIDs() + return c.mgr.nodeIDs() } // AllNodes returns the nodes in this configuration and all subconfigurations. func (c RawConfiguration) AllNodes() []*RawNode { - return c.RawManager.Nodes() + return c.mgr.getNodes() } // Size returns the number of nodes in this configuration. diff --git a/config_opts.go b/config_opts.go index cbd174f5..b7a6ab8c 100644 --- a/config_opts.go +++ b/config_opts.go @@ -10,26 +10,26 @@ type ConfigOption any // NodeListOption must be implemented by node providers. type NodeListOption interface { ConfigOption - newConfig(*RawManager) (RawConfiguration, error) + newConfig(*manager) (RawConfiguration, error) } type nodeIDMap struct { idMap map[string]uint32 } -func (o nodeIDMap) newConfig(mgr *RawManager) (cfg RawConfiguration, err error) { +func (o nodeIDMap) newConfig(mgr *manager) (cfg RawConfiguration, err error) { if len(o.idMap) == 0 { return RawConfiguration{}, fmt.Errorf("config: missing required node map") } nodes := make([]*RawNode, 0, len(o.idMap)) for naddr, id := range o.idMap { - node, found := mgr.Node(id) + node, found := mgr.node(id) if !found { node, err = NewRawNodeWithID(naddr, id) if err != nil { return RawConfiguration{}, err } - if err = mgr.AddNode(node); err != nil { + if err = mgr.addNode(node); err != nil { return RawConfiguration{}, err } } @@ -40,8 +40,8 @@ func (o nodeIDMap) newConfig(mgr *RawManager) (cfg RawConfiguration, err error) OrderedBy(ID).Sort(nodes) cfg = RawConfiguration{ - RawNodes: nodes, - RawManager: mgr, + RawNodes: nodes, + mgr: mgr, } return cfg, nil @@ -57,7 +57,7 @@ type nodeList struct { addrsList []string } -func (o nodeList) newConfig(mgr *RawManager) (cfg RawConfiguration, err error) { +func (o nodeList) newConfig(mgr *manager) (cfg RawConfiguration, err error) { if len(o.addrsList) == 0 { return RawConfiguration{}, fmt.Errorf("config: missing required node addresses") } @@ -67,8 +67,8 @@ func (o nodeList) newConfig(mgr *RawManager) (cfg RawConfiguration, err error) { if err != nil { return RawConfiguration{}, err } - if n, found := mgr.Node(node.ID()); !found { - if err = mgr.AddNode(node); err != nil { + if n, found := mgr.node(node.ID()); !found { + if err = mgr.addNode(node); err != nil { return RawConfiguration{}, err } } else { @@ -81,15 +81,15 @@ func (o nodeList) newConfig(mgr *RawManager) (cfg RawConfiguration, err error) { OrderedBy(ID).Sort(nodes) cfg = RawConfiguration{ - RawNodes: nodes, - RawManager: mgr, + RawNodes: nodes, + mgr: mgr, } return cfg, nil } // WithNodeList returns a NodeListOption containing the provided list of node addresses. -// With this option, node IDs are generated by the RawManager. +// With this option, node IDs are generated by the manager. func WithNodeList(addrsList []string) NodeListOption { return &nodeList{addrsList: addrsList} } @@ -98,13 +98,13 @@ type nodeIDs struct { nodeIDs []uint32 } -func (o nodeIDs) newConfig(mgr *RawManager) (cfg RawConfiguration, err error) { +func (o nodeIDs) newConfig(mgr *manager) (cfg RawConfiguration, err error) { if len(o.nodeIDs) == 0 { return RawConfiguration{}, fmt.Errorf("config: missing required node IDs") } nodes := make([]*RawNode, 0, len(o.nodeIDs)) for _, id := range o.nodeIDs { - node, found := mgr.Node(id) + node, found := mgr.node(id) if !found { // Node IDs must have been registered previously return RawConfiguration{}, fmt.Errorf("config: node %d not found", id) @@ -116,15 +116,15 @@ func (o nodeIDs) newConfig(mgr *RawManager) (cfg RawConfiguration, err error) { OrderedBy(ID).Sort(nodes) cfg = RawConfiguration{ - RawNodes: nodes, - RawManager: mgr, + RawNodes: nodes, + mgr: mgr, } return cfg, nil } // WithNodeIDs returns a NodeListOption containing a list of node IDs. -// This assumes that the provided node IDs have already been registered with the RawManager. +// This assumes that the provided node IDs have already been registered with the manager. func WithNodeIDs(ids []uint32) NodeListOption { return &nodeIDs{nodeIDs: ids} } @@ -134,7 +134,7 @@ type addNodes struct { new NodeListOption } -func (o addNodes) newConfig(mgr *RawManager) (nodes RawConfiguration, err error) { +func (o addNodes) newConfig(mgr *manager) (nodes RawConfiguration, err error) { newNodes, err := o.new.newConfig(mgr) if err != nil { return RawConfiguration{}, err @@ -154,7 +154,7 @@ type addConfig struct { add RawConfiguration } -func (o addConfig) newConfig(mgr *RawManager) (cfg RawConfiguration, err error) { +func (o addConfig) newConfig(mgr *manager) (cfg RawConfiguration, err error) { nodes := make([]*RawNode, 0, len(o.old.RawNodes)+len(o.add.RawNodes)) m := make(map[uint32]bool) for _, n := range append(o.old.RawNodes, o.add.RawNodes...) { @@ -168,8 +168,8 @@ func (o addConfig) newConfig(mgr *RawManager) (cfg RawConfiguration, err error) OrderedBy(ID).Sort(nodes) cfg = RawConfiguration{ - RawNodes: nodes, - RawManager: mgr, + RawNodes: nodes, + mgr: mgr, } return cfg, err diff --git a/config_test.go b/config_test.go index 9f4ba0c7..279ce4a7 100644 --- a/config_test.go +++ b/config_test.go @@ -12,6 +12,11 @@ import ( "github.com/relab/gorums/tests/dummy" ) +var ( + nodes = []string{"127.0.0.1:9080", "127.0.0.1:9081", "127.0.0.1:9082"} + nodeMap = map[string]uint32{"127.0.0.1:9080": 1, "127.0.0.1:9081": 2, "127.0.0.1:9082": 3, "127.0.0.1:9083": 4} +) + func TestNewConfigurationEmptyNodeList(t *testing.T) { wantErr := errors.New("config: missing required node addresses") _, err := gorums.NewRawConfiguration(gorums.WithNodeList([]string{})) @@ -25,7 +30,6 @@ func TestNewConfigurationEmptyNodeList(t *testing.T) { func TestNewConfigurationNodeList(t *testing.T) { cfg, err := gorums.NewRawConfiguration(gorums.WithNodeList(nodes)) - mgr := cfg.RawManager if err != nil { t.Fatal(err) } @@ -48,10 +52,10 @@ func TestNewConfigurationNodeList(t *testing.T) { } } - if mgr.Size() != len(nodes) { - t.Errorf("mgr.Size() = %d, expected %d", mgr.Size(), len(nodes)) + if len(cfg.AllNodeIDs()) != len(nodes) { + t.Errorf("mgr.Size() = %d, expected %d", len(cfg.AllNodeIDs()), len(nodes)) } - mgrNodes := cfg.Nodes() + mgrNodes := cfg.AllNodes() for _, n := range nodes { if !contains(mgrNodes, n) { t.Errorf("mgr.Nodes() = %v, expected %s", mgrNodes, n) @@ -61,7 +65,6 @@ func TestNewConfigurationNodeList(t *testing.T) { func TestNewConfigurationNodeMap(t *testing.T) { cfg, err := gorums.NewRawConfiguration(gorums.WithNodeMap(nodeMap)) - mgr := cfg.RawManager if err != nil { t.Fatal(err) } @@ -73,10 +76,10 @@ func TestNewConfigurationNodeMap(t *testing.T) { t.Errorf("cfg.Nodes()[%s] = %d, expected %d", node.Address(), node.ID(), nodeMap[node.Address()]) } } - if mgr.Size() != len(nodeMap) { - t.Errorf("mgr.Size() = %d, expected %d", mgr.Size(), len(nodeMap)) + if len(cfg.AllNodeIDs()) != len(nodeMap) { + t.Errorf("mgr.Size() = %d, expected %d", len(cfg.AllNodeIDs()), len(nodeMap)) } - for _, node := range mgr.Nodes() { + for _, node := range cfg.AllNodes() { if nodeMap[node.Address()] != node.ID() { t.Errorf("mgr.Nodes()[%s] = %d, expected %d", node.Address(), node.ID(), nodeMap[node.Address()]) } diff --git a/examples/storage/repl.go b/examples/storage/repl.go index 77cf61d9..8ec34234 100644 --- a/examples/storage/repl.go +++ b/examples/storage/repl.go @@ -129,7 +129,7 @@ func Repl(cfg *pb.Configuration) error { r.multicast(args[1:]) case "nodes": fmt.Println("Nodes: ") - for i, n := range cfg.RawManager.Nodes() { + for i, n := range cfg.AllNodes() { fmt.Printf("%d: %s\n", i, n.Address()) } default: @@ -293,7 +293,7 @@ func (r repl) parseConfiguration(cfgStr string) (cfg *pb.Configuration) { if i := strings.Index(cfgStr, ":"); i > -1 { var start, stop int var err error - numNodes := r.cfg.RawManager.Size() + numNodes := len(r.cfg.AllNodes()) if i == 0 { start = 0 } else { @@ -317,7 +317,7 @@ func (r repl) parseConfiguration(cfgStr string) (cfg *pb.Configuration) { return nil } nodes := make([]string, 0) - for _, node := range r.cfg.RawManager.Nodes()[start:stop] { + for _, node := range r.cfg.AllNodes()[start:stop] { nodes = append(nodes, node.Address()) } cfg, err = r.cfg.SubConfiguration(gorums.WithNodeList(nodes)) @@ -330,7 +330,7 @@ func (r repl) parseConfiguration(cfgStr string) (cfg *pb.Configuration) { // configuration using list of indices if indices := strings.Split(cfgStr, ","); len(indices) > 0 { selectedNodes := make([]string, 0, len(indices)) - nodes := r.cfg.RawManager.Nodes() + nodes := r.cfg.AllNodes() for _, index := range indices { i, err := strconv.Atoi(index) if err != nil { diff --git a/mgr.go b/mgr.go index 5afeda10..188b3531 100644 --- a/mgr.go +++ b/mgr.go @@ -12,12 +12,12 @@ import ( "google.golang.org/grpc/backoff" ) -// RawManager maintains a connection pool of nodes on +// manager maintains a connection pool of nodes on // which quorum calls can be performed. // // This struct is intended to be used by generated code. // You should use the generated `Manager` struct instead. -type RawManager struct { +type manager struct { mu sync.Mutex nodes []*RawNode lookup map[uint32]*RawNode @@ -27,12 +27,12 @@ type RawManager struct { nextMsgID uint64 } -// NewRawManager returns a new RawManager for managing connection to nodes added +// newManager returns a new manager for managing connection to nodes added // to the manager. This function accepts manager options used to configure // various aspects of the manager. This function is meant for internal use. // You should use the `NewManager` function in the generated code instead. -func NewRawManager(opts ...ManagerOption) *RawManager { - m := &RawManager{ +func newManager(opts ...ManagerOption) *manager { + m := &manager{ lookup: make(map[uint32]*RawNode), opts: newManagerOptions(), } @@ -56,7 +56,7 @@ func NewRawManager(opts ...ManagerOption) *RawManager { return m } -func (m *RawManager) closeNodeConns() { +func (m *manager) closeNodeConns() { for _, node := range m.nodes { err := node.close() if err != nil && m.logger != nil { @@ -66,7 +66,7 @@ func (m *RawManager) closeNodeConns() { } // Close closes all node connections and any client streams. -func (m *RawManager) Close() { +func (m *manager) close() { m.closeOnce.Do(func() { if m.logger != nil { m.logger.Printf("closing") @@ -77,7 +77,7 @@ func (m *RawManager) Close() { // NodeIDs returns the identifier of each available node. IDs are returned in // the same order as they were provided in the creation of the Manager. -func (m *RawManager) NodeIDs() []uint32 { +func (m *manager) nodeIDs() []uint32 { m.mu.Lock() defer m.mu.Unlock() ids := make([]uint32, 0, len(m.nodes)) @@ -88,7 +88,7 @@ func (m *RawManager) NodeIDs() []uint32 { } // Node returns the node with the given identifier if present. -func (m *RawManager) Node(id uint32) (node *RawNode, found bool) { +func (m *manager) node(id uint32) (node *RawNode, found bool) { m.mu.Lock() defer m.mu.Unlock() node, found = m.lookup[id] @@ -97,14 +97,14 @@ func (m *RawManager) Node(id uint32) (node *RawNode, found bool) { // Nodes returns a slice of each available node. IDs are returned in the same // order as they were provided in the creation of the Manager. -func (m *RawManager) Nodes() []*RawNode { +func (m *manager) getNodes() []*RawNode { m.mu.Lock() defer m.mu.Unlock() return m.nodes } // Size returns the number of nodes in the Manager. -func (m *RawManager) Size() (nodes int) { +func (m *manager) size() (nodes int) { m.mu.Lock() defer m.mu.Unlock() return len(m.nodes) @@ -112,8 +112,9 @@ func (m *RawManager) Size() (nodes int) { // AddNode adds the node to the manager's node pool // and establishes a connection to the node. -func (m *RawManager) AddNode(node *RawNode) error { - if _, found := m.Node(node.ID()); found { +// should only be used by tests +func (m *manager) addNode(node *RawNode) error { + if _, found := m.node(node.ID()); found { // Node IDs must be unique return fmt.Errorf("config: node %d (%s) already exists", node.ID(), node.Address()) } @@ -134,7 +135,7 @@ func (m *RawManager) AddNode(node *RawNode) error { } // remove a node from a manager, the node is closed seperately -func (m *RawManager) removeNode(node *RawNode) error { +func (m *manager) removeNode(node *RawNode) error { if m.logger != nil { m.logger.Printf("Removing node %s with id %d\n", node, node.id) } @@ -159,6 +160,6 @@ func (m *RawManager) removeNode(node *RawNode) error { } // getMsgID returns a unique message ID. -func (m *RawManager) getMsgID() uint64 { +func (m *manager) getMsgID() uint64 { return atomic.AddUint64(&m.nextMsgID, 1) } diff --git a/mgr_internal_test.go b/mgr_internal_test.go new file mode 100644 index 00000000..cc35f36c --- /dev/null +++ b/mgr_internal_test.go @@ -0,0 +1,89 @@ +package gorums + +import ( + "bytes" + "log" + "testing" + + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" +) + +var mgrTestNodeMap = map[string]uint32{"127.0.0.1:9080": 1, "127.0.0.1:9081": 2, "127.0.0.1:9082": 3, "127.0.0.1:9083": 4} + +func TestManagerLogging(t *testing.T) { + var ( + buf bytes.Buffer + logger = log.New(&buf, "logger: ", log.Lshortfile) + ) + buf.WriteString("\n") + _ = newManager( + WithNoConnect(), + WithLogger(logger), + ) + t.Log(buf.String()) +} + +func TestManagerAddNode(t *testing.T) { + cfg, err := NewRawConfiguration(WithNodeMap(mgrTestNodeMap), WithNoConnect()) + mgr := cfg.mgr + if err != nil { + t.Fatal(err) + } + tests := []struct { + addr string + id uint32 + err string + }{ + {"127.0.1.1:1234", 1, "config: node 1 (127.0.1.1:1234) already exists"}, + {"127.0.1.1:1234", 5, ""}, + {"127.0.1.1:1234", 6, ""}, // The same addr:port can have different IDs + {"127.0.1.1:1234", 2, "config: node 2 (127.0.1.1:1234) already exists"}, + } + for _, test := range tests { + node, err := NewRawNodeWithID(test.addr, test.id) + if err != nil { + t.Fatal(err) + } + err = mgr.addNode(node) + if err != nil && err.Error() != test.err { + t.Errorf("mgr.addNode(Node(%s, %d)) = %s, expected %s", test.addr, test.id, err.Error(), test.err) + } + } +} + +func TestManagerAddNodeWithConn(t *testing.T) { + addrs, teardown := TestSetup(t, 3, func(_ int) ServerIface { + srv := NewServer() + return srv + }) + defer teardown() + + cfg, err := NewRawConfiguration(WithNodeList(addrs[:2]), + WithGrpcDialOptions( + grpc.WithTransportCredentials(insecure.NewCredentials()), + ), + ) + if err != nil { + t.Fatal(err) + } + defer cfg.Close() + + mgr := cfg.mgr + + if mgr.size() != len(addrs)-1 { + t.Errorf("mgr.Size() = %d, expected %d", mgr.size(), len(addrs)-1) + } + + node, err := NewRawNode(addrs[2]) + if err != nil { + t.Fatal(err) + } + err = mgr.addNode(node) + if err != nil { + t.Errorf("mgr.AddNode(%s) = %q, expected %q", addrs[2], err.Error(), "") + } + if mgr.size() != len(addrs) { + t.Errorf("mgr.Size() = %d, expected %d", mgr.size(), len(addrs)) + } +} diff --git a/mgr_test.go b/mgr_test.go deleted file mode 100644 index eafcea48..00000000 --- a/mgr_test.go +++ /dev/null @@ -1,102 +0,0 @@ -package gorums_test - -import ( - "bytes" - "log" - "testing" - - "github.com/relab/gorums" - "github.com/relab/gorums/tests/dummy" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials/insecure" -) - -var ( - nodes = []string{"127.0.0.1:9080", "127.0.0.1:9081", "127.0.0.1:9082"} - nodeMap = map[string]uint32{"127.0.0.1:9080": 1, "127.0.0.1:9081": 2, "127.0.0.1:9082": 3, "127.0.0.1:9083": 4} -) - -func TestManagerLogging(t *testing.T) { - var ( - buf bytes.Buffer - logger = log.New(&buf, "logger: ", log.Lshortfile) - ) - buf.WriteString("\n") - _ = gorums.NewRawManager( - gorums.WithNoConnect(), - gorums.WithLogger(logger), - ) - t.Log(buf.String()) -} - -func TestManagerAddNode(t *testing.T) { - cfg, err := gorums.NewRawConfiguration(gorums.WithNodeMap(nodeMap), gorums.WithNoConnect()) - mgr := cfg.RawManager - if err != nil { - t.Fatal(err) - } - tests := []struct { - addr string - id uint32 - err string - }{ - {"127.0.1.1:1234", 1, "config: node 1 (127.0.1.1:1234) already exists"}, - {"127.0.1.1:1234", 5, ""}, - {"127.0.1.1:1234", 6, ""}, // The same addr:port can have different IDs - {"127.0.1.1:1234", 2, "config: node 2 (127.0.1.1:1234) already exists"}, - } - for _, test := range tests { - node, err := gorums.NewRawNodeWithID(test.addr, test.id) - if err != nil { - t.Fatal(err) - } - err = mgr.AddNode(node) - if err != nil && err.Error() != test.err { - t.Errorf("mgr.AddNode(Node(%s, %d)) = %s, expected %s", test.addr, test.id, err.Error(), test.err) - } - } -} - -// Proto definition in tests/dummy/dummy.proto -type dummySrv struct{} - -func (dummySrv) Test(ctx gorums.ServerCtx, _ *dummy.Empty) (resp *dummy.Empty, err error) { - return nil, nil -} - -func TestManagerAddNodeWithConn(t *testing.T) { - addrs, teardown := gorums.TestSetup(t, 3, func(_ int) gorums.ServerIface { - srv := gorums.NewServer() - dummy.RegisterDummyServer(srv, &dummySrv{}) - return srv - }) - defer teardown() - - ctx, err := gorums.NewRawConfiguration(gorums.WithNodeList(addrs[:2]), - gorums.WithGrpcDialOptions( - grpc.WithTransportCredentials(insecure.NewCredentials()), - ), - ) - if err != nil { - t.Fatal(err) - } - defer ctx.Close() - - mgr := ctx.RawManager - - if mgr.Size() != len(addrs)-1 { - t.Errorf("mgr.Size() = %d, expected %d", mgr.Size(), len(addrs)-1) - } - - node, err := gorums.NewRawNode(addrs[2]) - if err != nil { - t.Fatal(err) - } - err = mgr.AddNode(node) - if err != nil { - t.Errorf("mgr.AddNode(%s) = %q, expected %q", addrs[2], err.Error(), "") - } - if mgr.Size() != len(addrs) { - t.Errorf("mgr.Size() = %d, expected %d", mgr.Size(), len(addrs)) - } -} diff --git a/node.go b/node.go index d7a0ab91..213b2fef 100644 --- a/node.go +++ b/node.go @@ -27,7 +27,7 @@ type RawNode struct { addr string conn *grpc.ClientConn cancel func() - mgr *RawManager + mgr *manager // the default channel channel *channel @@ -92,7 +92,7 @@ func (n *RawNode) release() error { } // connect to this node and associate it with the manager. -func (n *RawNode) connect(mgr *RawManager) error { +func (n *RawNode) connect(mgr *manager) error { n.mgr = mgr if n.mgr.opts.noConnect { return nil diff --git a/rpc_test.go b/rpc_test.go index c201c489..824c693e 100644 --- a/rpc_test.go +++ b/rpc_test.go @@ -26,7 +26,7 @@ func TestRPCCallSuccess(t *testing.T) { node := cfg.Nodes()[0] ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() - response, err := node.RPCCall(ctx, gorums.CallData{ + response, err := node.RawNode.RPCCall(ctx, gorums.CallData{ Message: &dummy.Empty{}, Method: "dummy.Dummy.Test", }) @@ -53,7 +53,7 @@ func TestRPCCallDownedNode(t *testing.T) { node := cfg.Nodes()[0] ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() - response, err := node.RPCCall(ctx, gorums.CallData{ + response, err := node.RawNode.RPCCall(ctx, gorums.CallData{ Message: &dummy.Empty{}, Method: "dummy.Dummy.Test", }) @@ -80,7 +80,7 @@ func TestRPCCallTimedOut(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 0*time.Second) time.Sleep(50 * time.Millisecond) defer cancel() - response, err := node.RPCCall(ctx, gorums.CallData{ + response, err := node.RawNode.RPCCall(ctx, gorums.CallData{ Message: &dummy.Empty{}, Method: "dummy.Dummy.Test", }) diff --git a/server_test.go b/server_internal_test.go similarity index 72% rename from server_test.go rename to server_internal_test.go index 42117ada..de5520ca 100644 --- a/server_test.go +++ b/server_internal_test.go @@ -1,4 +1,4 @@ -package gorums_test +package gorums import ( "context" @@ -6,7 +6,6 @@ import ( "testing" "time" - "github.com/relab/gorums" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/metadata" @@ -16,7 +15,7 @@ func TestServerCallback(t *testing.T) { var message string signal := make(chan struct{}) - srv := gorums.NewServer(gorums.WithConnectCallback(func(ctx context.Context) { + srv := NewServer(WithConnectCallback(func(ctx context.Context) { m, ok := metadata.FromIncomingContext(ctx) if !ok { return @@ -35,20 +34,20 @@ func TestServerCallback(t *testing.T) { md := metadata.New(map[string]string{"message": "hello"}) - mgr := gorums.NewRawManager( - gorums.WithMetadata(md), - gorums.WithGrpcDialOptions( + mgr := newManager( + WithMetadata(md), + WithGrpcDialOptions( grpc.WithTransportCredentials(insecure.NewCredentials()), ), ) - defer mgr.Close() + defer mgr.close() - node, err := gorums.NewRawNode(lis.Addr().String()) + node, err := NewRawNode(lis.Addr().String()) if err != nil { t.Fatal(err) } - if err = mgr.AddNode(node); err != nil { + if err = mgr.addNode(node); err != nil { t.Fatal(err) } diff --git a/tests/config/config_gorums.pb.go b/tests/config/config_gorums.pb.go index 6bdb69ac..7e491a4f 100644 --- a/tests/config/config_gorums.pb.go +++ b/tests/config/config_gorums.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-gorums. DO NOT EDIT. // versions: -// protoc-gen-gorums v0.9.0-devel+abcec912 +// protoc-gen-gorums v0.9.0-devel+967e3a70 // protoc v6.30.2 // source: config/config.proto diff --git a/tests/correctable/correctable_gorums.pb.go b/tests/correctable/correctable_gorums.pb.go index 89fc56a8..06484517 100644 --- a/tests/correctable/correctable_gorums.pb.go +++ b/tests/correctable/correctable_gorums.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-gorums. DO NOT EDIT. // versions: -// protoc-gen-gorums v0.9.0-devel+abcec912 +// protoc-gen-gorums v0.9.0-devel+967e3a70 // protoc v6.30.2 // source: correctable/correctable.proto diff --git a/tests/dummy/dummy_gorums.pb.go b/tests/dummy/dummy_gorums.pb.go index 0ab8032d..31e9da48 100644 --- a/tests/dummy/dummy_gorums.pb.go +++ b/tests/dummy/dummy_gorums.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-gorums. DO NOT EDIT. // versions: -// protoc-gen-gorums v0.9.0-devel+abcec912 +// protoc-gen-gorums v0.9.0-devel+967e3a70 // protoc v6.30.2 // source: dummy/dummy.proto diff --git a/tests/metadata/metadata_gorums.pb.go b/tests/metadata/metadata_gorums.pb.go index c925935e..e24761b6 100644 --- a/tests/metadata/metadata_gorums.pb.go +++ b/tests/metadata/metadata_gorums.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-gorums. DO NOT EDIT. // versions: -// protoc-gen-gorums v0.9.0-devel+abcec912 +// protoc-gen-gorums v0.9.0-devel+967e3a70 // protoc v6.30.2 // source: metadata/metadata.proto diff --git a/tests/oneway/oneway_gorums.pb.go b/tests/oneway/oneway_gorums.pb.go index ae2cac33..d253b125 100644 --- a/tests/oneway/oneway_gorums.pb.go +++ b/tests/oneway/oneway_gorums.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-gorums. DO NOT EDIT. // versions: -// protoc-gen-gorums v0.9.0-devel+abcec912 +// protoc-gen-gorums v0.9.0-devel+967e3a70 // protoc v6.30.2 // source: oneway/oneway.proto diff --git a/tests/ordering/order_gorums.pb.go b/tests/ordering/order_gorums.pb.go index a1aedc5e..094283bb 100644 --- a/tests/ordering/order_gorums.pb.go +++ b/tests/ordering/order_gorums.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-gorums. DO NOT EDIT. // versions: -// protoc-gen-gorums v0.9.0-devel+abcec912 +// protoc-gen-gorums v0.9.0-devel+967e3a70 // protoc v6.30.2 // source: ordering/order.proto diff --git a/tests/qf/qf_gorums.pb.go b/tests/qf/qf_gorums.pb.go index e9954b21..07e12504 100644 --- a/tests/qf/qf_gorums.pb.go +++ b/tests/qf/qf_gorums.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-gorums. DO NOT EDIT. // versions: -// protoc-gen-gorums v0.9.0-devel+abcec912 +// protoc-gen-gorums v0.9.0-devel+967e3a70 // protoc v6.30.2 // source: qf/qf.proto diff --git a/tests/tls/tls_gorums.pb.go b/tests/tls/tls_gorums.pb.go index 6f1e5a51..4becf6ca 100644 --- a/tests/tls/tls_gorums.pb.go +++ b/tests/tls/tls_gorums.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-gorums. DO NOT EDIT. // versions: -// protoc-gen-gorums v0.9.0-devel+abcec912 +// protoc-gen-gorums v0.9.0-devel+967e3a70 // protoc v6.30.2 // source: tls/tls.proto diff --git a/tests/unresponsive/unresponsive_gorums.pb.go b/tests/unresponsive/unresponsive_gorums.pb.go index 8882dcb1..c4fca0b4 100644 --- a/tests/unresponsive/unresponsive_gorums.pb.go +++ b/tests/unresponsive/unresponsive_gorums.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-gorums. DO NOT EDIT. // versions: -// protoc-gen-gorums v0.9.0-devel+abcec912 +// protoc-gen-gorums v0.9.0-devel+967e3a70 // protoc v6.30.2 // source: unresponsive/unresponsive.proto