1+ /**
2+ * Licensed to the Apache Software Foundation (ASF) under one
3+ * or more contributor license agreements. See the NOTICE file
4+ * distributed with this work for additional information
5+ * regarding copyright ownership. The ASF licenses this file
6+ * to you under the Apache License, Version 2.0 (the
7+ * "License"); you may not use this file except in compliance
8+ * with the License. You may obtain a copy of the License at
9+ *
10+ * http://www.apache.org/licenses/LICENSE-2.0
11+ *
12+ * Unless required by applicable law or agreed to in writing, software
13+ * distributed under the License is distributed on an "AS IS" BASIS,
14+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+ * See the License for the specific language governing permissions and
16+ * limitations under the License.
17+ */
18+ package org .apache .hadoop .hdfs .server .federation .router .async ;
19+
20+ import static org .apache .hadoop .hdfs .server .federation .FederationTestUtils .simulateSlowNamenode ;
21+ import static org .apache .hadoop .hdfs .server .federation .router .async .TestDisableNameservicesExample .cluster ;
22+ import static org .apache .hadoop .hdfs .server .federation .router .async .TestDisableNameservicesExample .routerContext ;
23+ import static org .apache .hadoop .hdfs .server .federation .router .async .TestDisableNameservicesExample .setUp ;
24+ import static org .apache .hadoop .util .Time .monotonicNow ;
25+ import static org .junit .Assert .assertEquals ;
26+ import static org .junit .Assert .assertTrue ;
27+
28+ import java .io .IOException ;
29+ import java .lang .reflect .Method ;
30+ import java .util .Iterator ;
31+ import java .util .Map ;
32+ import java .util .Set ;
33+ import java .util .TreeMap ;
34+ import java .util .concurrent .TimeUnit ;
35+
36+ import org .apache .hadoop .conf .Configuration ;
37+ import org .apache .hadoop .fs .FileStatus ;
38+ import org .apache .hadoop .fs .FileSystem ;
39+ import org .apache .hadoop .fs .Path ;
40+ import org .apache .hadoop .hdfs .MiniDFSCluster ;
41+ import org .apache .hadoop .hdfs .protocol .ClientProtocol ;
42+ import org .apache .hadoop .hdfs .server .federation .MiniRouterDFSCluster .NamenodeContext ;
43+ import org .apache .hadoop .hdfs .server .federation .MiniRouterDFSCluster .RouterContext ;
44+ import org .apache .hadoop .hdfs .server .federation .RouterConfigBuilder ;
45+ import org .apache .hadoop .hdfs .server .federation .StateStoreDFSCluster ;
46+ import org .apache .hadoop .hdfs .server .federation .metrics .RBFMetrics ;
47+ import org .apache .hadoop .hdfs .server .federation .resolver .MembershipNamenodeResolver ;
48+ import org .apache .hadoop .hdfs .server .federation .resolver .MountTableManager ;
49+ import org .apache .hadoop .hdfs .server .federation .resolver .MountTableResolver ;
50+ import org .apache .hadoop .hdfs .server .federation .router .NameserviceManager ;
51+ import org .apache .hadoop .hdfs .server .federation .router .RBFConfigKeys ;
52+ import org .apache .hadoop .hdfs .server .federation .router .Router ;
53+ import org .apache .hadoop .hdfs .server .federation .router .RouterClient ;
54+ import org .apache .hadoop .hdfs .server .federation .store .DisabledNameserviceStore ;
55+ import org .apache .hadoop .hdfs .server .federation .store .StateStoreService ;
56+ import org .apache .hadoop .hdfs .server .federation .store .protocol .AddMountTableEntryRequest ;
57+ import org .apache .hadoop .hdfs .server .federation .store .protocol .DisableNameserviceRequest ;
58+ import org .apache .hadoop .hdfs .server .federation .store .records .MountTable ;
59+ import org .apache .hadoop .hdfs .server .namenode .NameNode ;
60+ import org .codehaus .jettison .json .JSONObject ;
61+ import org .junit .jupiter .api .Nested ;
62+ import org .junit .jupiter .api .extension .AfterAllCallback ;
63+ import org .junit .jupiter .api .extension .AfterEachCallback ;
64+ import org .junit .jupiter .api .extension .BeforeEachCallback ;
65+ import org .junit .jupiter .api .extension .ExtendWith ;
66+ import org .junit .jupiter .api .extension .ExtensionContext ;
67+ import org .junit .jupiter .params .ParameterizedTest ;
68+ import org .junit .jupiter .params .provider .ValueSource ;
69+
70+ /**
71+ * Test the behavior when disabling name services.
72+ */
73+ @ SuppressWarnings ("checkstyle:VisibilityModifier" )
74+ public class TestDisableNameservicesExample {
75+
76+ @ Nested
77+ @ ExtendWith (RouterServerHelper .class )
78+ class TestWithAsyncRouterRpc {
79+
80+ @ ParameterizedTest
81+ @ ValueSource (strings = {"ASYNC" })
82+ public void testMetricsAsync (String rpcMode ) throws Exception {
83+ testMetrics ();
84+ }
85+
86+ /* @ParameterizedTest
87+ @ValueSource(strings = {"ASYNC"})
88+ public void testDisablingAsync() throws Exception {
89+ testDisabling();
90+ }
91+
92+ @ParameterizedTest
93+ @ValueSource(strings = {"ASYNC"})
94+ public void testWithoutDisablingAsync() throws IOException {
95+ testWithoutDisabling();
96+ }*/
97+ }
98+
99+ @ Nested
100+ @ ExtendWith (RouterServerHelper .class )
101+ class TestWithSyncRouterRpc {
102+
103+ @ ParameterizedTest
104+ @ ValueSource (strings = {"SYNC" })
105+ public void testMetricsSync (String rpcMode ) throws Exception {
106+ testMetrics ();
107+ }
108+
109+ @ ParameterizedTest
110+ @ ValueSource (strings = {"SYNC" })
111+ public void testDisablingSync () throws Exception {
112+ testDisabling ();
113+ }
114+
115+ @ ParameterizedTest
116+ @ ValueSource (strings = {"SYNC" })
117+ public void testWithoutDisablingSync () throws IOException {
118+ testWithoutDisabling ();
119+ }
120+ }
121+
122+ static StateStoreDFSCluster cluster ;
123+ static RouterContext routerContext ;
124+ static RouterClient routerAdminClient ;
125+ static ClientProtocol routerProtocol ;
126+
127+ public static void setUp (String rpcMode ) throws Exception {
128+ // Build and start a federated cluster.
129+ cluster = new StateStoreDFSCluster (false , 2 );
130+ Configuration routerConf = new RouterConfigBuilder ()
131+ .stateStore ()
132+ .metrics ()
133+ .admin ()
134+ .rpc ()
135+ .build ();
136+ // Reduce the number of RPC threads to saturate the Router easy.
137+ routerConf .setInt (RBFConfigKeys .DFS_ROUTER_HANDLER_COUNT_KEY , 8 );
138+ routerConf .setInt (RBFConfigKeys .DFS_ROUTER_CLIENT_THREADS_SIZE , 4 );
139+
140+ // Use async router rpc.
141+ if (rpcMode .equals ("ASYNC" )) {
142+ routerConf .setBoolean (RBFConfigKeys .DFS_ROUTER_ASYNC_RPC_ENABLE_KEY , true );
143+ }
144+
145+ // Set the DNs to belong to only one subcluster.
146+ cluster .setIndependentDNs ();
147+
148+ cluster .addRouterOverrides (routerConf );
149+ // Override some settings for the client.
150+ cluster .startCluster ();
151+ cluster .startRouters ();
152+ cluster .waitClusterUp ();
153+
154+ routerContext = cluster .getRandomRouter ();
155+ routerProtocol = routerContext .getClient ().getNamenode ();
156+ routerAdminClient = routerContext .getAdminClient ();
157+
158+ setupNamespace ();
159+
160+ // Simulate one of the subclusters to be slow.
161+ MiniDFSCluster dfsCluster = cluster .getCluster ();
162+ NameNode nn0 = dfsCluster .getNameNode (0 );
163+ simulateSlowNamenode (nn0 , 1 );
164+ }
165+
166+ private static void setupNamespace () throws IOException {
167+
168+ // Setup a mount table to map to the two namespaces
169+ MountTableManager mountTable = routerAdminClient .getMountTableManager ();
170+ Map <String , String > destinations = new TreeMap <>();
171+ destinations .put ("ns0" , "/dirns0" );
172+ MountTable newEntry = MountTable .newInstance ("/dirns0" , destinations );
173+ AddMountTableEntryRequest request =
174+ AddMountTableEntryRequest .newInstance (newEntry );
175+ mountTable .addMountTableEntry (request );
176+
177+ destinations = new TreeMap <>();
178+ destinations .put ("ns1" , "/dirns1" );
179+ newEntry = MountTable .newInstance ("/dirns1" , destinations );
180+ request = AddMountTableEntryRequest .newInstance (newEntry );
181+ mountTable .addMountTableEntry (request );
182+
183+ // Refresh the cache in the Router
184+ Router router = routerContext .getRouter ();
185+ MountTableResolver mountTableResolver =
186+ (MountTableResolver ) router .getSubclusterResolver ();
187+ mountTableResolver .loadCache (true );
188+
189+ // Add a folder to each namespace
190+ NamenodeContext nn0 = cluster .getNamenode ("ns0" , null );
191+ nn0 .getFileSystem ().mkdirs (new Path ("/dirns0/0" ));
192+ nn0 .getFileSystem ().mkdirs (new Path ("/dir-ns" ));
193+ NamenodeContext nn1 = cluster .getNamenode ("ns1" , null );
194+ nn1 .getFileSystem ().mkdirs (new Path ("/dirns1/1" ));
195+ }
196+
197+ public static void tearDown () {
198+ if (cluster != null ) {
199+ cluster .stopRouter (routerContext );
200+ cluster .shutdown ();
201+ cluster = null ;
202+ }
203+ }
204+
205+ public void cleanup () throws IOException {
206+ Router router = routerContext .getRouter ();
207+ StateStoreService stateStore = router .getStateStore ();
208+ DisabledNameserviceStore store =
209+ stateStore .getRegisteredRecordStore (DisabledNameserviceStore .class );
210+ store .loadCache (true );
211+
212+ Set <String > disabled = store .getDisabledNameservices ();
213+ for (String nsId : disabled ) {
214+ store .enableNameservice (nsId );
215+ }
216+ store .loadCache (true );
217+ }
218+
219+ public void testWithoutDisabling () throws IOException {
220+ // ns0 is slow and renewLease should take a long time
221+ long t0 = monotonicNow ();
222+ routerProtocol .renewLease ("client0" , null );
223+ long t = monotonicNow () - t0 ;
224+ assertTrue ("It took too little: " + t + "ms" ,
225+ t > TimeUnit .SECONDS .toMillis (1 ));
226+ // Return the results from all subclusters even if slow
227+ FileSystem routerFs = routerContext .getFileSystem ();
228+ FileStatus [] filesStatus = routerFs .listStatus (new Path ("/" ));
229+ assertEquals (3 , filesStatus .length );
230+ assertEquals ("dir-ns" , filesStatus [0 ].getPath ().getName ());
231+ assertEquals ("dirns0" , filesStatus [1 ].getPath ().getName ());
232+ assertEquals ("dirns1" , filesStatus [2 ].getPath ().getName ());
233+ }
234+
235+ public void testDisabling () throws Exception {
236+ disableNameservice ("ns0" );
237+
238+ // renewLease should be fast as we are skipping ns0
239+ long t0 = monotonicNow ();
240+ routerProtocol .renewLease ("client0" , null );
241+ long t = monotonicNow () - t0 ;
242+ assertTrue ("It took too long: " + t + "ms" ,
243+ t < TimeUnit .SECONDS .toMillis (1 ));
244+ // We should not report anything from ns0
245+ FileSystem routerFs = routerContext .getFileSystem ();
246+
247+ FileStatus [] filesStatus = routerFs .listStatus (new Path ("/" ));
248+ assertEquals (2 , filesStatus .length );
249+ assertEquals ("dirns0" , filesStatus [0 ].getPath ().getName ());
250+ assertEquals ("dirns1" , filesStatus [1 ].getPath ().getName ());
251+
252+ filesStatus = routerFs .listStatus (new Path ("/dirns1" ));
253+ assertEquals (1 , filesStatus .length );
254+ assertEquals ("1" , filesStatus [0 ].getPath ().getName ());
255+ }
256+
257+ public void testMetrics () throws Exception {
258+ disableNameservice ("ns0" );
259+
260+ int numActive = 0 ;
261+ int numDisabled = 0 ;
262+ Router router = routerContext .getRouter ();
263+ RBFMetrics metrics = router .getMetrics ();
264+ String jsonString = metrics .getNameservices ();
265+ JSONObject jsonObject = new JSONObject (jsonString );
266+ Iterator <?> keys = jsonObject .keys ();
267+ while (keys .hasNext ()) {
268+ String key = (String ) keys .next ();
269+ JSONObject json = jsonObject .getJSONObject (key );
270+ String nsId = json .getString ("nameserviceId" );
271+ String state = json .getString ("state" );
272+ if (nsId .equals ("ns0" )) {
273+ assertEquals ("DISABLED" , state );
274+ numDisabled ++;
275+ } else {
276+ assertEquals ("ACTIVE" , state );
277+ numActive ++;
278+ }
279+ }
280+ assertEquals (1 , numActive );
281+ assertEquals (1 , numDisabled );
282+ }
283+
284+ private static void disableNameservice (final String nsId )
285+ throws IOException {
286+ NameserviceManager nsManager = routerAdminClient .getNameserviceManager ();
287+ DisableNameserviceRequest req =
288+ DisableNameserviceRequest .newInstance (nsId );
289+ nsManager .disableNameservice (req );
290+
291+ Router router = routerContext .getRouter ();
292+ StateStoreService stateStore = router .getStateStore ();
293+ DisabledNameserviceStore store =
294+ stateStore .getRegisteredRecordStore (DisabledNameserviceStore .class );
295+ store .loadCache (true );
296+ MembershipNamenodeResolver resolver =
297+ (MembershipNamenodeResolver ) router .getNamenodeResolver ();
298+ resolver .loadCache (true );
299+ }
300+ }
301+
302+ class RouterServerHelper implements BeforeEachCallback , AfterEachCallback , AfterAllCallback {
303+
304+ private static final ThreadLocal <RouterServerHelper > TEST_ROUTER_SERVER_TL =
305+ new InheritableThreadLocal <RouterServerHelper >();
306+
307+ @ Override
308+ public void afterEach (ExtensionContext context ) throws Exception {
309+ Router router = routerContext .getRouter ();
310+ StateStoreService stateStore = router .getStateStore ();
311+ DisabledNameserviceStore store =
312+ stateStore .getRegisteredRecordStore (DisabledNameserviceStore .class );
313+ store .loadCache (true );
314+
315+ Set <String > disabled = store .getDisabledNameservices ();
316+ for (String nsId : disabled ) {
317+ store .enableNameservice (nsId );
318+ }
319+ store .loadCache (true );
320+ }
321+
322+ @ Override
323+ public void beforeEach (ExtensionContext context ) throws Exception {
324+ Method testMethod = context .getRequiredTestMethod ();
325+ ValueSource enumAnnotation = testMethod .getAnnotation (ValueSource .class );
326+ if (enumAnnotation != null ) {
327+ String [] strings = enumAnnotation .strings ();
328+ for (String s : strings ) {
329+ if (TEST_ROUTER_SERVER_TL .get () == null ) {
330+ setUp (s );
331+ }
332+ }
333+ }
334+ TEST_ROUTER_SERVER_TL .set (RouterServerHelper .this );
335+ }
336+
337+ @ Override
338+ public void afterAll (ExtensionContext context ) throws Exception {
339+ if (cluster != null ) {
340+ cluster .stopRouter (routerContext );
341+ cluster .shutdown ();
342+ cluster = null ;
343+ }
344+ TEST_ROUTER_SERVER_TL .remove ();
345+ }
346+ }
0 commit comments