Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 5651f35

Browse files
zaharidichevadleong
authored andcommitted
Remove ZK candidate dep (linkerd#2361)
The artifact for com.twitter.common.zookeeper" % "candidate" % "0.0.84" on maven.twttr.com is not present anymore. Therefore we need to cpy the bits that we use from it and have it in our sourcecode. Fixes linkerd#2360 Signed-off-by: Zahari Dichev <[email protected]>
1 parent e038d9e commit 5651f35

File tree

6 files changed

+301
-8
lines changed

6 files changed

+301
-8
lines changed
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// =================================================================================================
2+
// Copyright 2011 Twitter, Inc.
3+
// -------------------------------------------------------------------------------------------------
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this work except in compliance with the License.
6+
// You may obtain a copy of the License in the LICENSE file, or 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+
17+
package com.twitter.common.zookeeper;
18+
19+
import com.google.common.base.Optional;
20+
import com.google.common.base.Supplier;
21+
22+
import com.twitter.finagle.common.zookeeper.Group.JoinException;
23+
import com.twitter.finagle.common.zookeeper.Group.WatchException;
24+
import com.twitter.finagle.common.zookeeper.ZooKeeperClient.ZooKeeperConnectionException;
25+
26+
import org.apache.zookeeper.KeeperException;
27+
28+
29+
/**
30+
* Interface definition for becoming or querying for a ZooKeeper-based group leader.
31+
*/
32+
public interface Candidate {
33+
34+
/**
35+
* Returns the current group leader by querying ZooKeeper synchronously.
36+
*
37+
* @return the current group leader's identifying data or {@link Optional#absent()} if there is
38+
* no leader
39+
* @throws ZooKeeperConnectionException if there was a problem connecting to ZooKeeper
40+
* @throws KeeperException if there was a problem reading the leader information
41+
* @throws InterruptedException if this thread is interrupted getting the leader
42+
*/
43+
public Optional<byte[]> getLeaderData()
44+
throws ZooKeeperConnectionException, KeeperException, InterruptedException;
45+
46+
/**
47+
* Encapsulates a leader that can be elected and subsequently defeated.
48+
*/
49+
interface Leader {
50+
51+
/**
52+
* Called when this leader has been elected.
53+
*
54+
* @param abdicate a command that can be used to abdicate leadership and force a new election
55+
*/
56+
void onElected(ExceptionalCommand<JoinException> abdicate);
57+
58+
/**
59+
* Called when the leader has been ousted. Can occur either if the leader abdicates or if an
60+
* external event causes the leader to lose its leadership role (session expiration).
61+
*/
62+
void onDefeated();
63+
}
64+
65+
/**
66+
* Offers this candidate in leadership elections for as long as the current jvm process is alive.
67+
* Upon election, the {@code onElected} callback will be executed and a command that can be used
68+
* to abdicate leadership will be passed in. If the elected leader jvm process dies or the
69+
* elected leader successfully abdicates then a new leader will be elected. Leaders that
70+
* successfully abdicate are removed from the group and will not be eligible for leadership
71+
* election unless {@link #offerLeadership(Leader)} is called again.
72+
*
73+
* @param leader the leader to notify of election and defeat events
74+
* @throws JoinException if there was a problem joining the group
75+
* @throws WatchException if there is a problem generating the 1st group membership list
76+
* @throws InterruptedException if interrupted waiting to join the group and determine initial
77+
* election results
78+
* @return a supplier that can be queried to find out if this leader is currently elected
79+
*/
80+
public Supplier<Boolean> offerLeadership(Leader leader)
81+
throws JoinException, WatchException, InterruptedException;
82+
}
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
// =================================================================================================
2+
// Copyright 2011 Twitter, Inc.
3+
// -------------------------------------------------------------------------------------------------
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this work except in compliance with the License.
6+
// You may obtain a copy of the License in the LICENSE file, or 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+
17+
package com.twitter.common.zookeeper;
18+
19+
import java.net.InetAddress;
20+
import java.net.UnknownHostException;
21+
import java.util.concurrent.atomic.AtomicBoolean;
22+
import java.util.logging.Level;
23+
import java.util.logging.Logger;
24+
25+
import javax.annotation.Nullable;
26+
27+
import com.google.common.base.Charsets;
28+
import com.google.common.base.Function;
29+
import com.google.common.base.Optional;
30+
import com.google.common.base.Preconditions;
31+
import com.google.common.base.Supplier;
32+
import com.google.common.collect.Iterables;
33+
import com.google.common.collect.Ordering;
34+
import org.apache.zookeeper.KeeperException;
35+
import com.twitter.finagle.common.zookeeper.Group;
36+
import com.twitter.finagle.common.zookeeper.Group.JoinException;
37+
import com.twitter.finagle.common.zookeeper.Group.GroupChangeListener;
38+
import com.twitter.finagle.common.zookeeper.Group.Membership;
39+
import com.twitter.finagle.common.zookeeper.Group.WatchException;
40+
import com.twitter.finagle.common.zookeeper.ZooKeeperClient.ZooKeeperConnectionException;
41+
42+
/**
43+
* Implements leader election for small groups of candidates. This implementation is subject to the
44+
* <a href="http://hadoop.apache.org/zookeeper/docs/r3.2.1/recipes.html#sc_leaderElection">
45+
* herd effect</a> for a given group and should only be used for small (~10 member) candidate pools.
46+
*/
47+
public class CandidateImpl implements Candidate {
48+
private static final Logger LOG = Logger.getLogger(CandidateImpl.class.getName());
49+
50+
private static final byte[] UNKNOWN_CANDIDATE_DATA = "<unknown>".getBytes(Charsets.UTF_8);
51+
52+
private static final Supplier<byte[]> IP_ADDRESS_DATA_SUPPLIER = new Supplier<byte[]>() {
53+
@Override public byte[] get() {
54+
try {
55+
return InetAddress.getLocalHost().getHostAddress().getBytes();
56+
} catch (UnknownHostException e) {
57+
LOG.log(Level.WARNING, "Failed to determine local address!", e);
58+
return UNKNOWN_CANDIDATE_DATA;
59+
}
60+
}
61+
};
62+
63+
private static final Function<Iterable<String>, String> MOST_RECENT_JUDGE =
64+
new Function<Iterable<String>, String>() {
65+
@Override public String apply(Iterable<String> candidates) {
66+
return Ordering.natural().min(candidates);
67+
}
68+
};
69+
70+
private final Group group;
71+
private final Function<Iterable<String>, String> judge;
72+
private final Supplier<byte[]> dataSupplier;
73+
74+
/**
75+
* Equivalent to {@link #CandidateImpl(Group, com.google.common.base.Function, Supplier)} using a
76+
* judge that always picks the lowest numbered candidate ephemeral node - by proxy the oldest or
77+
* 1st candidate and a default supplier that provides the ip address of this host according to
78+
* {@link java.net.InetAddress#getLocalHost()} as the leader identifying data.
79+
*/
80+
public CandidateImpl(Group group) {
81+
this(group, MOST_RECENT_JUDGE, IP_ADDRESS_DATA_SUPPLIER);
82+
}
83+
84+
/**
85+
* Creates a candidate that can be used to offer leadership for the given {@code group} using
86+
* a judge that always picks the lowest numbered candidate ephemeral node - by proxy the oldest
87+
* or 1st. The dataSupplier should produce bytes that identify this process as leader. These bytes
88+
* will become available to all participants via the {@link Candidate#getLeaderData()} method.
89+
*/
90+
public CandidateImpl(Group group, Supplier<byte[]> dataSupplier) {
91+
this(group, MOST_RECENT_JUDGE, dataSupplier);
92+
}
93+
94+
/**
95+
* Creates a candidate that can be used to offer leadership for the given {@code group}. The
96+
* {@code judge} is used to pick the current leader from all group members whenever the group
97+
* membership changes. To form a well-behaved election group with one leader, all candidates
98+
* should use the same judge. The dataSupplier should produce bytes that identify this process
99+
* as leader. These bytes will become available to all participants via the
100+
* {@link Candidate#getLeaderData()} method.
101+
*/
102+
public CandidateImpl(
103+
Group group,
104+
Function<Iterable<String>, String> judge,
105+
Supplier<byte[]> dataSupplier) {
106+
this.group = Preconditions.checkNotNull(group);
107+
this.judge = Preconditions.checkNotNull(judge);
108+
this.dataSupplier = Preconditions.checkNotNull(dataSupplier);
109+
}
110+
111+
@Override
112+
public Optional<byte[]> getLeaderData()
113+
throws ZooKeeperConnectionException, KeeperException, InterruptedException {
114+
115+
String leaderId = getLeader(group.getMemberIds());
116+
return leaderId == null
117+
? Optional.<byte[]>absent()
118+
: Optional.of(group.getMemberData(leaderId));
119+
}
120+
121+
@Override
122+
public Supplier<Boolean> offerLeadership(final Leader leader)
123+
throws JoinException, WatchException, InterruptedException {
124+
125+
final Membership membership = group.join(dataSupplier, () -> leader.onDefeated());
126+
127+
final AtomicBoolean elected = new AtomicBoolean(false);
128+
final AtomicBoolean abdicated = new AtomicBoolean(false);
129+
group.watch(new GroupChangeListener() {
130+
@Override public void onGroupChange(Iterable<String> memberIds) {
131+
boolean noCandidates = Iterables.isEmpty(memberIds);
132+
String memberId = membership.getMemberId();
133+
134+
if (noCandidates) {
135+
LOG.warning("All candidates have temporarily left the group: " + group);
136+
} else if (!Iterables.contains(memberIds, memberId)) {
137+
LOG.severe(String.format(
138+
"Current member ID %s is not a candidate for leader, current voting: %s",
139+
memberId, memberIds));
140+
} else {
141+
boolean electedLeader = memberId.equals(getLeader(memberIds));
142+
boolean previouslyElected = elected.getAndSet(electedLeader);
143+
144+
if (!previouslyElected && electedLeader) {
145+
LOG.info(String.format("Candidate %s is now leader of group: %s",
146+
membership.getMemberPath(), memberIds));
147+
148+
leader.onElected(new ExceptionalCommand<JoinException>() {
149+
@Override public void execute() throws JoinException {
150+
membership.cancel();
151+
abdicated.set(true);
152+
}
153+
});
154+
} else if (!electedLeader) {
155+
if (previouslyElected) {
156+
leader.onDefeated();
157+
}
158+
LOG.info(String.format(
159+
"Candidate %s waiting for the next leader election, current voting: %s",
160+
membership.getMemberPath(), memberIds));
161+
}
162+
}
163+
}
164+
});
165+
166+
return new Supplier<Boolean>() {
167+
@Override public Boolean get() {
168+
return !abdicated.get() && elected.get();
169+
}
170+
};
171+
}
172+
173+
@Nullable
174+
private String getLeader(Iterable<String> memberIds) {
175+
return Iterables.isEmpty(memberIds) ? null : judge.apply(memberIds);
176+
}
177+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// =================================================================================================
2+
// Copyright 2011 Twitter, Inc.
3+
// -------------------------------------------------------------------------------------------------
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this work except in compliance with the License.
6+
// You may obtain a copy of the License in the LICENSE file, or 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+
17+
package com.twitter.common.zookeeper;
18+
19+
/**
20+
* An interface that captures a unit of work.
21+
*
22+
* @param <E> The type of exception that the command throws.
23+
*
24+
* @author John Sirois
25+
*/
26+
public interface ExceptionalCommand<E extends Exception> {
27+
28+
/**
29+
* Performs a unit of work, possibly throwing {@code E} in the process.
30+
*
31+
* @throws E if there was a problem performing the work
32+
*/
33+
void execute() throws E;
34+
}

namer/zk-leader/src/main/scala/io/buoyant/namer/zk/ZkLeaderNamer.scala

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
package io.buoyant.namer.zk
22

3-
import com.twitter.common.zookeeper.Group.GroupChangeListener
4-
import com.twitter.common.zookeeper._
53
import com.twitter.finagle.util.InetSocketAddressUtil
64
import com.twitter.finagle.{Group => _, _}
75
import com.twitter.util._
86
import io.buoyant.config.types.HostAndPort
97
import java.net.InetSocketAddress
8+
9+
import com.twitter.common.zookeeper.{Candidate, CandidateImpl}
10+
import com.twitter.finagle.common.zookeeper.Group.GroupChangeListener
11+
import com.twitter.finagle.common.zookeeper.{Group, ZooKeeperClient, ZooKeeperUtils}
1012
import org.apache.zookeeper.KeeperException.NoNodeException
1113
import org.apache.zookeeper.data.ACL
14+
1215
import scala.collection.JavaConverters._
1316

1417
/**
@@ -51,7 +54,7 @@ case class ZkLeaderNamer(
5154
)
5255

5356
Closable.make { _ =>
54-
stop.execute()
57+
stop.run()
5558
Future.Unit
5659
}
5760
}

project/Deps.scala

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ object Deps {
1313

1414
def twitterUtil(mod: String) =
1515
"com.twitter" %% s"util-$mod" % "19.5.1"
16+
1617
// networking
1718
def finagle(mod: String) =
1819
"com.twitter" %% s"finagle-$mod" % "19.5.1"
@@ -22,10 +23,6 @@ object Deps {
2223

2324
val boringssl = "io.netty" % "netty-tcnative-boringssl-static" % "2.0.19.Final"
2425

25-
def zkCandidate =
26-
("com.twitter.common.zookeeper" % "candidate" % "0.0.84")
27-
.exclude("com.twitter.common", "util")
28-
2926
// Jackson (parsing)
3027
val jacksonVersion = "2.9.6"
3128
val jacksonCore =

project/LinkerdBuild.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ object LinkerdBuild extends Base {
115115

116116
val zkLeader = projectDir("namer/zk-leader")
117117
.dependsOn(core)
118-
.withLib(Deps.zkCandidate)
118+
.withTwitterLib(Deps.finagle("serversets").exclude("org.slf4j", "slf4j-jdk14"))
119119
.withTests()
120120

121121
val rancher = projectDir("namer/rancher")

0 commit comments

Comments
 (0)