commit 6233590bb44556aceb8826474d7064760d464c67 Author: David J. Allen Date: Sun Nov 17 13:47:37 2024 -0700 add initial files diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..1c5bc07 --- /dev/null +++ b/go.mod @@ -0,0 +1,11 @@ +module github.com/davidallendj/partitions + +go 1.23.3 + +require github.com/rs/zerolog v1.33.0 + +require ( + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + golang.org/x/sys v0.12.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..98afda4 --- /dev/null +++ b/go.sum @@ -0,0 +1,15 @@ +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= +github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/internal/groups/groups.go b/internal/groups/groups.go new file mode 100644 index 0000000..151db11 --- /dev/null +++ b/internal/groups/groups.go @@ -0,0 +1,39 @@ +package groups + +import ( + "slices" + + "github.com/davidallendj/partitions/internal/partitions" +) + +type Group struct { + Name string + Labels []string +} + +func (g *Group) GetNodeIDs(pm *partitions.DefaultManager) []string { + foundNodes := []string{} + for _, label := range g.Labels { + nodeID := pm.LookupMember(label) + if nodeID != nil { + // check and make sure we're not duplicating node IDs + if !slices.Contains(foundNodes, *nodeID) { + foundNodes = append(foundNodes, *nodeID) + } + } + } + return foundNodes +} + +func (g *Group) GetPartitions(pm *partitions.DefaultManager) []string { + foundPartitions := []string{} + for _, label := range g.Labels { + partition := pm.LookupPartitionByMemberID(label) + if partition != nil { + if !slices.Contains(foundPartitions, partition.ID) { + foundPartitions = append(foundPartitions, partition.ID) + } + } + } + return foundPartitions +} diff --git a/internal/nodes/nodes.go b/internal/nodes/nodes.go new file mode 100644 index 0000000..3b48264 --- /dev/null +++ b/internal/nodes/nodes.go @@ -0,0 +1,11 @@ +package nodes + +import "errors" + +var ( + ErrExists = errors.New("node exists") +) + +type Node struct { + ID string +} diff --git a/internal/partitions/manager.go b/internal/partitions/manager.go new file mode 100644 index 0000000..d6b928a --- /dev/null +++ b/internal/partitions/manager.go @@ -0,0 +1,149 @@ +package partitions + +import ( + "errors" + "fmt" + "slices" + + "github.com/davidallendj/partitions/internal/nodes" +) + +var ( + ErrExists = errors.New("partition exists") + ErrNotFound = errors.New("partition not found") +) + +type Manager[T comparable] struct { + partitions []partition[T] +} + +func (m *Manager[T]) CreatePartition(id string, members []T) error { + // todo: deduplicate node ids + + // check if the partition already exists and fail if it does + if m.ContainsPartition(id) { + return ErrExists + } + + // check the manager to see if there a nodes being added to multiple partitions + for _, member := range members { + if m.ContainsMember(member) { + return fmt.Errorf("%v: %v", nodes.ErrExists, member) + } + } + + // no other issues at this point, so add the partition with nodes + m.partitions = append(m.partitions, partition[T]{ID: id, Members: members}) + return nil +} + +func (m *Manager[T]) AddNodeToPartition(partitionID string, member T) error { + // do lookup to find existing partition + partition := m.LookupPartitionByID(partitionID) + if partition != nil { + // check if node already exists and fail if it does + if m.ContainsMember(member) { + return fmt.Errorf("%v: %v", nodes.ErrExists, member) + } + } else { + // no partition found, so return error + return ErrNotFound + } + // add member to partition and update manager + partition.Members = append(partition.Members, member) + return nil +} + +func (m *Manager[T]) LookupPartitionByID(id string) *partition[T] { + // try and get index of partition with ID + index := slices.IndexFunc(m.partitions, func(p partition[T]) bool { + return p.ID == id + }) + // found a partition, so return it + if index >= 0 { + return &m.partitions[index] + } + return nil +} + +func (m *Manager[T]) LookupPartitionByMemberID(member T) *partition[T] { + partitionIndex, _ := m.lookupMember(member) + if partitionIndex >= 0 { + return &m.partitions[partitionIndex] + } + return nil +} + +func (m *Manager[T]) LookupMember(member T) *T { + var ( + partitionIndex int + memberIndex int + ) + partitionIndex, memberIndex = m.lookupMember(member) + return m.getNodeFromPartition(partitionIndex, memberIndex) +} + +func (m *Manager[T]) ContainsPartition(id string) bool { + return m.LookupPartitionByID(id) != nil +} + +func (m *Manager[T]) ContainsMember(member T) bool { + var ( + partitionIndex int + memberIndex int + ) + + partitionIndex, memberIndex = m.lookupMember(member) + return partitionIndex >= 0 && memberIndex >= 0 +} + +func (m *Manager[T]) GetPartitions() []partition[T] { + return m.partitions +} + +func (m *Manager[T]) GetPartitionIDs() []string { + partitionIDs := []string{} + for _, partition := range m.partitions { + partitionIDs = append(partitionIDs, partition.ID) + } + return partitionIDs +} + +func (m *Manager[T]) GetPartitionMembers() []T { + members := []T{} + for _, partition := range m.partitions { + members = append(members, partition.Members...) + } + return members +} + +func (m *Manager[T]) getNodeFromPartition(partitionIndex int, memberIndex int) *T { + if partitionIndex >= 0 && memberIndex >= 0 { + return &m.partitions[partitionIndex].Members[memberIndex] + } + return nil +} + +func (m *Manager[T]) lookupMember(member T) (int, int) { + var ( + partitionIndex int + memberIndex int + ) + + // check all partitions for nodes + for _, partition := range m.partitions { + memberIndex = slices.IndexFunc(partition.Members, func(testMember T) bool { + return member == testMember + }) + // we found the node in the partition so return + if memberIndex >= 0 { + return partitionIndex, memberIndex + } + partitionIndex += 1 + } + // return negative values to indicate the node was not found + return -1, -1 +} + +type NodeManager = Manager[nodes.Node] +type DefaultManager = Manager[string] diff --git a/internal/partitions/partitions.go b/internal/partitions/partitions.go new file mode 100644 index 0000000..7e6607e --- /dev/null +++ b/internal/partitions/partitions.go @@ -0,0 +1,11 @@ +package partitions + +import "github.com/davidallendj/partitions/internal/nodes" + +type partition[T comparable] struct { + ID string + Members []T +} + +type nodePartition = partition[nodes.Node] +type defaultPartition = partition[string] diff --git a/main.go b/main.go new file mode 100644 index 0000000..62a249f --- /dev/null +++ b/main.go @@ -0,0 +1,71 @@ +package main + +import ( + "encoding/json" + + "github.com/davidallendj/partitions/internal/groups" + "github.com/davidallendj/partitions/internal/partitions" + "github.com/rs/zerolog/log" +) + +func main() { + var ( + pm = partitions.DefaultManager{} + n1 = "nid0001" + n2 = "nid0002" + n3 = "nid0003" + p1 = "test1" + p2 = "test2" + ) + + // create new partitions with partition manager while testing adding + // the same partition multiple times + unwrapError(pm.CreatePartition(p1, []string{n3})) + unwrapError(pm.CreatePartition(p2, nil)) + unwrapError(pm.CreatePartition(p2, nil)) + + // try and put the same node in multiple partitions which should cause error + unwrapError(pm.AddNodeToPartition(p1, n1)) + unwrapError(pm.AddNodeToPartition(p1, n1)) + unwrapError(pm.AddNodeToPartition(p2, n2)) + + // try and put the same node in multiple groups + var ( + g1 = groups.Group{ + Name: "group1", + Labels: []string{n1, n2, "hello"}, + } + g2 = groups.Group{ + Name: "group2", + Labels: []string{n1, n3, "world"}, + } + ) + + g1NodeIDs := ToJSON(g1.GetNodeIDs(&pm)) + g1PartitionIDs := ToJSON(g1.GetPartitions(&pm)) + g2NodeIDs := ToJSON(g2.GetNodeIDs(&pm)) + g2PartitionIDs := ToJSON(g2.GetPartitions(&pm)) + + log.Info().Any("manager.partitions", pm.GetPartitions()).Msg("partition manager") + log.Info(). + Any("group", g1). + Any("found node IDs in manager", g1NodeIDs). + Any("partitions containing found nodes", g1PartitionIDs). + Msg("group 1") + log.Info(). + Any("group", g2). + Any("found node IDs in manager", g2NodeIDs). + Any("partitions containing found nodes", g2PartitionIDs). + Msg("group 2") +} + +func unwrapError(err error) { + if err != nil { + log.Error().Err(err).Msg("something went wrong...") + } +} + +func ToJSON(v any) string { + b, _ := json.Marshal(v) + return string(b) +} diff --git a/partitions b/partitions new file mode 100755 index 0000000..a6ff178 Binary files /dev/null and b/partitions differ