From 9c029e3d432d30b49f60eafcaa813d3f94017210 Mon Sep 17 00:00:00 2001 From: Bhavesh Amre Date: Tue, 26 May 2026 15:57:40 +0530 Subject: [PATCH] RANGER-5585:Add unit test cases for plugin-yarn Module --- plugin-yarn/pom.xml | 18 + .../authorizer/TestRangerYarnAuthorizer.java | 576 +++++++++++++++ .../services/yarn/TestRangerServiceYarn.java | 299 ++++++++ .../services/yarn/client/TestYarnClient.java | 661 ++++++++++++++++++ .../yarn/client/TestYarnConnectionMgr.java | 55 ++ .../yarn/client/TestYarnResourceMgr.java | 216 ++++++ .../json/model/TestYarnSchedulerResponse.java | 183 +++++ 7 files changed, 2008 insertions(+) create mode 100644 plugin-yarn/src/test/java/org/apache/ranger/authorization/yarn/authorizer/TestRangerYarnAuthorizer.java create mode 100644 plugin-yarn/src/test/java/org/apache/ranger/services/yarn/TestRangerServiceYarn.java create mode 100644 plugin-yarn/src/test/java/org/apache/ranger/services/yarn/client/TestYarnClient.java create mode 100644 plugin-yarn/src/test/java/org/apache/ranger/services/yarn/client/TestYarnConnectionMgr.java create mode 100644 plugin-yarn/src/test/java/org/apache/ranger/services/yarn/client/TestYarnResourceMgr.java create mode 100644 plugin-yarn/src/test/java/org/apache/ranger/services/yarn/client/json/model/TestYarnSchedulerResponse.java diff --git a/plugin-yarn/pom.xml b/plugin-yarn/pom.xml index cae76d874fb..43971c6e616 100644 --- a/plugin-yarn/pom.xml +++ b/plugin-yarn/pom.xml @@ -195,6 +195,24 @@ ${jersey-client.version} runtime + + org.junit.jupiter + junit-jupiter + ${junit.jupiter.version} + test + + + org.mockito + mockito-core + ${mockito.version} + test + + + org.mockito + mockito-junit-jupiter + ${mockito.version} + test + org.slf4j log4j-over-slf4j diff --git a/plugin-yarn/src/test/java/org/apache/ranger/authorization/yarn/authorizer/TestRangerYarnAuthorizer.java b/plugin-yarn/src/test/java/org/apache/ranger/authorization/yarn/authorizer/TestRangerYarnAuthorizer.java new file mode 100644 index 00000000000..adfc19b98f5 --- /dev/null +++ b/plugin-yarn/src/test/java/org/apache/ranger/authorization/yarn/authorizer/TestRangerYarnAuthorizer.java @@ -0,0 +1,576 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ranger.authorization.yarn.authorizer; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.ipc.Server; +import org.apache.hadoop.security.UserGroupInformation; +import org.apache.hadoop.security.authorize.AccessControlList; +import org.apache.hadoop.yarn.security.AccessRequest; +import org.apache.hadoop.yarn.security.AccessType; +import org.apache.hadoop.yarn.security.Permission; +import org.apache.hadoop.yarn.security.PrivilegedEntity; +import org.apache.hadoop.yarn.security.PrivilegedEntity.EntityType; +import org.apache.ranger.audit.model.AuthzAuditEvent; +import org.apache.ranger.authorization.hadoop.config.RangerPluginConfig; +import org.apache.ranger.authorization.hadoop.constants.RangerHadoopConstants; +import org.apache.ranger.plugin.policyengine.RangerAccessRequest; +import org.apache.ranger.plugin.policyengine.RangerAccessResult; +import org.apache.ranger.plugin.policyengine.RangerAccessResultProcessor; +import org.apache.ranger.plugin.service.RangerBasePlugin; +import org.apache.ranger.plugin.util.RangerPerfTracer; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.MockedStatic; +import org.mockito.junit.jupiter.MockitoExtension; +import org.slf4j.Logger; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.net.InetAddress; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * @generated by Cursor + * @description : Unit Test cases for RangerYarnAuthorizer + */ +@ExtendWith(MockitoExtension.class) +@TestMethodOrder(MethodOrderer.MethodName.class) +class TestRangerYarnAuthorizer { + @AfterEach + void clearStaticYarnPlugin() throws Exception { + Field f = RangerYarnAuthorizer.class.getDeclaredField("yarnPlugin"); + f.setAccessible(true); + f.set(null, null); + } + + private static void setYarnPluginMock(RangerBasePlugin mockPlugin) throws Exception { + Field f = RangerYarnAuthorizer.class.getDeclaredField("yarnPlugin"); + f.setAccessible(true); + f.set(null, mockPlugin); + } + + @SuppressWarnings("rawtypes") + private static RangerBasePlugin newInnerPluginMock() throws ClassNotFoundException { + Class pluginClass = Class.forName( + "org.apache.ranger.authorization.yarn.authorizer.RangerYarnAuthorizer$RangerYarnPlugin"); + return (RangerBasePlugin) mock(pluginClass); + } + + private static void setYarnAuthEnabled(RangerYarnAuthorizer authorizer, boolean enabled) throws Exception { + Field f = RangerYarnAuthorizer.class.getDeclaredField("yarnAuthEnabled"); + f.setAccessible(true); + f.set(authorizer, enabled); + } + + @Test + void test01_isAdmin_whenAdminsNotSet_returnsFalse() throws Exception { + RangerYarnAuthorizer authorizer = new RangerYarnAuthorizer(); + UserGroupInformation ugi = UserGroupInformation.createRemoteUser("alice"); + + assertFalse(authorizer.isAdmin(ugi)); + } + + @Test + void test02_isAdmin_whenAclAllowsUser_returnsTrue() throws Exception { + RangerYarnAuthorizer authorizer = new RangerYarnAuthorizer(); + UserGroupInformation ugi = UserGroupInformation.createRemoteUser("bob"); + AccessControlList acl = mock(AccessControlList.class); + when(acl.isUserAllowed(ugi)).thenReturn(true); + authorizer.setAdmins(acl, ugi); + + assertTrue(authorizer.isAdmin(ugi)); + } + + @Test + void test03_setPermission_withEmptyList_completes() throws Exception { + RangerYarnAuthorizer authorizer = new RangerYarnAuthorizer(); + UserGroupInformation ugi = UserGroupInformation.createRemoteUser("carol"); + + assertDoesNotThrow(() -> authorizer.setPermission(Collections.emptyList(), ugi)); + } + + @Test + void test04_checkPermission_withNullPlugin_usesFallbackPathWithoutThrowing() throws Exception { + RangerYarnAuthorizer authorizer = new RangerYarnAuthorizer(); + UserGroupInformation ugi = UserGroupInformation.createRemoteUser("dave"); + PrivilegedEntity entity = new PrivilegedEntity(EntityType.QUEUE, "root.default"); + AccessRequest request = new AccessRequest(entity, ugi, AccessType.SUBMIT_APP, "10.0.0.1", "10.0.0.1", + null, Collections.emptyList()); + + assertDoesNotThrow(() -> authorizer.checkPermission(request)); + } + + @Test + void test05_init_withoutRangerAdminConfig_throws() { + RangerYarnAuthorizer authorizer = new RangerYarnAuthorizer(); + + assertThrows(IllegalArgumentException.class, () -> authorizer.init(new Configuration())); + } + + @Test + void test06_checkPermission_withMockPlugin_usesEngineResultWhenDetermined() throws Exception { + RangerBasePlugin mockPlugin = newInnerPluginMock(); + RangerAccessResult result = mock(RangerAccessResult.class); + when(result.getIsAllowed()).thenReturn(true); + when(mockPlugin.isAccessAllowed(any(RangerAccessRequest.class), any())).thenReturn(result); + setYarnPluginMock(mockPlugin); + + RangerYarnAuthorizer authorizer = new RangerYarnAuthorizer(); + setYarnAuthEnabled(authorizer, false); + UserGroupInformation ugi = UserGroupInformation.createRemoteUser("e1"); + PrivilegedEntity entity = new PrivilegedEntity(EntityType.QUEUE, "root"); + AccessRequest request = new AccessRequest(entity, ugi, AccessType.SUBMIT_APP, null, null, "10.0.0.2", + Collections.emptyList()); + + assertTrue(authorizer.checkPermission(request)); + } + + @Test + void test07_checkPermission_submit_app_withMockPlugin_deniesWhenDisallowed() throws Exception { + RangerBasePlugin mockPlugin = newInnerPluginMock(); + when(mockPlugin.isAccessAllowed(any(RangerAccessRequest.class), any())).thenAnswer(inv -> { + RangerAccessResult r = mock(RangerAccessResult.class); + when(r.getIsAllowed()).thenReturn(false); + return r; + }); + setYarnPluginMock(mockPlugin); + + RangerYarnAuthorizer authorizer = new RangerYarnAuthorizer(); + setYarnAuthEnabled(authorizer, false); + UserGroupInformation ugi = UserGroupInformation.createRemoteUser("e2"); + PrivilegedEntity entity = new PrivilegedEntity(EntityType.QUEUE, "root"); + AccessRequest request = new AccessRequest(entity, ugi, AccessType.SUBMIT_APP, "10.0.0.1", "10.0.0.1", + null, Collections.emptyList()); + + assertFalse(authorizer.checkPermission(request)); + } + + @Test + void test08_checkPermission_administer_queue_invokesMockPlugin() throws Exception { + RangerBasePlugin mockPlugin = newInnerPluginMock(); + when(mockPlugin.isAccessAllowed(any(RangerAccessRequest.class), any())).thenAnswer(inv -> { + RangerAccessResult r = mock(RangerAccessResult.class); + when(r.getIsAllowed()).thenReturn(true); + return r; + }); + setYarnPluginMock(mockPlugin); + + RangerYarnAuthorizer authorizer = new RangerYarnAuthorizer(); + setYarnAuthEnabled(authorizer, false); + UserGroupInformation ugi = UserGroupInformation.createRemoteUser("e3"); + PrivilegedEntity entity = new PrivilegedEntity(EntityType.QUEUE, "root"); + AccessRequest request = new AccessRequest(entity, ugi, AccessType.ADMINISTER_QUEUE, "10.0.0.1", + "10.0.0.1", null, Collections.emptyList()); + + assertTrue(authorizer.checkPermission(request)); + } + + @Test + void test09_checkPermission_runsAuditHandlerWhenPluginProcessesResult() throws Exception { + RangerBasePlugin mockPlugin = newInnerPluginMock(); + when(mockPlugin.isAccessAllowed(any(RangerAccessRequest.class), any())).thenAnswer(invocation -> { + RangerAccessResultProcessor proc = invocation.getArgument(1); + RangerAccessResult r = mock(RangerAccessResult.class); + when(r.getIsAudited()).thenReturn(true); + when(r.getIsAllowed()).thenReturn(true); + if (proc != null) { + proc.processResult(r); + } + return r; + }); + setYarnPluginMock(mockPlugin); + + RangerYarnAuthorizer authorizer = new RangerYarnAuthorizer(); + setYarnAuthEnabled(authorizer, false); + UserGroupInformation ugi = UserGroupInformation.createRemoteUser("e4"); + PrivilegedEntity entity = new PrivilegedEntity(EntityType.QUEUE, "root"); + AccessRequest request = new AccessRequest(entity, ugi, AccessType.SUBMIT_APP, "10.0.0.1", "10.0.0.1", + null, Collections.emptyList()); + + assertTrue(authorizer.checkPermission(request)); + } + + @Test + void test10_isAllowedByYarnAcl_whenAclMatchesHierarchy_returnsTrue() throws Exception { + RangerYarnAuthorizer authorizer = new RangerYarnAuthorizer(); + UserGroupInformation ugi = UserGroupInformation.createRemoteUser("fab"); + + PrivilegedEntity parent = new PrivilegedEntity(EntityType.QUEUE, "root"); + PrivilegedEntity child = new PrivilegedEntity(EntityType.QUEUE, "root.batch"); + + AccessControlList acl = mock(AccessControlList.class); + when(acl.isUserAllowed(ugi)).thenReturn(true); + Map acls = new HashMap<>(); + acls.put(AccessType.SUBMIT_APP, acl); + Permission perm = new Permission(parent, acls); + authorizer.setPermission(Collections.singletonList(perm), ugi); + + assertTrue(authorizer.isAllowedByYarnAcl(AccessType.SUBMIT_APP, child, ugi, null)); + } + + @Test + void test11_isAllowedByYarnAcl_whenNoMatchingAcl_returnsFalse() throws Exception { + RangerYarnAuthorizer authorizer = new RangerYarnAuthorizer(); + UserGroupInformation ugi = UserGroupInformation.createRemoteUser("gus"); + PrivilegedEntity entity = new PrivilegedEntity(EntityType.QUEUE, "other"); + + assertFalse(authorizer.isAllowedByYarnAcl(AccessType.SUBMIT_APP, entity, ugi, null)); + } + + @Test + void test12_checkPermission_whenEngineUndeterminedAndYarnAuthOn_fallsBackToAcl() throws Exception { + RangerBasePlugin mockPlugin = newInnerPluginMock(); + RangerAccessResult r = mock(RangerAccessResult.class); + when(r.getIsAccessDetermined()).thenReturn(false); + when(mockPlugin.isAccessAllowed(any(RangerAccessRequest.class), any())).thenReturn(r); + setYarnPluginMock(mockPlugin); + + RangerYarnAuthorizer authorizer = new RangerYarnAuthorizer(); + setYarnAuthEnabled(authorizer, true); + UserGroupInformation ugi = UserGroupInformation.createRemoteUser("fb"); + PrivilegedEntity entity = new PrivilegedEntity(EntityType.QUEUE, "solo"); + AccessRequest request = new AccessRequest(entity, ugi, AccessType.SUBMIT_APP, "10.0.0.1", + "10.0.0.1", null, Collections.emptyList()); + + assertFalse(authorizer.checkPermission(request)); + } + + @Test + void test13_isAllowedByYarnAcl_whenEntityEqualsAclEntity_matches() throws Exception { + RangerYarnAuthorizer authorizer = new RangerYarnAuthorizer(); + UserGroupInformation ugi = UserGroupInformation.createRemoteUser("hal"); + PrivilegedEntity entity = new PrivilegedEntity(EntityType.QUEUE, "same.q"); + + AccessControlList acl = mock(AccessControlList.class); + when(acl.isUserAllowed(ugi)).thenReturn(true); + Map acls = new HashMap<>(); + acls.put(AccessType.SUBMIT_APP, acl); + Permission perm = new Permission(entity, acls); + authorizer.setPermission(Collections.singletonList(perm), ugi); + + assertTrue(authorizer.isAllowedByYarnAcl(AccessType.SUBMIT_APP, entity, ugi, null)); + } + + @Test + void test14_init_whenYarnPluginAlreadyRegistered_configuresFromExistingPlugin() throws Exception { + RangerBasePlugin mockPlugin = newInnerPluginMock(); + RangerPluginConfig cfg = mock(RangerPluginConfig.class); + when(cfg.getBoolean(eq(RangerHadoopConstants.RANGER_ADD_YARN_PERMISSION_PROP), anyBoolean())).thenReturn(false); + when(cfg.get(eq(RangerHadoopConstants.AUDITLOG_YARN_MODULE_ACL_NAME_PROP), anyString())).thenReturn("yarn-acl"); + when(mockPlugin.getConfig()).thenReturn(cfg); + setYarnPluginMock(mockPlugin); + + RangerYarnAuthorizer authorizer = new RangerYarnAuthorizer(); + authorizer.init(new Configuration()); + + verify(cfg).setIsFallbackSupported(false); + } + + @Test + void test15_checkPermission_whenPerfTracingEnabled_invokesPerfTracer() throws Exception { + RangerBasePlugin mockPlugin = newInnerPluginMock(); + RangerAccessResult result = mock(RangerAccessResult.class); + when(result.getIsAllowed()).thenReturn(true); + when(mockPlugin.isAccessAllowed(any(RangerAccessRequest.class), any())).thenReturn(result); + setYarnPluginMock(mockPlugin); + + RangerPerfTracer tracer = mock(RangerPerfTracer.class); + try (MockedStatic perf = mockStatic(RangerPerfTracer.class)) { + perf.when(() -> RangerPerfTracer.isPerfTraceEnabled(any(Logger.class))).thenReturn(true); + perf.when(() -> RangerPerfTracer.getPerfTracer(any(Logger.class), anyString())).thenReturn(tracer); + + RangerYarnAuthorizer authorizer = new RangerYarnAuthorizer(); + setYarnAuthEnabled(authorizer, false); + UserGroupInformation ugi = UserGroupInformation.createRemoteUser("perf1"); + PrivilegedEntity entity = new PrivilegedEntity(EntityType.QUEUE, "root"); + AccessRequest request = new AccessRequest(entity, ugi, AccessType.SUBMIT_APP, null, null, null, + Collections.emptyList()); + + assertTrue(authorizer.checkPermission(request)); + perf.verify(() -> RangerPerfTracer.log(tracer), atLeastOnce()); + } + } + + @Test + void test16_checkPermission_yarnAclFallback_whenPerfEnabled_tracesNativeAclPath() throws Exception { + RangerBasePlugin mockPlugin = newInnerPluginMock(); + RangerAccessResult r = mock(RangerAccessResult.class); + when(r.getIsAccessDetermined()).thenReturn(false); + when(mockPlugin.isAccessAllowed(any(RangerAccessRequest.class), any())).thenReturn(r); + setYarnPluginMock(mockPlugin); + + RangerPerfTracer tracer = mock(RangerPerfTracer.class); + try (MockedStatic perf = mockStatic(RangerPerfTracer.class)) { + perf.when(() -> RangerPerfTracer.isPerfTraceEnabled(any(Logger.class))).thenReturn(true); + perf.when(() -> RangerPerfTracer.getPerfTracer(any(Logger.class), anyString())).thenReturn(tracer); + + RangerYarnAuthorizer authorizer = new RangerYarnAuthorizer(); + setYarnAuthEnabled(authorizer, true); + UserGroupInformation ugi = UserGroupInformation.createRemoteUser("perf2"); + PrivilegedEntity entity = new PrivilegedEntity(EntityType.QUEUE, "solo"); + AccessRequest request = new AccessRequest(entity, ugi, AccessType.SUBMIT_APP, null, null, null, + Collections.emptyList()); + + assertFalse(authorizer.checkPermission(request)); + perf.verify(() -> RangerPerfTracer.log(tracer), atLeastOnce()); + } + } + + @Test + void test17_checkPermission_applicationMaxPriority_mapsAccessTypeToNullWithoutFailure() throws Exception { + RangerBasePlugin mockPlugin = newInnerPluginMock(); + when(mockPlugin.isAccessAllowed(any(RangerAccessRequest.class), any())).thenAnswer(inv -> { + RangerAccessResult res = mock(RangerAccessResult.class); + when(res.getIsAllowed()).thenReturn(true); + return res; + }); + setYarnPluginMock(mockPlugin); + + RangerYarnAuthorizer authorizer = new RangerYarnAuthorizer(); + setYarnAuthEnabled(authorizer, false); + UserGroupInformation ugi = UserGroupInformation.createRemoteUser("prio"); + PrivilegedEntity entity = new PrivilegedEntity(EntityType.QUEUE, "root"); + AccessRequest request = new AccessRequest(entity, ugi, AccessType.APPLICATION_MAX_PRIORITY, "10.0.0.1", + "10.0.0.1", null, Collections.emptyList()); + + assertTrue(authorizer.checkPermission(request)); + } + + @Test + void test18_checkPermission_nullEntity_remoteIpUsesServerWhenAddressMissing() throws Exception { + RangerBasePlugin mockPlugin = newInnerPluginMock(); + when(mockPlugin.isAccessAllowed(any(RangerAccessRequest.class), any())).thenAnswer(inv -> { + RangerAccessResult res = mock(RangerAccessResult.class); + when(res.getIsAllowed()).thenReturn(true); + return res; + }); + setYarnPluginMock(mockPlugin); + + try (MockedStatic server = mockStatic(Server.class)) { + server.when(Server::getRemoteIp).thenReturn(InetAddress.getByName("192.168.1.10")); + + RangerYarnAuthorizer authorizer = new RangerYarnAuthorizer(); + setYarnAuthEnabled(authorizer, false); + UserGroupInformation ugi = UserGroupInformation.createRemoteUser("nent"); + AccessRequest request = new AccessRequest(null, ugi, AccessType.SUBMIT_APP, null, null, null, + Collections.emptyList()); + + assertTrue(authorizer.checkPermission(request)); + } + } + + @Test + void test19_isAllowedByYarnAcl_whenPermissionMapNull_skipsAccessTypeLookup() throws Exception { + RangerYarnAuthorizer authorizer = new RangerYarnAuthorizer(); + UserGroupInformation ugi = UserGroupInformation.createRemoteUser("nilmap"); + PrivilegedEntity entity = new PrivilegedEntity(EntityType.QUEUE, "root.x"); + Permission perm = new Permission(new PrivilegedEntity(EntityType.QUEUE, "root"), null); + authorizer.setPermission(Collections.singletonList(perm), ugi); + + assertFalse(authorizer.isAllowedByYarnAcl(AccessType.SUBMIT_APP, entity, ugi, null)); + } + + @Test + void test20_isSelfOrChildOf_whenParentAlreadyEndsWithDot_stillMatchesHierarchy() throws Exception { + RangerYarnAuthorizer authorizer = new RangerYarnAuthorizer(); + UserGroupInformation ugi = UserGroupInformation.createRemoteUser("dotp"); + PrivilegedEntity parent = new PrivilegedEntity(EntityType.QUEUE, "root."); + PrivilegedEntity child = new PrivilegedEntity(EntityType.QUEUE, "root.batch"); + + AccessControlList acl = mock(AccessControlList.class); + when(acl.isUserAllowed(ugi)).thenReturn(true); + Map acls = new HashMap<>(); + acls.put(AccessType.SUBMIT_APP, acl); + Permission perm = new Permission(parent, acls); + authorizer.setPermission(Collections.singletonList(perm), ugi); + + assertTrue(authorizer.isAllowedByYarnAcl(AccessType.SUBMIT_APP, child, ugi, null)); + } + + @Test + void test21_isSelfOrChildOf_whenQueueHasNoDot_doesNotPrefixMatch() throws Exception { + RangerYarnAuthorizer authorizer = new RangerYarnAuthorizer(); + UserGroupInformation ugi = UserGroupInformation.createRemoteUser("nodot"); + PrivilegedEntity parent = new PrivilegedEntity(EntityType.QUEUE, "root"); + PrivilegedEntity child = new PrivilegedEntity(EntityType.QUEUE, "rootx"); + + AccessControlList acl = mock(AccessControlList.class); + when(acl.isUserAllowed(ugi)).thenReturn(true); + Map acls = new HashMap<>(); + acls.put(AccessType.SUBMIT_APP, acl); + Permission perm = new Permission(parent, acls); + authorizer.setPermission(Collections.singletonList(perm), ugi); + + assertFalse(authorizer.isAllowedByYarnAcl(AccessType.SUBMIT_APP, child, ugi, null)); + } + + @Test + void test22_isSelfOrChildOf_whenParentQueueNameBlank_skipsHierarchyMatch() throws Exception { + RangerYarnAuthorizer authorizer = new RangerYarnAuthorizer(); + UserGroupInformation ugi = UserGroupInformation.createRemoteUser("blankp"); + PrivilegedEntity parent = new PrivilegedEntity(EntityType.QUEUE, ""); + PrivilegedEntity child = new PrivilegedEntity(EntityType.QUEUE, "root.batch"); + + AccessControlList acl = mock(AccessControlList.class); + when(acl.isUserAllowed(ugi)).thenReturn(true); + Map acls = new HashMap<>(); + acls.put(AccessType.SUBMIT_APP, acl); + Permission perm = new Permission(parent, acls); + authorizer.setPermission(Collections.singletonList(perm), ugi); + + assertFalse(authorizer.isAllowedByYarnAcl(AccessType.SUBMIT_APP, child, ugi, null)); + } + + @Test + void test23_isAllowedByYarnAcl_whenAclDeniedUser_returnsFalse() throws Exception { + RangerYarnAuthorizer authorizer = new RangerYarnAuthorizer(); + UserGroupInformation ugi = UserGroupInformation.createRemoteUser("denied"); + PrivilegedEntity parent = new PrivilegedEntity(EntityType.QUEUE, "root"); + PrivilegedEntity child = new PrivilegedEntity(EntityType.QUEUE, "root.batch"); + + AccessControlList acl = mock(AccessControlList.class); + when(acl.isUserAllowed(ugi)).thenReturn(false); + Map acls = new HashMap<>(); + acls.put(AccessType.SUBMIT_APP, acl); + Permission perm = new Permission(parent, acls); + authorizer.setPermission(Collections.singletonList(perm), ugi); + + assertFalse(authorizer.isAllowedByYarnAcl(AccessType.SUBMIT_APP, child, ugi, null)); + } + + @Test + void test24_auditHandler_processResult_coversFlagTransitions() throws Exception { + Class clazz = Class.forName( + "org.apache.ranger.authorization.yarn.authorizer.RangerYarnAuthorizer$RangerYarnAuditHandler"); + Constructor ctor = clazz.getDeclaredConstructor(String.class); + ctor.setAccessible(true); + Object handler = ctor.newInstance("yarn-mod"); + + Method process = clazz.getDeclaredMethod("processResult", RangerAccessResult.class); + process.setAccessible(true); + RangerAccessResult notAudited = mock(RangerAccessResult.class); + when(notAudited.getIsAudited()).thenReturn(false); + process.invoke(handler, notAudited); + + RangerAccessResult audited = mock(RangerAccessResult.class); + when(audited.getIsAudited()).thenReturn(true); + process.invoke(handler, audited); + RangerAccessResult second = mock(RangerAccessResult.class); + process.invoke(handler, second); + } + + @Test + void test25_auditHandler_logYarnAclEvent_setsAccessResultForGrantAndDeny() throws Exception { + Class clazz = Class.forName( + "org.apache.ranger.authorization.yarn.authorizer.RangerYarnAuthorizer$RangerYarnAuditHandler"); + Constructor ctor = clazz.getDeclaredConstructor(String.class); + ctor.setAccessible(true); + Object handler = ctor.newInstance("yarn-mod"); + + AuthzAuditEvent event = mock(AuthzAuditEvent.class); + Field auditField = clazz.getDeclaredField("auditEvent"); + auditField.setAccessible(true); + auditField.set(handler, event); + + Method logYarnAcl = clazz.getDeclaredMethod("logYarnAclEvent", boolean.class); + logYarnAcl.setAccessible(true); + logYarnAcl.invoke(handler, true); + logYarnAcl.invoke(handler, false); + + verify(event).setAccessResult((short) 1); + verify(event).setAccessResult((short) 0); + verify(event, times(2)).setAclEnforcer("yarn-mod"); + verify(event, times(2)).setPolicyId(-1); + } + + @Test + void test26_checkPermission_whenServerReturnsNullRemoteIp_clientIpStaysNull() throws Exception { + RangerBasePlugin mockPlugin = newInnerPluginMock(); + when(mockPlugin.isAccessAllowed(any(RangerAccessRequest.class), any())).thenAnswer(inv -> { + RangerAccessResult res = mock(RangerAccessResult.class); + when(res.getIsAllowed()).thenReturn(true); + return res; + }); + setYarnPluginMock(mockPlugin); + + try (MockedStatic server = mockStatic(Server.class)) { + server.when(Server::getRemoteIp).thenReturn(null); + + RangerYarnAuthorizer authorizer = new RangerYarnAuthorizer(); + setYarnAuthEnabled(authorizer, false); + UserGroupInformation ugi = UserGroupInformation.createRemoteUser("noip"); + AccessRequest request = new AccessRequest(new PrivilegedEntity(EntityType.QUEUE, "q"), ugi, + AccessType.SUBMIT_APP, null, null, null, Collections.emptyList()); + + assertTrue(authorizer.checkPermission(request)); + } + } + + @Test + void test27_checkPermission_whenNoPluginAndYarnAuthDisabled_usesNegatedEngineDecision() throws Exception { + RangerYarnAuthorizer authorizer = new RangerYarnAuthorizer(); + setYarnAuthEnabled(authorizer, false); + UserGroupInformation ugi = UserGroupInformation.createRemoteUser("noplug"); + PrivilegedEntity entity = new PrivilegedEntity(EntityType.QUEUE, "root"); + AccessRequest request = new AccessRequest(entity, ugi, AccessType.SUBMIT_APP, "10.0.0.1", "10.0.0.1", null, + Collections.emptyList()); + + assertFalse(authorizer.checkPermission(request)); + } + + @Test + void test28_checkPermission_whenYarnAuthOnAndEngineDetermined_skipsYarnNativeAcl() throws Exception { + RangerBasePlugin mockPlugin = newInnerPluginMock(); + RangerAccessResult r = mock(RangerAccessResult.class); + when(r.getIsAccessDetermined()).thenReturn(true); + when(r.getIsAllowed()).thenReturn(true); + when(mockPlugin.isAccessAllowed(any(RangerAccessRequest.class), any())).thenReturn(r); + setYarnPluginMock(mockPlugin); + + RangerYarnAuthorizer authorizer = new RangerYarnAuthorizer(); + setYarnAuthEnabled(authorizer, true); + UserGroupInformation ugi = UserGroupInformation.createRemoteUser("determined"); + PrivilegedEntity entity = new PrivilegedEntity(EntityType.QUEUE, "root"); + AccessRequest request = new AccessRequest(entity, ugi, AccessType.SUBMIT_APP, "10.0.0.1", "10.0.0.1", null, + Collections.emptyList()); + + assertTrue(authorizer.checkPermission(request)); + } +} diff --git a/plugin-yarn/src/test/java/org/apache/ranger/services/yarn/TestRangerServiceYarn.java b/plugin-yarn/src/test/java/org/apache/ranger/services/yarn/TestRangerServiceYarn.java new file mode 100644 index 00000000000..0e722fcd5bd --- /dev/null +++ b/plugin-yarn/src/test/java/org/apache/ranger/services/yarn/TestRangerServiceYarn.java @@ -0,0 +1,299 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ranger.services.yarn; + +import org.apache.ranger.authorization.yarn.authorizer.RangerYarnAuthorizer; +import org.apache.ranger.plugin.model.RangerPolicy; +import org.apache.ranger.plugin.model.RangerService; +import org.apache.ranger.plugin.model.RangerServiceDef; +import org.apache.ranger.plugin.model.RangerServiceDef.RangerAccessTypeDef; +import org.apache.ranger.plugin.model.RangerServiceDef.RangerResourceDef; +import org.apache.ranger.plugin.resourcematcher.RangerDefaultResourceMatcher; +import org.apache.ranger.plugin.service.RangerBaseService; +import org.apache.ranger.plugin.service.ResourceLookupContext; +import org.apache.ranger.services.yarn.client.YarnResourceMgr; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.MockedStatic; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mockStatic; + +/** + * @generated by Cursor + * @description : Unit Test cases for RangerServiceYarn + */ +@ExtendWith(MockitoExtension.class) +@TestMethodOrder(MethodOrderer.MethodName.class) +class TestRangerServiceYarn { + @Test + void test01_validateConfig_whenConfigsNull_returnsEmptyMap() { + RangerServiceYarn svc = new RangerServiceYarn(); + + Map response = svc.validateConfig(); + + assertTrue(response.isEmpty()); + } + + @Test + void test02_validateConfig_delegatesToYarnResourceMgr() { + Map cfg = new HashMap<>(); + cfg.put("yarn.url", "http://rm:8088"); + cfg.put("username", "u"); + cfg.put("password", "p"); + Map expected = new HashMap<>(); + expected.put("connectivityStatus", Boolean.TRUE); + + try (MockedStatic mocked = mockStatic(YarnResourceMgr.class)) { + mocked.when(() -> YarnResourceMgr.validateConfig(eq("sn"), eq(cfg))).thenReturn(expected); + + RangerServiceYarn svc = new RangerServiceYarn(); + svc.setServiceName("sn"); + svc.setConfigs(cfg); + + assertSame(expected, svc.validateConfig()); + } + } + + @Test + void test03_lookupResource_whenContextNull_returnsEmpty() { + assertTrue(new RangerServiceYarn().lookupResource(null).isEmpty()); + } + + @Test + void test04_lookupResource_delegatesToYarnResourceMgr() { + Map cfg = new HashMap<>(); + cfg.put("yarn.url", "http://rm:8088"); + ResourceLookupContext ctx = new ResourceLookupContext(); + List expected = Collections.singletonList("default"); + + try (MockedStatic mocked = mockStatic(YarnResourceMgr.class)) { + mocked.when(() -> YarnResourceMgr.getYarnResources(eq("sn"), eq(cfg), eq(ctx))).thenReturn(expected); + + RangerServiceYarn svc = new RangerServiceYarn(); + svc.setServiceName("sn"); + svc.setConfigs(cfg); + + assertEquals(expected, svc.lookupResource(ctx)); + } + } + + @Test + void test05_getDefaultRangerPolicies_whenHierarchyDisabled_returnsEmptyList() throws Exception { + RangerServiceDef def = new RangerServiceDef(); + def.setName("yarn"); + def.setResources(new ArrayList<>()); + Map options = new HashMap<>(); + options.put("create.default.policy.per.hierarchy", "false"); + def.setOptions(options); + + RangerService rs = new RangerService(); + rs.setName("yarn-svc"); + rs.setConfigs(new HashMap<>()); + + RangerServiceYarn svc = new RangerServiceYarn(); + svc.init(def, rs); + + List policies = svc.getDefaultRangerPolicies(); + + assertTrue(policies.isEmpty()); + } + + @Test + void test06_init_setsServiceFieldsFromRangerService() { + RangerServiceDef def = new RangerServiceDef(); + def.setName("yarn"); + RangerService rs = new RangerService(); + rs.setName("svc1"); + rs.setType("yarn"); + Map cfg = new HashMap<>(); + cfg.put("yarn.url", "http://rm:8088"); + rs.setConfigs(cfg); + + RangerServiceYarn svc = new RangerServiceYarn(); + svc.init(def, rs); + + assertEquals("svc1", svc.getServiceName()); + assertEquals("yarn", svc.getServiceType()); + assertEquals(cfg, svc.getConfigs()); + assertSame(def, svc.getServiceDef()); + } + + @Test + void test07_validateConfig_whenYarnResourceMgrThrows_propagates() { + Map cfg = new HashMap<>(); + cfg.put("yarn.url", "http://rm:8088"); + + try (MockedStatic mocked = mockStatic(YarnResourceMgr.class)) { + mocked.when(() -> YarnResourceMgr.validateConfig(eq("sn"), eq(cfg))) + .thenThrow(new IllegalStateException("bad")); + + RangerServiceYarn svc = new RangerServiceYarn(); + svc.setServiceName("sn"); + svc.setConfigs(cfg); + + assertThrows(IllegalStateException.class, svc::validateConfig); + } + } + + @Test + void test08_lookupResource_whenYarnResourceMgrThrows_propagates() { + Map cfg = new HashMap<>(); + cfg.put("yarn.url", "http://rm:8088"); + ResourceLookupContext ctx = new ResourceLookupContext(); + + try (MockedStatic mocked = mockStatic(YarnResourceMgr.class)) { + mocked.when(() -> YarnResourceMgr.getYarnResources(eq("sn"), eq(cfg), eq(ctx))) + .thenThrow(new IllegalStateException("lookup")); + + RangerServiceYarn svc = new RangerServiceYarn(); + svc.setServiceName("sn"); + svc.setConfigs(cfg); + + assertThrows(IllegalStateException.class, () -> svc.lookupResource(ctx)); + } + } + + @Test + void test09_getDefaultRangerPolicies_withHierarchy_addsSubmitAppForLookupUser() throws Exception { + RangerServiceDef def = new RangerServiceDef(); + def.setName("yarn"); + Map options = new HashMap<>(); + options.put("create.default.policy.per.hierarchy", "true"); + def.setOptions(options); + + RangerAccessTypeDef accessType = new RangerAccessTypeDef(); + accessType.setName(RangerServiceYarn.ACCESS_TYPE_SUBMIT_APP); + def.setAccessTypes(Collections.singletonList(accessType)); + + RangerResourceDef queue = new RangerResourceDef(); + queue.setName(RangerYarnAuthorizer.KEY_RESOURCE_QUEUE); + queue.setMandatory(true); + queue.setMatcher(RangerDefaultResourceMatcher.class.getName()); + def.setResources(Collections.singletonList(queue)); + + RangerService rs = new RangerService(); + rs.setName("yarn-defpol"); + rs.setConfigs(new HashMap<>()); + + RangerServiceYarn svc = new RangerServiceYarn(); + Field lookUpField = RangerBaseService.class.getDeclaredField("lookUpUser"); + lookUpField.setAccessible(true); + lookUpField.set(svc, "lookup-runner"); + svc.init(def, rs); + + List policies = svc.getDefaultRangerPolicies(); + + assertFalse(policies.isEmpty()); + } + + @Test + void test10_getDefaultRangerPolicies_customPolicyWithoutAllInName_doesNotAugmentResources() throws Exception { + RangerServiceDef def = new RangerServiceDef(); + def.setName("yarn"); + def.setResources(new ArrayList<>()); + Map options = new HashMap<>(); + options.put("create.default.policy.per.hierarchy", "false"); + def.setOptions(options); + + RangerService rs = new RangerService(); + rs.setName("yarn-custom"); + Map svcCfg = new HashMap<>(); + svcCfg.put("setup.additional.default.policies", "true"); + svcCfg.put("default-policy.1.name", "restricted-queues"); + svcCfg.put("default-policy.1.resource.queue", "root"); + rs.setConfigs(svcCfg); + + RangerServiceYarn svc = new RangerServiceYarn(); + svc.init(def, rs); + + List policies = svc.getDefaultRangerPolicies(); + + assertEquals(1, policies.size()); + assertFalse(policies.get(0).getName().contains("all")); + } + + @Test + void test11_getDefaultRangerPolicies_whenLookupUserBlank_skipsUserGrant() throws Exception { + RangerServiceDef def = new RangerServiceDef(); + def.setName("yarn"); + Map options = new HashMap<>(); + options.put("create.default.policy.per.hierarchy", "true"); + def.setOptions(options); + + RangerAccessTypeDef accessType = new RangerAccessTypeDef(); + accessType.setName(RangerServiceYarn.ACCESS_TYPE_SUBMIT_APP); + def.setAccessTypes(Collections.singletonList(accessType)); + + RangerResourceDef queue = new RangerResourceDef(); + queue.setName(RangerYarnAuthorizer.KEY_RESOURCE_QUEUE); + queue.setMandatory(true); + queue.setMatcher(RangerDefaultResourceMatcher.class.getName()); + def.setResources(Collections.singletonList(queue)); + + RangerService rs = new RangerService(); + rs.setName("yarn-nolookup"); + rs.setConfigs(new HashMap<>()); + + RangerServiceYarn svc = new RangerServiceYarn(); + Field lookUpField = RangerBaseService.class.getDeclaredField("lookUpUser"); + lookUpField.setAccessible(true); + lookUpField.set(svc, ""); + svc.init(def, rs); + + assertFalse(svc.getDefaultRangerPolicies().isEmpty()); + } + + @Test + void test12_getDefaultRangerPolicies_policyNameContainsAllButNoQueueResource_logsWarning() throws Exception { + RangerServiceDef def = new RangerServiceDef(); + def.setName("yarn"); + def.setResources(new ArrayList<>()); + Map options = new HashMap<>(); + options.put("create.default.policy.per.hierarchy", "false"); + def.setOptions(options); + + RangerService rs = new RangerService(); + rs.setName("yarn-warn-q"); + Map svcCfg = new HashMap<>(); + svcCfg.put("setup.additional.default.policies", "true"); + svcCfg.put("default-policy.1.name", "custom-all-queues"); + svcCfg.put("default-policy.1.resource.cluster", "c1"); + rs.setConfigs(svcCfg); + + RangerServiceYarn svc = new RangerServiceYarn(); + svc.init(def, rs); + + assertFalse(svc.getDefaultRangerPolicies().isEmpty()); + } +} diff --git a/plugin-yarn/src/test/java/org/apache/ranger/services/yarn/client/TestYarnClient.java b/plugin-yarn/src/test/java/org/apache/ranger/services/yarn/client/TestYarnClient.java new file mode 100644 index 00000000000..356f3ffab02 --- /dev/null +++ b/plugin-yarn/src/test/java/org/apache/ranger/services/yarn/client/TestYarnClient.java @@ -0,0 +1,661 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ranger.services.yarn.client; + +import org.apache.ranger.plugin.client.HadoopException; +import org.apache.ranger.plugin.util.RangerJersey2ClientBuilder; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import javax.security.auth.Subject; +import javax.ws.rs.ProcessingException; +import javax.ws.rs.client.Client; +import javax.ws.rs.client.Invocation; +import javax.ws.rs.client.WebTarget; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import java.security.PrivilegedAction; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.ArgumentMatchers.same; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; + +/** + * @generated by Cursor + * @description : Unit Test cases for YarnClient + */ +@ExtendWith(MockitoExtension.class) +@TestMethodOrder(MethodOrderer.MethodName.class) +class TestYarnClient { + private Map sampleConfigs() { + Map cfg = new HashMap<>(); + cfg.put("yarn.url", "http://rm:8088"); + cfg.put("username", "u"); + cfg.put("password", "p"); + return cfg; + } + + private static void mockSubjectDoAsRunsAction(MockedStatic subjectStatic) { + subjectStatic.when(() -> Subject.doAs(any(Subject.class), any(PrivilegedAction.class))) + .thenAnswer(invocation -> { + PrivilegedAction action = invocation.getArgument(1); + return action.run(); + }); + } + + private static String fairSchedulerJsonQueues(String... queues) { + StringBuilder queuesJson = new StringBuilder(); + for (int i = 0; i < queues.length; i++) { + if (i > 0) { + queuesJson.append(","); + } + queuesJson.append("{\"queueName\":\"").append(queues[i]).append("\"}"); + } + return "{\"scheduler\":{\"schedulerInfo\":{\"type\":\"fairScheduler\",\"rootQueue\":" + + "{\"queueName\":\"root\",\"childQueues\":{\"queue\":[" + queuesJson + "]}}}}}"; + } + + @Test + void test01_getYarnClient_whenConfigsEmptyOrNull_throwsHadoopException() { + assertThrows(HadoopException.class, () -> YarnClient.getYarnClient("svc", Collections.emptyMap())); + assertThrows(HadoopException.class, () -> YarnClient.getYarnClient("svc", null)); + } + + @Test + void test02_getYarnClient_whenConfigsValid_returnsInstance() { + Map cfg = sampleConfigs(); + + YarnClient client = YarnClient.getYarnClient("svc", cfg); + + assertNotNull(client); + } + + @Test + void test03_connectionTest_whenQueuesFound_marksConnectivitySuccess() { + Map cfg = sampleConfigs(); + YarnClient mockClient = mock(YarnClient.class); + + try (MockedStatic mocked = mockStatic(YarnClient.class, Mockito.CALLS_REAL_METHODS)) { + mocked.when(() -> YarnClient.getYarnClient(eq("svc"), eq(cfg))).thenReturn(mockClient); + mocked.when(() -> YarnClient.getYarnResource(same(mockClient), eq(""), isNull())) + .thenReturn(Collections.singletonList("root")); + + Map response = YarnClient.connectionTest("svc", cfg); + + assertTrue(Boolean.TRUE.equals(response.get("connectivityStatus"))); + } + } + + @Test + void test04_connectionTest_whenNoQueues_marksConnectivityFailure() { + Map cfg = sampleConfigs(); + YarnClient mockClient = mock(YarnClient.class); + + try (MockedStatic mocked = mockStatic(YarnClient.class, Mockito.CALLS_REAL_METHODS)) { + mocked.when(() -> YarnClient.getYarnClient(eq("svc"), eq(cfg))).thenReturn(mockClient); + mocked.when(() -> YarnClient.getYarnResource(same(mockClient), eq(""), isNull())) + .thenReturn(Collections.emptyList()); + + Map response = YarnClient.connectionTest("svc", cfg); + + assertFalse(Boolean.TRUE.equals(response.get("connectivityStatus"))); + } + } + + @Test + void test05_getYarnResource_whenClientNull_throwsHadoopException() { + assertThrows(HadoopException.class, () -> YarnClient.getYarnResource(null, "q", null)); + } + + @Test + void test06_getYarnResource_whenQueueNameNull_returnsEmptyList() { + Map cfg = sampleConfigs(); + YarnClient client = YarnClient.getYarnClient("svc", cfg); + + List queues = YarnClient.getYarnResource(client, null, null); + + assertNotNull(queues); + assertTrue(queues.isEmpty()); + } + + @Test + void test07_timedTask_invokesCallable() throws Exception { + String value = YarnClient.timedTask(() -> "ok", 1, TimeUnit.SECONDS); + + assertEquals("ok", value); + } + + @Test + void test08_getYarnResource_whenGetQueueListThrows_wrapsAsHadoopException() { + Map cfg = sampleConfigs(); + YarnClient client = new ExplodingGetQueueListYarnClient("svc", cfg); + + assertThrows(HadoopException.class, () -> YarnClient.getYarnResource(client, "q", null)); + } + + @Test + void test09_getQueueList_whenYarnUrlBlank_returnsNull() { + Map cfg = sampleConfigs(); + cfg.put("yarn.url", " "); + YarnClient client = new FixedSubjectYarnClient("svc", cfg); + + try (MockedStatic subjectStatic = mockStatic(Subject.class)) { + mockSubjectDoAsRunsAction(subjectStatic); + + List out = client.getQueueList("root", null); + + assertNull(out); + } + } + + @Test + void test10_getQueueList_whenResponse200_returnsMatchingQueues() { + Map cfg = sampleConfigs(); + YarnClient client = new FixedSubjectYarnClient("svc", cfg); + String json = fairSchedulerJsonQueues("root", "default", "prodline"); + + Response response = mock(Response.class); + when(response.getStatus()).thenReturn(200); + when(response.readEntity(String.class)).thenReturn(json); + + Client jaxrsClient = mock(Client.class); + WebTarget target = mock(WebTarget.class); + Invocation.Builder builder = mock(Invocation.Builder.class); + when(jaxrsClient.target(anyString())).thenReturn(target); + when(target.request(MediaType.APPLICATION_JSON)).thenReturn(builder); + when(builder.get()).thenReturn(response); + + try (MockedStatic subjectStatic = mockStatic(Subject.class); + MockedStatic jer = mockStatic(RangerJersey2ClientBuilder.class)) { + mockSubjectDoAsRunsAction(subjectStatic); + jer.when(RangerJersey2ClientBuilder::newClient).thenReturn(jaxrsClient); + + List out = client.getQueueList("pro", null); + + assertNotNull(out); + assertTrue(out.contains("prodline")); + assertFalse(out.contains("root")); + } + } + + @Test + void test11_getQueueList_skipsAlreadyListedQueues() { + Map cfg = sampleConfigs(); + YarnClient client = new FixedSubjectYarnClient("svc", cfg); + String json = fairSchedulerJsonQueues("root", "default"); + + Response response = mock(Response.class); + when(response.getStatus()).thenReturn(200); + when(response.readEntity(String.class)).thenReturn(json); + + Client jaxrsClient = mock(Client.class); + WebTarget target = mock(WebTarget.class); + Invocation.Builder builder = mock(Invocation.Builder.class); + when(jaxrsClient.target(anyString())).thenReturn(target); + when(target.request(MediaType.APPLICATION_JSON)).thenReturn(builder); + when(builder.get()).thenReturn(response); + + try (MockedStatic subjectStatic = mockStatic(Subject.class); + MockedStatic jer = mockStatic(RangerJersey2ClientBuilder.class)) { + mockSubjectDoAsRunsAction(subjectStatic); + jer.when(RangerJersey2ClientBuilder::newClient).thenReturn(jaxrsClient); + + List out = client.getQueueList("", Collections.singletonList("root")); + + assertNotNull(out); + assertTrue(out.contains("default")); + assertFalse(out.contains("root")); + } + } + + @Test + void test12_getQueueList_whenJsonInvalid_throwsHadoopException() { + Map cfg = sampleConfigs(); + YarnClient client = new FixedSubjectYarnClient("svc", cfg); + + Response response = mock(Response.class); + when(response.getStatus()).thenReturn(200); + when(response.readEntity(String.class)).thenReturn("not-json{"); + + Client jaxrsClient = mock(Client.class); + WebTarget target = mock(WebTarget.class); + Invocation.Builder builder = mock(Invocation.Builder.class); + when(jaxrsClient.target(anyString())).thenReturn(target); + when(target.request(MediaType.APPLICATION_JSON)).thenReturn(builder); + when(builder.get()).thenReturn(response); + + try (MockedStatic subjectStatic = mockStatic(Subject.class); + MockedStatic jer = mockStatic(RangerJersey2ClientBuilder.class)) { + mockSubjectDoAsRunsAction(subjectStatic); + jer.when(RangerJersey2ClientBuilder::newClient).thenReturn(jaxrsClient); + + assertThrows(HadoopException.class, () -> client.getQueueList("x", null)); + } + } + + @Test + void test13_getQueueList_whenHttpGetThrows_continuesToFailure() { + Map cfg = new HashMap<>(); + cfg.put("yarn.url", "http://bad:8088"); + cfg.put("username", "u"); + cfg.put("password", "p"); + YarnClient client = new FixedSubjectYarnClient("svc", cfg); + + Client jaxrsClient = mock(Client.class); + WebTarget target = mock(WebTarget.class); + Invocation.Builder builder = mock(Invocation.Builder.class); + when(jaxrsClient.target(anyString())).thenReturn(target); + when(target.request(MediaType.APPLICATION_JSON)).thenReturn(builder); + when(builder.get()).thenThrow(new ProcessingException("network")); + + try (MockedStatic subjectStatic = mockStatic(Subject.class); + MockedStatic jer = mockStatic(RangerJersey2ClientBuilder.class)) { + mockSubjectDoAsRunsAction(subjectStatic); + jer.when(RangerJersey2ClientBuilder::newClient).thenReturn(jaxrsClient); + + assertThrows(HadoopException.class, () -> client.getQueueList("q", null)); + } + } + + @Test + void test14_getQueueList_whenStatusNot200_fallsThroughToFailure() { + Map cfg = sampleConfigs(); + YarnClient client = new FixedSubjectYarnClient("svc", cfg); + + Response response = mock(Response.class); + when(response.getStatus()).thenReturn(404); + when(response.readEntity(String.class)).thenReturn("err"); + + Client jaxrsClient = mock(Client.class); + WebTarget target = mock(WebTarget.class); + Invocation.Builder builder = mock(Invocation.Builder.class); + when(jaxrsClient.target(anyString())).thenReturn(target); + when(target.request(MediaType.APPLICATION_JSON)).thenReturn(builder); + when(builder.get()).thenReturn(response); + + try (MockedStatic subjectStatic = mockStatic(Subject.class); + MockedStatic jer = mockStatic(RangerJersey2ClientBuilder.class)) { + mockSubjectDoAsRunsAction(subjectStatic); + jer.when(RangerJersey2ClientBuilder::newClient).thenReturn(jaxrsClient); + + assertThrows(HadoopException.class, () -> client.getQueueList("q", null)); + } + } + + @Test + void test15_getQueueList_whenSecondUrlSucceeds_returnsQueues() { + Map cfg = new HashMap<>(); + cfg.put("yarn.url", "http://skip:8088,http://ok:8088"); + cfg.put("username", "u"); + cfg.put("password", "p"); + YarnClient client = new FixedSubjectYarnClient("svc", cfg); + String json = fairSchedulerJsonQueues("alpha"); + + Response bad = mock(Response.class); + when(bad.getStatus()).thenReturn(500); + + Response good = mock(Response.class); + when(good.getStatus()).thenReturn(200); + when(good.readEntity(String.class)).thenReturn(json); + + Client jaxrsClient = mock(Client.class); + WebTarget target = mock(WebTarget.class); + Invocation.Builder builder = mock(Invocation.Builder.class); + when(jaxrsClient.target(anyString())).thenReturn(target); + when(target.request(MediaType.APPLICATION_JSON)).thenReturn(builder); + when(builder.get()).thenReturn(bad, good); + + try (MockedStatic subjectStatic = mockStatic(Subject.class); + MockedStatic jer = mockStatic(RangerJersey2ClientBuilder.class)) { + mockSubjectDoAsRunsAction(subjectStatic); + jer.when(RangerJersey2ClientBuilder::newClient).thenReturn(jaxrsClient); + + List out = client.getQueueList("al", null); + + assertNotNull(out); + assertTrue(out.contains("alpha")); + } + } + + @Test + void test16_getQueueList_whenTimedTaskThrows_wrapsAsHadoopException() { + Map cfg = sampleConfigs(); + YarnClient client = new FixedSubjectYarnClient("svc", cfg); + + try (MockedStatic yc = mockStatic(YarnClient.class, Mockito.CALLS_REAL_METHODS)) { + yc.when(() -> YarnClient.timedTask(any(), any(Long.class), any(TimeUnit.class))) + .thenThrow(new RuntimeException("clock")); + + assertThrows(HadoopException.class, () -> client.getQueueList("q", null)); + } + } + + @Test + void test17_getQueueList_whenReadEntityThrowsHadoopException_propagates() { + Map cfg = sampleConfigs(); + YarnClient client = new FixedSubjectYarnClient("svc", cfg); + + Response response = mock(Response.class); + when(response.getStatus()).thenReturn(200); + when(response.readEntity(String.class)).thenThrow(new HadoopException("rest")); + + Client jaxrsClient = mock(Client.class); + WebTarget target = mock(WebTarget.class); + Invocation.Builder builder = mock(Invocation.Builder.class); + when(jaxrsClient.target(anyString())).thenReturn(target); + when(target.request(MediaType.APPLICATION_JSON)).thenReturn(builder); + when(builder.get()).thenReturn(response); + + try (MockedStatic subjectStatic = mockStatic(Subject.class); + MockedStatic jer = mockStatic(RangerJersey2ClientBuilder.class)) { + mockSubjectDoAsRunsAction(subjectStatic); + jer.when(RangerJersey2ClientBuilder::newClient).thenReturn(jaxrsClient); + + assertThrows(HadoopException.class, () -> client.getQueueList("q", null)); + } + } + + @Test + void test18_getQueueList_skipsBlankUrlSegmentsBetweenSeparators() { + Map cfg = new HashMap<>(); + cfg.put("yarn.url", "http://a:8088,,;http://b:8088"); + cfg.put("username", "u"); + cfg.put("password", "p"); + YarnClient client = new FixedSubjectYarnClient("svc", cfg); + String json = fairSchedulerJsonQueues("z1"); + + Response response = mock(Response.class); + when(response.getStatus()).thenReturn(200); + when(response.readEntity(String.class)).thenReturn(json); + + Client jaxrsClient = mock(Client.class); + WebTarget target = mock(WebTarget.class); + Invocation.Builder builder = mock(Invocation.Builder.class); + when(jaxrsClient.target(anyString())).thenReturn(target); + when(target.request(MediaType.APPLICATION_JSON)).thenReturn(builder); + when(builder.get()).thenReturn(response); + + try (MockedStatic subjectStatic = mockStatic(Subject.class); + MockedStatic jer = mockStatic(RangerJersey2ClientBuilder.class)) { + mockSubjectDoAsRunsAction(subjectStatic); + jer.when(RangerJersey2ClientBuilder::newClient).thenReturn(jaxrsClient); + + List out = client.getQueueList("z", null); + + assertNotNull(out); + assertTrue(out.contains("z1")); + } + } + + @Test + void test19_connectionTest_whenGetYarnResourceReturnsNull_marksFailure() { + Map cfg = sampleConfigs(); + + try (MockedStatic mocked = mockStatic(YarnClient.class, Mockito.CALLS_REAL_METHODS)) { + YarnClient mockClient = mock(YarnClient.class); + mocked.when(() -> YarnClient.getYarnClient(eq("svc"), eq(cfg))).thenReturn(mockClient); + mocked.when(() -> YarnClient.getYarnResource(same(mockClient), eq(""), isNull())).thenReturn(null); + + Map response = YarnClient.connectionTest("svc", cfg); + + assertFalse(Boolean.TRUE.equals(response.get("connectivityStatus"))); + } + } + + @Test + void test20_constructor_whenOptionalFieldsBlank_logsWarnings() { + Map cfg = new HashMap<>(); + cfg.put("yarn.url", ""); + cfg.put("username", ""); + cfg.put("password", ""); + + assertDoesNotThrow(() -> new YarnClient("svc", cfg)); + } + + @Test + void test21_getQueueList_whenSubjectMissing_returnsNullWithoutHttp() { + Map cfg = sampleConfigs(); + YarnClient client = new NullSubjectYarnClient("svc", cfg); + + assertNull(client.getQueueList("any", null)); + } + + @Test + void test22_getQueueList_whenSchedulerJsonNull_returnsEmptyLeafList() { + Map cfg = sampleConfigs(); + YarnClient client = new FixedSubjectYarnClient("svc", cfg); + + Response response = mock(Response.class); + when(response.getStatus()).thenReturn(200); + when(response.readEntity(String.class)).thenReturn("null"); + + Client jaxrsClient = mock(Client.class); + WebTarget target = mock(WebTarget.class); + Invocation.Builder builder = mock(Invocation.Builder.class); + when(jaxrsClient.target(anyString())).thenReturn(target); + when(target.request(MediaType.APPLICATION_JSON)).thenReturn(builder); + when(builder.get()).thenReturn(response); + + try (MockedStatic subjectStatic = mockStatic(Subject.class); + MockedStatic jer = mockStatic(RangerJersey2ClientBuilder.class)) { + mockSubjectDoAsRunsAction(subjectStatic); + jer.when(RangerJersey2ClientBuilder::newClient).thenReturn(jaxrsClient); + + List out = client.getQueueList("pre", null); + + assertNotNull(out); + assertTrue(out.isEmpty()); + } + } + + @Test + void test23_getQueueList_whenNoQueuesMatchPrefix_returnsEmptyList() { + Map cfg = sampleConfigs(); + YarnClient client = new FixedSubjectYarnClient("svc", cfg); + String json = fairSchedulerJsonQueues("alpha", "beta"); + + Response response = mock(Response.class); + when(response.getStatus()).thenReturn(200); + when(response.readEntity(String.class)).thenReturn(json); + + Client jaxrsClient = mock(Client.class); + WebTarget target = mock(WebTarget.class); + Invocation.Builder builder = mock(Invocation.Builder.class); + when(jaxrsClient.target(anyString())).thenReturn(target); + when(target.request(MediaType.APPLICATION_JSON)).thenReturn(builder); + when(builder.get()).thenReturn(response); + + try (MockedStatic subjectStatic = mockStatic(Subject.class); + MockedStatic jer = mockStatic(RangerJersey2ClientBuilder.class)) { + mockSubjectDoAsRunsAction(subjectStatic); + jer.when(RangerJersey2ClientBuilder::newClient).thenReturn(jaxrsClient); + + List out = client.getQueueList("zzz", null); + + assertNotNull(out); + assertTrue(out.isEmpty()); + } + } + + @Test + void test24_getQueueList_whenResponseGetStatusThrows_nonHadoopExceptionHandledPerUrl() { + Map cfg = sampleConfigs(); + YarnClient client = new FixedSubjectYarnClient("svc", cfg); + + Response response = mock(Response.class); + when(response.getStatus()).thenThrow(new RuntimeException("bad-status")); + + Client jaxrsClient = mock(Client.class); + WebTarget target = mock(WebTarget.class); + Invocation.Builder builder = mock(Invocation.Builder.class); + when(jaxrsClient.target(anyString())).thenReturn(target); + when(target.request(MediaType.APPLICATION_JSON)).thenReturn(builder); + when(builder.get()).thenReturn(response); + + try (MockedStatic subjectStatic = mockStatic(Subject.class); + MockedStatic jer = mockStatic(RangerJersey2ClientBuilder.class)) { + mockSubjectDoAsRunsAction(subjectStatic); + jer.when(RangerJersey2ClientBuilder::newClient).thenReturn(jaxrsClient); + + assertThrows(HadoopException.class, () -> client.getQueueList("q", null)); + } + } + + @Test + void test25_getQueueResponse_whenHttpReturnsNullResponse_retriesUntilFailure() { + Map cfg = sampleConfigs(); + YarnClient client = new FixedSubjectYarnClient("svc", cfg); + + Client jaxrsClient = mock(Client.class); + WebTarget target = mock(WebTarget.class); + Invocation.Builder builder = mock(Invocation.Builder.class); + when(jaxrsClient.target(anyString())).thenReturn(target); + when(target.request(MediaType.APPLICATION_JSON)).thenReturn(builder); + when(builder.get()).thenReturn(null); + + try (MockedStatic subjectStatic = mockStatic(Subject.class); + MockedStatic jer = mockStatic(RangerJersey2ClientBuilder.class)) { + mockSubjectDoAsRunsAction(subjectStatic); + jer.when(RangerJersey2ClientBuilder::newClient).thenReturn(jaxrsClient); + + assertThrows(HadoopException.class, () -> client.getQueueList("q", null)); + } + } + + @Test + void test26_getQueueList_whenYarnUrlUnset_returnsNullFromPrivilegedAction() { + Map cfg = new HashMap<>(); + cfg.put("username", "u"); + cfg.put("password", "p"); + YarnClient client = new FixedSubjectYarnClient("svc", cfg); + + try (MockedStatic subjectStatic = mockStatic(Subject.class)) { + mockSubjectDoAsRunsAction(subjectStatic); + assertNull(client.getQueueList("q", null)); + } + } + + @Test + void test27_getQueueList_whenNon200ResponseCloseThrowsHadoopException_propagates() { + Map cfg = sampleConfigs(); + YarnClient client = new FixedSubjectYarnClient("svc", cfg); + + Response response = mock(Response.class); + when(response.getStatus()).thenReturn(500); + doThrow(new HadoopException("close")).when(response).close(); + + Client jaxrsClient = mock(Client.class); + WebTarget target = mock(WebTarget.class); + Invocation.Builder builder = mock(Invocation.Builder.class); + when(jaxrsClient.target(anyString())).thenReturn(target); + when(target.request(MediaType.APPLICATION_JSON)).thenReturn(builder); + when(builder.get()).thenReturn(response); + + try (MockedStatic subjectStatic = mockStatic(Subject.class); + MockedStatic jer = mockStatic(RangerJersey2ClientBuilder.class)) { + mockSubjectDoAsRunsAction(subjectStatic); + jer.when(RangerJersey2ClientBuilder::newClient).thenReturn(jaxrsClient); + + assertThrows(HadoopException.class, () -> client.getQueueList("q", null)); + } + } + + @Test + void test28_getQueueList_after200ResponseCloseThrows_propagatesHadoopException() { + Map cfg = sampleConfigs(); + YarnClient client = new FixedSubjectYarnClient("svc", cfg); + String json = fairSchedulerJsonQueues("leaf"); + + Response response = mock(Response.class); + when(response.getStatus()).thenReturn(200); + when(response.readEntity(String.class)).thenReturn(json); + doThrow(new HadoopException("close")).when(response).close(); + + Client jaxrsClient = mock(Client.class); + WebTarget target = mock(WebTarget.class); + Invocation.Builder builder = mock(Invocation.Builder.class); + when(jaxrsClient.target(anyString())).thenReturn(target); + when(target.request(MediaType.APPLICATION_JSON)).thenReturn(builder); + when(builder.get()).thenReturn(response); + + try (MockedStatic subjectStatic = mockStatic(Subject.class); + MockedStatic jer = mockStatic(RangerJersey2ClientBuilder.class)) { + mockSubjectDoAsRunsAction(subjectStatic); + jer.when(RangerJersey2ClientBuilder::newClient).thenReturn(jaxrsClient); + + assertThrows(HadoopException.class, () -> client.getQueueList("leaf", null)); + } + } + + private static final class FixedSubjectYarnClient extends YarnClient { + private FixedSubjectYarnClient(String serviceName, Map configs) { + super(serviceName, configs); + } + + @Override + protected Subject getLoginSubject() { + return new Subject(); + } + } + + private static final class ExplodingGetQueueListYarnClient extends YarnClient { + private ExplodingGetQueueListYarnClient(String serviceName, Map configs) { + super(serviceName, configs); + } + + @Override + public List getQueueList(String queueNameMatching, List existingQueueList) { + throw new RuntimeException("boom"); + } + } + + private static final class NullSubjectYarnClient extends YarnClient { + private NullSubjectYarnClient(String serviceName, Map configs) { + super(serviceName, configs); + } + + @Override + protected Subject getLoginSubject() { + return null; + } + } +} diff --git a/plugin-yarn/src/test/java/org/apache/ranger/services/yarn/client/TestYarnConnectionMgr.java b/plugin-yarn/src/test/java/org/apache/ranger/services/yarn/client/TestYarnConnectionMgr.java new file mode 100644 index 00000000000..4041ba02819 --- /dev/null +++ b/plugin-yarn/src/test/java/org/apache/ranger/services/yarn/client/TestYarnConnectionMgr.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ranger.services.yarn.client; + +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * @generated by Cursor + * @description : Unit Test cases for YarnConnectionMgr + */ +@ExtendWith(MockitoExtension.class) +@TestMethodOrder(MethodOrderer.MethodName.class) +class TestYarnConnectionMgr { + @Test + void test01_getYarnClient_withValidConfigs_returnsClient() { + Map cfg = new HashMap<>(); + cfg.put("yarn.url", "http://rm:8088"); + cfg.put("username", "u"); + cfg.put("password", "p"); + + YarnClient client = YarnConnectionMgr.getYarnClient("svc", cfg); + + assertNotNull(client); + } + + @Test + void test02_getYarnClient_withNullConfigs_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> YarnConnectionMgr.getYarnClient("svc", null)); + } +} diff --git a/plugin-yarn/src/test/java/org/apache/ranger/services/yarn/client/TestYarnResourceMgr.java b/plugin-yarn/src/test/java/org/apache/ranger/services/yarn/client/TestYarnResourceMgr.java new file mode 100644 index 00000000000..69a3cb57908 --- /dev/null +++ b/plugin-yarn/src/test/java/org/apache/ranger/services/yarn/client/TestYarnResourceMgr.java @@ -0,0 +1,216 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ranger.services.yarn.client; + +import org.apache.ranger.plugin.service.ResourceLookupContext; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.MockedStatic; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; + +/** + * @generated by Cursor + * @description : Unit Test cases for YarnResourceMgr + */ +@ExtendWith(MockitoExtension.class) +@TestMethodOrder(MethodOrderer.MethodName.class) +class TestYarnResourceMgr { + private Map sampleConfigs() { + Map cfg = new HashMap<>(); + cfg.put("yarn.url", "http://rm:8088"); + cfg.put("username", "u"); + cfg.put("password", "p"); + return cfg; + } + + @Test + void test01_validateConfig_delegatesToYarnClient() { + Map cfg = sampleConfigs(); + Map expected = new HashMap<>(); + expected.put("connectivityStatus", Boolean.TRUE); + + try (MockedStatic mocked = mockStatic(YarnClient.class)) { + mocked.when(() -> YarnClient.connectionTest(eq("svc"), eq(cfg))).thenReturn(expected); + + Map actual = YarnResourceMgr.validateConfig("svc", cfg); + + assertEquals(expected, actual); + mocked.verify(() -> YarnClient.connectionTest(eq("svc"), eq(cfg))); + } + } + + @Test + void test02_validateConfig_whenYarnClientThrows_propagatesException() { + Map cfg = sampleConfigs(); + RuntimeException boom = new RuntimeException("boom"); + + try (MockedStatic mocked = mockStatic(YarnClient.class)) { + mocked.when(() -> YarnClient.connectionTest(eq("svc"), eq(cfg))).thenThrow(boom); + + assertThrows(RuntimeException.class, () -> YarnResourceMgr.validateConfig("svc", cfg)); + } + } + + @Test + void test03_getYarnResources_whenConfigsEmpty_returnsNull() { + ResourceLookupContext ctx = new ResourceLookupContext(); + ctx.setUserInput("q"); + ctx.setResourceName("queue"); + + List out = YarnResourceMgr.getYarnResources("svc", Collections.emptyMap(), ctx); + + assertNull(out); + } + + @Test + void test04_getYarnResources_whenConfigsPresent_returnsQueueListFromClient() { + Map cfg = sampleConfigs(); + ResourceLookupContext ctx = new ResourceLookupContext(); + ctx.setUserInput("root"); + ctx.setResourceName("queue"); + + List expected = Collections.singletonList("root.default"); + YarnClient mockClient = mock(YarnClient.class); + when(mockClient.getQueueList(eq("root"), isNull())).thenReturn(expected); + + try (MockedStatic mgr = mockStatic(YarnConnectionMgr.class)) { + mgr.when(() -> YarnConnectionMgr.getYarnClient(eq("svc"), eq(cfg))).thenReturn(mockClient); + + List actual = YarnResourceMgr.getYarnResources("svc", cfg, ctx); + + assertEquals(expected, actual); + } + } + + @Test + void test05_getYarnResource_whenClientNull_returnsNull() { + Map cfg = sampleConfigs(); + + try (MockedStatic mgr = mockStatic(YarnConnectionMgr.class)) { + mgr.when(() -> YarnConnectionMgr.getYarnClient(anyString(), any())).thenReturn(null); + + assertNull(YarnResourceMgr.getYarnResource("svc", cfg, "q", null)); + } + } + + @Test + void test06_getYarnResource_whenClientPresent_returnsFilteredQueues() { + Map cfg = sampleConfigs(); + List expected = Collections.singletonList("prod"); + YarnClient mockClient = mock(YarnClient.class); + when(mockClient.getQueueList(eq("pro"), eq(Collections.singletonList("dev")))).thenReturn(expected); + + try (MockedStatic mgr = mockStatic(YarnConnectionMgr.class)) { + mgr.when(() -> YarnConnectionMgr.getYarnClient(eq("svc"), eq(cfg))).thenReturn(mockClient); + + List actual = YarnResourceMgr.getYarnResource("svc", cfg, "pro", + Collections.singletonList("dev")); + + assertEquals(expected, actual); + } + } + + @Test + void test07_getYarnResources_whenResourceMapContainsQueue_passesExistingQueues() { + Map cfg = sampleConfigs(); + ResourceLookupContext ctx = new ResourceLookupContext(); + ctx.setUserInput("needle"); + ctx.setResourceName("queue"); + Map> resourceMap = new HashMap<>(); + resourceMap.put("queue", Collections.singletonList("existing")); + ctx.setResources(resourceMap); + + List expected = Collections.singletonList("needle-found"); + YarnClient mockClient = mock(YarnClient.class); + when(mockClient.getQueueList(eq("needle"), eq(Collections.singletonList("existing")))).thenReturn( + expected); + + try (MockedStatic mgr = mockStatic(YarnConnectionMgr.class)) { + mgr.when(() -> YarnConnectionMgr.getYarnClient(eq("svc"), eq(cfg))).thenReturn(mockClient); + + assertEquals(expected, YarnResourceMgr.getYarnResources("svc", cfg, ctx)); + } + } + + @Test + void test08_getYarnResources_whenConfigsNull_logsAndReturnsNull() { + ResourceLookupContext ctx = new ResourceLookupContext(); + ctx.setUserInput("x"); + ctx.setResourceName("queue"); + + assertNull(YarnResourceMgr.getYarnResources("svc", null, ctx)); + } + + @Test + void test09_getYarnResources_whenResourceMapMissingQueueKey_doesNotPassExistingQueues() { + Map cfg = sampleConfigs(); + ResourceLookupContext ctx = new ResourceLookupContext(); + ctx.setUserInput("root"); + ctx.setResourceName("queue"); + Map> resourceMap = new HashMap<>(); + resourceMap.put("other", Collections.singletonList("x")); + ctx.setResources(resourceMap); + + List expected = Collections.singletonList("root.default"); + YarnClient mockClient = mock(YarnClient.class); + when(mockClient.getQueueList(eq("root"), isNull())).thenReturn(expected); + + try (MockedStatic mgr = mockStatic(YarnConnectionMgr.class)) { + mgr.when(() -> YarnConnectionMgr.getYarnClient(eq("svc"), eq(cfg))).thenReturn(mockClient); + + assertEquals(expected, YarnResourceMgr.getYarnResources("svc", cfg, ctx)); + } + } + + @Test + void test10_getYarnResources_whenResourceMapEmpty_skipsExistingQueuesBranch() { + Map cfg = sampleConfigs(); + ResourceLookupContext ctx = new ResourceLookupContext(); + ctx.setUserInput("root"); + ctx.setResourceName("queue"); + ctx.setResources(new HashMap>()); + + List expected = Collections.singletonList("root.default"); + YarnClient mockClient = mock(YarnClient.class); + when(mockClient.getQueueList(eq("root"), isNull())).thenReturn(expected); + + try (MockedStatic mgr = mockStatic(YarnConnectionMgr.class)) { + mgr.when(() -> YarnConnectionMgr.getYarnClient(eq("svc"), eq(cfg))).thenReturn(mockClient); + + assertEquals(expected, YarnResourceMgr.getYarnResources("svc", cfg, ctx)); + } + } +} diff --git a/plugin-yarn/src/test/java/org/apache/ranger/services/yarn/client/json/model/TestYarnSchedulerResponse.java b/plugin-yarn/src/test/java/org/apache/ranger/services/yarn/client/json/model/TestYarnSchedulerResponse.java new file mode 100644 index 00000000000..18246099f8e --- /dev/null +++ b/plugin-yarn/src/test/java/org/apache/ranger/services/yarn/client/json/model/TestYarnSchedulerResponse.java @@ -0,0 +1,183 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ranger.services.yarn.client.json.model; + +import com.google.gson.Gson; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * @generated by Cursor + * @description : Unit Test cases for YarnSchedulerResponse + */ +@ExtendWith(MockitoExtension.class) +@TestMethodOrder(MethodOrderer.MethodName.class) +class TestYarnSchedulerResponse { + @Test + void test01_getQueueNames_fairScheduler_json_collectsRootAndChildren() { + String json = "{\"scheduler\":{\"schedulerInfo\":{\"type\":\"fairScheduler\",\"rootQueue\":" + + "{\"queueName\":\"root\",\"childQueues\":{\"queue\":[{\"queueName\":\"default\"}]}}}}}"; + + YarnSchedulerResponse response = new Gson().fromJson(json, YarnSchedulerResponse.class); + + List names = response.getQueueNames(); + + assertTrue(names.contains("root")); + assertTrue(names.contains("default")); + } + + @Test + void test02_getQueueNames_whenSchedulerNull_returnsEmpty() { + YarnSchedulerResponse response = new YarnSchedulerResponse(); + + List names = response.getQueueNames(); + + assertTrue(names.isEmpty()); + } + + @Test + void test03_getQueueNames_capacityScheduler_nested_fqdn() { + String json = "{\"scheduler\":{\"schedulerInfo\":{\"type\":\"capacityScheduler\"," + + "\"queueName\":\"root\",\"queues\":{\"queue\":[{\"queueName\":\"leaf\"," + + "\"type\":\"capacitySchedulerLeafQueueInfo\"}]}}}}"; + + List names = new Gson().fromJson(json, YarnSchedulerResponse.class).getQueueNames(); + + assertTrue(names.contains("root")); + assertTrue(names.contains("root.leaf")); + } + + @Test + void test04_yarnScheduler_collectQueueNames_whenSchedulerInfoNull_noOp() { + YarnSchedulerResponse.YarnScheduler scheduler = + new YarnSchedulerResponse.YarnScheduler(); + List acc = new ArrayList<>(); + scheduler.collectQueueNames(acc); + assertTrue(acc.isEmpty()); + } + + @Test + void test05_schedulerInfo_fair_whenRootNull_collectDoesNothing() { + YarnSchedulerResponse.YarnSchedulerInfo info = + new YarnSchedulerResponse.YarnSchedulerInfo(); + List q = new ArrayList<>(); + info.collectQueueNames(q, null); + assertTrue(q.isEmpty()); + } + + @Test + void test06_schedulerInfo_unknownType_collectDoesNothing() { + YarnSchedulerResponse.YarnSchedulerInfo info = + new Gson().fromJson("{\"type\":\"other\"}", YarnSchedulerResponse.YarnSchedulerInfo.class); + List q = new ArrayList<>(); + info.collectQueueNames(q, null); + assertTrue(q.isEmpty()); + } + + @Test + void test07_yarnQueues_collect_whenQueueListNull_noOp() { + YarnSchedulerResponse.YarnQueues queues = new YarnSchedulerResponse.YarnQueues(); + List q = new ArrayList<>(); + queues.collectQueueNames(q, "parent"); + assertTrue(q.isEmpty()); + } + + @Test + void test08_rootQueue_whenQueueNameNull_noNamesAdded() { + YarnSchedulerResponse.RootQueue root = new YarnSchedulerResponse.RootQueue(); + List q = new ArrayList<>(); + root.collectQueueNames(q); + assertTrue(q.isEmpty()); + } + + @Test + void test09_childQueues_whenQueueNull_collectDoesNothing() { + YarnSchedulerResponse.ChildQueues c = new YarnSchedulerResponse.ChildQueues(); + List q = new ArrayList<>(); + c.collectQueueNames(q); + assertTrue(q.isEmpty()); + } + + @Test + void test10_getters_on_schedulerInfo_and_queues() { + YarnSchedulerResponse.YarnSchedulerInfo info = + new YarnSchedulerResponse.YarnSchedulerInfo(); + assertNull(info.getType()); + assertNull(info.getRootQueue()); + assertNull(info.getQueueName()); + assertNull(info.getQueues()); + } + + @Test + void test11_getScheduler_on_response_and_yarnScheduler() { + YarnSchedulerResponse r = new YarnSchedulerResponse(); + assertNull(r.getScheduler()); + YarnSchedulerResponse.YarnScheduler s = + new YarnSchedulerResponse.YarnScheduler(); + assertNull(s.getSchedulerInfo()); + } + + @Test + void test12_fairScheduler_whenRootQueueNull_collectSkipsChildren() { + YarnSchedulerResponse.YarnSchedulerInfo info = new Gson().fromJson( + "{\"type\":\"fairScheduler\"}", YarnSchedulerResponse.YarnSchedulerInfo.class); + + List q = new ArrayList<>(); + info.collectQueueNames(q, null); + assertTrue(q.isEmpty()); + } + + @Test + void test13_capacityScheduler_whenQueueNameNull_skipsQueuesTraversal() { + YarnSchedulerResponse.YarnSchedulerInfo info = new Gson().fromJson( + "{\"type\":\"capacityScheduler\",\"queues\":{\"queue\":[]}}", + YarnSchedulerResponse.YarnSchedulerInfo.class); + + List q = new ArrayList<>(); + info.collectQueueNames(q, null); + assertTrue(q.isEmpty()); + } + + @Test + void test14_rootQueue_getters_reflectJsonTree() { + String json = "{\"queueName\":\"r\",\"childQueues\":{\"queue\":[{\"queueName\":\"leaf\"}]}}"; + YarnSchedulerResponse.RootQueue rq = new Gson().fromJson(json, + YarnSchedulerResponse.RootQueue.class); + + assertEquals("r", rq.getQueueName()); + assertEquals(1, rq.getChildQueues().getQueue().size()); + assertEquals("leaf", rq.getChildQueues().getQueue().get(0).getQueueName()); + } + + @Test + void test16_yarnQueues_whenQueueFieldAbsent_getQueueReturnsNull() { + YarnSchedulerResponse.YarnQueues yq = new Gson().fromJson("{}", YarnSchedulerResponse.YarnQueues.class); + + assertNull(yq.getQueue()); + } +}