blob: a4ea1b7389742ef5b0560d5e8610af5099ed7f45 [file] [log] [blame]
Tunahan Sezen1f65c902020-09-08 13:10:16 +00001/*
Joey Armstrong53fcac22023-01-11 13:25:01 -05002 * Copyright 2020-2023 Open Networking Foundation (ONF) and the ONF Contributors
Tunahan Sezen1f65c902020-09-08 13:10:16 +00003 *
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 */
16package org.opencord.maclearner.app.impl;
17
18import com.google.common.hash.Hashing;
19import org.onosproject.cluster.NodeId;
20
21import java.nio.charset.Charset;
22import java.util.ArrayList;
23import java.util.Collections;
24import java.util.List;
25
26/**
27 * Returns a server hosting a given key based on consistent hashing.
28 */
29public class ConsistentHasher {
30
31 private static class Entry implements Comparable<Entry> {
32 private final NodeId server;
33 private final int hash;
34
35 public Entry(NodeId server, int hash) {
36 this.server = server;
37 this.hash = hash;
38 }
39
40 public Entry(int hash) {
41 server = null;
42 this.hash = hash;
43 }
44
45 @Override
46 public int compareTo(Entry o) {
47 if (this.hash > o.hash) {
48 return 1;
49 } else if (this.hash < o.hash) {
50 return -1;
51 } // else
52 return 0;
53 }
54 }
55
56 private final int weight;
57
58 private List<Entry> table;
59
60 /**
61 * Creates a new hasher with the given list of servers.
62 *
63 * @param servers list of servers
64 * @param weight weight
65 */
66 public ConsistentHasher(List<NodeId> servers, int weight) {
67 this.weight = weight;
68
69 this.table = new ArrayList<>();
70
71 servers.forEach(this::addServer);
72 }
73
74 /**
75 * Adds a new server to the list of servers.
76 *
77 * @param server server ID
78 */
79 public synchronized void addServer(NodeId server) {
80 // Add weight number of buckets for each server
81 for (int i = 0; i < weight; i++) {
82 String label = server.toString() + i;
83 int hash = getHash(label);
84 Entry e = new Entry(server, hash);
85 table.add(e);
86 }
87
88 Collections.sort(table);
89 }
90
91 /**
92 * Removes a server from the list of servers.
93 *
94 * @param server server ID
95 */
96 public synchronized void removeServer(NodeId server) {
97 table.removeIf(e -> e.server.equals(server));
98 }
99
100 /**
101 * Hashes a given input string and finds that server that should
102 * handle the given ID.
103 *
104 * @param s input
105 * @return server ID
106 */
107 public synchronized NodeId hash(String s) {
108 Entry temp = new Entry(getHash(s));
109
110 int pos = Collections.binarySearch(this.table, temp);
111
112 // translate a negative not-found result into the closest following match
113 if (pos < 0) {
114 pos = Math.abs(pos + 1);
115 }
116
117 // wraparound if the hash was after the last entry in the table
118 if (pos == this.table.size()) {
119 pos = 0;
120 }
121
122 return table.get(pos).server;
123 }
124
125 private int getHash(String s) {
126 // stable, uniformly-distributed hash
127 return Hashing.murmur3_128().hashString(s, Charset.defaultCharset()).asInt();
128 }
129}