-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 4b78d4e
Showing
13 changed files
with
447 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
# This workflow will build a golang project | ||
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go | ||
|
||
name: go-coverage | ||
|
||
on: | ||
push: | ||
branches: [ "default" ] | ||
pull_request: | ||
branches: [ "default" ] | ||
|
||
jobs: | ||
|
||
build: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v3 | ||
|
||
- name: Set up Go | ||
uses: actions/setup-go@v4 | ||
with: | ||
go-version: '1.22' | ||
|
||
- name: test coverage | ||
run: go test ./... -coverprofile=cover.out && go tool cover -func cover.out |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
# This workflow will build a golang project | ||
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go | ||
|
||
name: gocheck | ||
|
||
on: | ||
push: | ||
branches: [ "default" ] | ||
pull_request: | ||
branches: [ "default" ] | ||
|
||
jobs: | ||
|
||
build: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v3 | ||
|
||
- name: Set up Go | ||
uses: actions/setup-go@v4 | ||
with: | ||
go-version: '1.22' | ||
|
||
- name: Test pkg | ||
run: cd pkg && go test -v . |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
# This workflow will build a golang project | ||
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go | ||
|
||
name: gofmt | ||
|
||
on: | ||
push: | ||
branches: [ "default" ] | ||
pull_request: | ||
branches: [ "default" ] | ||
|
||
jobs: | ||
|
||
build: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v3 | ||
|
||
- name: Set up Go | ||
uses: actions/setup-go@v4 | ||
with: | ||
go-version: '1.22' | ||
|
||
- name: gofmt check | ||
run: gofmt -l -s -d . |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
name: Golangci-lint | ||
|
||
on: | ||
push: | ||
branches: [ "default" ] | ||
pull_request: | ||
branches: [ "default" ] | ||
|
||
permissions: | ||
contents: read | ||
pull-requests: read | ||
|
||
jobs: | ||
golangci: | ||
name: lint | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v4 | ||
- uses: actions/setup-go@v5 | ||
with: | ||
go-version: stable | ||
- name: golangci-lint | ||
uses: golangci/golangci-lint-action@v6 | ||
with: | ||
version: latest | ||
args: --timeout=30m --issues-exit-code=0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
# This workflow will build a golang project | ||
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go | ||
|
||
name: govet | ||
|
||
on: | ||
push: | ||
branches: [ "default" ] | ||
pull_request: | ||
branches: [ "default" ] | ||
|
||
jobs: | ||
|
||
build: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v3 | ||
|
||
- name: Set up Go | ||
uses: actions/setup-go@v4 | ||
with: | ||
go-version: '1.22' | ||
|
||
- name: go vet check | ||
run: go vet pkg/*.go |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
*.test | ||
*.out | ||
go.work | ||
.idea |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
BSD 2-Clause License | ||
|
||
Copyright (c) 2024, Nikita Ronja Gillmann <[email protected]> | ||
All rights reserved. | ||
|
||
Redistribution and use in source and binary forms, with or without | ||
modification, are permitted provided that the following conditions are met: | ||
|
||
* Redistributions of source code must retain the above copyright notice, this | ||
list of conditions and the following disclaimer. | ||
|
||
* Redistributions in binary form must reproduce the above copyright notice, | ||
this list of conditions and the following disclaimer in the documentation | ||
and/or other materials provided with the distribution. | ||
|
||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
small consistent hashring implementation in go using bytes, 2-clause bsd licensed. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
module github.com/nikkicoon/consistenthashring | ||
|
||
go 1.22 | ||
|
||
require github.com/stretchr/testify v1.9.0 | ||
|
||
require ( | ||
github.com/davecgh/go-spew v1.1.1 // indirect | ||
github.com/pmezard/go-difflib v1.0.0 // indirect | ||
gopkg.in/yaml.v3 v3.0.1 // indirect | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | ||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= | ||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= | ||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= | ||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= | ||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
package pkg | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
"slices" | ||
"strconv" | ||
) | ||
|
||
type ConsistentHashRing struct { | ||
// Number of Labels per entry (vnodes per node) | ||
Labels int | ||
// Slice of Keys | ||
Keys [][]byte | ||
// Slice of Node | ||
Nodes []Node | ||
} | ||
|
||
func NewConsistentHashRing(labels int) *ConsistentHashRing { | ||
return &ConsistentHashRing{ | ||
Labels: labels, | ||
} | ||
} | ||
|
||
// String approximates a pretty-print of the contents of the ConsistentHashRing | ||
// XXX: Maybe do a not-pretty-print variant as a basis for a PrettyPrint function. | ||
func (c *ConsistentHashRing) String() string { | ||
var a, b string | ||
for _, v := range c.Nodes { | ||
a += "\t\t\t" + v.String() | ||
} | ||
for _, v := range c.Keys { | ||
b += "\t\t\t" + string(v) + "\n" | ||
} | ||
return fmt.Sprintf("{\n\tLabels: %d\n\tKeys: {\n%s\t}\n\tNodes: {\n%s\t}\n}", c.Labels, b, a) | ||
} | ||
|
||
type Node struct { | ||
Hash []byte | ||
Host string | ||
IP string | ||
SID string | ||
} | ||
|
||
func NewNode(host string, ip string, sid string) Node { | ||
return Node{ | ||
Hash: []byte{}, | ||
Host: host, | ||
IP: ip, | ||
SID: sid, | ||
} | ||
} | ||
|
||
func (n Node) String() string { | ||
result := make([]byte, len(n.Hash)) | ||
buff := bytes.NewBuffer(result) | ||
for _, b := range n.Hash { | ||
fmt.Fprintf(buff, "0x%02x ", b) | ||
} | ||
return fmt.Sprintf("Hash: {%s} Host: %s IP: %s SID: %s\n", buff.String(), n.Host, n.IP, n.SID) | ||
} | ||
|
||
// Add adds a node given its name. | ||
// The given nodeName is hashed among the number of labels. | ||
func (c *ConsistentHashRing) Add(nodeName string, node Node) { | ||
for i := 0; i < c.Labels; i++ { | ||
hash := CalculateHash(nodeName + strconv.Itoa(i)) | ||
node.Hash = hash | ||
c.Nodes = append(c.Nodes, node) | ||
c.Keys = SortedInsertByte(c.Keys, hash) | ||
} | ||
} | ||
|
||
// Get returns a node given a key. | ||
// The node replica with a hash value nearest but not | ||
// less than that of the given name is returned. If the hash | ||
// of the given name is greater than the greatest hash, | ||
// return the lowest hashed node. | ||
// If the Hashring is empty or any other case happens, | ||
// return an empty Node type. | ||
func (c *ConsistentHashRing) Get(keyname string) *Node { | ||
// if empty, return empty | ||
if len(c.Nodes) == 0 { | ||
return &Node{} | ||
} | ||
hash := CalculateHash(keyname) | ||
idx, ok := binarySearchBytes(c.Keys, hash, 0, len(c.Keys)-1) | ||
if !ok { | ||
//return &Node{} | ||
idx = 0 | ||
} | ||
if idx == len(c.Keys) { | ||
idx = 0 | ||
} | ||
var x []byte | ||
if len(c.Keys) > 0 { | ||
x = c.Keys[idx] | ||
} else { | ||
return &Node{} | ||
} | ||
for k, v := range c.Nodes { | ||
if bytes.Equal(v.Hash, x) { | ||
return &c.Nodes[k] | ||
} | ||
} | ||
return &Node{} | ||
} | ||
|
||
// Delete deletes a node given its name. | ||
func (c *ConsistentHashRing) Delete(nodeName string) { | ||
for i := 0; i < c.Labels; i++ { | ||
hash := CalculateHash(nodeName + strconv.Itoa(i)) | ||
// delete from c.Nodes where hash matches | ||
for k, v := range c.Nodes { | ||
if bytes.Equal(v.Hash, hash) { | ||
c.Nodes = slices.Delete(c.Nodes, k, k+1) | ||
} | ||
} | ||
// delete from c.Keys | ||
for k, val := range c.Keys { | ||
if bytes.Equal(val, hash) { | ||
c.Keys = slices.Delete(c.Keys, k, k+1) | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
package pkg_test | ||
|
||
import ( | ||
"github.com/nikkicoon/consistenthashring/pkg" | ||
"github.com/stretchr/testify/assert" | ||
"testing" | ||
) | ||
|
||
func TestCalculateHash(t *testing.T) { | ||
res := pkg.CalculateHash("test") | ||
assert.Equal(t, []byte{0xa9, 0x4a, 0x8f, 0xe5, 0xcc, 0xb1, 0x9b, 0xa6, 0x1c, 0x4c, 0x8, 0x73, 0xd3, 0x91, 0xe9, 0x87, 0x98, 0x2f, 0xbb, 0xd3}, res) | ||
} | ||
|
||
func BenchmarkCalculateHash(b *testing.B) { | ||
for n := 0; n < 6000000; n++ { | ||
pkg.CalculateHash("test") | ||
} | ||
b.Elapsed() | ||
} | ||
|
||
func BenchmarkCalculateHashGoRoutines(b *testing.B) { | ||
for n := 0; n < 6000000; n++ { | ||
go pkg.CalculateHash("test") | ||
} | ||
b.Elapsed() | ||
} | ||
|
||
func TestConsistentHashRing_Get(t *testing.T) { | ||
t.Parallel() | ||
cr := pkg.NewConsistentHashRing(5) | ||
n := pkg.NewNode("foobar.de", "127.0.0.1", "0") | ||
cr.Add("node0", n) | ||
hashesNode0 := [][]byte{ | ||
{0x25, 0xc3, 0xcf, 0xcb, 0x8b, 0x52, 0x0d, 0xbc, 0x5f, 0x85, 0xe9, 0xfb, 0x16, 0xe8, 0x6b, 0xaa, 0x13, 0x9f, 0x7a, 0x99}, | ||
{0xaa, 0x99, 0x2f, 0xdc, 0x24, 0x46, 0xe7, 0x27, 0xc3, 0x30, 0xa3, 0xcc, 0x4b, 0x9c, 0x2f, 0x46, 0x7e, 0x43, 0x8d, 0x6e}, | ||
{0x68, 0x4e, 0x61, 0xda, 0xe6, 0xe5, 0x5c, 0xfc, 0x61, 0x1a, 0x2b, 0xbd, 0xa6, 0x99, 0x4a, 0x84, 0xfd, 0xab, 0x91, 0x4e}, | ||
{0x43, 0xc9, 0x45, 0xef, 0x85, 0x95, 0x9c, 0x47, 0x12, 0x1d, 0x74, 0x31, 0x0c, 0xfa, 0x9f, 0xa9, 0x34, 0xa7, 0x30, 0xb3}, | ||
{0x28, 0x4b, 0xff, 0x8f, 0x21, 0xb8, 0x41, 0x0c, 0x4e, 0xb8, 0x84, 0x49, 0x26, 0xe6, 0x63, 0x60, 0x87, 0x99, 0xd0, 0xc2}} | ||
q := cr.Get("node0") | ||
if q != nil { | ||
assert.Equal(t, "foobar.de", q.Host) | ||
assert.Equal(t, 5, len(cr.Nodes)) | ||
for i := 0; i < len(cr.Nodes); i++ { | ||
assert.Equal(t, hashesNode0[i], cr.Nodes[i].Hash) | ||
} | ||
} else { | ||
panic("some error with get('node0')") | ||
} | ||
cr.Delete("node0") | ||
l := cr.Get("node9000") | ||
assert.IsType(t, &pkg.Node{}, l) | ||
assert.Equal(t, pkg.Node{}, *l) | ||
} | ||
|
||
func TestConsistentHashRing_Add(t *testing.T) { | ||
cr := pkg.NewConsistentHashRing(2000) | ||
cr.Add("node0", pkg.NewNode("host0.domain.tld", "123.123.123.123", "0")) | ||
cr.Add("node1", pkg.NewNode("host1.domain.tld", "123.123.123.123", "0")) | ||
cr.Add("node2", pkg.NewNode("host2.domain.tld", "123.123.123.123", "0")) | ||
cr.Add("node3", pkg.NewNode("host3.domain.tld", "123.123.123.123", "0")) | ||
cr.Add("node4", pkg.NewNode("host4.domain.tld", "123.123.123.123", "0")) | ||
cr.Add("node5", pkg.NewNode("host5.domain.tld", "123.123.123.123", "0")) | ||
for _, res := range cr.Nodes { | ||
assert.NotZero(t, res.Hash) | ||
assert.NotNil(t, res.Hash) | ||
} | ||
assert.NotZero(t, len(cr.Nodes)) | ||
assert.NotZero(t, len(cr.Keys)) | ||
} |
Oops, something went wrong.