blob: 52e9b9609fca04432a9464e452fec84459fbabcd [file] [log] [blame]
Jonathan Hart4f178fa2020-02-03 10:46:01 -08001/*
2 * Copyright 2020-present Open Networking Foundation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package org.opencord.olt.impl;
18
19import com.google.common.hash.Hashing;
20import org.onosproject.cluster.NodeId;
21
22import java.nio.charset.Charset;
23import java.util.ArrayList;
24import java.util.Collections;
25import java.util.List;
26
27/**
28 * Returns a server hosting a given key based on consistent hashing.
29 */
30public class ConsistentHasher {
31
32 private static class Entry implements Comparable<Entry> {
33 private final NodeId server;
34 private final int hash;
35
36 public Entry(NodeId server, int hash) {
37 this.server = server;
38 this.hash = hash;
39 }
40
41 public Entry(int hash) {
42 server = null;
43 this.hash = hash;
44 }
45
46 @Override
47 public int compareTo(Entry o) {
48 if (this.hash > o.hash) {
49 return 1;
50 } else if (this.hash < o.hash) {
51 return -1;
52 } // else
53 return 0;
54 }
55 }
56
57 private final int weight;
58
59 private List<Entry> table;
60
61 /**
62 * Creates a new hasher with the given list of servers.
63 *
64 * @param servers list of servers
65 * @param weight weight
66 */
67 public ConsistentHasher(List<NodeId> servers, int weight) {
68 this.weight = weight;
69
70 this.table = new ArrayList<>();
71
72 servers.forEach(this::addServer);
73 }
74
75 /**
76 * Adds a new server to the list of servers.
77 *
78 * @param server server ID
79 */
80 public synchronized void addServer(NodeId server) {
81 // Add weight number of buckets for each server
82 for (int i = 0; i < weight; i++) {
83 String label = server.toString() + i;
84 int hash = getHash(label);
85 Entry e = new Entry(server, hash);
86 table.add(e);
87 }
88
89 Collections.sort(table);
90 }
91
92 /**
93 * Removes a server from the list of servers.
94 *
95 * @param server server ID
96 */
97 public synchronized void removeServer(NodeId server) {
98 table.removeIf(e -> e.server.equals(server));
99 }
100
101 /**
102 * Hashes a given input string and finds that server that should
103 * handle the given ID.
104 *
105 * @param s input
106 * @return server ID
107 */
108 public synchronized NodeId hash(String s) {
109 Entry temp = new Entry(getHash(s));
110
111 int pos = Collections.binarySearch(this.table, temp);
112
113 // translate a negative not-found result into the closest following match
114 if (pos < 0) {
115 pos = Math.abs(pos + 1);
116 }
117
118 // wraparound if the hash was after the last entry in the table
119 if (pos == this.table.size()) {
120 pos = 0;
121 }
122
123 return table.get(pos).server;
124 }
125
126 private int getHash(String s) {
127 // stable, uniformly-distributed hash
128 return Hashing.murmur3_128().hashString(s, Charset.defaultCharset()).asInt();
129 }
130}