[
  {
    "path": ".gitignore",
    "content": "coverage\n*.html\n\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2016 Sung-jin Hong\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "hashring\n============================\n\nImplements consistent hashing that can be used when\nthe number of server nodes can increase or decrease (like in memcached).\nThe hashing ring is built using the same algorithm as libketama.\n\nThis is a port of Python hash_ring library <https://pypi.python.org/pypi/hash_ring/>\nin Go with the extra methods to add and remove nodes.\n\n\nUsing\n============================\n\nImporting ::\n\n```go\nimport \"github.com/serialx/hashring\"\n```\n\nBasic example usage ::\n\n```go\nmemcacheServers := []string{\"192.168.0.246:11212\",\n                            \"192.168.0.247:11212\",\n                            \"192.168.0.249:11212\"}\n\nring := hashring.New(memcacheServers)\nserver, _ := ring.GetNode(\"my_key\")\n```\n\nTo fulfill replication requirements, you can also get a list of servers that should store your key.\n```go\nserversInRing := []string{\"192.168.0.246:11212\",\n                          \"192.168.0.247:11212\",\n                          \"192.168.0.248:11212\",\n                          \"192.168.0.249:11212\",\n                          \"192.168.0.250:11212\",\n                          \"192.168.0.251:11212\",\n                          \"192.168.0.252:11212\"}\n\nreplicaCount := 3\nring := hashring.New(serversInRing)\nserver, _ := ring.GetNodes(\"my_key\", replicaCount)\n```\n\nUsing weights example ::\n\n```go\nweights := make(map[string]int)\nweights[\"192.168.0.246:11212\"] = 1\nweights[\"192.168.0.247:11212\"] = 2\nweights[\"192.168.0.249:11212\"] = 1\n\nring := hashring.NewWithWeights(weights)\nserver, _ := ring.GetNode(\"my_key\")\n```\n\nAdding and removing nodes example ::\n\n```go\nmemcacheServers := []string{\"192.168.0.246:11212\",\n                            \"192.168.0.247:11212\",\n                            \"192.168.0.249:11212\"}\n\nring := hashring.New(memcacheServers)\nring = ring.RemoveNode(\"192.168.0.246:11212\")\nring = ring.AddNode(\"192.168.0.250:11212\")\nserver, _ := ring.GetNode(\"my_key\")\n```\n"
  },
  {
    "path": "allnodes_test.go",
    "content": "package hashring\n\nimport (\n\t\"fmt\"\n\t\"sort\"\n\t\"strconv\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc generateWeights(n int) map[string]int {\n\tresult := make(map[string]int)\n\tfor i := 0; i < n; i++ {\n\t\tresult[fmt.Sprintf(\"%03d\", i)] = i + 1\n\t}\n\treturn result\n}\n\nfunc generateNodes(n int) []string {\n\tresult := make([]string, 0, n)\n\tfor i := 0; i < n; i++ {\n\t\tresult = append(result, fmt.Sprintf(\"%03d\", i))\n\t}\n\treturn result\n}\n\nfunc TestListOf1000Nodes(t *testing.T) {\n\ttestData := map[string]struct {\n\t\tring *HashRing\n\t}{\n\t\t\"nodes\":   {ring: New(generateNodes(1000))},\n\t\t\"weights\": {ring: NewWithWeights(generateWeights(1000))},\n\t}\n\n\tfor testName, data := range testData {\n\t\tring := data.ring\n\t\tt.Run(testName, func(t *testing.T) {\n\t\t\tnodes, ok := ring.GetNodes(\"key\", ring.Size())\n\t\t\tassert.True(t, ok)\n\t\t\tif !assert.Equal(t, ring.Size(), len(nodes)) {\n\t\t\t\t// print debug info on failure\n\t\t\t\tsort.Strings(nodes)\n\t\t\t\tfmt.Printf(\"%v\\n\", nodes)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// assert that each node shows up exatly once\n\t\t\tsort.Strings(nodes)\n\t\t\tfor i, node := range nodes {\n\t\t\t\tactual, err := strconv.ParseInt(node, 10, 64)\n\t\t\t\tif !assert.NoError(t, err) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tif !assert.Equal(t, int64(i), actual) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "benchmark_test.go",
    "content": "package hashring\n\nimport \"testing\"\n\nfunc BenchmarkNew(b *testing.B) {\n\tnodes := []string{\"a\", \"b\", \"c\", \"d\", \"e\", \"f\", \"g\"}\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_ = New(nodes)\n\t}\n}\n\nfunc BenchmarkHashes(b *testing.B) {\n\tnodes := []string{\"a\", \"b\", \"c\", \"d\", \"e\", \"f\", \"g\"}\n\tring := New(nodes)\n\ttt := []struct {\n\t\tkey   string\n\t\tnodes []string\n\t}{\n\t\t{\"test\", []string{\"a\", \"b\"}},\n\t\t{\"test\", []string{\"a\", \"b\"}},\n\t\t{\"test1\", []string{\"b\", \"d\"}},\n\t\t{\"test2\", []string{\"f\", \"b\"}},\n\t\t{\"test3\", []string{\"f\", \"c\"}},\n\t\t{\"test4\", []string{\"c\", \"b\"}},\n\t\t{\"test5\", []string{\"f\", \"a\"}},\n\t\t{\"aaaa\", []string{\"b\", \"a\"}},\n\t\t{\"bbbb\", []string{\"f\", \"a\"}},\n\t}\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\to := tt[i%len(tt)]\n\t\tring.GetNodes(o.key, 2)\n\t}\n}\n\nfunc BenchmarkHashesSingle(b *testing.B) {\n\tnodes := []string{\"a\", \"b\", \"c\", \"d\", \"e\", \"f\", \"g\"}\n\tring := New(nodes)\n\ttt := []struct {\n\t\tkey   string\n\t\tnodes []string\n\t}{\n\t\t{\"test\", []string{\"a\", \"b\"}},\n\t\t{\"test\", []string{\"a\", \"b\"}},\n\t\t{\"test1\", []string{\"b\", \"d\"}},\n\t\t{\"test2\", []string{\"f\", \"b\"}},\n\t\t{\"test3\", []string{\"f\", \"c\"}},\n\t\t{\"test4\", []string{\"c\", \"b\"}},\n\t\t{\"test5\", []string{\"f\", \"a\"}},\n\t\t{\"aaaa\", []string{\"b\", \"a\"}},\n\t\t{\"bbbb\", []string{\"f\", \"a\"}},\n\t}\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\to := tt[i%len(tt)]\n\t\tring.GetNode(o.key)\n\t}\n}\n"
  },
  {
    "path": "example_test.go",
    "content": "package hashring\n\nimport (\n\t\"crypto/sha1\"\n\t\"fmt\"\n)\n\nfunc ExampleGetAllNodes() {\n\thashRing := New([]string{\"node1\", \"node2\", \"node3\"})\n\tnodes, _ := hashRing.GetNodes(\"key\", hashRing.Size())\n\tfmt.Printf(\"%v\", nodes)\n\t// Output: [node3 node2 node1]\n}\n\nfunc ExampleCustomHashError() {\n\t_, err := NewHash(sha1.New).Use(NewInt64PairHashKey)\n\tfmt.Printf(\"%s\", err.Error())\n\t// Output: can't use given hash.Hash with given hashKeyFunc: expected 16 bytes, got 20 bytes\n}\n\nfunc ExampleCustomHash() {\n\thashFunc, _ := NewHash(sha1.New).FirstBytes(16).Use(NewInt64PairHashKey)\n\thashRing := NewWithHash([]string{\"node1\", \"node2\", \"node3\"}, hashFunc)\n\tnodes, _ := hashRing.GetNodes(\"key\", hashRing.Size())\n\tfmt.Printf(\"%v\", nodes)\n\t// Output: [node2 node1 node3]\n}\n\nfunc ExampleNewHashFunc() {\n\thashFunc, _ := NewHash(sha1.New).FirstBytes(16).Use(NewInt64PairHashKey)\n\tfmt.Printf(\"%v\\n\", hashFunc([]byte(\"test\")))\n\t// Output: &{-6441359348440544599 -8653224871661646820}\n}\n"
  },
  {
    "path": "hash.go",
    "content": "package hashring\n\nimport (\n\t\"fmt\"\n\t\"hash\"\n)\n\n// HashSum allows to use a builder pattern to create different HashFunc objects.\n// See examples for details.\ntype HashSum struct {\n\tfunctions []func([]byte) []byte\n}\n\nfunc (r *HashSum) Use(\n\thashKeyFunc func(bytes []byte) (HashKey, error),\n) (HashFunc, error) {\n\n\t// build final hash function\n\tcomposed := func(bytes []byte) []byte {\n\t\tfor _, f := range r.functions {\n\t\t\tbytes = f(bytes)\n\t\t}\n\t\treturn bytes\n\t}\n\n\t// check function composition for errors\n\ttestResult := composed([]byte(\"test\"))\n\t_, err := hashKeyFunc(testResult)\n\tif err != nil {\n\t\tconst msg = \"can't use given hash.Hash with given hashKeyFunc\"\n\t\treturn nil, fmt.Errorf(\"%s: %w\", msg, err)\n\t}\n\n\t// build HashFunc\n\treturn func(key []byte) HashKey {\n\t\tbytes := composed(key)\n\t\thashKey, err := hashKeyFunc(bytes)\n\t\tif err != nil {\n\t\t\t// panic because we already checked HashSum earlier\n\t\t\tpanic(fmt.Sprintf(\"hashKeyFunc failure: %v\", err))\n\t\t}\n\t\treturn hashKey\n\t}, nil\n}\n\n// NewHash creates a new *HashSum object which can be used to create HashFunc.\n// HashFunc object is thread safe if the hasher argument produces a new hash.Hash \n// each time. The produced hash.Hash is allowed to be non thread-safe.\nfunc NewHash(hasher func() hash.Hash) *HashSum {\n\treturn &HashSum{\n\t\tfunctions: []func(key []byte) []byte{\n\t\t\tfunc(key []byte) []byte {\n\t\t\t\thash := hasher()\n\t\t\t\thash.Write(key)\n\t\t\t\treturn hash.Sum(nil)\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc (r *HashSum) FirstBytes(n int) *HashSum {\n\tr.functions = append(r.functions, func(bytes []byte) []byte {\n\t\treturn bytes[:n]\n\t})\n\treturn r\n}\n\nfunc (r *HashSum) LastBytes(n int) *HashSum {\n\tr.functions = append(r.functions, func(bytes []byte) []byte {\n\t\treturn bytes[len(bytes)-n:]\n\t})\n\treturn r\n}\n"
  },
  {
    "path": "hashring.go",
    "content": "package hashring\n\nimport (\n\t\"crypto/md5\"\n\t\"fmt\"\n\t\"sort\"\n\t\"strconv\"\n)\n\nvar defaultHashFunc = func() HashFunc {\n\thashFunc, err := NewHash(md5.New).Use(NewInt64PairHashKey)\n\tif err != nil {\n\t\tpanic(fmt.Sprintf(\"failed to create defaultHashFunc: %s\", err.Error()))\n\t}\n\treturn hashFunc\n}()\n\ntype HashKey interface {\n\tLess(other HashKey) bool\n}\ntype HashKeyOrder []HashKey\n\nfunc (h HashKeyOrder) Len() int      { return len(h) }\nfunc (h HashKeyOrder) Swap(i, j int) { h[i], h[j] = h[j], h[i] }\nfunc (h HashKeyOrder) Less(i, j int) bool {\n\treturn h[i].Less(h[j])\n}\n\ntype HashFunc func([]byte) HashKey\n\ntype HashRing struct {\n\tring       map[HashKey]string\n\tsortedKeys []HashKey\n\tnodes      []string\n\tweights    map[string]int\n\thashFunc   HashFunc\n}\n\ntype Uint32HashKey uint32\n\nfunc (k Uint32HashKey) Less(other HashKey) bool {\n\treturn k < other.(Uint32HashKey)\n}\n\nfunc New(nodes []string) *HashRing {\n\treturn NewWithHash(nodes, defaultHashFunc)\n}\n\nfunc NewWithHash(\n\tnodes []string,\n\thashKey HashFunc,\n) *HashRing {\n\thashRing := &HashRing{\n\t\tring:       make(map[HashKey]string),\n\t\tsortedKeys: make([]HashKey, 0),\n\t\tnodes:      nodes,\n\t\tweights:    make(map[string]int),\n\t\thashFunc:   hashKey,\n\t}\n\thashRing.generateCircle()\n\treturn hashRing\n}\n\nfunc NewWithWeights(weights map[string]int) *HashRing {\n\treturn NewWithHashAndWeights(weights, defaultHashFunc)\n}\n\nfunc NewWithHashAndWeights(\n\tweights map[string]int,\n\thashFunc HashFunc,\n) *HashRing {\n\tnodes := make([]string, 0, len(weights))\n\tfor node := range weights {\n\t\tnodes = append(nodes, node)\n\t}\n\thashRing := &HashRing{\n\t\tring:       make(map[HashKey]string),\n\t\tsortedKeys: make([]HashKey, 0),\n\t\tnodes:      nodes,\n\t\tweights:    weights,\n\t\thashFunc:   hashFunc,\n\t}\n\thashRing.generateCircle()\n\treturn hashRing\n}\n\nfunc (h *HashRing) Size() int {\n\treturn len(h.nodes)\n}\n\nfunc (h *HashRing) UpdateWithWeights(weights map[string]int) {\n\tnodesChgFlg := false\n\tif len(weights) != len(h.weights) {\n\t\tnodesChgFlg = true\n\t} else {\n\t\tfor node, newWeight := range weights {\n\t\t\toldWeight, ok := h.weights[node]\n\t\t\tif !ok || oldWeight != newWeight {\n\t\t\t\tnodesChgFlg = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif nodesChgFlg {\n\t\tnewhring := NewWithHashAndWeights(weights, h.hashFunc)\n\t\th.weights = newhring.weights\n\t\th.nodes = newhring.nodes\n\t\th.ring = newhring.ring\n\t\th.sortedKeys = newhring.sortedKeys\n\t}\n}\n\nfunc (h *HashRing) generateCircle() {\n\ttotalWeight := 0\n\tfor _, node := range h.nodes {\n\t\tif weight, ok := h.weights[node]; ok {\n\t\t\ttotalWeight += weight\n\t\t} else {\n\t\t\ttotalWeight += 1\n\t\t\th.weights[node] = 1\n\t\t}\n\t}\n\n\tfor _, node := range h.nodes {\n\t\tweight := h.weights[node]\n\n\t\tfor j := 0; j < weight; j++ {\n\t\t\tnodeKey := node + \"-\" + strconv.FormatInt(int64(j), 10)\n\t\t\tkey := h.hashFunc([]byte(nodeKey))\n\t\t\th.ring[key] = node\n\t\t\th.sortedKeys = append(h.sortedKeys, key)\n\t\t}\n\t}\n\n\tsort.Sort(HashKeyOrder(h.sortedKeys))\n}\n\nfunc (h *HashRing) GetNode(stringKey string) (node string, ok bool) {\n\tpos, ok := h.GetNodePos(stringKey)\n\tif !ok {\n\t\treturn \"\", false\n\t}\n\treturn h.ring[h.sortedKeys[pos]], true\n}\n\nfunc (h *HashRing) GetNodePos(stringKey string) (pos int, ok bool) {\n\tif len(h.ring) == 0 {\n\t\treturn 0, false\n\t}\n\n\tkey := h.GenKey(stringKey)\n\n\tnodes := h.sortedKeys\n\tpos = sort.Search(len(nodes), func(i int) bool { return key.Less(nodes[i]) })\n\n\tif pos == len(nodes) {\n\t\t// Wrap the search, should return First node\n\t\treturn 0, true\n\t} else {\n\t\treturn pos, true\n\t}\n}\n\nfunc (h *HashRing) GenKey(key string) HashKey {\n\treturn h.hashFunc([]byte(key))\n}\n\n// GetNodes iterates over the hash ring and returns the nodes in the order\n// which is determined by the key. GetNodes is thread safe if the hash\n// which was used to configure the hash ring is thread safe.\nfunc (h *HashRing) GetNodes(stringKey string, size int) (nodes []string, ok bool) {\n\tpos, ok := h.GetNodePos(stringKey)\n\tif !ok {\n\t\treturn nil, false\n\t}\n\n\tif size > len(h.nodes) {\n\t\treturn nil, false\n\t}\n\n\treturnedValues := make(map[string]bool, size)\n\t//mergedSortedKeys := append(h.sortedKeys[pos:], h.sortedKeys[:pos]...)\n\tresultSlice := make([]string, 0, size)\n\n\tfor i := pos; i < pos+len(h.sortedKeys); i++ {\n\t\tkey := h.sortedKeys[i%len(h.sortedKeys)]\n\t\tval := h.ring[key]\n\t\tif !returnedValues[val] {\n\t\t\treturnedValues[val] = true\n\t\t\tresultSlice = append(resultSlice, val)\n\t\t}\n\t\tif len(returnedValues) == size {\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn resultSlice, len(resultSlice) == size\n}\n\nfunc (h *HashRing) AddNode(node string) *HashRing {\n\treturn h.AddWeightedNode(node, 1)\n}\n\nfunc (h *HashRing) AddWeightedNode(node string, weight int) *HashRing {\n\tif weight <= 0 {\n\t\treturn h\n\t}\n\n\tif _, ok := h.weights[node]; ok {\n\t\treturn h\n\t}\n\n\tnodes := make([]string, len(h.nodes), len(h.nodes)+1)\n\tcopy(nodes, h.nodes)\n\tnodes = append(nodes, node)\n\n\tweights := make(map[string]int)\n\tfor eNode, eWeight := range h.weights {\n\t\tweights[eNode] = eWeight\n\t}\n\tweights[node] = weight\n\n\thashRing := &HashRing{\n\t\tring:       make(map[HashKey]string),\n\t\tsortedKeys: make([]HashKey, 0),\n\t\tnodes:      nodes,\n\t\tweights:    weights,\n\t\thashFunc:   h.hashFunc,\n\t}\n\thashRing.generateCircle()\n\treturn hashRing\n}\n\nfunc (h *HashRing) UpdateWeightedNode(node string, weight int) *HashRing {\n\tif weight <= 0 {\n\t\treturn h\n\t}\n\n\t/* node is not need to update for node is not existed or weight is not changed */\n\tif oldWeight, ok := h.weights[node]; (!ok) || (ok && oldWeight == weight) {\n\t\treturn h\n\t}\n\n\tnodes := make([]string, len(h.nodes))\n\tcopy(nodes, h.nodes)\n\n\tweights := make(map[string]int)\n\tfor eNode, eWeight := range h.weights {\n\t\tweights[eNode] = eWeight\n\t}\n\tweights[node] = weight\n\n\thashRing := &HashRing{\n\t\tring:       make(map[HashKey]string),\n\t\tsortedKeys: make([]HashKey, 0),\n\t\tnodes:      nodes,\n\t\tweights:    weights,\n\t\thashFunc:   h.hashFunc,\n\t}\n\thashRing.generateCircle()\n\treturn hashRing\n}\nfunc (h *HashRing) RemoveNode(node string) *HashRing {\n\t/* if node isn't exist in hashring, don't refresh hashring */\n\tif _, ok := h.weights[node]; !ok {\n\t\treturn h\n\t}\n\n\tnodes := make([]string, 0)\n\tfor _, eNode := range h.nodes {\n\t\tif eNode != node {\n\t\t\tnodes = append(nodes, eNode)\n\t\t}\n\t}\n\n\tweights := make(map[string]int)\n\tfor eNode, eWeight := range h.weights {\n\t\tif eNode != node {\n\t\t\tweights[eNode] = eWeight\n\t\t}\n\t}\n\n\thashRing := &HashRing{\n\t\tring:       make(map[HashKey]string),\n\t\tsortedKeys: make([]HashKey, 0),\n\t\tnodes:      nodes,\n\t\tweights:    weights,\n\t\thashFunc:   h.hashFunc,\n\t}\n\thashRing.generateCircle()\n\treturn hashRing\n}\n"
  },
  {
    "path": "hashring_test.go",
    "content": "package hashring\n\nimport (\n\t\"fmt\"\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc expectWeights(t *testing.T, ring *HashRing, expectedWeights map[string]int) {\n\tweightsEquality := reflect.DeepEqual(ring.weights, expectedWeights)\n\tif !weightsEquality {\n\t\tt.Error(\"Weights expected\", expectedWeights, \"but got\", ring.weights)\n\t}\n}\n\ntype testPair struct {\n\tkey  string\n\tnode string\n}\n\ntype testNodes struct {\n\tkey   string\n\tnodes []string\n}\n\nfunc assert2Nodes(t *testing.T, prefix string, ring *HashRing, data []testNodes) {\n\tt.Run(prefix, func(t *testing.T) {\n\t\tallActual := make([]string, 0)\n\t\tallExpected := make([]string, 0)\n\t\tfor _, pair := range data {\n\t\t\tnodes, ok := ring.GetNodes(pair.key, 2)\n\t\t\tif assert.True(t, ok) {\n\t\t\t\tallActual = append(allActual, fmt.Sprintf(\"%s - %v\", pair.key, nodes))\n\t\t\t\tallExpected = append(allExpected, fmt.Sprintf(\"%s - %v\", pair.key, pair.nodes))\n\t\t\t}\n\t\t}\n\t\tassert.Equal(t, allExpected, allActual)\n\t})\n}\n\nfunc assertNodes(t *testing.T, prefix string, ring *HashRing, allExpected []testPair) {\n\tt.Run(prefix, func(t *testing.T) {\n\t\tallActual := make([]testPair, 0)\n\t\tfor _, pair := range allExpected {\n\t\t\tnode, ok := ring.GetNode(pair.key)\n\t\t\tif assert.True(t, ok) {\n\t\t\t\tallActual = append(allActual, testPair{key: pair.key, node: node})\n\t\t\t}\n\t\t}\n\t})\n}\n\nfunc expectNodesABC(t *testing.T, prefix string, ring *HashRing) {\n\n\tassertNodes(t, prefix, ring, []testPair{\n\t\t{\"test\", \"a\"},\n\t\t{\"test\", \"a\"},\n\t\t{\"test1\", \"b\"},\n\t\t{\"test2\", \"b\"},\n\t\t{\"test3\", \"c\"},\n\t\t{\"test4\", \"a\"},\n\t\t{\"test5\", \"c\"},\n\t\t{\"aaaa\", \"c\"},\n\t\t{\"bbbb\", \"a\"},\n\t})\n}\n\nfunc expectNodeRangesABC(t *testing.T, prefix string, ring *HashRing) {\n\tassert2Nodes(t, prefix, ring, []testNodes{\n\t\t{\"test\", []string{\"a\", \"c\"}},\n\t\t{\"test\", []string{\"a\", \"c\"}},\n\t\t{\"test1\", []string{\"b\", \"a\"}},\n\t\t{\"test2\", []string{\"b\", \"a\"}},\n\t\t{\"test3\", []string{\"c\", \"b\"}},\n\t\t{\"test4\", []string{\"a\", \"c\"}},\n\t\t{\"test5\", []string{\"c\", \"b\"}},\n\t\t{\"aaaa\", []string{\"c\", \"b\"}},\n\t\t{\"bbbb\", []string{\"a\", \"c\"}},\n\t})\n}\n\nfunc expectNodesABCD(t *testing.T, prefix string, ring *HashRing) {\n\tassertNodes(t, prefix, ring, []testPair{\n\t\t{\"test\", \"d\"},\n\t\t{\"test\", \"d\"},\n\t\t{\"test1\", \"b\"},\n\t\t{\"test2\", \"b\"},\n\t\t{\"test3\", \"c\"},\n\t\t{\"test4\", \"d\"},\n\t\t{\"test5\", \"c\"},\n\t\t{\"aaaa\", \"c\"},\n\t\t{\"bbbb\", \"d\"},\n\t})\n}\n\nfunc TestNew(t *testing.T) {\n\tnodes := []string{\"a\", \"b\", \"c\"}\n\tring := New(nodes)\n\n\texpectNodesABC(t, \"TestNew_1_\", ring)\n\texpectNodeRangesABC(t, \"\", ring)\n}\n\nfunc TestNewEmpty(t *testing.T) {\n\tnodes := []string{}\n\tring := New(nodes)\n\n\tnode, ok := ring.GetNode(\"test\")\n\tif ok || node != \"\" {\n\t\tt.Error(\"GetNode(test) expected (\\\"\\\", false) but got (\", node, \",\", ok, \")\")\n\t}\n\n\tnodes, rok := ring.GetNodes(\"test\", 2)\n\tif rok || !(len(nodes) == 0) {\n\t\tt.Error(\"GetNode(test) expected ( [], false ) but got (\", nodes, \",\", rok, \")\")\n\t}\n}\n\nfunc TestForMoreNodes(t *testing.T) {\n\tnodes := []string{\"a\", \"b\", \"c\"}\n\tring := New(nodes)\n\n\tnodes, ok := ring.GetNodes(\"test\", 5)\n\tif ok || !(len(nodes) == 0) {\n\t\tt.Error(\"GetNode(test) expected ( [], false ) but got (\", nodes, \",\", ok, \")\")\n\t}\n}\n\nfunc TestForEqualNodes(t *testing.T) {\n\tnodes := []string{\"a\", \"b\", \"c\"}\n\tring := New(nodes)\n\n\tnodes, ok := ring.GetNodes(\"test\", 3)\n\tif !ok && (len(nodes) == 3) {\n\t\tt.Error(\"GetNode(test) expected ( [a b c], true ) but got (\", nodes, \",\", ok, \")\")\n\t}\n}\n\nfunc TestNewSingle(t *testing.T) {\n\tnodes := []string{\"a\"}\n\tring := New(nodes)\n\n\tassertNodes(t, \"\", ring, []testPair{\n\t\t{\"test\", \"a\"},\n\t\t{\"test\", \"a\"},\n\t\t{\"test1\", \"a\"},\n\t\t{\"test2\", \"a\"},\n\t\t{\"test3\", \"a\"},\n\n\t\t{\"test14\", \"a\"},\n\n\t\t{\"test15\", \"a\"},\n\t\t{\"test16\", \"a\"},\n\t\t{\"test17\", \"a\"},\n\t\t{\"test18\", \"a\"},\n\t\t{\"test19\", \"a\"},\n\t\t{\"test20\", \"a\"},\n\t})\n}\n\nfunc TestNewWeighted(t *testing.T) {\n\tweights := make(map[string]int)\n\tweights[\"a\"] = 1\n\tweights[\"b\"] = 2\n\tweights[\"c\"] = 1\n\tring := NewWithWeights(weights)\n\n\tassertNodes(t, \"\", ring, []testPair{\n\t\t{\"test\", \"b\"},\n\t\t{\"test\", \"b\"},\n\t\t{\"test1\", \"b\"},\n\t\t{\"test2\", \"b\"},\n\t\t{\"test3\", \"c\"},\n\t\t{\"test4\", \"b\"},\n\t\t{\"test5\", \"c\"},\n\t\t{\"aaaa\", \"c\"},\n\t\t{\"bbbb\", \"b\"},\n\t})\n\tassert2Nodes(t, \"\", ring, []testNodes{\n\t\t{\"test\", []string{\"b\", \"a\"}},\n\t})\n}\n\nfunc TestRemoveNode(t *testing.T) {\n\tnodes := []string{\"a\", \"b\", \"c\"}\n\tring := New(nodes)\n\tring = ring.RemoveNode(\"b\")\n\n\tassertNodes(t, \"\", ring, []testPair{\n\t\t{\"test\", \"a\"},\n\t\t{\"test\", \"a\"},\n\t\t{\"test1\", \"a\"},\n\t\t{\"test2\", \"a\"},\n\t\t{\"test3\", \"c\"},\n\t\t{\"test4\", \"a\"},\n\t\t{\"test5\", \"c\"},\n\t\t{\"aaaa\", \"c\"},\n\t\t{\"bbbb\", \"a\"},\n\t})\n\n\tassert2Nodes(t, \"\", ring, []testNodes{\n\t\t{\"test\", []string{\"a\", \"c\"}},\n\t})\n}\n\nfunc TestAddNode(t *testing.T) {\n\tnodes := []string{\"a\", \"c\"}\n\tring := New(nodes)\n\tring = ring.AddNode(\"b\")\n\n\texpectNodesABC(t, \"TestAddNode_1_\", ring)\n\n\tdefaultWeights := map[string]int{\n\t\t\"a\": 1,\n\t\t\"b\": 1,\n\t\t\"c\": 1,\n\t}\n\texpectWeights(t, ring, defaultWeights)\n}\n\nfunc TestAddNode2(t *testing.T) {\n\tnodes := []string{\"a\", \"c\"}\n\tring := New(nodes)\n\tring = ring.AddNode(\"b\")\n\tring = ring.AddNode(\"b\")\n\n\texpectNodesABC(t, \"TestAddNode2_\", ring)\n\texpectNodeRangesABC(t, \"\", ring)\n}\n\nfunc TestAddNode3(t *testing.T) {\n\tnodes := []string{\"a\", \"b\", \"c\"}\n\tring := New(nodes)\n\tring = ring.AddNode(\"d\")\n\n\texpectNodesABCD(t, \"TestAddNode3_1_\", ring)\n\n\tring = ring.AddNode(\"e\")\n\n\tassertNodes(t, \"TestAddNode3_2_\", ring, []testPair{\n\t\t{\"test\", \"d\"},\n\t\t{\"test\", \"d\"},\n\t\t{\"test1\", \"b\"},\n\t\t{\"test2\", \"e\"},\n\t\t{\"test3\", \"c\"},\n\t\t{\"test4\", \"d\"},\n\t\t{\"test5\", \"c\"},\n\t\t{\"aaaa\", \"c\"},\n\t\t{\"bbbb\", \"d\"},\n\t})\n\n\tassert2Nodes(t, \"\", ring, []testNodes{\n\t\t{\"test\", []string{\"d\", \"a\"}},\n\t})\n\n\tring = ring.AddNode(\"f\")\n\n\tassertNodes(t, \"TestAddNode3_3_\", ring, []testPair{\n\t\t{\"test\", \"d\"},\n\t\t{\"test\", \"d\"},\n\t\t{\"test1\", \"b\"},\n\t\t{\"test2\", \"e\"},\n\t\t{\"test3\", \"c\"},\n\t\t{\"test4\", \"d\"},\n\t\t{\"test5\", \"c\"},\n\t\t{\"aaaa\", \"c\"},\n\t\t{\"bbbb\", \"d\"},\n\t})\n\n\tassert2Nodes(t, \"\", ring, []testNodes{\n\t\t{\"test\", []string{\"d\", \"a\"}},\n\t})\n}\n\nfunc TestDuplicateNodes(t *testing.T) {\n\tnodes := []string{\"a\", \"a\", \"a\", \"a\", \"b\"}\n\tring := New(nodes)\n\n\tassertNodes(t, \"TestDuplicateNodes_\", ring, []testPair{\n\t\t{\"test\", \"a\"},\n\t\t{\"test\", \"a\"},\n\t\t{\"test1\", \"b\"},\n\t\t{\"test2\", \"b\"},\n\t\t{\"test3\", \"b\"},\n\t\t{\"test4\", \"a\"},\n\t\t{\"test5\", \"b\"},\n\t\t{\"aaaa\", \"b\"},\n\t\t{\"bbbb\", \"a\"},\n\t})\n}\n\nfunc TestAddWeightedNode(t *testing.T) {\n\tnodes := []string{\"a\", \"c\"}\n\tring := New(nodes)\n\tring = ring.AddWeightedNode(\"b\", 0)\n\tring = ring.AddWeightedNode(\"b\", 2)\n\tring = ring.AddWeightedNode(\"b\", 2)\n\n\tassertNodes(t, \"TestAddWeightedNode_\", ring, []testPair{\n\t\t{\"test\", \"b\"},\n\t\t{\"test\", \"b\"},\n\t\t{\"test1\", \"b\"},\n\t\t{\"test2\", \"b\"},\n\t\t{\"test3\", \"c\"},\n\t\t{\"test4\", \"b\"},\n\t\t{\"test5\", \"c\"},\n\t\t{\"aaaa\", \"c\"},\n\t\t{\"bbbb\", \"b\"},\n\t})\n\n\tassert2Nodes(t, \"\", ring, []testNodes{\n\t\t{\"test\", []string{\"b\", \"a\"}},\n\t})\n}\n\nfunc TestUpdateWeightedNode(t *testing.T) {\n\tnodes := []string{\"a\", \"c\"}\n\tring := New(nodes)\n\tring = ring.AddWeightedNode(\"b\", 1)\n\tring = ring.UpdateWeightedNode(\"b\", 2)\n\tring = ring.UpdateWeightedNode(\"b\", 2)\n\tring = ring.UpdateWeightedNode(\"b\", 0)\n\tring = ring.UpdateWeightedNode(\"d\", 2)\n\n\tassertNodes(t, \"TestUpdateWeightedNode_\", ring, []testPair{\n\t\t{\"test\", \"b\"},\n\t\t{\"test\", \"b\"},\n\t\t{\"test1\", \"b\"},\n\t\t{\"test2\", \"b\"},\n\t\t{\"test3\", \"c\"},\n\t\t{\"test4\", \"b\"},\n\t\t{\"test5\", \"c\"},\n\t\t{\"aaaa\", \"c\"},\n\t\t{\"bbbb\", \"b\"},\n\t})\n\n\tassert2Nodes(t, \"\", ring, []testNodes{\n\t\t{\"test\", []string{\"b\", \"a\"}},\n\t})\n}\n\nfunc TestRemoveAddNode(t *testing.T) {\n\tnodes := []string{\"a\", \"b\", \"c\"}\n\tring := New(nodes)\n\n\texpectNodesABC(t, \"1_\", ring)\n\texpectNodeRangesABC(t, \"2_\", ring)\n\n\tring = ring.RemoveNode(\"b\")\n\n\tassertNodes(t, \"3_\", ring, []testPair{\n\t\t{\"test\", \"a\"},\n\t\t{\"test\", \"a\"},\n\t\t{\"test1\", \"a\"},\n\t\t{\"test2\", \"a\"},\n\t\t{\"test3\", \"c\"},\n\t\t{\"test4\", \"a\"},\n\t\t{\"test5\", \"c\"},\n\t\t{\"aaaa\", \"c\"},\n\t\t{\"bbbb\", \"a\"},\n\t})\n\n\tassert2Nodes(t, \"4_\", ring, []testNodes{\n\t\t{\"test\", []string{\"a\", \"c\"}},\n\t\t{\"test\", []string{\"a\", \"c\"}},\n\t\t{\"test1\", []string{\"a\", \"c\"}},\n\t\t{\"test2\", []string{\"a\", \"c\"}},\n\t\t{\"test3\", []string{\"c\", \"a\"}},\n\t\t{\"test4\", []string{\"a\", \"c\"}},\n\t\t{\"test5\", []string{\"c\", \"a\"}},\n\t\t{\"aaaa\", []string{\"c\", \"a\"}},\n\t\t{\"bbbb\", []string{\"a\", \"c\"}},\n\t})\n\n\tring = ring.AddNode(\"b\")\n\n\texpectNodesABC(t, \"5_\", ring)\n\texpectNodeRangesABC(t, \"6_\", ring)\n}\n\nfunc TestRemoveAddWeightedNode(t *testing.T) {\n\tweights := make(map[string]int)\n\tweights[\"a\"] = 1\n\tweights[\"b\"] = 2\n\tweights[\"c\"] = 1\n\tring := NewWithWeights(weights)\n\n\texpectWeights(t, ring, weights)\n\n\tassertNodes(t, \"1_\", ring, []testPair{\n\t\t{\"test\", \"b\"},\n\t\t{\"test\", \"b\"},\n\t\t{\"test1\", \"b\"},\n\t\t{\"test2\", \"b\"},\n\t\t{\"test3\", \"c\"},\n\t\t{\"test4\", \"b\"},\n\t\t{\"test5\", \"c\"},\n\t\t{\"aaaa\", \"c\"},\n\t\t{\"bbbb\", \"b\"},\n\t})\n\n\tassert2Nodes(t, \"2_\", ring, []testNodes{\n\t\t{\"test\", []string{\"b\", \"a\"}},\n\t\t{\"test\", []string{\"b\", \"a\"}},\n\t\t{\"test1\", []string{\"b\", \"a\"}},\n\t\t{\"test2\", []string{\"b\", \"a\"}},\n\t\t{\"test3\", []string{\"c\", \"b\"}},\n\t\t{\"test4\", []string{\"b\", \"a\"}},\n\t\t{\"test5\", []string{\"c\", \"b\"}},\n\t\t{\"aaaa\", []string{\"c\", \"b\"}},\n\t\t{\"bbbb\", []string{\"b\", \"a\"}},\n\t})\n\n\tring = ring.RemoveNode(\"c\")\n\n\tdelete(weights, \"c\")\n\texpectWeights(t, ring, weights)\n\n\tassertNodes(t, \"3_\", ring, []testPair{\n\t\t{\"test\", \"b\"},\n\t\t{\"test\", \"b\"},\n\t\t{\"test1\", \"b\"},\n\t\t{\"test2\", \"b\"},\n\t\t{\"test3\", \"b\"},\n\t\t{\"test4\", \"b\"},\n\t\t{\"test5\", \"b\"},\n\t\t{\"aaaa\", \"b\"},\n\t\t{\"bbbb\", \"b\"},\n\t})\n\n\tassert2Nodes(t, \"4_\", ring, []testNodes{\n\t\t{\"test\", []string{\"b\", \"a\"}},\n\t\t{\"test\", []string{\"b\", \"a\"}},\n\t\t{\"test1\", []string{\"b\", \"a\"}},\n\t\t{\"test2\", []string{\"b\", \"a\"}},\n\t\t{\"test3\", []string{\"b\", \"a\"}},\n\t\t{\"test4\", []string{\"b\", \"a\"}},\n\t\t{\"test5\", []string{\"b\", \"a\"}},\n\t\t{\"aaaa\", []string{\"b\", \"a\"}},\n\t\t{\"bbbb\", []string{\"b\", \"a\"}},\n\t})\n}\n\nfunc TestAddRemoveNode(t *testing.T) {\n\tnodes := []string{\"a\", \"b\", \"c\"}\n\tring := New(nodes)\n\tring = ring.AddNode(\"d\")\n\n\texpectNodesABCD(t, \"1_\", ring)\n\n\tassert2Nodes(t, \"2_\", ring, []testNodes{\n\t\t{\"test\", []string{\"d\", \"a\"}},\n\t\t{\"test\", []string{\"d\", \"a\"}},\n\t\t{\"test1\", []string{\"b\", \"d\"}},\n\t\t{\"test2\", []string{\"b\", \"d\"}},\n\t\t{\"test3\", []string{\"c\", \"b\"}},\n\t\t{\"test4\", []string{\"d\", \"a\"}},\n\t\t{\"test5\", []string{\"c\", \"b\"}},\n\t\t{\"aaaa\", []string{\"c\", \"b\"}},\n\t\t{\"bbbb\", []string{\"d\", \"a\"}},\n\t})\n\n\tring = ring.AddNode(\"e\")\n\n\tassertNodes(t, \"3_\", ring, []testPair{\n\t\t{\"test\", \"a\"},\n\t\t{\"test\", \"a\"},\n\t\t{\"test1\", \"b\"},\n\t\t{\"test2\", \"b\"},\n\t\t{\"test3\", \"c\"},\n\t\t{\"test4\", \"c\"},\n\t\t{\"test5\", \"a\"},\n\t\t{\"aaaa\", \"b\"},\n\t\t{\"bbbb\", \"e\"},\n\t})\n\n\tassert2Nodes(t, \"4_\", ring, []testNodes{\n\t\t{\"test\", []string{\"d\", \"a\"}},\n\t\t{\"test\", []string{\"d\", \"a\"}},\n\t\t{\"test1\", []string{\"b\", \"d\"}},\n\t\t{\"test2\", []string{\"e\", \"b\"}},\n\t\t{\"test3\", []string{\"c\", \"e\"}},\n\t\t{\"test4\", []string{\"d\", \"a\"}},\n\t\t{\"test5\", []string{\"c\", \"e\"}},\n\t\t{\"aaaa\", []string{\"c\", \"e\"}},\n\t\t{\"bbbb\", []string{\"d\", \"a\"}},\n\t})\n\n\tring = ring.AddNode(\"f\")\n\n\tassertNodes(t, \"5_\", ring, []testPair{\n\t\t{\"test\", \"a\"},\n\t\t{\"test\", \"a\"},\n\t\t{\"test1\", \"b\"},\n\t\t{\"test2\", \"f\"},\n\t\t{\"test3\", \"f\"},\n\t\t{\"test4\", \"c\"},\n\t\t{\"test5\", \"f\"},\n\t\t{\"aaaa\", \"b\"},\n\t\t{\"bbbb\", \"e\"},\n\t})\n\n\tassert2Nodes(t, \"6_\", ring, []testNodes{\n\t\t{\"test\", []string{\"d\", \"a\"}},\n\t\t{\"test\", []string{\"d\", \"a\"}},\n\t\t{\"test1\", []string{\"b\", \"d\"}},\n\t\t{\"test2\", []string{\"e\", \"f\"}},\n\t\t{\"test3\", []string{\"c\", \"e\"}},\n\t\t{\"test4\", []string{\"d\", \"a\"}},\n\t\t{\"test5\", []string{\"c\", \"e\"}},\n\t\t{\"aaaa\", []string{\"c\", \"e\"}},\n\t\t{\"bbbb\", []string{\"d\", \"a\"}},\n\t})\n\n\tring = ring.RemoveNode(\"e\")\n\n\tassertNodes(t, \"7_\", ring, []testPair{\n\t\t{\"test\", \"a\"},\n\t\t{\"test\", \"a\"},\n\t\t{\"test1\", \"b\"},\n\t\t{\"test2\", \"f\"},\n\t\t{\"test3\", \"f\"},\n\t\t{\"test4\", \"c\"},\n\t\t{\"test5\", \"f\"},\n\t\t{\"aaaa\", \"b\"},\n\t\t{\"bbbb\", \"f\"},\n\t})\n\n\tassert2Nodes(t, \"8_\", ring, []testNodes{\n\t\t{\"test\", []string{\"d\", \"a\"}},\n\t\t{\"test\", []string{\"d\", \"a\"}},\n\t\t{\"test1\", []string{\"b\", \"d\"}},\n\t\t{\"test2\", []string{\"f\", \"b\"}},\n\t\t{\"test3\", []string{\"c\", \"f\"}},\n\t\t{\"test4\", []string{\"d\", \"a\"}},\n\t\t{\"test5\", []string{\"c\", \"f\"}},\n\t\t{\"aaaa\", []string{\"c\", \"f\"}},\n\t\t{\"bbbb\", []string{\"d\", \"a\"}},\n\t})\n\n\tring = ring.RemoveNode(\"f\")\n\n\texpectNodesABCD(t, \"TestAddRemoveNode_5_\", ring)\n\n\tassert2Nodes(t, \"\", ring, []testNodes{\n\t\t{\"test\", []string{\"d\", \"a\"}},\n\t\t{\"test\", []string{\"d\", \"a\"}},\n\t\t{\"test1\", []string{\"b\", \"d\"}},\n\t\t{\"test2\", []string{\"b\", \"d\"}},\n\t\t{\"test3\", []string{\"c\", \"b\"}},\n\t\t{\"test4\", []string{\"d\", \"a\"}},\n\t\t{\"test5\", []string{\"c\", \"b\"}},\n\t\t{\"aaaa\", []string{\"c\", \"b\"}},\n\t\t{\"bbbb\", []string{\"d\", \"a\"}},\n\t})\n\n\tring = ring.RemoveNode(\"d\")\n\n\texpectNodesABC(t, \"TestAddRemoveNode_6_\", ring)\n\texpectNodeRangesABC(t, \"\", ring)\n}\n"
  },
  {
    "path": "key.go",
    "content": "package hashring\n\nimport (\n\t\"encoding/binary\"\n\t\"fmt\"\n)\n\ntype Int64PairHashKey struct {\n\tHigh int64\n\tLow  int64\n}\n\nfunc (k *Int64PairHashKey) Less(other HashKey) bool {\n\to := other.(*Int64PairHashKey)\n\tif k.High < o.High {\n\t\treturn true\n\t}\n\treturn k.High == o.High && k.Low < o.Low\n}\n\nfunc NewInt64PairHashKey(bytes []byte) (HashKey, error) {\n\tconst expected = 16\n\tif len(bytes) != expected {\n\t\treturn nil, fmt.Errorf(\n\t\t\t\"expected %d bytes, got %d bytes\",\n\t\t\texpected, len(bytes),\n\t\t)\n\t}\n\treturn &Int64PairHashKey{\n\t\tHigh: int64(binary.LittleEndian.Uint64(bytes[:8])),\n\t\tLow:  int64(binary.LittleEndian.Uint64(bytes[8:])),\n\t}, nil\n}\n"
  }
]