From 991e3be9e2fbd68ac6e27e7f865d5c0fb243d4b0 Mon Sep 17 00:00:00 2001 From: Bhavesh Amre Date: Tue, 26 May 2026 16:51:35 +0530 Subject: [PATCH] RANGER-5581:Add unit test cases for plugin-ozone Module --- plugin-ozone/pom.xml | 6 + .../authorizer/TestRangerOzoneAuthorizer.java | 114 +++- .../ozone/TestRangerServiceOzone.java | 247 ++++++++ .../ozone/client/TestOzoneClient.java | 565 ++++++++++++++++++ .../ozone/client/TestOzoneConnectionMgr.java | 241 ++++++++ .../ozone/client/TestOzoneResourceMgr.java | 420 +++++++++++++ 6 files changed, 1583 insertions(+), 10 deletions(-) create mode 100644 plugin-ozone/src/test/java/org/apache/ranger/services/ozone/TestRangerServiceOzone.java create mode 100644 plugin-ozone/src/test/java/org/apache/ranger/services/ozone/client/TestOzoneClient.java create mode 100644 plugin-ozone/src/test/java/org/apache/ranger/services/ozone/client/TestOzoneConnectionMgr.java create mode 100644 plugin-ozone/src/test/java/org/apache/ranger/services/ozone/client/TestOzoneResourceMgr.java diff --git a/plugin-ozone/pom.xml b/plugin-ozone/pom.xml index 6794bf5f20a..732d9b5bb06 100644 --- a/plugin-ozone/pom.xml +++ b/plugin-ozone/pom.xml @@ -159,6 +159,12 @@ limitations under the License. ${junit.jupiter.version} test + + org.mockito + mockito-core + ${mockito.version} + test + org.mockito mockito-junit-jupiter diff --git a/plugin-ozone/src/test/java/org/apache/ranger/authorization/ozone/authorizer/TestRangerOzoneAuthorizer.java b/plugin-ozone/src/test/java/org/apache/ranger/authorization/ozone/authorizer/TestRangerOzoneAuthorizer.java index 1f365dd61be..d04b395842f 100644 --- a/plugin-ozone/src/test/java/org/apache/ranger/authorization/ozone/authorizer/TestRangerOzoneAuthorizer.java +++ b/plugin-ozone/src/test/java/org/apache/ranger/authorization/ozone/authorizer/TestRangerOzoneAuthorizer.java @@ -29,11 +29,18 @@ import org.apache.hadoop.security.UserGroupInformation; import org.apache.ranger.authorization.hadoop.config.RangerPluginConfig; import org.apache.ranger.plugin.model.RangerInlinePolicy; +import org.apache.ranger.plugin.policyengine.RangerAccessRequestImpl; +import org.apache.ranger.plugin.policyengine.RangerAccessResult; import org.apache.ranger.plugin.service.RangerBasePlugin; import org.apache.ranger.plugin.util.JsonUtilsV2; import org.junit.jupiter.api.BeforeAll; +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.lang.reflect.Field; import java.net.InetAddress; import java.util.Arrays; import java.util.Collections; @@ -47,8 +54,17 @@ 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.Mockito.mock; +import static org.mockito.Mockito.when; -public class TestRangerOzoneAuthorizer { +/** + * @generated by Cursor + * @description : Unit Test cases for RangerOzoneAuthorizer + */ +@ExtendWith(MockitoExtension.class) +@TestMethodOrder(MethodOrderer.MethodName.class) +class TestRangerOzoneAuthorizer { private static final String RANGER_SERVICE_TYPE = "ozone"; private static final String RANGER_APP_ID = "om"; private static final String OZONE_SERVICE_ID = "om"; @@ -62,10 +78,10 @@ public class TestRangerOzoneAuthorizer { private final UserGroupInformation user2 = UserGroupInformation.createRemoteUser("user2"); private final String role1 = "role1"; - private final OzoneObj vol1 = new OzoneObjInfo.Builder().setResType(OzoneObj.ResourceType.VOLUME).setStoreType(OzoneObj.StoreType.OZONE).setVolumeName("vol1").build(); - private final OzoneObj buck1 = new OzoneObjInfo.Builder().setResType(OzoneObj.ResourceType.BUCKET).setStoreType(OzoneObj.StoreType.OZONE).setVolumeName("vol1").setBucketName("buck1").build(); - private final OzoneObj key1 = new OzoneObjInfo.Builder().setResType(OzoneObj.ResourceType.KEY).setStoreType(OzoneObj.StoreType.OZONE).setVolumeName("vol1").setBucketName("buck1").setKeyName("key1").build(); - private final OzoneObj vol2 = new OzoneObjInfo.Builder().setResType(OzoneObj.ResourceType.VOLUME).setStoreType(OzoneObj.StoreType.OZONE).setVolumeName("vol2").build(); + private final OzoneObj vol1 = new OzoneObjInfo.Builder().setResType(OzoneObj.ResourceType.VOLUME).setStoreType(OzoneObj.StoreType.OZONE).setVolumeName("vol1").build(); + private final OzoneObj buck1 = new OzoneObjInfo.Builder().setResType(OzoneObj.ResourceType.BUCKET).setStoreType(OzoneObj.StoreType.OZONE).setVolumeName("vol1").setBucketName("buck1").build(); + private final OzoneObj key1 = new OzoneObjInfo.Builder().setResType(OzoneObj.ResourceType.KEY).setStoreType(OzoneObj.StoreType.OZONE).setVolumeName("vol1").setBucketName("buck1").setKeyName("key1").build(); + private final OzoneObj vol2 = new OzoneObjInfo.Builder().setResType(OzoneObj.ResourceType.VOLUME).setStoreType(OzoneObj.StoreType.OZONE).setVolumeName("vol2").build(); private final OzoneGrant grantList = new OzoneGrant(new HashSet<>(Arrays.asList(vol1, buck1)), Collections.singleton(IAccessAuthorizer.ACLType.LIST)); private final OzoneGrant grantRead = new OzoneGrant(Collections.singleton(key1), Collections.singleton(IAccessAuthorizer.ACLType.READ)); @@ -78,7 +94,7 @@ public class TestRangerOzoneAuthorizer { .setOwnerName(OWNER_NAME); @BeforeAll - public static void setUpBeforeClass() { + static void setUpBeforeClass() { RangerPluginConfig pluginConfig = new RangerPluginConfig(RANGER_SERVICE_TYPE, null, RANGER_APP_ID, null, null, null); // loads ranger-ozone-security.xml RangerBasePlugin plugin = new RangerBasePlugin(pluginConfig); @@ -91,7 +107,7 @@ public static void setUpBeforeClass() { } @Test - public void testAssumeRoleDeny() { + void testAssumeRoleDeny() { // user2 should not be allowed to assume role1 - no Ranger policy grants this permission AssumeRoleRequest request = new AssumeRoleRequest(hostname, ipAddress, user2, role1, null); @@ -99,7 +115,7 @@ public void testAssumeRoleDeny() { } @Test - public void testAssumeRoleWithEmptyGrants() throws Exception { + void testAssumeRoleWithEmptyGrants() throws Exception { Set grants = Collections.emptySet(); AssumeRoleRequest request = new AssumeRoleRequest(hostname, ipAddress, user1, role1, grants); @@ -135,7 +151,7 @@ public void testAssumeRoleWithEmptyGrants() throws Exception { } @Test - public void testAssumeRoleWithNullGrants() throws Exception { + void testAssumeRoleWithNullGrants() throws Exception { Set grants = null; AssumeRoleRequest request = new AssumeRoleRequest(hostname, ipAddress, user1, role1, grants); @@ -171,7 +187,7 @@ public void testAssumeRoleWithNullGrants() throws Exception { } @Test - public void testAssumeRoleWithGrants() throws Exception { + void testAssumeRoleWithGrants() throws Exception { Set grants = new HashSet<>(Arrays.asList(grantList, grantRead)); AssumeRoleRequest request = new AssumeRoleRequest(hostname, ipAddress, user1, role1, grants); @@ -201,4 +217,82 @@ public void testAssumeRoleWithGrants() throws Exception { assertTrue(ozoneAuthorizer.checkAccess(buck1, ctxListWithSessionPolicy), "session-policy should allow list on bucket vol1/buck1"); assertTrue(ozoneAuthorizer.checkAccess(key1, ctxReadWithSessionPolicy), "session-policy should allow read on key vol1/buck1/key1"); } + + @Test + void test05_checkAccess_whenPolicyAllows_returnsTrue() throws Exception { + RangerBasePlugin active = readRangerPluginField(); + + try { + RangerBasePlugin plugin = mock(RangerBasePlugin.class); + when(plugin.getClusterName()).thenReturn("test-cluster"); + + RangerAccessResult result = mock(RangerAccessResult.class); + when(result.getIsAllowed()).thenReturn(true); + when(plugin.isAccessAllowed(any(RangerAccessRequestImpl.class))).thenReturn(result); + + RangerOzoneAuthorizer authorizer = new RangerOzoneAuthorizer(plugin); + RequestContext context = reqCtxBuilder.setAclRights(IAccessAuthorizer.ACLType.READ).build(); + + assertTrue(authorizer.checkAccess(key1, context)); + } finally { + writeRangerPluginField(active); + } + } + + @Test + void test06_checkAccess_whenPrefixResource_returnsFalse() throws Exception { + RangerBasePlugin active = readRangerPluginField(); + + try { + RangerBasePlugin plugin = mock(RangerBasePlugin.class); + when(plugin.getClusterName()).thenReturn("test-cluster"); + + RangerOzoneAuthorizer authorizer = new RangerOzoneAuthorizer(plugin); + OzoneObj prefix = new OzoneObjInfo.Builder() + .setResType(OzoneObj.ResourceType.PREFIX) + .setStoreType(OzoneObj.StoreType.OZONE) + .setVolumeName("vol1") + .setBucketName("buck1") + .setPrefixName("pre") + .build(); + RequestContext context = reqCtxBuilder.setAclRights(IAccessAuthorizer.ACLType.READ).build(); + + assertFalse(authorizer.checkAccess(prefix, context)); + } finally { + writeRangerPluginField(active); + } + } + + @Test + void test07_generateAssumeRoleSessionPolicy_whenTargetRoleNameNull_throwsOmException() { + AssumeRoleRequest request = new AssumeRoleRequest(hostname, ipAddress, user1, null, null); + + assertThrows(OMException.class, () -> ozoneAuthorizer.generateAssumeRoleSessionPolicy(request)); + } + + @Test + void test08_generateAssumeRoleSessionPolicy_whenPluginUninitialized_throwsOmException() throws Exception { + RangerBasePlugin active = readRangerPluginField(); + + try { + writeRangerPluginField(null); + AssumeRoleRequest request = new AssumeRoleRequest(hostname, ipAddress, user1, role1, null); + + assertThrows(OMException.class, () -> ozoneAuthorizer.generateAssumeRoleSessionPolicy(request)); + } finally { + writeRangerPluginField(active); + } + } + + private static RangerBasePlugin readRangerPluginField() throws Exception { + Field field = RangerOzoneAuthorizer.class.getDeclaredField("rangerPlugin"); + field.setAccessible(true); + return (RangerBasePlugin) field.get(null); + } + + private static void writeRangerPluginField(RangerBasePlugin plugin) throws Exception { + Field field = RangerOzoneAuthorizer.class.getDeclaredField("rangerPlugin"); + field.setAccessible(true); + field.set(null, plugin); + } } diff --git a/plugin-ozone/src/test/java/org/apache/ranger/services/ozone/TestRangerServiceOzone.java b/plugin-ozone/src/test/java/org/apache/ranger/services/ozone/TestRangerServiceOzone.java new file mode 100644 index 00000000000..930b9064345 --- /dev/null +++ b/plugin-ozone/src/test/java/org/apache/ranger/services/ozone/TestRangerServiceOzone.java @@ -0,0 +1,247 @@ +/* + * 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.ozone; + +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.policyengine.RangerPolicyEngine; +import org.apache.ranger.plugin.service.RangerBaseService; +import org.apache.ranger.plugin.service.ResourceLookupContext; +import org.apache.ranger.plugin.store.EmbeddedServiceDefsUtil; +import org.apache.ranger.services.ozone.client.OzoneResourceMgr; +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.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +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.assertSame; +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.anyMap; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.same; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.never; + +/** + * @generated by Cursor + * @description : Unit Test cases for RangerServiceOzone + */ +@ExtendWith(MockitoExtension.class) +@TestMethodOrder(MethodOrderer.MethodName.class) +class TestRangerServiceOzone { + private static final String CREATE_DEFAULT_POLICY_PER_HIERARCHY = "create.default.policy.per.hierarchy"; + + @Test + void test01_init_whenServiceProvided_setsIdentifiers() throws Exception { + RangerServiceOzone ozone = new RangerServiceOzone(); + RangerServiceDef def = EmbeddedServiceDefsUtil.instance().getEmbeddedServiceDef(EmbeddedServiceDefsUtil.EMBEDDED_SERVICEDEF_OZONE_NAME); + + assertNotNull(def); + + Map cfg = sampleServiceConfigs(); + RangerService service = buildService("svc-ozone", "ozone", cfg); + + ozone.init(def, service); + + assertEquals(def, ozone.getServiceDef()); + assertEquals(service, ozone.getService()); + assertEquals(cfg, ozone.getConfigs()); + assertEquals("svc-ozone", ozone.getServiceName()); + assertEquals("ozone", ozone.getServiceType()); + } + + @Test + void test02_init_whenServiceIsNull_throwsNullPointerException() throws Exception { + RangerServiceOzone ozone = new RangerServiceOzone(); + RangerServiceDef def = EmbeddedServiceDefsUtil.instance().getEmbeddedServiceDef(EmbeddedServiceDefsUtil.EMBEDDED_SERVICEDEF_OZONE_NAME); + + assertNotNull(def); + assertThrows(NullPointerException.class, () -> ozone.init(def, null)); + } + + @Test + void test03_validateConfig_whenConfigsAbsent_returnsEmptyResponse() throws Exception { + RangerServiceOzone ozone = initOzone(sampleServiceConfigs()); + ozone.setConfigs(null); + + Map out = ozone.validateConfig(); + + assertNotNull(out); + assertTrue(out.isEmpty()); + } + + @Test + void test04_validateConfig_whenConfigsAvailable_delegatesToOzoneResourceMgr() throws Exception { + RangerServiceOzone ozone = initOzone(sampleServiceConfigs()); + Map expected = Collections.singletonMap("connectivityStatus", Boolean.TRUE); + + try (MockedStatic mocked = mockStatic(OzoneResourceMgr.class)) { + mocked.when(() -> OzoneResourceMgr.connectionTest(eq("ozone-svc"), anyMap())).thenReturn(expected); + + Map actual = ozone.validateConfig(); + + assertSame(expected, actual); + mocked.verify(() -> OzoneResourceMgr.connectionTest(eq("ozone-svc"), anyMap())); + } + } + + @Test + void test05_lookupResource_whenContextIsNull_returnsEmptyList() throws Exception { + RangerServiceOzone ozone = initOzoneForLookup(sampleServiceConfigs()); + + try (MockedStatic mocked = mockStatic(OzoneResourceMgr.class)) { + List result = ozone.lookupResource(null); + + assertEquals(Collections.emptyList(), result); + + mocked.verify(() -> OzoneResourceMgr.getOzoneResources(anyString(), anyString(), anyMap(), any()), + never()); + } + } + + @Test + void test06_lookupResource_whenContextPresent_delegatesToOzoneResourceMgr() throws Exception { + RangerServiceOzone ozone = initOzoneForLookup(sampleServiceConfigs()); + ResourceLookupContext ctx = sampleLookupContext(); + List expected = Arrays.asList("/a", "/b"); + + try (MockedStatic mocked = mockStatic(OzoneResourceMgr.class)) { + mocked.when(() -> OzoneResourceMgr.getOzoneResources(eq("svc-lookup"), eq("ozone"), anyMap(), same(ctx))).thenReturn(expected); + + List result = ozone.lookupResource(ctx); + + assertSame(expected, result); + } + } + + @Test + void test07_getDefaultRangerPolicies_addsOwnerGrantForPoliciesNamedAll() throws Exception { + RangerServiceOzone ozone = new RangerServiceOzone(); + RangerServiceDef def = EmbeddedServiceDefsUtil.instance().getEmbeddedServiceDef(EmbeddedServiceDefsUtil.EMBEDDED_SERVICEDEF_OZONE_NAME); + + ozone.init(def, buildService("def-svc", "ozone", defaultPolicyConfigs())); + + List policies = ozone.getDefaultRangerPolicies(); + + assertFalse(policies.isEmpty()); + + boolean foundExtended = policies.stream().anyMatch(p -> + p.getName() != null + && p.getName().startsWith("all") + && p.getPolicyItems().stream().anyMatch(pi -> + pi.getUsers() != null && pi.getUsers().contains(RangerPolicyEngine.RESOURCE_OWNER))); + + assertTrue(foundExtended); + } + + @Test + void test08_getDefaultRangerPolicies_whenLookupUserPresent_addsDedicatedPolicyItems() throws Exception { + RangerServiceOzone ozone = new RangerServiceOzone(); + + Field lookUpField = RangerBaseService.class.getDeclaredField("lookUpUser"); + lookUpField.setAccessible(true); + lookUpField.set(ozone, "resolver-user"); + + RangerServiceDef def = EmbeddedServiceDefsUtil.instance().getEmbeddedServiceDef(EmbeddedServiceDefsUtil.EMBEDDED_SERVICEDEF_OZONE_NAME); + ozone.init(def, buildService("def-svc-2", "ozone", defaultPolicyConfigs())); + + List policies = ozone.getDefaultRangerPolicies(); + + Set allPolicyNames = new HashSet<>(); + for (RangerPolicy p : policies) { + if (p.getName() != null && p.getName().startsWith("all")) { + boolean hasLookup = p.getPolicyItems().stream().anyMatch(pi -> + pi.getUsers() != null && pi.getUsers().contains("resolver-user")); + assertTrue(hasLookup, "expected lookup user grants on policy " + p.getName()); + allPolicyNames.add(p.getName()); + } + } + + assertFalse(allPolicyNames.isEmpty()); + } + + private RangerServiceOzone initOzone(Map cfg) throws Exception { + RangerServiceOzone svc = new RangerServiceOzone(); + + RangerServiceDef def = EmbeddedServiceDefsUtil.instance().getEmbeddedServiceDef(EmbeddedServiceDefsUtil.EMBEDDED_SERVICEDEF_OZONE_NAME); + assertNotNull(def); + + svc.init(def, buildService("ozone-svc", "ozone", cfg)); + return svc; + } + + private RangerServiceOzone initOzoneForLookup(Map cfg) throws Exception { + RangerServiceOzone svc = new RangerServiceOzone(); + + RangerServiceDef def = EmbeddedServiceDefsUtil.instance().getEmbeddedServiceDef(EmbeddedServiceDefsUtil.EMBEDDED_SERVICEDEF_OZONE_NAME); + assertNotNull(def); + + svc.init(def, buildService("svc-lookup", "ozone", cfg)); + return svc; + } + + private RangerService buildService(String name, String type, Map cfg) { + RangerService service = new RangerService(); + service.setName(name); + service.setType(type); + service.setConfigs(cfg); + return service; + } + + private Map sampleServiceConfigs() { + Map cfg = new HashMap<>(); + cfg.put("username", "u"); + cfg.put("password", "p"); + cfg.put("ozone.om.http-address", "localhost:9862"); + return cfg; + } + + private ResourceLookupContext sampleLookupContext() { + ResourceLookupContext ctx = new ResourceLookupContext(); + ctx.setUserInput("volume-a"); + ctx.setResourceName("volume"); + return ctx; + } + + private Map defaultPolicyConfigs() { + Map cfg = sampleServiceConfigs(); + cfg.put("setup.additional.default.policies", "false"); + cfg.put(CREATE_DEFAULT_POLICY_PER_HIERARCHY, Boolean.TRUE.toString()); + return cfg; + } +} diff --git a/plugin-ozone/src/test/java/org/apache/ranger/services/ozone/client/TestOzoneClient.java b/plugin-ozone/src/test/java/org/apache/ranger/services/ozone/client/TestOzoneClient.java new file mode 100644 index 00000000000..cdbbce234c0 --- /dev/null +++ b/plugin-ozone/src/test/java/org/apache/ranger/services/ozone/client/TestOzoneClient.java @@ -0,0 +1,565 @@ +/* + * 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.ozone.client; + +import org.apache.hadoop.hdds.conf.ConfigurationSource; +import org.apache.hadoop.ozone.client.ObjectStore; +import org.apache.hadoop.ozone.client.OzoneBucket; +import org.apache.hadoop.ozone.client.OzoneClientFactory; +import org.apache.hadoop.ozone.client.OzoneKey; +import org.apache.hadoop.ozone.client.OzoneVolume; +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.MockedConstruction; +import org.mockito.MockedStatic; +import org.mockito.junit.jupiter.MockitoExtension; + +import javax.security.auth.Subject; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.security.PrivilegedExceptionAction; +import java.util.ArrayList; +import java.util.Arrays; +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.assertNotNull; +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.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockConstruction; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * @generated by Cursor + * @description : Unit Test cases for OzoneClient + */ +@ExtendWith(MockitoExtension.class) +@TestMethodOrder(MethodOrderer.MethodName.class) +class TestOzoneClient { + @Test + void test01_close_whenHadoopClientCloseThrows_swallowsIOException() throws Exception { + org.apache.hadoop.ozone.client.OzoneClient hdc = mock(org.apache.hadoop.ozone.client.OzoneClient.class); + doThrow(new IOException("close-fail")).when(hdc).close(); + + try (MockedStatic subjectStatic = mockStatic(Subject.class); + MockedStatic factoryStatic = mockStatic(OzoneClientFactory.class)) { + stubSubjectExecutesExceptionAction(subjectStatic); + stubFactoryReturns(factoryStatic, hdc); + + OzoneClient rangerClient = new OzoneClient("svc", minimalConnectionProperties()); + rangerClient.close(); + + verify(hdc).close(); + } + } + + @Test + void test02_connectionTest_whenVolumesEmpty_returnsFailureMap() throws Exception { + org.apache.hadoop.ozone.client.OzoneClient hdc = mock(org.apache.hadoop.ozone.client.OzoneClient.class); + ObjectStore objectStore = mock(ObjectStore.class); + when(hdc.getObjectStore()).thenReturn(objectStore); + when(objectStore.listVolumes(eq(""))).thenReturn(Collections.emptyIterator()); + + try (MockedStatic subjectStatic = mockStatic(Subject.class); + MockedStatic factoryStatic = mockStatic(OzoneClientFactory.class)) { + stubSubjectExecutesExceptionAction(subjectStatic); + stubFactoryReturns(factoryStatic, hdc); + + Map out = OzoneClient.connectionTest("svc", minimalConnectionProperties()); + + assertEquals(Boolean.FALSE, out.get("connectivityStatus")); + verify(hdc).close(); + } + } + + @Test + void test03_connectionTest_whenVolumesPresent_returnsSuccessMap() throws Exception { + org.apache.hadoop.ozone.client.OzoneClient hdc = mock(org.apache.hadoop.ozone.client.OzoneClient.class); + ObjectStore objectStore = mock(ObjectStore.class); + when(hdc.getObjectStore()).thenReturn(objectStore); + + OzoneVolume vol = mock(OzoneVolume.class); + when(vol.getName()).thenReturn("vol-a"); + List singleVol = Collections.singletonList(vol); + when(objectStore.listVolumes(eq(""))).thenAnswer(invocation -> singleVol.iterator()); + + try (MockedStatic subjectStatic = mockStatic(Subject.class); + MockedStatic factoryStatic = mockStatic(OzoneClientFactory.class)) { + stubSubjectExecutesExceptionAction(subjectStatic); + stubFactoryReturns(factoryStatic, hdc); + + Map out = OzoneClient.connectionTest("svc", minimalConnectionProperties()); + + assertEquals(Boolean.TRUE, out.get("connectivityStatus")); + verify(hdc).close(); + } + } + + @Test + void test04_connectionTest_whenListVolumesIteratorNull_handlesGracefully() throws Exception { + org.apache.hadoop.ozone.client.OzoneClient hdc = mock(org.apache.hadoop.ozone.client.OzoneClient.class); + ObjectStore objectStore = mock(ObjectStore.class); + when(hdc.getObjectStore()).thenReturn(objectStore); + when(objectStore.listVolumes(eq(""))).thenReturn(null); + + try (MockedStatic subjectStatic = mockStatic(Subject.class); + MockedStatic factoryStatic = mockStatic(OzoneClientFactory.class)) { + stubSubjectExecutesExceptionAction(subjectStatic); + stubFactoryReturns(factoryStatic, hdc); + + Map out = OzoneClient.connectionTest("svc", minimalConnectionProperties()); + + assertEquals(Boolean.FALSE, out.get("connectivityStatus")); + } + } + + @Test + void test05_constructor_skipsEntriesWithNullValues() throws Exception { + Map props = minimalConnectionProperties(); + props.put("ignored.null.value", null); + + org.apache.hadoop.ozone.client.OzoneClient hdc = mock(org.apache.hadoop.ozone.client.OzoneClient.class); + + try (MockedStatic subjectStatic = mockStatic(Subject.class); + MockedStatic factoryStatic = mockStatic(OzoneClientFactory.class)) { + stubSubjectExecutesExceptionAction(subjectStatic); + stubFactoryReturns(factoryStatic, hdc); + + OzoneClient rangerClient = new OzoneClient("svc", props); + assertNotNull(rangerClient); + rangerClient.close(); + } + } + + @Test + void test06_getBucketList_whenBucketsPresent_returnsNames() throws Exception { + org.apache.hadoop.ozone.client.OzoneClient hdc = mock(org.apache.hadoop.ozone.client.OzoneClient.class); + ObjectStore objectStore = mock(ObjectStore.class); + OzoneVolume ozoneVolume = mock(OzoneVolume.class); + OzoneBucket b1 = mock(OzoneBucket.class); + + when(hdc.getObjectStore()).thenReturn(objectStore); + when(objectStore.getVolume(eq("v1"))).thenReturn(ozoneVolume); + + when(b1.getName()).thenReturn("buck-a"); + List buckets = new ArrayList<>(); + buckets.add(b1); + + when(ozoneVolume.listBuckets(eq("pre"))).thenAnswer(invocation -> buckets.iterator()); + + try (MockedStatic subjectStatic = mockStatic(Subject.class); + MockedStatic factoryStatic = mockStatic(OzoneClientFactory.class)) { + stubSubjectExecutesExceptionAction(subjectStatic); + stubFactoryReturns(factoryStatic, hdc); + + OzoneClient rangerClient = new OzoneClient("svc", minimalConnectionProperties()); + List names = rangerClient.getBucketList("pre", Collections.singletonList("v1")); + + assertEquals(Collections.singletonList("buck-a"), names); + } + } + + @Test + void test07_getBucketList_whenIteratorNull_returnsEmpty() throws Exception { + org.apache.hadoop.ozone.client.OzoneClient hdc = mock(org.apache.hadoop.ozone.client.OzoneClient.class); + ObjectStore objectStore = mock(ObjectStore.class); + OzoneVolume ozoneVolume = mock(OzoneVolume.class); + when(hdc.getObjectStore()).thenReturn(objectStore); + when(objectStore.getVolume(eq("v1"))).thenReturn(ozoneVolume); + when(ozoneVolume.listBuckets(eq("pre"))).thenReturn(null); + + try (MockedStatic subjectStatic = mockStatic(Subject.class); + MockedStatic factoryStatic = mockStatic(OzoneClientFactory.class)) { + stubSubjectExecutesExceptionAction(subjectStatic); + stubFactoryReturns(factoryStatic, hdc); + + OzoneClient rangerClient = new OzoneClient("svc", minimalConnectionProperties()); + + assertTrue(rangerClient.getBucketList("pre", Collections.singletonList("v1")).isEmpty()); + } + } + + @Test + void test08_getBucketList_whenListVolumesThrowsIOException_returnsEmptyAndLogs() throws Exception { + org.apache.hadoop.ozone.client.OzoneClient hdc = mock(org.apache.hadoop.ozone.client.OzoneClient.class); + ObjectStore objectStore = mock(ObjectStore.class); + when(hdc.getObjectStore()).thenReturn(objectStore); + when(objectStore.getVolume(anyString())).thenThrow(new IOException("no-volume")); + + try (MockedStatic subjectStatic = mockStatic(Subject.class); + MockedStatic factoryStatic = mockStatic(OzoneClientFactory.class)) { + stubSubjectExecutesExceptionAction(subjectStatic); + stubFactoryReturns(factoryStatic, hdc); + + OzoneClient rangerClient = new OzoneClient("svc", minimalConnectionProperties()); + + assertTrue(rangerClient.getBucketList("bp", Collections.singletonList("v1")).isEmpty()); + } + } + + @Test + void test09_getBucketList_whenVolumeListNull_skipsReads() throws Exception { + try (MockedStatic subjectStatic = mockStatic(Subject.class); + MockedStatic factoryStatic = mockStatic(OzoneClientFactory.class)) { + org.apache.hadoop.ozone.client.OzoneClient hdc = mock(org.apache.hadoop.ozone.client.OzoneClient.class); + stubSubjectExecutesExceptionAction(subjectStatic); + stubFactoryReturns(factoryStatic, hdc); + + OzoneClient rangerClient = new OzoneClient("svc", minimalConnectionProperties()); + + assertTrue(rangerClient.getBucketList("bp", null).isEmpty()); + assertTrue(rangerClient.getBucketList("bp", Collections.emptyList()).isEmpty()); + + verify(hdc, never()).getObjectStore(); + } + } + + @Test + void test10_getKeyList_whenKeysPresent_returnsNames() throws Exception { + org.apache.hadoop.ozone.client.OzoneClient hdc = mock(org.apache.hadoop.ozone.client.OzoneClient.class); + ObjectStore objectStore = mock(ObjectStore.class); + OzoneVolume ozoneVolume = mock(OzoneVolume.class); + OzoneBucket bucket = mock(OzoneBucket.class); + OzoneKey ozoneKey = mock(OzoneKey.class); + + when(hdc.getObjectStore()).thenReturn(objectStore); + when(objectStore.getVolume(eq("v1"))).thenReturn(ozoneVolume); + + when(bucket.getName()).thenReturn("b-target"); + when(ozoneKey.getName()).thenReturn("key-1"); + + when(bucket.listKeys(eq("kp"))) + .thenAnswer(invocation -> Collections.singletonList(ozoneKey).iterator()); + + when(ozoneVolume.listBuckets(isNull())) + .thenAnswer(invocation -> Collections.singletonList(bucket).iterator()); + + try (MockedStatic subjectStatic = mockStatic(Subject.class); + MockedStatic factoryStatic = mockStatic(OzoneClientFactory.class)) { + stubSubjectExecutesExceptionAction(subjectStatic); + stubFactoryReturns(factoryStatic, hdc); + + OzoneClient rangerClient = new OzoneClient("svc", minimalConnectionProperties()); + + List names = rangerClient.getKeyList("kp", Collections.singletonList("v1"), Collections.singletonList("b-target")); + + assertEquals(Collections.singletonList("key-1"), names); + } + } + + @Test + void test11_getKeyList_whenBucketNotListed_skipsKeys() throws Exception { + org.apache.hadoop.ozone.client.OzoneClient hdc = mock(org.apache.hadoop.ozone.client.OzoneClient.class); + ObjectStore objectStore = mock(ObjectStore.class); + OzoneVolume ozoneVolume = mock(OzoneVolume.class); + OzoneBucket other = mock(OzoneBucket.class); + + when(hdc.getObjectStore()).thenReturn(objectStore); + when(objectStore.getVolume(eq("v1"))).thenReturn(ozoneVolume); + when(other.getName()).thenReturn("other-bucket"); + when(ozoneVolume.listBuckets(isNull())).thenAnswer(invocation -> Collections.singletonList(other).iterator()); + + try (MockedStatic subjectStatic = mockStatic(Subject.class); + MockedStatic factoryStatic = mockStatic(OzoneClientFactory.class)) { + stubSubjectExecutesExceptionAction(subjectStatic); + stubFactoryReturns(factoryStatic, hdc); + + OzoneClient rangerClient = new OzoneClient("svc", minimalConnectionProperties()); + + assertTrue(rangerClient.getKeyList("kp", Collections.singletonList("v1"), + Collections.singletonList("wanted-bucket")).isEmpty()); + + verify(other, never()).listKeys(anyString()); + } + } + + @Test + void test12_getKeyList_whenBucketIteratorNull_returnsEmpty() throws Exception { + org.apache.hadoop.ozone.client.OzoneClient hdc = mock(org.apache.hadoop.ozone.client.OzoneClient.class); + ObjectStore objectStore = mock(ObjectStore.class); + OzoneVolume ozoneVolume = mock(OzoneVolume.class); + when(hdc.getObjectStore()).thenReturn(objectStore); + when(objectStore.getVolume(anyString())).thenReturn(ozoneVolume); + when(ozoneVolume.listBuckets(isNull())).thenReturn(null); + + try (MockedStatic subjectStatic = mockStatic(Subject.class); + MockedStatic factoryStatic = mockStatic(OzoneClientFactory.class)) { + stubSubjectExecutesExceptionAction(subjectStatic); + stubFactoryReturns(factoryStatic, hdc); + + OzoneClient rangerClient = new OzoneClient("svc", minimalConnectionProperties()); + + assertTrue(rangerClient.getKeyList("kp", Collections.singletonList("v1"), + Collections.singletonList("b")).isEmpty()); + } + } + + @Test + void test13_getVolumeList_whenHadoopThrowsIOException_returnsEmpty() throws Exception { + org.apache.hadoop.ozone.client.OzoneClient hdc = mock(org.apache.hadoop.ozone.client.OzoneClient.class); + ObjectStore objectStore = mock(ObjectStore.class); + when(hdc.getObjectStore()).thenReturn(objectStore); + when(objectStore.listVolumes(eq("pfx"))).thenThrow(new IOException("no-list")); + + try (MockedStatic subjectStatic = mockStatic(Subject.class); + MockedStatic factoryStatic = mockStatic(OzoneClientFactory.class)) { + stubSubjectExecutesExceptionAction(subjectStatic); + stubFactoryReturns(factoryStatic, hdc); + + OzoneClient rangerClient = new OzoneClient("svc", minimalConnectionProperties()); + + assertTrue(rangerClient.getVolumeList("pfx").isEmpty()); + } + } + + @Test + void test14_getVolumeList_whenInnerClientNull_returnsEmptyAfterClear() throws Exception { + try (MockedStatic subjectStatic = mockStatic(Subject.class); + MockedStatic factoryStatic = mockStatic(OzoneClientFactory.class)) { + org.apache.hadoop.ozone.client.OzoneClient hdc = mock(org.apache.hadoop.ozone.client.OzoneClient.class); + + stubSubjectExecutesExceptionAction(subjectStatic); + stubFactoryReturns(factoryStatic, hdc); + + OzoneClient rangerClient = new OzoneClient("svc", minimalConnectionProperties()); + Field inner = OzoneClient.class.getDeclaredField("ozoneClient"); + inner.setAccessible(true); + inner.set(rangerClient, null); + + assertTrue(rangerClient.getVolumeList("pfx").isEmpty()); + } + } + + @Test + void test15_getVolumeList_whenVolumesPresent_collectsNames() throws Exception { + org.apache.hadoop.ozone.client.OzoneClient hdc = mock(org.apache.hadoop.ozone.client.OzoneClient.class); + ObjectStore objectStore = mock(ObjectStore.class); + OzoneVolume vol = mock(OzoneVolume.class); + when(vol.getName()).thenReturn("v-a"); + when(objectStore.listVolumes(eq("vp"))).thenAnswer(invocation -> Collections.singletonList(vol).iterator()); + when(hdc.getObjectStore()).thenReturn(objectStore); + + try (MockedStatic subjectStatic = mockStatic(Subject.class); + MockedStatic factoryStatic = mockStatic(OzoneClientFactory.class)) { + stubSubjectExecutesExceptionAction(subjectStatic); + stubFactoryReturns(factoryStatic, hdc); + + OzoneClient rangerClient = new OzoneClient("svc", minimalConnectionProperties()); + + assertEquals(Collections.singletonList("v-a"), rangerClient.getVolumeList("vp")); + } + } + + @Test + void test16_getVolumeList_whenVolumeIteratorNull_returnsEmpty() throws Exception { + org.apache.hadoop.ozone.client.OzoneClient hdc = mock(org.apache.hadoop.ozone.client.OzoneClient.class); + ObjectStore objectStore = mock(ObjectStore.class); + when(hdc.getObjectStore()).thenReturn(objectStore); + when(objectStore.listVolumes(eq("pfx"))).thenReturn(null); + + try (MockedStatic subjectStatic = mockStatic(Subject.class); + MockedStatic factoryStatic = mockStatic(OzoneClientFactory.class)) { + stubSubjectExecutesExceptionAction(subjectStatic); + stubFactoryReturns(factoryStatic, hdc); + + OzoneClient rangerClient = new OzoneClient("svc", minimalConnectionProperties()); + + assertTrue(rangerClient.getVolumeList("pfx").isEmpty()); + } + } + + @Test + void test17_getKeyList_whenBucketListNull_throwsNullPointer() throws Exception { + org.apache.hadoop.ozone.client.OzoneClient hdc = mock(org.apache.hadoop.ozone.client.OzoneClient.class); + ObjectStore objectStore = mock(ObjectStore.class); + OzoneVolume ozoneVolume = mock(OzoneVolume.class); + OzoneBucket bucket = mock(OzoneBucket.class); + + when(hdc.getObjectStore()).thenReturn(objectStore); + when(objectStore.getVolume(eq("v1"))).thenReturn(ozoneVolume); + when(bucket.getName()).thenReturn("b-any"); + when(ozoneVolume.listBuckets(isNull())).thenAnswer(invocation -> Collections.singletonList(bucket).iterator()); + + try (MockedStatic subjectStatic = mockStatic(Subject.class); + MockedStatic factoryStatic = mockStatic(OzoneClientFactory.class)) { + stubSubjectExecutesExceptionAction(subjectStatic); + stubFactoryReturns(factoryStatic, hdc); + + OzoneClient rangerClient = new OzoneClient("svc", minimalConnectionProperties()); + + assertThrows(NullPointerException.class, () -> rangerClient.getKeyList("kp", Collections.singletonList("v1"), null)); + } + } + + @Test + void test18_getKeyList_whenKeyIteratorNull_skipsKeys() throws Exception { + org.apache.hadoop.ozone.client.OzoneClient hdc = mock(org.apache.hadoop.ozone.client.OzoneClient.class); + ObjectStore objectStore = mock(ObjectStore.class); + OzoneVolume ozoneVolume = mock(OzoneVolume.class); + OzoneBucket bucket = mock(OzoneBucket.class); + + when(hdc.getObjectStore()).thenReturn(objectStore); + when(objectStore.getVolume(eq("v1"))).thenReturn(ozoneVolume); + when(bucket.getName()).thenReturn("b1"); + when(bucket.listKeys(eq("kp"))).thenReturn(null); + when(ozoneVolume.listBuckets(isNull())).thenAnswer(invocation -> Collections.singletonList(bucket).iterator()); + + try (MockedStatic subjectStatic = mockStatic(Subject.class); + MockedStatic factoryStatic = mockStatic(OzoneClientFactory.class)) { + stubSubjectExecutesExceptionAction(subjectStatic); + stubFactoryReturns(factoryStatic, hdc); + + OzoneClient rangerClient = new OzoneClient("svc", minimalConnectionProperties()); + + assertTrue(rangerClient.getKeyList("kp", Collections.singletonList("v1"), + Collections.singletonList("b1")).isEmpty()); + } + } + + @Test + void test19_getVolumeList_whenMultipleVolumes_appendsAll() throws Exception { + org.apache.hadoop.ozone.client.OzoneClient hdc = mock(org.apache.hadoop.ozone.client.OzoneClient.class); + ObjectStore objectStore = mock(ObjectStore.class); + OzoneVolume v1 = mock(OzoneVolume.class); + OzoneVolume v2 = mock(OzoneVolume.class); + when(v1.getName()).thenReturn("va"); + when(v2.getName()).thenReturn("vb"); + List both = Arrays.asList(v1, v2); + when(objectStore.listVolumes(eq("pref"))).thenAnswer(invocation -> both.iterator()); + when(hdc.getObjectStore()).thenReturn(objectStore); + + try (MockedStatic subjectStatic = mockStatic(Subject.class); + MockedStatic factoryStatic = mockStatic(OzoneClientFactory.class)) { + stubSubjectExecutesExceptionAction(subjectStatic); + stubFactoryReturns(factoryStatic, hdc); + + OzoneClient rangerClient = new OzoneClient("svc", minimalConnectionProperties()); + + assertEquals(Arrays.asList("va", "vb"), rangerClient.getVolumeList("pref")); + } + } + + @Test + void test20_connectionTest_whenGetVolumeListReturnsNull_reportsDisconnected() throws Exception { + Map cfg = minimalConnectionProperties(); + + try (MockedConstruction cons = mockConstruction(OzoneClient.class, + (mock, context) -> when(mock.getVolumeList(eq(""))).thenReturn(null))) { + Map out = OzoneClient.connectionTest("svc", cfg); + + assertEquals(Boolean.FALSE, out.get("connectivityStatus")); + + assertEquals(1, cons.constructed().size()); + verify(cons.constructed().get(0)).close(); + } + } + + @Test + void test21_getBucketList_whenInnerClientNull_returnsEmpty() throws Exception { + try (MockedStatic subjectStatic = mockStatic(Subject.class); + MockedStatic factoryStatic = mockStatic(OzoneClientFactory.class)) { + org.apache.hadoop.ozone.client.OzoneClient hdc = mock(org.apache.hadoop.ozone.client.OzoneClient.class); + + stubSubjectExecutesExceptionAction(subjectStatic); + stubFactoryReturns(factoryStatic, hdc); + + OzoneClient rangerClient = new OzoneClient("svc", minimalConnectionProperties()); + Field inner = OzoneClient.class.getDeclaredField("ozoneClient"); + inner.setAccessible(true); + inner.set(rangerClient, null); + + assertTrue(rangerClient.getBucketList("pre", Collections.singletonList("v1")).isEmpty()); + } + } + + @Test + void test22_getKeyList_whenInnerClientNull_returnsEmpty() throws Exception { + try (MockedStatic subjectStatic = mockStatic(Subject.class); + MockedStatic factoryStatic = mockStatic(OzoneClientFactory.class)) { + org.apache.hadoop.ozone.client.OzoneClient hdc = mock(org.apache.hadoop.ozone.client.OzoneClient.class); + + stubSubjectExecutesExceptionAction(subjectStatic); + stubFactoryReturns(factoryStatic, hdc); + + OzoneClient rangerClient = new OzoneClient("svc", minimalConnectionProperties()); + Field inner = OzoneClient.class.getDeclaredField("ozoneClient"); + inner.setAccessible(true); + inner.set(rangerClient, null); + + assertTrue(rangerClient.getKeyList("kp", Collections.singletonList("v1"), + Collections.singletonList("b")).isEmpty()); + } + } + + @Test + void test23_getKeyList_whenVolumeListEmpty_skipsStoreAccess() throws Exception { + org.apache.hadoop.ozone.client.OzoneClient hdc = mock(org.apache.hadoop.ozone.client.OzoneClient.class); + + try (MockedStatic subjectStatic = mockStatic(Subject.class); + MockedStatic factoryStatic = mockStatic(OzoneClientFactory.class)) { + stubSubjectExecutesExceptionAction(subjectStatic); + stubFactoryReturns(factoryStatic, hdc); + + OzoneClient rangerClient = new OzoneClient("svc", minimalConnectionProperties()); + + assertTrue(rangerClient.getKeyList("kp", Collections.emptyList(), + Collections.singletonList("b")).isEmpty()); + + verify(hdc, never()).getObjectStore(); + } + } + + private static Map minimalConnectionProperties() { + Map cfg = new HashMap<>(); + cfg.put("username", "u"); + cfg.put("password", "p"); + cfg.put("hadoop.security.authentication", "simple"); + cfg.put("ozone.om.service.ids", "ozone1"); + return cfg; + } + + private static void stubSubjectExecutesExceptionAction(MockedStatic subjectStatic) { + subjectStatic.when(() -> Subject.doAs(any(Subject.class), any(PrivilegedExceptionAction.class))) + .thenAnswer(invocation -> { + PrivilegedExceptionAction pea = invocation.getArgument(1); + return pea.run(); + }); + } + + private static void stubFactoryReturns(MockedStatic factoryStatic, + org.apache.hadoop.ozone.client.OzoneClient hadoopClient) { + factoryStatic.when(() -> OzoneClientFactory.getRpcClient(anyString(), any(ConfigurationSource.class))).thenReturn(hadoopClient); + } +} diff --git a/plugin-ozone/src/test/java/org/apache/ranger/services/ozone/client/TestOzoneConnectionMgr.java b/plugin-ozone/src/test/java/org/apache/ranger/services/ozone/client/TestOzoneConnectionMgr.java new file mode 100644 index 00000000000..a71ae908974 --- /dev/null +++ b/plugin-ozone/src/test/java/org/apache/ranger/services/ozone/client/TestOzoneConnectionMgr.java @@ -0,0 +1,241 @@ +/* + * 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.ozone.client; + +import org.apache.hadoop.hdds.conf.ConfigurationSource; +import org.apache.hadoop.ozone.client.OzoneClientFactory; +import org.apache.ranger.plugin.util.TimedEventUtil; +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 javax.security.auth.Subject; + +import java.security.PrivilegedExceptionAction; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.TimeUnit; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +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.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * @generated by Cursor + * @description : Unit Test cases for OzoneConnectionMgr + */ +@ExtendWith(MockitoExtension.class) +@TestMethodOrder(MethodOrderer.MethodName.class) +class TestOzoneConnectionMgr { + /** + * Extends manager to expose protected cache fields for assertions. + */ + private static final class OzoneConnectionMgrHarness extends OzoneConnectionMgr { + } + + @Test + void test01_getOzoneConnection_whenServiceTypeNull_returnsNull() { + OzoneConnectionMgrHarness mgr = new OzoneConnectionMgrHarness(); + Map config = new HashMap<>(); + config.put("k", "v"); + + assertNull(mgr.getOzoneConnection("svc", null, config)); + } + + @Test + void test02_getOzoneConnection_whenTimedTaskReturnsClient_cachesConnection() throws Exception { + OzoneConnectionMgrHarness mgr = new OzoneConnectionMgrHarness(); + OzoneClient freshClient = mock(OzoneClient.class); + Map config = new HashMap<>(); + config.put("ozone.om.service.ids", "ozone1"); + + try (MockedStatic timed = mockStatic(TimedEventUtil.class)) { + timed.when(() -> TimedEventUtil.timedTask(any(Callable.class), eq(5L), eq(TimeUnit.SECONDS))).thenReturn(freshClient); + + OzoneClient result = mgr.getOzoneConnection("new-svc", "ozone", config); + + assertSame(freshClient, result); + assertSame(freshClient, mgr.ozoneConnectionCache.get("new-svc")); + assertEquals(Boolean.TRUE, mgr.repoConnectStatusMap.get("new-svc")); + } + } + + @Test + void test03_getOzoneConnection_whenConfigsNullOnMiss_logsAndReturnsNull() { + OzoneConnectionMgrHarness mgr = new OzoneConnectionMgrHarness(); + + assertNull(mgr.getOzoneConnection("svc", "ozone", null)); + assertTrue(mgr.ozoneConnectionCache.isEmpty()); + } + + @Test + void test04_getOzoneConnection_whenTimedTaskThrows_returnsNull() throws Exception { + OzoneConnectionMgrHarness mgr = new OzoneConnectionMgrHarness(); + Map config = minimalConfig(); + + try (MockedStatic timed = mockStatic(TimedEventUtil.class)) { + timed.when(() -> TimedEventUtil.timedTask(any(Callable.class), eq(5L), eq(TimeUnit.SECONDS))) + .thenThrow(new IllegalStateException("connect-fail")); + + assertNull(mgr.getOzoneConnection("boom", "ozone", config)); + assertEquals(Boolean.TRUE, mgr.repoConnectStatusMap.get("boom")); + } + } + + @Test + void test05_getOzoneConnection_whenPutIfAbsentReturnsExisting_closesNewborn() throws Exception { + OzoneConnectionMgrHarness mgr = new OzoneConnectionMgrHarness(); + OzoneClient winner = mock(OzoneClient.class); + OzoneClient loser = mock(OzoneClient.class); + Map config = minimalConfig(); + + try (MockedStatic timed = mockStatic(TimedEventUtil.class)) { + timed.when(() -> TimedEventUtil.timedTask(any(Callable.class), eq(5L), eq(TimeUnit.SECONDS))) + .thenAnswer(invocation -> { + mgr.ozoneConnectionCache.put("svc", winner); + return loser; + }); + + OzoneClient result = mgr.getOzoneConnection("svc", "ozone", config); + + assertSame(winner, result); + verify(loser).close(); + verify(winner, never()).close(); + assertEquals(Boolean.TRUE, mgr.repoConnectStatusMap.get("svc")); + } + } + + @Test + void test06_getOzoneConnection_whenCachedPingFails_reloadsConnection() throws Exception { + OzoneConnectionMgrHarness mgr = new OzoneConnectionMgrHarness(); + Map config = minimalConfig(); + OzoneClient stale = mock(OzoneClient.class); + OzoneClient fresh = mock(OzoneClient.class); + + when(stale.getVolumeList(isNull())).thenThrow(new IllegalStateException("stale-connection")); + + mgr.ozoneConnectionCache.put("rec", stale); + + try (MockedStatic timed = mockStatic(TimedEventUtil.class)) { + timed.when(() -> TimedEventUtil.timedTask(any(Callable.class), eq(5L), eq(TimeUnit.SECONDS))).thenReturn(fresh); + + OzoneClient result = mgr.getOzoneConnection("rec", "ozone", config); + + assertSame(fresh, result); + verify(stale).close(); + verify(stale).getVolumeList(null); + assertSame(fresh, mgr.ozoneConnectionCache.get("rec")); + } + } + + @Test + void test07_getOzoneConnection_whenCacheHitHealthy_returnsExistingClient() throws Exception { + OzoneConnectionMgrHarness mgr = new OzoneConnectionMgrHarness(); + Map config = minimalConfig(); + OzoneClient cached = mock(OzoneClient.class); + + when(cached.getVolumeList(isNull())).thenReturn(Collections.emptyList()); + mgr.ozoneConnectionCache.put("reuse", cached); + + OzoneClient result = mgr.getOzoneConnection("reuse", "ozone", config); + + assertSame(cached, result); + verify(cached).getVolumeList(null); + } + + @Test + void test08_getOzoneConnection_whenCallableBuildsClient_invokesOzoneClientConstructor() throws Exception { + OzoneConnectionMgrHarness mgr = new OzoneConnectionMgrHarness(); + Map config = minimalConfig(); + + org.apache.hadoop.ozone.client.OzoneClient hdc = mock(org.apache.hadoop.ozone.client.OzoneClient.class); + + try (MockedStatic timed = mockStatic(TimedEventUtil.class); + MockedStatic subjectStatic = mockStatic(Subject.class); + MockedStatic factoryStatic = mockStatic(OzoneClientFactory.class)) { + subjectStatic.when(() -> Subject.doAs(any(Subject.class), any(PrivilegedExceptionAction.class))) + .thenAnswer(invocation -> { + PrivilegedExceptionAction pea = invocation.getArgument(1); + return pea.run(); + }); + factoryStatic.when(() -> OzoneClientFactory.getRpcClient(anyString(), any(ConfigurationSource.class))).thenReturn(hdc); + + timed.when(() -> TimedEventUtil.timedTask(any(Callable.class), eq(5L), eq(TimeUnit.SECONDS))) + .thenAnswer(invocation -> { + @SuppressWarnings("unchecked") + Callable callable = invocation.getArgument(0); + return callable.call(); + }); + + OzoneClient result = mgr.getOzoneConnection("live", "ozone", config); + + assertNotNull(result); + assertSame(result, mgr.ozoneConnectionCache.get("live")); + factoryStatic.verify(() -> OzoneClientFactory.getRpcClient(anyString(), any(ConfigurationSource.class))); + } + } + + @Test + void test09_getOzoneConnection_whenTimedTaskReturnsNull_butCacheFilled_usesWinner() throws Exception { + OzoneConnectionMgrHarness mgr = new OzoneConnectionMgrHarness(); + OzoneClient winner = mock(OzoneClient.class); + Map config = minimalConfig(); + + try (MockedStatic timed = mockStatic(TimedEventUtil.class)) { + timed.when(() -> TimedEventUtil.timedTask(any(Callable.class), eq(5L), eq(TimeUnit.SECONDS))).thenAnswer( + invocation -> { + mgr.ozoneConnectionCache.put("svc", winner); + return null; + }); + + OzoneClient result = mgr.getOzoneConnection("svc", "ozone", config); + + assertSame(winner, result); + verify(winner, never()).close(); + assertSame(winner, mgr.ozoneConnectionCache.get("svc")); + assertEquals(Boolean.TRUE, mgr.repoConnectStatusMap.get("svc")); + } + } + + private static Map minimalConfig() { + Map cfg = new HashMap<>(); + cfg.put("username", "u"); + cfg.put("password", "p"); + cfg.put("hadoop.security.authentication", "simple"); + cfg.put("ozone.om.service.ids", "ozone1"); + return cfg; + } +} diff --git a/plugin-ozone/src/test/java/org/apache/ranger/services/ozone/client/TestOzoneResourceMgr.java b/plugin-ozone/src/test/java/org/apache/ranger/services/ozone/client/TestOzoneResourceMgr.java new file mode 100644 index 00000000000..ce58fdec8bf --- /dev/null +++ b/plugin-ozone/src/test/java/org/apache/ranger/services/ozone/client/TestOzoneResourceMgr.java @@ -0,0 +1,420 @@ +/* + * 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.ozone.client; + +import org.apache.ranger.plugin.client.HadoopException; +import org.apache.ranger.plugin.service.ResourceLookupContext; +import org.apache.ranger.plugin.util.TimedEventUtil; +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.MockedConstruction; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.TimeUnit; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * @generated by Cursor + * @description : Unit Test cases for OzoneResourceMgr + */ +@ExtendWith(MockitoExtension.class) +@TestMethodOrder(MethodOrderer.MethodName.class) +class TestOzoneResourceMgr { + @Test + void test01_connectionTest_whenOzoneClientOk_returnsMap() throws Exception { + Map cfg = new HashMap<>(); + cfg.put("ozone.om.service.ids", "ozone1"); + Map expected = Collections.singletonMap("connectivityStatus", Boolean.TRUE); + + try (MockedStatic mocked = Mockito.mockStatic(OzoneClient.class)) { + mocked.when(() -> OzoneClient.connectionTest(Mockito.eq("svc"), Mockito.eq(cfg))).thenReturn(expected); + + Map result = OzoneResourceMgr.connectionTest("svc", cfg); + + assertSame(expected, result); + mocked.verify(() -> OzoneClient.connectionTest(Mockito.eq("svc"), Mockito.eq(cfg))); + } + } + + @Test + void test02_connectionTest_whenOzoneClientThrowsPropagatesHadoopException() { + Map cfg = new HashMap<>(); + HadoopException hadoopFail = new HadoopException("boom", new RuntimeException("root")); + + try (MockedStatic mocked = Mockito.mockStatic(OzoneClient.class)) { + mocked.when(() -> OzoneClient.connectionTest(Mockito.eq("svc"), Mockito.eq(cfg))).thenThrow(hadoopFail); + + assertThrows(HadoopException.class, () -> OzoneResourceMgr.connectionTest("svc", cfg)); + } + } + + @Test + void test03_connectionTest_whenUncheckedException_propagates() { + Map cfg = new HashMap<>(); + cfg.put("k", "v"); + IllegalStateException fail = new IllegalStateException("not-hadoop"); + + try (MockedStatic mocked = Mockito.mockStatic(OzoneClient.class)) { + mocked.when(() -> OzoneClient.connectionTest(Mockito.eq("svc"), Mockito.eq(cfg))).thenThrow(fail); + + assertThrows(IllegalStateException.class, () -> OzoneResourceMgr.connectionTest("svc", cfg)); + } + } + + @Test + void test04_getOzoneResources_whenBucketPrefix_resolvesViaTimedTask() throws Exception { + ResourceLookupContext ctx = new ResourceLookupContext(); + ctx.setUserInput("bpre"); + ctx.setResourceName("bucket"); + ctx.setResources(Collections.singletonMap("volume", Collections.singletonList("v1"))); + + Map cfg = new HashMap<>(); + OzoneClient client = Mockito.mock(OzoneClient.class); + List want = Arrays.asList("b-a", "b-b"); + Mockito.when(client.getBucketList(Mockito.eq("bpre"), Mockito.eq(Collections.singletonList("v1")))).thenReturn(want); + + try (MockedStatic timed = Mockito.mockStatic(TimedEventUtil.class); + MockedConstruction ignored = Mockito.mockConstruction(OzoneConnectionMgr.class, + (mock, context) -> Mockito.when(mock.getOzoneConnection(Mockito.eq("svc"), Mockito.eq("ozone"), Mockito.eq(cfg))).thenReturn(client))) { + timed.when(() -> TimedEventUtil.timedTask(Mockito.any(Callable.class), Mockito.eq(5L), Mockito.eq(TimeUnit.SECONDS))).then(inv -> { + @SuppressWarnings("unchecked") + Callable> callable = inv.getArgument(0); + + return callable.call(); + }); + + assertEquals(want, OzoneResourceMgr.getOzoneResources("svc", "ozone", cfg, ctx)); + } + } + + @Test + void test05_getOzoneResources_whenKeyPrefix_resolvesViaTimedTask() throws Exception { + ResourceLookupContext ctx = new ResourceLookupContext(); + ctx.setUserInput("kpre"); + ctx.setResourceName("key"); + + Map> res = new HashMap<>(); + res.put("volume", Collections.singletonList("v1")); + res.put("bucket", Collections.singletonList("b1")); + ctx.setResources(res); + + Map cfg = new HashMap<>(); + OzoneClient client = Mockito.mock(OzoneClient.class); + Mockito.when(client.getKeyList(Mockito.eq("kpre"), Mockito.eq(Collections.singletonList("v1")), + Mockito.eq(Collections.singletonList("b1")))).thenReturn(Collections.singletonList("k1")); + + try (MockedStatic timed = Mockito.mockStatic(TimedEventUtil.class); + MockedConstruction ignored = Mockito.mockConstruction(OzoneConnectionMgr.class, + (mock, context) -> Mockito.when(mock.getOzoneConnection(Mockito.eq("svc"), Mockito.eq("ozone"), Mockito.eq(cfg))).thenReturn(client))) { + timed.when(() -> TimedEventUtil.timedTask(Mockito.any(Callable.class), Mockito.eq(5L), Mockito.eq(TimeUnit.SECONDS))).then(inv -> { + @SuppressWarnings("unchecked") + Callable> callable = inv.getArgument(0); + + return callable.call(); + }); + + assertEquals(Collections.singletonList("k1"), OzoneResourceMgr.getOzoneResources("svc", "ozone", cfg, ctx)); + } + } + + @Test + void test06_getOzoneResources_whenResourceUnknown_doesNotScheduleCallable() throws Exception { + ResourceLookupContext ctx = new ResourceLookupContext(); + ctx.setUserInput("x"); + ctx.setResourceName("unknown"); + + Map cfg = new HashMap<>(); + + try (MockedStatic timed = Mockito.mockStatic(TimedEventUtil.class); + MockedConstruction ignored = Mockito.mockConstruction(OzoneConnectionMgr.class, + (mock, context) -> Mockito.when(mock.getOzoneConnection(Mockito.anyString(), Mockito.anyString(), Mockito.anyMap())) + .thenReturn(Mockito.mock(OzoneClient.class)))) { + timed.when(() -> TimedEventUtil.timedTask(Mockito.any(Callable.class), Mockito.anyLong(), Mockito.any(TimeUnit.class))) + .thenThrow(new AssertionError("timedTask should not run")); + + assertNull(OzoneResourceMgr.getOzoneResources("svc", "ozone", cfg, ctx)); + } + } + + @Test + void test07_getOzoneResources_whenResourceNameTrimmedNormalizesCase() throws Exception { + ResourceLookupContext ctx = new ResourceLookupContext(); + ctx.setUserInput("vp"); + ctx.setResourceName(" VOLUME "); + + Map cfg = new HashMap<>(); + OzoneClient client = Mockito.mock(OzoneClient.class); + Mockito.when(client.getVolumeList(Mockito.eq("vp"))).thenReturn(Collections.singletonList("only")); + + try (MockedStatic timed = Mockito.mockStatic(TimedEventUtil.class); + MockedConstruction ignored = Mockito.mockConstruction(OzoneConnectionMgr.class, + (mock, context) -> Mockito.when(mock.getOzoneConnection(Mockito.eq("svc"), Mockito.eq("ozone"), Mockito.eq(cfg))).thenReturn(client))) { + timed.when(() -> TimedEventUtil.timedTask(Mockito.any(Callable.class), Mockito.eq(5L), Mockito.eq(TimeUnit.SECONDS))).then(inv -> { + @SuppressWarnings("unchecked") + Callable> callable = inv.getArgument(0); + + return callable.call(); + }); + + assertEquals(Collections.singletonList("only"), OzoneResourceMgr.getOzoneResources("svc", "ozone", cfg, ctx)); + } + } + + @Test + void test08_getOzoneResources_whenServiceNameNull_returnsNull() throws Exception { + ResourceLookupContext ctx = new ResourceLookupContext(); + ctx.setUserInput("vol"); + ctx.setResourceName("volume"); + + Map cfg = new HashMap<>(); + + try (MockedStatic timed = Mockito.mockStatic(TimedEventUtil.class); + MockedConstruction ignored = Mockito.mockConstruction(OzoneConnectionMgr.class)) { + timed.when(() -> TimedEventUtil.timedTask(Mockito.any(Callable.class), Mockito.anyLong(), Mockito.any(TimeUnit.class))) + .thenThrow(new AssertionError("timedTask should not run")); + + assertNull(OzoneResourceMgr.getOzoneResources(null, "ozone", cfg, ctx)); + } + } + + @Test + void test09_getOzoneResources_whenUserInputNull_returnsNull() throws Exception { + ResourceLookupContext ctx = new ResourceLookupContext(); + ctx.setUserInput(null); + ctx.setResourceName("volume"); + + try (MockedStatic timed = Mockito.mockStatic(TimedEventUtil.class); + MockedConstruction ignored = Mockito.mockConstruction(OzoneConnectionMgr.class)) { + timed.when(() -> TimedEventUtil.timedTask(Mockito.any(Callable.class), Mockito.anyLong(), Mockito.any(TimeUnit.class))) + .thenThrow(new AssertionError("timedTask should not run")); + + assertNull(OzoneResourceMgr.getOzoneResources("svc", "ozone", new HashMap<>(), ctx)); + } + } + + @Test + void test10_getOzoneResources_whenUserInputEmptyOnVolume_noCallable() throws Exception { + ResourceLookupContext ctx = new ResourceLookupContext(); + ctx.setUserInput(""); + ctx.setResourceName("volume"); + + Map cfg = new HashMap<>(); + + try (MockedStatic timed = Mockito.mockStatic(TimedEventUtil.class); + MockedConstruction ignored = Mockito.mockConstruction(OzoneConnectionMgr.class, + (mock, context) -> Mockito.when(mock.getOzoneConnection(Mockito.eq("svc"), Mockito.eq("ozone"), Mockito.eq(cfg))) + .thenReturn(Mockito.mock(OzoneClient.class)))) { + timed.when(() -> TimedEventUtil.timedTask(Mockito.any(Callable.class), Mockito.anyLong(), Mockito.any(TimeUnit.class))) + .thenThrow(new AssertionError("timedTask should not run")); + + assertNull(OzoneResourceMgr.getOzoneResources("svc", "ozone", cfg, ctx)); + } + } + + @Test + void test11_getOzoneResources_whenOzoneClientNull_returnsNull() throws Exception { + ResourceLookupContext ctx = new ResourceLookupContext(); + ctx.setUserInput("vol"); + ctx.setResourceName("volume"); + + Map cfg = new HashMap<>(); + + try (MockedStatic timed = Mockito.mockStatic(TimedEventUtil.class); + MockedConstruction ignored = Mockito.mockConstruction(OzoneConnectionMgr.class, + (mock, context) -> Mockito.when(mock.getOzoneConnection(Mockito.anyString(), Mockito.anyString(), Mockito.anyMap())).thenReturn(null))) { + timed.when(() -> TimedEventUtil.timedTask(Mockito.any(Callable.class), Mockito.anyLong(), Mockito.any(TimeUnit.class))) + .thenThrow(new AssertionError("timedTask should not run")); + + assertNull(OzoneResourceMgr.getOzoneResources("svc", "ozone", cfg, ctx)); + } + } + + @Test + void test12_getOzoneResources_whenTimedTaskThrows_propagates() throws Exception { + ResourceLookupContext ctx = new ResourceLookupContext(); + ctx.setUserInput("v"); + ctx.setResourceName("volume"); + + Map cfg = new HashMap<>(); + OzoneClient client = Mockito.mock(OzoneClient.class); + + try (MockedStatic timed = Mockito.mockStatic(TimedEventUtil.class); + MockedConstruction ignored = Mockito.mockConstruction(OzoneConnectionMgr.class, + (mock, context) -> Mockito.when(mock.getOzoneConnection(Mockito.eq("svc"), Mockito.eq("ozone"), Mockito.eq(cfg))).thenReturn(client))) { + timed.when(() -> TimedEventUtil.timedTask(Mockito.any(Callable.class), Mockito.eq(5L), Mockito.eq(TimeUnit.SECONDS))) + .thenThrow(new IllegalStateException("timed-fail")); + + assertThrows(IllegalStateException.class, () -> OzoneResourceMgr.getOzoneResources("svc", "ozone", cfg, ctx)); + } + } + + @Test + void test13_getOzoneResources_whenKeyCallableThrows_propagatesException() throws Exception { + ResourceLookupContext ctx = new ResourceLookupContext(); + ctx.setUserInput("kpre"); + ctx.setResourceName("key"); + + Map> res = new HashMap<>(); + res.put("volume", Collections.singletonList("v1")); + res.put("bucket", Collections.singletonList("b1")); + ctx.setResources(res); + + Map cfg = new HashMap<>(); + OzoneClient client = Mockito.mock(OzoneClient.class); + + Mockito.when(client.getKeyList(Mockito.eq("kpre"), Mockito.eq(Collections.singletonList("v1")), + Mockito.eq(Collections.singletonList("b1")))).thenThrow(new NullPointerException("simulated")); + + try (MockedStatic timed = Mockito.mockStatic(TimedEventUtil.class); + MockedConstruction ignored = Mockito.mockConstruction(OzoneConnectionMgr.class, + (mock, context) -> Mockito.when(mock.getOzoneConnection(Mockito.eq("svc"), Mockito.eq("ozone"), Mockito.eq(cfg))).thenReturn(client))) { + timed.when(() -> TimedEventUtil.timedTask(Mockito.any(Callable.class), Mockito.eq(5L), Mockito.eq(TimeUnit.SECONDS))).then(inv -> { + @SuppressWarnings("unchecked") + Callable> callable = inv.getArgument(0); + + return callable.call(); + }); + + assertThrows(NullPointerException.class, () -> OzoneResourceMgr.getOzoneResources("svc", "ozone", cfg, ctx)); + } + } + + @Test + void test14_getOzoneResources_whenVolumePrefix_resolvesViaTimedTask() throws Exception { + ResourceLookupContext ctx = new ResourceLookupContext(); + ctx.setUserInput("vol"); + ctx.setResourceName("volume"); + + Map cfg = new HashMap<>(); + OzoneClient client = Mockito.mock(OzoneClient.class); + List want = Arrays.asList("v1", "v2"); + Mockito.when(client.getVolumeList(Mockito.eq("vol"))).thenReturn(want); + + try (MockedStatic timed = Mockito.mockStatic(TimedEventUtil.class); + MockedConstruction ignored = Mockito.mockConstruction(OzoneConnectionMgr.class, + (mock, context) -> Mockito.when(mock.getOzoneConnection(Mockito.eq("svc"), Mockito.eq("ozone"), Mockito.eq(cfg))).thenReturn(client))) { + timed.when(() -> TimedEventUtil.timedTask(Mockito.any(Callable.class), Mockito.eq(5L), Mockito.eq(TimeUnit.SECONDS))).then(inv -> { + @SuppressWarnings("unchecked") + Callable> callable = inv.getArgument(0); + + return callable.call(); + }); + + List out = OzoneResourceMgr.getOzoneResources("svc", "ozone", cfg, ctx); + + assertEquals(want, out); + } + } + + @Test + void test15_privateConstructor_rejectsInstantiation() throws Exception { + Constructor ctor = OzoneResourceMgr.class.getDeclaredConstructor(); + ctor.setAccessible(true); + + InvocationTargetException ite = assertThrows(InvocationTargetException.class, ctor::newInstance); + assertTrue(ite.getCause() instanceof UnsupportedOperationException); + } + + @Test + void test16_getOzoneResources_whenResourceNameNull_skipsPrefixes() throws Exception { + ResourceLookupContext ctx = new ResourceLookupContext(); + ctx.setUserInput("vol"); + ctx.setResourceName(null); + + Map cfg = new HashMap<>(); + + try (MockedStatic timed = Mockito.mockStatic(TimedEventUtil.class); + MockedConstruction ignored = Mockito.mockConstruction(OzoneConnectionMgr.class, + (mock, context) -> Mockito.when(mock.getOzoneConnection(Mockito.eq("svc"), Mockito.eq("ozone"), Mockito.eq(cfg))) + .thenReturn(Mockito.mock(OzoneClient.class)))) { + timed.when(() -> TimedEventUtil.timedTask(Mockito.any(Callable.class), Mockito.anyLong(), Mockito.any(TimeUnit.class))) + .thenThrow(new AssertionError("timedTask should not run")); + + assertNull(OzoneResourceMgr.getOzoneResources("svc", "ozone", cfg, ctx)); + } + } + + @Test + void test17_getOzoneResources_whenResourceMapEmpty_skipsInheritedLists() throws Exception { + ResourceLookupContext ctx = new ResourceLookupContext(); + ctx.setUserInput("buck"); + ctx.setResourceName("bucket"); + ctx.setResources(new HashMap<>()); + + Map cfg = new HashMap<>(); + OzoneClient client = Mockito.mock(OzoneClient.class); + Mockito.when(client.getBucketList(Mockito.eq("buck"), Mockito.isNull())).thenReturn(Collections.singletonList("only-b")); + + try (MockedStatic timed = Mockito.mockStatic(TimedEventUtil.class); + MockedConstruction ignored = Mockito.mockConstruction(OzoneConnectionMgr.class, + (mock, context) -> Mockito.when(mock.getOzoneConnection(Mockito.eq("svc"), Mockito.eq("ozone"), Mockito.eq(cfg))).thenReturn(client))) { + timed.when(() -> TimedEventUtil.timedTask(Mockito.any(Callable.class), Mockito.eq(5L), Mockito.eq(TimeUnit.SECONDS))).then(inv -> { + @SuppressWarnings("unchecked") + Callable> callable = inv.getArgument(0); + + return callable.call(); + }); + + assertEquals(Collections.singletonList("only-b"), OzoneResourceMgr.getOzoneResources("svc", "ozone", cfg, ctx)); + } + } + + @Test + void test18_getOzoneResources_whenResourceMapHasNoOzoneKeys_listsStayNull() throws Exception { + ResourceLookupContext ctx = new ResourceLookupContext(); + ctx.setUserInput("bpre"); + ctx.setResourceName("bucket"); + ctx.setResources(Collections.singletonMap("other", Collections.singletonList("orphan"))); + + Map cfg = new HashMap<>(); + OzoneClient client = Mockito.mock(OzoneClient.class); + Mockito.when(client.getBucketList(Mockito.eq("bpre"), Mockito.isNull())).thenReturn(Collections.singletonList("b2")); + + try (MockedStatic timed = Mockito.mockStatic(TimedEventUtil.class); + MockedConstruction ignored = Mockito.mockConstruction(OzoneConnectionMgr.class, + (mock, context) -> Mockito.when(mock.getOzoneConnection(Mockito.eq("svc"), Mockito.eq("ozone"), Mockito.eq(cfg))).thenReturn(client))) { + timed.when(() -> TimedEventUtil.timedTask(Mockito.any(Callable.class), Mockito.eq(5L), Mockito.eq(TimeUnit.SECONDS))).then(inv -> { + @SuppressWarnings("unchecked") + Callable> callable = inv.getArgument(0); + + return callable.call(); + }); + + assertEquals(Collections.singletonList("b2"), OzoneResourceMgr.getOzoneResources("svc", "ozone", cfg, ctx)); + } + } +}