Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom subconfigurations for a tree #155

Draft
wants to merge 7 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,16 @@ func (c RawConfiguration) Equal(b RawConfiguration) bool {
return true
}

// Contains returns true if nodeID is in configuration c.
func (c RawConfiguration) Contains(nodeID uint32) bool {
for i := range c {
if c[i].ID() == nodeID {
return true
}
}
return false
}

func (c RawConfiguration) getMsgID() uint64 {
return c[0].mgr.getMsgID()
}
2 changes: 1 addition & 1 deletion config_opts.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ func (o nodeIDs) newConfig(mgr *RawManager) (nodes RawConfiguration, err error)

// WithNodeIDs returns a NodeListOption containing a list of node IDs.
// This assumes that the provided node IDs have already been registered with the manager.
func WithNodeIDs(ids []uint32) NodeListOption {
func WithNodeIDs(ids ...uint32) NodeListOption {
return &nodeIDs{nodeIDs: ids}
}

Expand Down
4 changes: 2 additions & 2 deletions config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ func TestNewConfigurationNodeIDs(t *testing.T) {

// Identical configurations c1 == c2
nodeIDs := c1.NodeIDs()
c2, err := gorums.NewRawConfiguration(mgr, gorums.WithNodeIDs(nodeIDs))
c2, err := gorums.NewRawConfiguration(mgr, gorums.WithNodeIDs(nodeIDs...))
if err != nil {
t.Fatal(err)
}
Expand All @@ -91,7 +91,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 := gorums.NewRawConfiguration(mgr, gorums.WithNodeIDs(nodeIDs[:len(nodeIDs)-1]...))
if err != nil {
t.Fatal(err)
}
Expand Down
65 changes: 65 additions & 0 deletions config_tree.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package gorums

import (
"fmt"
)

// SubConfigOption must be implemented by subconfiguration providers.
type SubConfigOption interface {
subConfig(*RawManager) ([]RawConfiguration, error)
}

// Derive subconfigurations from the manager's base configuration.
func SubConfigurations(mgr *RawManager, opt SubConfigOption) ([]RawConfiguration, error) {
if opt == nil {
return nil, ConfigCreationError(fmt.Errorf("missing required subconfiguration option"))
}
return opt.subConfig(mgr)
}

type treeConfig struct {
myID uint32
bf int
base NodeListOption
}

func (o treeConfig) subConfig(mgr *RawManager) (configs []RawConfiguration, err error) {
base, err := o.base.newConfig(mgr)
if err != nil {
return nil, err
}
myID, found := mgr.Node(o.myID)
if !found {
return nil, ConfigCreationError(fmt.Errorf("node ID %d not found", o.myID))
}

excludeCfg := RawConfiguration{myID}
for levelSize := o.bf; levelSize <= len(base); levelSize *= o.bf {
levelCfg := subLevelConfiguration(base, o.myID, levelSize)
// compute subconfiguration at level, excluding own configuration group
subCfg, _ := levelCfg.Except(excludeCfg).newConfig(mgr)
configs = append(configs, subCfg)
excludeCfg, _ = excludeCfg.And(levelCfg).newConfig(mgr)
}
return configs, nil
}

// subLevelConfiguration returns a configuration of size that contains myID.
func subLevelConfiguration(base RawConfiguration, myID uint32, size int) RawConfiguration {
for i := 0; i < len(base); i += size {
if base[i : i+size].Contains(myID) {
return base[i : i+size]
}
}
return RawConfiguration{}
}

// WithTreeConfigurations returns a SubConfigOption that can be used to create
// a set of subconfigurations representing a tree of nodes.
func WithTreeConfigurations(branchFactor int, myID uint32, base NodeListOption) SubConfigOption {
return &treeConfig{
bf: branchFactor,
myID: myID,
base: base,
}
}
90 changes: 90 additions & 0 deletions config_tree_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package gorums_test

import (
"fmt"
"testing"

"github.com/relab/gorums"
)

// TODO check if okay to use node id 0? the Node.ID() function returns 0 if node == nil.

func TestTreeConfiguration(t *testing.T) {
const branchFactor = 2
tests := []struct {
nodeIDs map[string]uint32 // all nodes in the configuration
id uint32 // my ID to create subconfigurations from
wantConfig [][]uint32 // index: tree level, value: list of node IDs in the configuration
}{
{nodeIDs: nodeMapIDs(0, 8), id: 0, wantConfig: [][]uint32{{1}, {2, 3}, {4, 5, 6, 7}}},
{nodeIDs: nodeMapIDs(0, 8), id: 1, wantConfig: [][]uint32{{0}, {2, 3}, {4, 5, 6, 7}}},
{nodeIDs: nodeMapIDs(0, 8), id: 2, wantConfig: [][]uint32{{3}, {0, 1}, {4, 5, 6, 7}}},
{nodeIDs: nodeMapIDs(0, 8), id: 3, wantConfig: [][]uint32{{2}, {0, 1}, {4, 5, 6, 7}}},
{nodeIDs: nodeMapIDs(0, 8), id: 4, wantConfig: [][]uint32{{5}, {6, 7}, {0, 1, 2, 3}}},
{nodeIDs: nodeMapIDs(0, 8), id: 5, wantConfig: [][]uint32{{4}, {6, 7}, {0, 1, 2, 3}}},
{nodeIDs: nodeMapIDs(0, 8), id: 6, wantConfig: [][]uint32{{7}, {4, 5}, {0, 1, 2, 3}}},
{nodeIDs: nodeMapIDs(0, 8), id: 7, wantConfig: [][]uint32{{6}, {4, 5}, {0, 1, 2, 3}}},
//
{nodeIDs: nodeMapIDs(0, 16), id: 0, wantConfig: [][]uint32{{1}, {2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11, 12, 13, 14, 15}}},
{nodeIDs: nodeMapIDs(0, 16), id: 1, wantConfig: [][]uint32{{0}, {2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11, 12, 13, 14, 15}}},
{nodeIDs: nodeMapIDs(0, 16), id: 2, wantConfig: [][]uint32{{3}, {0, 1}, {4, 5, 6, 7}, {8, 9, 10, 11, 12, 13, 14, 15}}},
{nodeIDs: nodeMapIDs(0, 16), id: 3, wantConfig: [][]uint32{{2}, {0, 1}, {4, 5, 6, 7}, {8, 9, 10, 11, 12, 13, 14, 15}}},
{nodeIDs: nodeMapIDs(0, 16), id: 4, wantConfig: [][]uint32{{5}, {6, 7}, {0, 1, 2, 3}, {8, 9, 10, 11, 12, 13, 14, 15}}},
{nodeIDs: nodeMapIDs(0, 16), id: 5, wantConfig: [][]uint32{{4}, {6, 7}, {0, 1, 2, 3}, {8, 9, 10, 11, 12, 13, 14, 15}}},
{nodeIDs: nodeMapIDs(0, 16), id: 6, wantConfig: [][]uint32{{7}, {4, 5}, {0, 1, 2, 3}, {8, 9, 10, 11, 12, 13, 14, 15}}},
{nodeIDs: nodeMapIDs(0, 16), id: 7, wantConfig: [][]uint32{{6}, {4, 5}, {0, 1, 2, 3}, {8, 9, 10, 11, 12, 13, 14, 15}}},
//
{nodeIDs: nodeMapIDs(0, 16), id: 8, wantConfig: [][]uint32{{9}, {10, 11}, {12, 13, 14, 15}, {0, 1, 2, 3, 4, 5, 6, 7}}},
{nodeIDs: nodeMapIDs(0, 16), id: 9, wantConfig: [][]uint32{{8}, {10, 11}, {12, 13, 14, 15}, {0, 1, 2, 3, 4, 5, 6, 7}}},
{nodeIDs: nodeMapIDs(0, 16), id: 10, wantConfig: [][]uint32{{11}, {8, 9}, {12, 13, 14, 15}, {0, 1, 2, 3, 4, 5, 6, 7}}},
{nodeIDs: nodeMapIDs(0, 16), id: 11, wantConfig: [][]uint32{{10}, {8, 9}, {12, 13, 14, 15}, {0, 1, 2, 3, 4, 5, 6, 7}}},
{nodeIDs: nodeMapIDs(0, 16), id: 12, wantConfig: [][]uint32{{13}, {14, 15}, {8, 9, 10, 11}, {0, 1, 2, 3, 4, 5, 6, 7}}},
{nodeIDs: nodeMapIDs(0, 16), id: 13, wantConfig: [][]uint32{{12}, {14, 15}, {8, 9, 10, 11}, {0, 1, 2, 3, 4, 5, 6, 7}}},
{nodeIDs: nodeMapIDs(0, 16), id: 14, wantConfig: [][]uint32{{15}, {12, 13}, {8, 9, 10, 11}, {0, 1, 2, 3, 4, 5, 6, 7}}},
{nodeIDs: nodeMapIDs(0, 16), id: 15, wantConfig: [][]uint32{{14}, {12, 13}, {8, 9, 10, 11}, {0, 1, 2, 3, 4, 5, 6, 7}}},
}
for _, test := range tests {
t.Run(fmt.Sprintf("%d", len(test.nodeIDs)), func(t *testing.T) {
mgr := gorums.NewRawManager(gorums.WithNoConnect())
cfgs, err := gorums.SubConfigurations(mgr,
gorums.WithTreeConfigurations(branchFactor, test.id, gorums.WithNodeMap(test.nodeIDs)))
if err != nil {
t.Fatal(err)
}
if len(cfgs) != log2(len(test.nodeIDs)) {
t.Errorf("len(cfgs) = %d, expected %d", len(cfgs), 2)
}
for i, cfg := range cfgs {
if len(cfg) != len(test.wantConfig[i]) {
t.Errorf("len(cfg) = %d, expected %d", len(cfg), len(test.wantConfig[i]))
}
for _, id := range test.wantConfig[i] {
if !cfg.Contains(id) {
t.Errorf("{%v}.Contains(%d) = false, expected true", cfg.NodeIDs(), id)
}
}
// fmt.Printf("c%d (%d): %v\n", i, cfg.Size(), cfg.NodeIDs())
}
})
}
}

// nodeMapIDs returns a map of localhost node IDs for the given node count.
func nodeMapIDs(s, n int) map[string]uint32 {
basePort := 9080
nodeMapIDs := make(map[string]uint32)
for i := s; i < s+n; i++ {
nodeMapIDs[fmt.Sprintf("127.0.0.1:%d", basePort+i)] = uint32(i)
}
return nodeMapIDs
}

// log2 returns the base 2 logarithm of x.
func log2(x int) int {
y := 0
for x > 1 {
x >>= 1
y++
}
return y
}