Skip to content

Commit ad226a2

Browse files
committed
Client and Server AlignmentContext
1 parent 2702db5 commit ad226a2

9 files changed

Lines changed: 332 additions & 11 deletions

File tree

‎hadoop-hdds/common/src/main/java/org/apache/hadoop/ipc_/AlignmentContext.java‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import java.io.IOException;
2222

23+
import com.google.protobuf.Message;
2324
import org.apache.hadoop.ipc_.protobuf.RpcHeaderProtos.RpcRequestHeaderProto;
2425
import org.apache.hadoop.ipc_.protobuf.RpcHeaderProtos.RpcResponseHeaderProto;
2526

@@ -86,7 +87,8 @@ long receiveRequestState(RpcRequestHeaderProto header, long threshold)
8687
*
8788
* @param protocolName the name of the protocol
8889
* @param method the method call to check
90+
* @param payload the payload to check
8991
* @return true if this method is async, false otherwise.
9092
*/
91-
boolean isCoordinatedCall(String protocolName, String method);
93+
boolean isCoordinatedCall(String protocolName, String method, Message payload);
9294
}

‎hadoop-hdds/common/src/main/java/org/apache/hadoop/ipc_/ProtobufRpcEngine.java‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -582,6 +582,10 @@ RequestHeaderProto getRequestHeader() throws IOException {
582582
return requestHeader;
583583
}
584584

585+
Message getPayload() throws IOException {
586+
return payload;
587+
}
588+
585589
@Override
586590
public void writeTo(ResponseBuffer out) throws IOException {
587591
requestHeader.writeDelimitedTo(out);

‎hadoop-hdds/common/src/main/java/org/apache/hadoop/ipc_/Server.java‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2699,16 +2699,18 @@ private void processRpcRequest(RpcRequestHeaderProto header,
26992699
(call.rpcRequest instanceof ProtobufRpcEngine.RpcProtobufRequest)) {
27002700
// if call.rpcRequest is not RpcProtobufRequest, will skip the following
27012701
// step and treat the call as uncoordinated. As currently only certain
2702-
// ClientProtocol methods request made through RPC protobuf needs to be
2702+
// OzoneManagerProtocol methods request made through RPC protobuf needs to be
27032703
// coordinated.
27042704
String methodName;
27052705
String protoName;
27062706
ProtobufRpcEngine.RpcProtobufRequest req =
27072707
(ProtobufRpcEngine.RpcProtobufRequest) call.rpcRequest;
2708+
Message payload;
27082709
try {
27092710
methodName = req.getRequestHeader().getMethodName();
27102711
protoName = req.getRequestHeader().getDeclaringClassProtocolName();
2711-
if (alignmentContext.isCoordinatedCall(protoName, methodName)) {
2712+
payload = req.getPayload();
2713+
if (alignmentContext.isCoordinatedCall(protoName, methodName, payload)) {
27122714
call.markCallCoordinated(true);
27132715
long stateId;
27142716
stateId = alignmentContext.receiveRequestState(

‎hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/OMConfigKeys.java‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ public final class OMConfigKeys {
5151
public static final int OZONE_OM_DB_MAX_OPEN_FILES_DEFAULT
5252
= -1;
5353

54+
public static final String OZONE_OM_STATE_CONTEXT_ENABLED_KEY =
55+
"ozone.om.state.context.enabled";
56+
public static final boolean OZONE_OM_STATE_CONTEXT_ENABLED_DEFAULT = false;
57+
5458
public static final String OZONE_OM_INTERNAL_SERVICE_ID =
5559
"ozone.om.internal.service.id";
5660

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.hadoop.ozone.om.ha;
19+
20+
import java.io.IOException;
21+
import java.util.concurrent.atomic.LongAccumulator;
22+
import org.apache.hadoop.ipc.AlignmentContext;
23+
import org.apache.hadoop.ipc.protobuf.RpcHeaderProtos.RpcRequestHeaderProto;
24+
import org.apache.hadoop.ipc.protobuf.RpcHeaderProtos.RpcResponseHeaderProto;
25+
26+
/**
27+
* Global State Id context for the client.
28+
* <p>
29+
* This is the client side implementation responsible for receiving
30+
* state alignment info from server(s).
31+
*/
32+
public class ClientAlignmentContext implements AlignmentContext {
33+
34+
private final LongAccumulator lastSeenStateId;
35+
36+
public ClientAlignmentContext(LongAccumulator lastSeenStateId) {
37+
this.lastSeenStateId = lastSeenStateId;
38+
}
39+
40+
@Override
41+
public long getLastSeenStateId() {
42+
return lastSeenStateId.get();
43+
}
44+
45+
@Override
46+
public boolean isCoordinatedCall(String protocolName, String method) {
47+
throw new UnsupportedOperationException(
48+
"Client should not be checking uncoordinated call");
49+
}
50+
51+
/**
52+
* Client side implementation only receives state alignment info.
53+
* It does not provide state alignment info therefore this does nothing.
54+
*/
55+
@Override
56+
public void updateResponseState(RpcResponseHeaderProto.Builder header) {
57+
// Do nothing.
58+
}
59+
60+
/**
61+
* Client side implementation for receiving state alignment info
62+
* in responses.
63+
*/
64+
@Override
65+
public synchronized void receiveResponseState(RpcResponseHeaderProto header) {
66+
lastSeenStateId.accumulate(header.getStateId());
67+
}
68+
69+
/**
70+
* Client side implementation for providing state alignment info in requests.
71+
*/
72+
@Override
73+
public synchronized void updateRequestState(RpcRequestHeaderProto.Builder header) {
74+
if (lastSeenStateId.get() != Long.MIN_VALUE) {
75+
header.setStateId(lastSeenStateId.get());
76+
}
77+
}
78+
79+
/**
80+
* Client side implementation only provides state alignment info in requests.
81+
* Client does not receive RPC requests therefore this does nothing.
82+
*/
83+
@Override
84+
public long receiveRequestState(RpcRequestHeaderProto header, long threshold)
85+
throws IOException {
86+
// Do nothing.
87+
return 0;
88+
}
89+
}

‎hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/ha/OMFailoverProxyProviderBase.java‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ protected T createOMProxy(InetSocketAddress omAddress) throws IOException {
146146
NetUtils.getDefaultSocketFactory(hadoopConf),
147147
(int) OmUtils.getOMClientRpcTimeOut(getConf()),
148148
connectionRetryPolicy
149+
// TODO: Implement and add alignment context here
149150
).getProxy();
150151
}
151152

‎hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestOzoneManagerHAWithAllRunning.java‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1158,4 +1158,22 @@ void testOMRatisSnapshot() throws Exception {
11581158
"snapshot indices");
11591159

11601160
}
1161+
1162+
/**
1163+
* This test checks if after a client writes we can see the state id in
1164+
* updated via the response.
1165+
*/
1166+
@Test
1167+
void testStateTransferOnWrite() throws Exception {
1168+
1169+
}
1170+
1171+
/**
1172+
* This test checks if after a client reads we can see the state id in
1173+
* updated via the response.
1174+
*/
1175+
@Test
1176+
void testStateTransferOnRead() throws Exception {
1177+
1178+
}
11611179
}
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.hadoop.ozone.om;
19+
20+
import com.google.protobuf.Message;
21+
import java.io.IOException;
22+
import java.util.concurrent.TimeUnit;
23+
import org.apache.hadoop.ipc_.AlignmentContext;
24+
import org.apache.hadoop.ipc_.RetriableException;
25+
import org.apache.hadoop.ipc_.protobuf.RpcHeaderProtos.RpcRequestHeaderProto;
26+
import org.apache.hadoop.ipc_.protobuf.RpcHeaderProtos.RpcResponseHeaderProto;
27+
import org.apache.hadoop.ozone.OmUtils;
28+
import org.apache.hadoop.ozone.om.protocol.OzoneManagerProtocol;
29+
import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMRequest;
30+
import org.apache.ratis.proto.RaftProtos.RaftPeerRole;
31+
import org.slf4j.Logger;
32+
import org.slf4j.LoggerFactory;
33+
34+
/**
35+
* This is the server side implementation responsible for passing
36+
* state alignment info to clients.
37+
* <p>
38+
* Unlike HDFS's ClientNamenodeProtocol that has a RPC method for each
39+
* distinct call, OM (OzoneManagerService) only contains a single RPC method
40+
* (i.e. submitRequest(OMRequest)). Therefore, we need to query the OMRequest
41+
* to get the OMRequest parameter from the RPC method get the corresponding cmdType
42+
* to check whether we can the request to be run on non-leader OMs.
43+
*/
44+
public class OmAlignmentContext implements AlignmentContext {
45+
46+
private static final Logger LOG =
47+
LoggerFactory.getLogger(OmAlignmentContext.class);
48+
/**
49+
* Estimated number of journal transactions a typical OM can execute
50+
* per second. The number is used to estimate how long a client's
51+
* RPC request will wait in the call queue before the Observer catches up
52+
* with its state id.
53+
*/
54+
private static final long ESTIMATED_TRANSACTIONS_PER_SECOND = 10000L;
55+
56+
/**
57+
* The client wait time on an RPC request is composed of
58+
* the server execution time plus the communication time.
59+
* This is an expected fraction of the total wait time spent on
60+
* server execution.
61+
*/
62+
private static final float ESTIMATED_SERVER_TIME_MULTIPLIER = 0.8f;
63+
64+
private final OzoneManager ozoneManager;
65+
66+
/**
67+
* Server side constructor.
68+
* @param ozoneManager server side state provider
69+
*/
70+
OmAlignmentContext(OzoneManager ozoneManager) {
71+
this.ozoneManager = ozoneManager;
72+
}
73+
74+
/**
75+
* Server side implementation for providing state alignment info in responses.
76+
*/
77+
@Override
78+
public void updateResponseState(RpcResponseHeaderProto.Builder header) {
79+
// Using getCorrectLastAppliedOrWrittenTxId will acquire the lock on
80+
// FSEditLog. This is needed so that ANN will return the correct state id
81+
// it currently has. But this may not be necessary for Observer, may want
82+
// revisit for optimization. Same goes to receiveRequestState.
83+
header.setStateId(getLastSeenStateId());
84+
}
85+
86+
/**
87+
* Server side implementation only provides state alignment info.
88+
* It does not receive state alignment info therefore this does nothing.
89+
*/
90+
@Override
91+
public void receiveResponseState(RpcResponseHeaderProto header) {
92+
// Do nothing.
93+
}
94+
95+
/**
96+
* Server side implementation only receives state alignment info.
97+
* It does not build RPC requests therefore this does nothing.
98+
*/
99+
@Override
100+
public void updateRequestState(RpcRequestHeaderProto.Builder header) {
101+
// Do nothing.
102+
}
103+
104+
/**
105+
* Server-side implementation for processing state alignment info in
106+
* requests.
107+
* For Follower/Listener it compares the client and the server states and determines
108+
* if it makes sense to wait until the server catches up with the client
109+
* state. If not the server throws RetriableException so that the client
110+
* could retry the call according to the retry policy with another Follower/Listener
111+
* or the Leader.
112+
*
113+
* @param header The RPC request header.
114+
* @param clientWaitTime time in milliseconds indicating how long client
115+
* waits for the server response. It is used to verify if the client's
116+
* state is too far ahead of the server's
117+
* @return the minimum of the state ids of the client or the server.
118+
* @throws RetriableException if Observer is too far behind.
119+
*/
120+
@Override
121+
public long receiveRequestState(RpcRequestHeaderProto header,
122+
long clientWaitTime) throws IOException {
123+
RaftPeerRole selfRole;
124+
if (ozoneManager.getOmRatisServer() == null) {
125+
selfRole = RaftPeerRole.LEADER;
126+
} else {
127+
selfRole = ozoneManager.getSelfRole(ozoneManager.getOmRatisServer().getLeaderId());
128+
}
129+
130+
if (!header.hasStateId() && !RaftPeerRole.LEADER.equals(selfRole)) {
131+
// This could happen if client configured with non-follower proxy provider
132+
// (e.g., ConfiguredFailoverProxyProvider) is accessing a cluster with.
133+
// In this case, we should let the client failover to the
134+
// leader node, rather than potentially serving stale result (client
135+
// stateId is 0 if not set).
136+
throw new IOException("Node received request without "
137+
+ "stateId. This mostly likely is because client is not configured "
138+
+ "with ObserverReadProxyProvider");
139+
}
140+
long serverStateId = getLastSeenStateId();
141+
long clientStateId = header.getStateId();
142+
LOG.trace("Client State ID= {} and Server State ID= {}",
143+
clientStateId, serverStateId);
144+
145+
if (clientStateId > serverStateId &&
146+
RaftPeerRole.LEADER.equals(selfRole)) {
147+
LOG.warn("The client stateId: {} is greater than "
148+
+ "the server stateId: {} This is unexpected. "
149+
+ "Resetting client stateId to server stateId",
150+
clientStateId, serverStateId);
151+
return serverStateId;
152+
}
153+
if ((RaftPeerRole.FOLLOWER.equals(selfRole) || RaftPeerRole.LISTENER.equals(selfRole)) &&
154+
clientStateId - serverStateId >
155+
ESTIMATED_TRANSACTIONS_PER_SECOND
156+
* TimeUnit.MILLISECONDS.toSeconds(clientWaitTime)
157+
* ESTIMATED_SERVER_TIME_MULTIPLIER) {
158+
throw new RetriableException(
159+
"Follower / Listener Node is too far behind: serverStateId = "
160+
+ serverStateId + " clientStateId = " + clientStateId);
161+
}
162+
return clientStateId;
163+
}
164+
165+
@Override
166+
public long getLastSeenStateId() {
167+
return ozoneManager.getOmRatisServer().getLastAppliedTermIndex().getIndex();
168+
}
169+
170+
@Override
171+
public boolean isCoordinatedCall(String protocolName, String methodName, Message payload) {
172+
// See OzoneManagerProtocolPB @ProtocolInfo annotation for the protocol name
173+
if (!protocolName.equals(OzoneManagerProtocol.class.getCanonicalName()) ||
174+
!(methodName.equals("submitRequest")) ||
175+
!(payload instanceof OMRequest)) {
176+
return false;
177+
}
178+
179+
OMRequest omRequest = (OMRequest) payload;
180+
181+
return OmUtils.isReadOnly(omRequest);
182+
}
183+
184+
}

0 commit comments

Comments
 (0)