diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/dns/AddDnsServerCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/dns/AddDnsServerCmd.java index 298ddd64a31c..21279b8719fa 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/dns/AddDnsServerCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/dns/AddDnsServerCmd.java @@ -46,7 +46,7 @@ requestHasSensitiveInfo = true, responseHasSensitiveInfo = false, since = "4.23.0", - authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) + authorized = {RoleType.Admin}) public class AddDnsServerCmd extends BaseCmd { ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/dns/DeleteDnsServerCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/dns/DeleteDnsServerCmd.java index 099fc62f354c..cb001f69523c 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/dns/DeleteDnsServerCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/dns/DeleteDnsServerCmd.java @@ -40,7 +40,7 @@ entityType = {DnsServer.class}, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.23.0", - authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) + authorized = {RoleType.Admin}) public class DeleteDnsServerCmd extends BaseAsyncCmd { ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/dns/UpdateDnsServerCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/dns/UpdateDnsServerCmd.java index 6b790fa8ade8..7a84c54dc666 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/dns/UpdateDnsServerCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/dns/UpdateDnsServerCmd.java @@ -41,7 +41,7 @@ entityType = {DnsServer.class}, requestHasSensitiveInfo = true, responseHasSensitiveInfo = false, since = "4.23.0", - authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) + authorized = {RoleType.Admin}) public class UpdateDnsServerCmd extends BaseCmd { ///////////////////////////////////////////////////// diff --git a/engine/schema/src/main/resources/META-INF/db/views/nic_dns_view.sql b/engine/schema/src/main/resources/META-INF/db/views/cloud.nic_dns_view.sql similarity index 100% rename from engine/schema/src/main/resources/META-INF/db/views/nic_dns_view.sql rename to engine/schema/src/main/resources/META-INF/db/views/cloud.nic_dns_view.sql diff --git a/server/src/main/java/org/apache/cloudstack/dns/DnsProviderManagerImpl.java b/server/src/main/java/org/apache/cloudstack/dns/DnsProviderManagerImpl.java index e1ace5d7a3cf..ef55eaf66c19 100644 --- a/server/src/main/java/org/apache/cloudstack/dns/DnsProviderManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/dns/DnsProviderManagerImpl.java @@ -96,6 +96,7 @@ import com.cloud.user.dao.AccountDao; import com.cloud.utils.Pair; import com.cloud.utils.StringUtils; +import com.cloud.utils.UriUtils; import com.cloud.utils.component.ManagerBase; import com.cloud.utils.component.PluggableService; import com.cloud.utils.db.Filter; @@ -107,9 +108,7 @@ import com.cloud.vm.Nic; import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachineManager; -import com.cloud.vm.dao.NicDao; import com.cloud.vm.dao.NicDetailsDao; -import com.cloud.vm.dao.UserVmDao; import com.cloud.vm.dao.VMInstanceDao; @Component @@ -126,10 +125,6 @@ public class DnsProviderManagerImpl extends ManagerBase implements DnsProviderMa @Inject DnsZoneNetworkMapDao dnsZoneNetworkMapDao; @Inject - UserVmDao userVmDao; - @Inject - NicDao nicDao; - @Inject DomainDao domainDao; @Inject DnsZoneJoinDao dnsZoneJoinDao; @@ -162,14 +157,36 @@ private DnsProvider getProviderByType(DnsProviderType type) { throw new CloudRuntimeException("No plugin found for DNS provider type: " + type); } + /** + * Rejects a DNS provider URL that resolves to an illegal address before any provider client + * is given the chance to connect to it. See {@link UriUtils#validateUrl(String)} for the exact rules + * enforced (including the requirement that the URL declares an {@code http}/{@code https} scheme). + * + * @throws InvalidParameterValueException if the URL is blank, fails validation + */ + private void validateDnsServerUrl(String trimmedUrl) { + if (StringUtils.isBlank(trimmedUrl)) { + throw new InvalidParameterValueException("URL cannot be blank."); + } + try { + UriUtils.validateUrl(trimmedUrl); + } catch (IllegalArgumentException e) { + throw new InvalidParameterValueException(e.getMessage()); + } + } + @Override @ActionEvent(eventType = EventTypes.EVENT_DNS_SERVER_ADD, eventDescription = "Adding a DNS Server") public DnsServer addDnsServer(AddDnsServerCmd cmd) { Account caller = CallContext.current().getCallingAccount(); - DnsServer existing = dnsServerDao.findByUrlAndAccount(cmd.getUrl(), caller.getId()); + enforceRootAdminOnly(caller.getId()); + + String dnsUrl = StringUtils.trim(cmd.getUrl()); + validateDnsServerUrl(dnsUrl); + DnsServer existing = dnsServerDao.findByUrlAndAccount(dnsUrl, caller.getId()); if (existing != null) { throw new InvalidParameterValueException( - "This Account already has a DNS server integration for URL: " + cmd.getUrl()); + "This Account already has a DNS server integration for URL: " + dnsUrl); } boolean isDnsPublic = cmd.isPublic(); @@ -190,7 +207,7 @@ public DnsServer addDnsServer(AddDnsServerCmd cmd) { } DnsProviderType type = cmd.getProvider(); - DnsServerVO server = new DnsServerVO(cmd.getName(), cmd.getUrl(), cmd.getPort(), type, + DnsServerVO server = new DnsServerVO(cmd.getName(), dnsUrl, cmd.getPort(), type, cmd.getDnsUserName(), cmd.getDnsApiKey(), isDnsPublic, publicDomainSuffix, cmd.getNameServers(), caller.getAccountId(), caller.getDomainId()); @@ -245,6 +262,8 @@ public DnsServer updateDnsServer(UpdateDnsServerCmd cmd) { } Account caller = CallContext.current().getCallingAccount(); + enforceRootAdminOnly(caller.getId()); + accountMgr.checkAccess(caller, null, true, dnsServer); boolean validationRequired = false; @@ -255,13 +274,15 @@ public DnsServer updateDnsServer(UpdateDnsServerCmd cmd) { dnsServer.setName(cmd.getName()); } - if (cmd.getUrl() != null) { - if (!cmd.getUrl().equals(originalUrl)) { - DnsServer duplicate = dnsServerDao.findByUrlAndAccount(cmd.getUrl(), dnsServer.getAccountId()); + if (StringUtils.isNotBlank(cmd.getUrl())) { + String dnsUrl = StringUtils.trim(cmd.getUrl()); + if (!dnsUrl.equals(originalUrl)) { + validateDnsServerUrl(dnsUrl); + DnsServer duplicate = dnsServerDao.findByUrlAndAccount(dnsUrl, dnsServer.getAccountId()); if (duplicate != null && duplicate.getId() != dnsServer.getId()) { throw new InvalidParameterValueException("Another DNS server with this URL already exists."); } - dnsServer.setUrl(cmd.getUrl()); + dnsServer.setUrl(dnsUrl); validationRequired = true; } } @@ -330,6 +351,7 @@ public boolean deleteDnsServer(DeleteDnsServerCmd cmd) { throw new InvalidParameterValueException(String.format("DNS server with ID: %s not found.", dnsServerId)); } Account caller = CallContext.current().getCallingAccount(); + enforceRootAdminOnly(caller.getId()); accountMgr.checkAccess(caller, null, true, dnsServer); return Transaction.execute((TransactionCallback) status -> { if (cmd.getCleanup()) { @@ -1263,4 +1285,10 @@ public void syncDnsRecordsState(Long instanceId, String dnsRecordUrl, long dnsZo provider.addRecord(dnsServer, dnsZone, recordIpv6); } } + + void enforceRootAdminOnly(Long callerId) { + if (!accountMgr.isRootAdmin(callerId)) { + throw new PermissionDeniedException("This API can only be called by root admin"); + } + } } diff --git a/server/src/test/java/org/apache/cloudstack/dns/DnsProviderManagerImplTest.java b/server/src/test/java/org/apache/cloudstack/dns/DnsProviderManagerImplTest.java index 8ce31d4e7c72..f70d5915a653 100644 --- a/server/src/test/java/org/apache/cloudstack/dns/DnsProviderManagerImplTest.java +++ b/server/src/test/java/org/apache/cloudstack/dns/DnsProviderManagerImplTest.java @@ -42,11 +42,20 @@ import java.util.List; import java.util.Map; +import org.apache.cloudstack.acl.SecurityChecker; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.command.user.dns.AddDnsServerCmd; +import org.apache.cloudstack.api.command.user.dns.AssociateDnsZoneToNetworkCmd; +import org.apache.cloudstack.api.command.user.dns.CreateDnsRecordCmd; import org.apache.cloudstack.api.command.user.dns.CreateDnsZoneCmd; +import org.apache.cloudstack.api.command.user.dns.DeleteDnsRecordCmd; import org.apache.cloudstack.api.command.user.dns.DeleteDnsServerCmd; import org.apache.cloudstack.api.command.user.dns.DisassociateDnsZoneFromNetworkCmd; import org.apache.cloudstack.api.command.user.dns.ListDnsRecordsCmd; +import org.apache.cloudstack.api.command.user.dns.ListDnsServersCmd; +import org.apache.cloudstack.api.command.user.dns.ListDnsZonesCmd; +import org.apache.cloudstack.api.command.user.dns.UpdateDnsServerCmd; import org.apache.cloudstack.api.command.user.dns.UpdateDnsZoneCmd; import org.apache.cloudstack.api.response.DnsRecordResponse; import org.apache.cloudstack.api.response.DnsServerResponse; @@ -83,6 +92,8 @@ import org.springframework.test.util.ReflectionTestUtils; import com.cloud.domain.dao.DomainDao; +import com.cloud.event.ActionEventUtils; +import com.cloud.event.EventTypes; import com.cloud.exception.InvalidParameterValueException; import com.cloud.exception.PermissionDeniedException; import com.cloud.network.dao.NetworkDao; @@ -90,9 +101,13 @@ import com.cloud.user.Account; import com.cloud.user.AccountManager; import com.cloud.user.AccountVO; +import com.cloud.user.dao.AccountDao; +import com.cloud.utils.Pair; +import com.cloud.utils.db.TransactionCallbackWithExceptionNoReturn; import com.cloud.utils.db.Transaction; import com.cloud.utils.db.TransactionCallback; import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.VMInstanceVO; import com.cloud.vm.VirtualMachine; import com.cloud.vm.dao.NicDao; import com.cloud.vm.dao.NicDetailsDao; @@ -174,7 +189,9 @@ public void setUp() throws Exception { manager.setDnsProviders(Collections.singletonList(dnsProviderMock)); doNothing().when(accountMgr).checkAccess(any(Account.class), - nullable(org.apache.cloudstack.acl.SecurityChecker.AccessType.class), eq(true), any()); + nullable(SecurityChecker.AccessType.class), eq(true), any()); + + when(accountMgr.isRootAdmin(callerMock.getId())).thenReturn(true); } @After @@ -424,7 +441,7 @@ public void testDeleteDnsServerWithCleanup() throws Exception { when(cmd.getCleanup()).thenReturn(true); when(dnsServerDao.findById(SERVER_ID)).thenReturn(serverVO); doNothing().when(accountMgr).checkAccess(any(Account.class), - nullable(org.apache.cloudstack.acl.SecurityChecker.AccessType.class), eq(true), any()); + nullable(SecurityChecker.AccessType.class), eq(true), any()); List zones = Collections.singletonList(ZONE_ID); when(dnsZoneDao.findDnsZoneIdsByServerId(SERVER_ID)).thenReturn(zones); @@ -451,7 +468,7 @@ public void testDeleteDnsZoneSuccess() throws Exception { when(dnsZoneDao.findById(ZONE_ID)).thenReturn(zoneVO); when(dnsServerDao.findById(anyLong())).thenReturn(serverVO); doNothing().when(accountMgr).checkAccess(any(Account.class), - nullable(org.apache.cloudstack.acl.SecurityChecker.AccessType.class), eq(true), any()); + nullable(SecurityChecker.AccessType.class), eq(true), any()); when(dnsZoneNetworkMapDao.findByZoneId(ZONE_ID)).thenReturn(null); when(dnsZoneDao.remove(ZONE_ID)).thenReturn(true); @@ -615,8 +632,8 @@ public void testCheckDnsServerPermissionNonOwnerPublicOutsideDomain() { Mockito.doReturn(true).when(serverVO).getPublicServer(); when(serverOwner.getDomainId()).thenReturn(20L); when(callerMock.getDomainId()).thenReturn(DOMAIN_ID); - ReflectionTestUtils.setField(manager, "accountDao", Mockito.mock(com.cloud.user.dao.AccountDao.class)); - com.cloud.user.dao.AccountDao accountDaoMock = (com.cloud.user.dao.AccountDao) ReflectionTestUtils + ReflectionTestUtils.setField(manager, "accountDao", Mockito.mock(AccountDao.class)); + AccountDao accountDaoMock = (AccountDao) ReflectionTestUtils .getField(manager, "accountDao"); when(accountDaoMock.findByIdIncludingRemoved(ACCOUNT_ID)).thenReturn(serverOwner); when(domainDao.isChildDomain(20L, DOMAIN_ID)).thenReturn(false); @@ -659,8 +676,7 @@ public void testStartWithProviders() { @Test(expected = InvalidParameterValueException.class) public void testAssociateZoneToNetworkZoneNotFound() { - org.apache.cloudstack.api.command.user.dns.AssociateDnsZoneToNetworkCmd cmd = mock( - org.apache.cloudstack.api.command.user.dns.AssociateDnsZoneToNetworkCmd.class); + AssociateDnsZoneToNetworkCmd cmd = mock(AssociateDnsZoneToNetworkCmd.class); when(cmd.getDnsZoneId()).thenReturn(ZONE_ID); when(dnsZoneDao.findById(ZONE_ID)).thenReturn(null); manager.associateZoneToNetwork(cmd); @@ -668,8 +684,7 @@ public void testAssociateZoneToNetworkZoneNotFound() { @Test(expected = InvalidParameterValueException.class) public void testAssociateZoneToNetworkNetworkNotFound() { - org.apache.cloudstack.api.command.user.dns.AssociateDnsZoneToNetworkCmd cmd = mock( - org.apache.cloudstack.api.command.user.dns.AssociateDnsZoneToNetworkCmd.class); + AssociateDnsZoneToNetworkCmd cmd = mock(AssociateDnsZoneToNetworkCmd.class); when(cmd.getDnsZoneId()).thenReturn(ZONE_ID); when(cmd.getNetworkId()).thenReturn(NETWORK_ID); when(dnsZoneDao.findById(ZONE_ID)).thenReturn(zoneVO); @@ -680,8 +695,7 @@ public void testAssociateZoneToNetworkNetworkNotFound() { @Test(expected = CloudRuntimeException.class) public void testAssociateZoneToNetworkNonSharedNetwork() { - org.apache.cloudstack.api.command.user.dns.AssociateDnsZoneToNetworkCmd cmd = mock( - org.apache.cloudstack.api.command.user.dns.AssociateDnsZoneToNetworkCmd.class); + AssociateDnsZoneToNetworkCmd cmd = mock(AssociateDnsZoneToNetworkCmd.class); when(cmd.getDnsZoneId()).thenReturn(ZONE_ID); when(cmd.getNetworkId()).thenReturn(NETWORK_ID); when(dnsZoneDao.findById(ZONE_ID)).thenReturn(zoneVO); @@ -694,8 +708,7 @@ public void testAssociateZoneToNetworkNonSharedNetwork() { @Test public void testAssociateZoneToNetworkSuccess() { - org.apache.cloudstack.api.command.user.dns.AssociateDnsZoneToNetworkCmd cmd = mock( - org.apache.cloudstack.api.command.user.dns.AssociateDnsZoneToNetworkCmd.class); + AssociateDnsZoneToNetworkCmd cmd = mock(AssociateDnsZoneToNetworkCmd.class); when(cmd.getDnsZoneId()).thenReturn(ZONE_ID); when(cmd.getNetworkId()).thenReturn(NETWORK_ID); @@ -715,8 +728,7 @@ public void testAssociateZoneToNetworkSuccess() { @Test(expected = InvalidParameterValueException.class) public void testAssociateZoneToNetworkAlreadyAssociated() { - org.apache.cloudstack.api.command.user.dns.AssociateDnsZoneToNetworkCmd cmd = mock( - org.apache.cloudstack.api.command.user.dns.AssociateDnsZoneToNetworkCmd.class); + AssociateDnsZoneToNetworkCmd cmd = mock(AssociateDnsZoneToNetworkCmd.class); when(cmd.getDnsZoneId()).thenReturn(ZONE_ID); when(cmd.getNetworkId()).thenReturn(NETWORK_ID); when(dnsZoneDao.findById(ZONE_ID)).thenReturn(zoneVO); @@ -731,8 +743,7 @@ public void testAssociateZoneToNetworkAlreadyAssociated() { @Test public void testCreateDnsRecordSuccess() throws Exception { - org.apache.cloudstack.api.command.user.dns.CreateDnsRecordCmd cmd = mock( - org.apache.cloudstack.api.command.user.dns.CreateDnsRecordCmd.class); + CreateDnsRecordCmd cmd = mock(CreateDnsRecordCmd.class); when(cmd.getName()).thenReturn("www"); when(cmd.getDnsZoneId()).thenReturn(ZONE_ID); when(cmd.getType()).thenReturn(DnsRecord.RecordType.A); @@ -748,8 +759,7 @@ public void testCreateDnsRecordSuccess() throws Exception { @Test public void testDeleteDnsRecordSuccess() throws Exception { - org.apache.cloudstack.api.command.user.dns.DeleteDnsRecordCmd cmd = mock( - org.apache.cloudstack.api.command.user.dns.DeleteDnsRecordCmd.class); + DeleteDnsRecordCmd cmd = mock(DeleteDnsRecordCmd.class); when(cmd.getDnsZoneId()).thenReturn(ZONE_ID); when(cmd.getName()).thenReturn("www"); when(dnsZoneDao.findById(ZONE_ID)).thenReturn(zoneVO); @@ -769,10 +779,8 @@ public void testConfigure() throws Exception { @Test public void testAddDnsServerSuccess() throws Exception { - org.apache.cloudstack.api.command.user.dns.AddDnsServerCmd cmd = mock( - org.apache.cloudstack.api.command.user.dns.AddDnsServerCmd.class); - when(accountMgr.isRootAdmin(callerMock.getId())).thenReturn(true); - when(cmd.getUrl()).thenReturn("http://newpdns:8081"); + AddDnsServerCmd cmd = mock(AddDnsServerCmd.class); + when(cmd.getUrl()).thenReturn("http://192.0.2.1:8081"); when(cmd.getProvider()).thenReturn(DnsProviderType.PowerDNS); when(dnsServerDao.findByUrlAndAccount(anyString(), anyLong())).thenReturn(null); when(dnsProviderMock.validateAndResolveServer(any())).thenReturn("resolved-id"); @@ -784,11 +792,10 @@ public void testAddDnsServerSuccess() throws Exception { @Test public void testListDnsServers() { - org.apache.cloudstack.api.command.user.dns.ListDnsServersCmd cmd = mock( - org.apache.cloudstack.api.command.user.dns.ListDnsServersCmd.class); + ListDnsServersCmd cmd = mock(ListDnsServersCmd.class); when(domainDao.getDomainParentIds(anyLong())).thenReturn(Collections.emptySet()); List servers = Collections.singletonList(serverVO); - com.cloud.utils.Pair, Integer> searchPair = new com.cloud.utils.Pair<>(servers, 1); + Pair, Integer> searchPair = new Pair<>(servers, 1); when(dnsServerDao.searchDnsServer(any(), anyLong(), any(), any(), any(), any())).thenReturn(searchPair); DnsServerJoinVO joinVO = mock(DnsServerJoinVO.class); @@ -802,8 +809,7 @@ public void testListDnsServers() { @Test public void testUpdateDnsServer() throws Exception { - org.apache.cloudstack.api.command.user.dns.UpdateDnsServerCmd cmd = mock( - org.apache.cloudstack.api.command.user.dns.UpdateDnsServerCmd.class); + UpdateDnsServerCmd cmd = mock(UpdateDnsServerCmd.class); when(cmd.getId()).thenReturn(SERVER_ID); when(cmd.getName()).thenReturn("updated-name"); when(dnsServerDao.findById(SERVER_ID)).thenReturn(serverVO); @@ -815,13 +821,12 @@ public void testUpdateDnsServer() throws Exception { @Test public void testListDnsZones() { - org.apache.cloudstack.api.command.user.dns.ListDnsZonesCmd cmd = mock( - org.apache.cloudstack.api.command.user.dns.ListDnsZonesCmd.class); + ListDnsZonesCmd cmd = mock(ListDnsZonesCmd.class); when(cmd.getId()).thenReturn(null); when(cmd.getDnsServerId()).thenReturn(null); when(dnsServerDao.listDnsServerIdsByAccountId(anyLong())).thenReturn(Collections.emptyList()); List zones = Collections.singletonList(zoneVO); - com.cloud.utils.Pair, Integer> searchPair = new com.cloud.utils.Pair<>(zones, 1); + Pair, Integer> searchPair = new Pair<>(zones, 1); when(dnsZoneDao.searchZones(any(), anyLong(), any(), any(), any(), any())).thenReturn(searchPair); DnsZoneJoinVO joinVO = mock(DnsZoneJoinVO.class); @@ -833,39 +838,60 @@ public void testListDnsZones() { @Test(expected = InvalidParameterValueException.class) public void testAddDnsServerAlreadyExists() { - org.apache.cloudstack.api.command.user.dns.AddDnsServerCmd cmd = mock( - org.apache.cloudstack.api.command.user.dns.AddDnsServerCmd.class); - when(cmd.getUrl()).thenReturn("http://newpdns:8081"); + AddDnsServerCmd cmd = mock(AddDnsServerCmd.class); + when(cmd.getUrl()).thenReturn("http://192.0.2.1:8081"); when(dnsServerDao.findByUrlAndAccount(anyString(), anyLong())).thenReturn(serverVO); manager.addDnsServer(cmd); } @Test - public void testAddDnsServerNormalUser() throws Exception { - org.apache.cloudstack.api.command.user.dns.AddDnsServerCmd cmd = mock( - org.apache.cloudstack.api.command.user.dns.AddDnsServerCmd.class); - when(accountMgr.isRootAdmin(callerMock.getId())).thenReturn(false); - when(accountMgr.isDomainAdmin(callerMock.getId())).thenReturn(false); - when(cmd.getUrl()).thenReturn("http://newpdns:8081"); + public void testAddDnsServerTrimsUrlBeforeDuplicateCheckAndPersistence() throws Exception { + AddDnsServerCmd cmd = mock(AddDnsServerCmd.class); + when(cmd.getUrl()).thenReturn(" http://192.0.2.1:8081 "); + when(cmd.getProvider()).thenReturn(DnsProviderType.PowerDNS); + when(dnsServerDao.findByUrlAndAccount(anyString(), anyLong())).thenReturn(null); + when(dnsProviderMock.validateAndResolveServer(any())).thenReturn("resolved-id"); + when(dnsServerDao.persist(any())).thenReturn(serverVO); + + manager.addDnsServer(cmd); + + verify(dnsServerDao).findByUrlAndAccount(eq("http://192.0.2.1:8081"), anyLong()); + verify(dnsServerDao).persist(Mockito.argThat(s -> "http://192.0.2.1:8081".equals(((DnsServerVO) s).getUrl()))); + } + + @Test(expected = InvalidParameterValueException.class) + public void testAddDnsServerRejectsLoopbackUrl() { + AddDnsServerCmd cmd = mock(AddDnsServerCmd.class); + when(cmd.getUrl()).thenReturn("http://127.0.0.1:8081"); + manager.addDnsServer(cmd); + } + + @Test(expected = InvalidParameterValueException.class) + public void testAddDnsServerRejectsUrlWithoutScheme() { + AddDnsServerCmd cmd = mock(AddDnsServerCmd.class); + when(cmd.getUrl()).thenReturn("192.0.2.1:8081"); + manager.addDnsServer(cmd); + } + + @Test + public void testAddDnsServerAllowsPrivateAddressForRootAdmin() throws Exception { + AddDnsServerCmd cmd = mock(AddDnsServerCmd.class); + when(cmd.getUrl()).thenReturn("http://192.168.1.1:8081"); when(cmd.getProvider()).thenReturn(DnsProviderType.PowerDNS); - when(cmd.getNameServers()).thenReturn(Collections.emptyList()); - when(cmd.isPublic()).thenReturn(true); - when(cmd.getPublicDomainSuffix()).thenReturn("example.com"); when(dnsServerDao.findByUrlAndAccount(anyString(), anyLong())).thenReturn(null); when(dnsProviderMock.validateAndResolveServer(any())).thenReturn("resolved-id"); when(dnsServerDao.persist(any())).thenReturn(serverVO); + DnsServer result = manager.addDnsServer(cmd); assertNotNull(result); - verify(dnsServerDao).persist(Mockito.argThat( - s -> !((DnsServerVO) s).getPublicServer() && ((DnsServerVO) s).getPublicDomainSuffix() == null)); + verify(dnsServerDao).persist(any()); } @Test(expected = InvalidParameterValueException.class) public void testAddDnsServerPublicWithoutSuffixRejected() { - org.apache.cloudstack.api.command.user.dns.AddDnsServerCmd cmd = mock( - org.apache.cloudstack.api.command.user.dns.AddDnsServerCmd.class); + AddDnsServerCmd cmd = mock(AddDnsServerCmd.class); when(accountMgr.isRootAdmin(callerMock.getId())).thenReturn(true); - when(cmd.getUrl()).thenReturn("http://newpdns:8081"); + when(cmd.getUrl()).thenReturn("http://192.0.2.1:8081"); when(cmd.isPublic()).thenReturn(true); when(dnsServerDao.findByUrlAndAccount(anyString(), anyLong())).thenReturn(null); manager.addDnsServer(cmd); @@ -873,8 +899,7 @@ public void testAddDnsServerPublicWithoutSuffixRejected() { @Test(expected = InvalidParameterValueException.class) public void testUpdateDnsServerPublicWithoutSuffixRejected() { - org.apache.cloudstack.api.command.user.dns.UpdateDnsServerCmd cmd = mock( - org.apache.cloudstack.api.command.user.dns.UpdateDnsServerCmd.class); + UpdateDnsServerCmd cmd = mock(UpdateDnsServerCmd.class); when(cmd.getId()).thenReturn(SERVER_ID); when(cmd.isPublic()).thenReturn(true); when(accountMgr.isRootAdmin(callerMock.getId())).thenReturn(true); @@ -884,10 +909,8 @@ public void testUpdateDnsServerPublicWithoutSuffixRejected() { @Test(expected = CloudRuntimeException.class) public void testAddDnsServerValidationFailure() throws Exception { - org.apache.cloudstack.api.command.user.dns.AddDnsServerCmd cmd = mock( - org.apache.cloudstack.api.command.user.dns.AddDnsServerCmd.class); - when(accountMgr.isRootAdmin(callerMock.getId())).thenReturn(true); - when(cmd.getUrl()).thenReturn("http://newpdns:8081"); + AddDnsServerCmd cmd = mock(AddDnsServerCmd.class); + when(cmd.getUrl()).thenReturn("http://192.0.2.1:8081"); when(cmd.getProvider()).thenReturn(DnsProviderType.PowerDNS); when(cmd.getNameServers()).thenReturn(Collections.emptyList()); when(dnsServerDao.findByUrlAndAccount(anyString(), anyLong())).thenReturn(null); @@ -895,12 +918,18 @@ public void testAddDnsServerValidationFailure() throws Exception { manager.addDnsServer(cmd); } + @Test(expected = PermissionDeniedException.class) + public void testAddDnsServerNormalUser() throws Exception { + AddDnsServerCmd cmd = mock(AddDnsServerCmd.class); + when(accountMgr.isRootAdmin(callerMock.getId())).thenReturn(false); + manager.addDnsServer(cmd); + } + @Test(expected = InvalidParameterValueException.class) public void testUpdateDnsServerUrlDuplicate() { - org.apache.cloudstack.api.command.user.dns.UpdateDnsServerCmd cmd = mock( - org.apache.cloudstack.api.command.user.dns.UpdateDnsServerCmd.class); + UpdateDnsServerCmd cmd = mock(UpdateDnsServerCmd.class); when(cmd.getId()).thenReturn(SERVER_ID); - when(cmd.getUrl()).thenReturn("http://duplicate:8081"); + when(cmd.getUrl()).thenReturn("http://192.0.2.1:8081"); DnsServerVO existingServer = mock(DnsServerVO.class); when(existingServer.getId()).thenReturn(SERVER_ID + 1); // Different ID implies duplicate @@ -911,12 +940,56 @@ public void testUpdateDnsServerUrlDuplicate() { manager.updateDnsServer(cmd); } + @Test(expected = InvalidParameterValueException.class) + public void testUpdateDnsServerRejectsLoopbackUrl() { + UpdateDnsServerCmd cmd = mock(UpdateDnsServerCmd.class); + when(cmd.getId()).thenReturn(SERVER_ID); + when(cmd.getUrl()).thenReturn("http://127.0.0.1:8081"); + when(dnsServerDao.findById(SERVER_ID)).thenReturn(serverVO); + Mockito.doReturn("http://original:8081").when(serverVO).getUrl(); + + manager.updateDnsServer(cmd); + } + + @Test + public void testUpdateDnsServerAllowsPrivateAddressForRootAdmin() throws Exception { + UpdateDnsServerCmd cmd = mock(UpdateDnsServerCmd.class); + when(cmd.getId()).thenReturn(SERVER_ID); + when(cmd.getUrl()).thenReturn("http://192.168.1.1:8081"); + when(dnsServerDao.findById(SERVER_ID)).thenReturn(serverVO); + Mockito.doReturn("http://original:8081").when(serverVO).getUrl(); + Mockito.doReturn(DnsProviderType.PowerDNS).when(serverVO).getProviderType(); + when(dnsServerDao.findByUrlAndAccount(anyString(), anyLong())).thenReturn(null); + doNothing().when(dnsProviderMock).validate(any()); + when(dnsServerDao.update(anyLong(), any())).thenReturn(true); + + DnsServer result = manager.updateDnsServer(cmd); + assertNotNull(result); + verify(dnsProviderMock).validate(any()); + } + + @Test + public void testUpdateDnsServerTreatsWhitespaceOnlyUrlChangeAsUnchanged() throws Exception { + UpdateDnsServerCmd cmd = mock(UpdateDnsServerCmd.class); + Integer unchangedPort = serverVO.getPort(); + when(cmd.getId()).thenReturn(SERVER_ID); + when(cmd.getUrl()).thenReturn(" http://192.0.2.1:8081 "); + when(cmd.getPort()).thenReturn(unchangedPort); + when(dnsServerDao.findById(SERVER_ID)).thenReturn(serverVO); + Mockito.doReturn("http://192.0.2.1:8081").when(serverVO).getUrl(); + when(dnsServerDao.update(anyLong(), any())).thenReturn(true); + + DnsServer result = manager.updateDnsServer(cmd); + assertNotNull(result); + verify(dnsProviderMock, never()).validate(any()); + verify(serverVO, never()).setUrl(anyString()); + } + @Test public void testUpdateDnsServerUrlValid() throws Exception { - org.apache.cloudstack.api.command.user.dns.UpdateDnsServerCmd cmd = mock( - org.apache.cloudstack.api.command.user.dns.UpdateDnsServerCmd.class); + UpdateDnsServerCmd cmd = mock(UpdateDnsServerCmd.class); when(cmd.getId()).thenReturn(SERVER_ID); - when(cmd.getUrl()).thenReturn("http://new-url:8081"); + when(cmd.getUrl()).thenReturn("http://192.0.2.1:8081"); when(dnsServerDao.findById(SERVER_ID)).thenReturn(serverVO); Mockito.doReturn("http://original:8081").when(serverVO).getUrl(); @@ -932,8 +1005,7 @@ public void testUpdateDnsServerUrlValid() throws Exception { @Test(expected = InvalidParameterValueException.class) public void testUpdateDnsServerValidationException() throws Exception { - org.apache.cloudstack.api.command.user.dns.UpdateDnsServerCmd cmd = mock( - org.apache.cloudstack.api.command.user.dns.UpdateDnsServerCmd.class); + UpdateDnsServerCmd cmd = mock(UpdateDnsServerCmd.class); when(cmd.getId()).thenReturn(SERVER_ID); when(cmd.getDnsApiKey()).thenReturn("new-api-key"); @@ -950,10 +1022,10 @@ public void testUpdateDnsServerValidationException() throws Exception { @Test public void testVmLifecycleSubscriberStateUnchanged() { DnsProviderManagerImpl.VmLifecycleSubscriber subscriber = manager.new VmLifecycleSubscriber(); - java.util.Map event = new java.util.HashMap<>(); - event.put(org.apache.cloudstack.api.ApiConstants.OLD_STATE, com.cloud.vm.VirtualMachine.State.Running); - event.put(org.apache.cloudstack.api.ApiConstants.NEW_STATE, com.cloud.vm.VirtualMachine.State.Running); - event.put(org.apache.cloudstack.api.ApiConstants.INSTANCE_ID, 10L); + Map event = new HashMap<>(); + event.put(ApiConstants.OLD_STATE, VirtualMachine.State.Running); + event.put(ApiConstants.NEW_STATE, VirtualMachine.State.Running); + event.put(ApiConstants.INSTANCE_ID, 10L); subscriber.onPublishMessage("sender", "subject", event); verify(vmInstanceDao, never()).findByIdIncludingRemoved(anyLong()); @@ -962,10 +1034,10 @@ public void testVmLifecycleSubscriberStateUnchanged() { @Test public void testVmLifecycleSubscriberRunning() { DnsProviderManagerImpl.VmLifecycleSubscriber subscriber = manager.new VmLifecycleSubscriber(); - java.util.Map event = new java.util.HashMap<>(); - event.put(org.apache.cloudstack.api.ApiConstants.OLD_STATE, com.cloud.vm.VirtualMachine.State.Starting); - event.put(org.apache.cloudstack.api.ApiConstants.NEW_STATE, com.cloud.vm.VirtualMachine.State.Running); - event.put(org.apache.cloudstack.api.ApiConstants.INSTANCE_ID, 12L); + Map event = new HashMap<>(); + event.put(ApiConstants.OLD_STATE, VirtualMachine.State.Starting); + event.put(ApiConstants.NEW_STATE, VirtualMachine.State.Running); + event.put(ApiConstants.INSTANCE_ID, 12L); // Expect handleVmEvent to be called, which accesses // vmInstanceDao.findByIdIncludingRemoved @@ -978,10 +1050,10 @@ public void testVmLifecycleSubscriberRunning() { @Test public void testVmLifecycleSubscriberDestroyed() { DnsProviderManagerImpl.VmLifecycleSubscriber subscriber = manager.new VmLifecycleSubscriber(); - java.util.Map event = new java.util.HashMap<>(); - event.put(org.apache.cloudstack.api.ApiConstants.OLD_STATE, com.cloud.vm.VirtualMachine.State.Running); - event.put(org.apache.cloudstack.api.ApiConstants.NEW_STATE, VirtualMachine.State.Destroyed); - event.put(org.apache.cloudstack.api.ApiConstants.INSTANCE_ID, 15L); + Map event = new HashMap<>(); + event.put(ApiConstants.OLD_STATE, VirtualMachine.State.Running); + event.put(ApiConstants.NEW_STATE, VirtualMachine.State.Destroyed); + event.put(ApiConstants.INSTANCE_ID, 15L); when(nicDnsJoinDao.listIncludingRemovedByVmId(15L)).thenReturn(null); subscriber.onPublishMessage("sender", "subject", event); verify(nicDnsJoinDao, times(1)).listIncludingRemovedByVmId(15L); @@ -990,10 +1062,10 @@ public void testVmLifecycleSubscriberDestroyed() { @Test public void testVmLifecycleSubscriberUnsupportedState() { DnsProviderManagerImpl.VmLifecycleSubscriber subscriber = manager.new VmLifecycleSubscriber(); - java.util.Map event = new java.util.HashMap<>(); - event.put(org.apache.cloudstack.api.ApiConstants.OLD_STATE, com.cloud.vm.VirtualMachine.State.Running); - event.put(org.apache.cloudstack.api.ApiConstants.NEW_STATE, com.cloud.vm.VirtualMachine.State.Starting); - event.put(org.apache.cloudstack.api.ApiConstants.INSTANCE_ID, 20L); + Map event = new HashMap<>(); + event.put(ApiConstants.OLD_STATE, VirtualMachine.State.Running); + event.put(ApiConstants.NEW_STATE, VirtualMachine.State.Starting); + event.put(ApiConstants.INSTANCE_ID, 20L); subscriber.onPublishMessage("sender", "subject", event); verify(vmInstanceDao, never()).findByIdIncludingRemoved(anyLong()); @@ -1011,10 +1083,10 @@ public void testVmLifecycleSubscriberException() { @Test public void testNicLifecycleSubscriberCreate() { DnsProviderManagerImpl.NicLifecycleSubscriber subscriber = manager.new NicLifecycleSubscriber(); - java.util.Map event = new java.util.HashMap<>(); - event.put(org.apache.cloudstack.api.ApiConstants.EVENT_TYPE, com.cloud.event.EventTypes.EVENT_NIC_CREATE); - event.put(org.apache.cloudstack.api.ApiConstants.NIC_ID, 100L); - event.put(org.apache.cloudstack.api.ApiConstants.INSTANCE_ID, 200L); + Map event = new HashMap<>(); + event.put(ApiConstants.EVENT_TYPE, EventTypes.EVENT_NIC_CREATE); + event.put(ApiConstants.NIC_ID, 100L); + event.put(ApiConstants.INSTANCE_ID, 200L); when(vmInstanceDao.findById(200L)).thenReturn(null); // Short circuits handleNicEvent @@ -1025,10 +1097,10 @@ public void testNicLifecycleSubscriberCreate() { @Test public void testNicLifecycleSubscriberDelete() { DnsProviderManagerImpl.NicLifecycleSubscriber subscriber = manager.new NicLifecycleSubscriber(); - java.util.Map event = new java.util.HashMap<>(); - event.put(org.apache.cloudstack.api.ApiConstants.EVENT_TYPE, com.cloud.event.EventTypes.EVENT_NIC_DELETE); - event.put(org.apache.cloudstack.api.ApiConstants.NIC_ID, 101L); - event.put(org.apache.cloudstack.api.ApiConstants.INSTANCE_ID, 201L); + Map event = new HashMap<>(); + event.put(ApiConstants.EVENT_TYPE, EventTypes.EVENT_NIC_DELETE); + event.put(ApiConstants.NIC_ID, 101L); + event.put(ApiConstants.INSTANCE_ID, 201L); when(nicDnsJoinDao.findByIdIncludingRemoved(101L)).thenReturn(null); subscriber.onPublishMessage("sender", "subject", event); verify(nicDnsJoinDao, times(1)).findByIdIncludingRemoved(101L); @@ -1037,8 +1109,8 @@ public void testNicLifecycleSubscriberDelete() { @Test public void testNicLifecycleSubscriberMissingData() { DnsProviderManagerImpl.NicLifecycleSubscriber subscriber = manager.new NicLifecycleSubscriber(); - java.util.Map event = new java.util.HashMap<>(); - event.put(org.apache.cloudstack.api.ApiConstants.EVENT_TYPE, com.cloud.event.EventTypes.EVENT_NIC_CREATE); + Map event = new HashMap<>(); + event.put(ApiConstants.EVENT_TYPE, EventTypes.EVENT_NIC_CREATE); // Missing NIC_ID and INSTANCE_ID subscriber.onPublishMessage("sender", "subject", event); @@ -1048,10 +1120,10 @@ public void testNicLifecycleSubscriberMissingData() { @Test public void testNicLifecycleSubscriberUnsupportedEvent() { DnsProviderManagerImpl.NicLifecycleSubscriber subscriber = manager.new NicLifecycleSubscriber(); - java.util.Map event = new java.util.HashMap<>(); - event.put(org.apache.cloudstack.api.ApiConstants.EVENT_TYPE, "unsupported-event"); - event.put(org.apache.cloudstack.api.ApiConstants.NIC_ID, 102L); - event.put(org.apache.cloudstack.api.ApiConstants.INSTANCE_ID, 202L); + Map event = new HashMap<>(); + event.put(ApiConstants.EVENT_TYPE, "unsupported-event"); + event.put(ApiConstants.NIC_ID, 102L); + event.put(ApiConstants.INSTANCE_ID, 202L); subscriber.onPublishMessage("sender", "subject", event); verify(vmInstanceDao, never()).findById(anyLong()); @@ -1085,8 +1157,7 @@ public void testPrepareDnsRecordUrlTrimsSubdomain() { @Test public void testCreateDnsRecordAlreadyExistsThrowsCloudRuntimeException() throws Exception { - org.apache.cloudstack.api.command.user.dns.CreateDnsRecordCmd cmd = mock( - org.apache.cloudstack.api.command.user.dns.CreateDnsRecordCmd.class); + CreateDnsRecordCmd cmd = mock(CreateDnsRecordCmd.class); when(cmd.getName()).thenReturn("www"); when(cmd.getDnsZoneId()).thenReturn(ZONE_ID); when(cmd.getType()).thenReturn(DnsRecord.RecordType.A); @@ -1105,8 +1176,7 @@ public void testCreateDnsRecordAlreadyExistsThrowsCloudRuntimeException() throws @Test public void testDeleteDnsRecordProviderReturnsNullReturnsFalse() throws Exception { - org.apache.cloudstack.api.command.user.dns.DeleteDnsRecordCmd cmd = mock( - org.apache.cloudstack.api.command.user.dns.DeleteDnsRecordCmd.class); + DeleteDnsRecordCmd cmd = mock(DeleteDnsRecordCmd.class); when(cmd.getDnsZoneId()).thenReturn(ZONE_ID); when(cmd.getName()).thenReturn("www"); when(dnsZoneDao.findById(ZONE_ID)).thenReturn(zoneVO); @@ -1151,7 +1221,7 @@ public void testSyncDnsRecordsStateOnlyIpv4AddsAAndDeletesAAAA() throws Exceptio @Test public void testHandleVmCreateEventFoundButNoActiveNics() throws DnsProviderException { - com.cloud.vm.VMInstanceVO instanceMock = mock(com.cloud.vm.VMInstanceVO.class); + VMInstanceVO instanceMock = mock(VMInstanceVO.class); when(vmInstanceDao.findById(30L)).thenReturn(instanceMock); when(nicDnsJoinDao.listActiveByVmId(30L)).thenReturn(Collections.emptyList()); @@ -1163,8 +1233,7 @@ public void testHandleVmCreateEventFoundButNoActiveNics() throws DnsProviderExce @Test public void testHandleVmDestroyEventNicWithNullDnsUrlIsSkipped() throws DnsProviderException { - NicDnsJoinVO nicMock = - mock(NicDnsJoinVO.class); + NicDnsJoinVO nicMock = mock(NicDnsJoinVO.class); when(nicMock.getNicDnsName()).thenReturn(null); when(nicDnsJoinDao.listIncludingRemovedByVmId(31L)) .thenReturn(Collections.singletonList(nicMock)); @@ -1176,8 +1245,7 @@ public void testHandleVmDestroyEventNicWithNullDnsUrlIsSkipped() throws DnsProvi @Test public void testHandleVmDestroyEventWithValidDnsUrlTriggersCleanup() throws Exception { - NicDnsJoinVO nicMock = - mock(NicDnsJoinVO.class); + NicDnsJoinVO nicMock = mock(NicDnsJoinVO.class); when(nicMock.getNicDnsName()).thenReturn("myvm.example.com"); when(nicMock.getDnsZoneId()).thenReturn(ZONE_ID); when(nicDnsJoinDao.listIncludingRemovedByVmId(32L)) @@ -1188,12 +1256,12 @@ public void testHandleVmDestroyEventWithValidDnsUrlTriggersCleanup() throws Exce when(nicDnsJoinDao.listActiveByVmIdZoneAndDnsRecord(eq(32L), eq(ZONE_ID), anyString())) .thenReturn(Collections.emptyList()); - try (MockedStatic txMock = - Mockito.mockStatic(com.cloud.utils.db.Transaction.class)) { - txMock.when(() -> com.cloud.utils.db.Transaction.execute( - any(com.cloud.utils.db.TransactionCallbackWithExceptionNoReturn.class))) + try (MockedStatic txMock = + Mockito.mockStatic(Transaction.class)) { + txMock.when(() -> Transaction.execute( + any(TransactionCallbackWithExceptionNoReturn.class))) .thenAnswer(invocation -> { - com.cloud.utils.db.TransactionCallbackWithExceptionNoReturn cb = + TransactionCallbackWithExceptionNoReturn cb = invocation.getArgument(0); try { cb.doInTransactionWithoutResult(null); @@ -1205,14 +1273,14 @@ public void testHandleVmDestroyEventWithValidDnsUrlTriggersCleanup() throws Exce manager.handleVmDestroyEvent(32L); - verify(nicDetailsDao).removeDetail(nicMock.getId(), org.apache.cloudstack.api.ApiConstants.NIC_DNS_NAME); + verify(nicDetailsDao).removeDetail(nicMock.getId(), ApiConstants.NIC_DNS_NAME); verify(dnsProviderMock, times(2)).deleteRecord(eq(serverVO), eq(zoneVO), any(DnsRecord.class)); } } @Test public void testHandleNicPlugVmNotRunningExitsEarly() throws DnsProviderException { - com.cloud.vm.VMInstanceVO instanceMock = mock(com.cloud.vm.VMInstanceVO.class); + VMInstanceVO instanceMock = mock(VMInstanceVO.class); when(instanceMock.getState()).thenReturn(VirtualMachine.State.Destroyed); when(vmInstanceDao.findById(33L)).thenReturn(instanceMock); manager.handleNicPlug(33L, 500L); @@ -1222,8 +1290,7 @@ public void testHandleNicPlugVmNotRunningExitsEarly() throws DnsProviderExceptio @Test public void testHandleNicUnplugNicHasValidDnsUrlTriggersSyncCleanup() throws Exception { - NicDnsJoinVO nicMock = - mock(NicDnsJoinVO.class); + NicDnsJoinVO nicMock = mock(NicDnsJoinVO.class); when(nicMock.getNicDnsName()).thenReturn("myvm.example.com"); when(nicMock.getDnsZoneId()).thenReturn(ZONE_ID); when(nicDnsJoinDao.findByIdIncludingRemoved(600L)).thenReturn(nicMock); @@ -1233,12 +1300,12 @@ public void testHandleNicUnplugNicHasValidDnsUrlTriggersSyncCleanup() throws Exc when(nicDnsJoinDao.listActiveByVmIdZoneAndDnsRecord(eq(34L), eq(ZONE_ID), anyString())) .thenReturn(Collections.emptyList()); - try (MockedStatic txMock = - Mockito.mockStatic(com.cloud.utils.db.Transaction.class)) { - txMock.when(() -> com.cloud.utils.db.Transaction.execute( - any(com.cloud.utils.db.TransactionCallbackWithExceptionNoReturn.class))) + try (MockedStatic txMock = + Mockito.mockStatic(Transaction.class)) { + txMock.when(() -> Transaction.execute( + any(TransactionCallbackWithExceptionNoReturn.class))) .thenAnswer(invocation -> { - com.cloud.utils.db.TransactionCallbackWithExceptionNoReturn cb = + TransactionCallbackWithExceptionNoReturn cb = invocation.getArgument(0); try { cb.doInTransactionWithoutResult(null); @@ -1250,14 +1317,14 @@ public void testHandleNicUnplugNicHasValidDnsUrlTriggersSyncCleanup() throws Exc manager.handleNicUnplug(34L, 600L); - verify(nicDetailsDao).removeDetail(600L, org.apache.cloudstack.api.ApiConstants.NIC_DNS_NAME); + verify(nicDetailsDao).removeDetail(600L, ApiConstants.NIC_DNS_NAME); verify(dnsProviderMock, times(2)).deleteRecord(eq(serverVO), eq(zoneVO), any(DnsRecord.class)); } } @Test public void testHandleVmHostnameChangedVmFoundButNoActiveNicsExitsEarly() throws DnsProviderException { - com.cloud.vm.VMInstanceVO instanceMock = mock(com.cloud.vm.VMInstanceVO.class); + VMInstanceVO instanceMock = mock(VMInstanceVO.class); when(vmInstanceDao.findById(35L)).thenReturn(instanceMock); when(nicDnsJoinDao.listActiveByVmId(35L)).thenReturn(Collections.emptyList()); @@ -1269,14 +1336,13 @@ public void testHandleVmHostnameChangedVmFoundButNoActiveNicsExitsEarly() throws @Test public void testIsDnsCollisionReturnsTrueForDifferentInstance() { - NicDnsJoinVO existing = - mock(NicDnsJoinVO.class); + NicDnsJoinVO existing = mock(NicDnsJoinVO.class); when(existing.getInstanceId()).thenReturn(99L); when(nicDnsJoinDao.findActiveByDnsRecordAndZone(ZONE_ID, "vm.example.com")).thenReturn(existing); - try (MockedStatic aeMock = - Mockito.mockStatic(com.cloud.event.ActionEventUtils.class)) { - aeMock.when(() -> com.cloud.event.ActionEventUtils.onActionEvent( + try (MockedStatic aeMock = + Mockito.mockStatic(ActionEventUtils.class)) { + aeMock.when(() -> ActionEventUtils.onActionEvent( anyLong(), anyLong(), anyLong(), anyString(), anyString(), anyLong(), anyString())) .thenReturn(1L); boolean result = (boolean) ReflectionTestUtils.invokeMethod( @@ -1306,8 +1372,8 @@ public void testIsDnsCollisionReturnsFalseWhenSameInstance() { @Test public void testHandleNicPlugRunningVmNicFoundButZoneNullExitsGracefully() throws DnsProviderException { - com.cloud.vm.VMInstanceVO instanceMock = mock(com.cloud.vm.VMInstanceVO.class); - when(instanceMock.getState()).thenReturn(com.cloud.vm.VirtualMachine.State.Running); + VMInstanceVO instanceMock = mock(VMInstanceVO.class); + when(instanceMock.getState()).thenReturn(VirtualMachine.State.Running); when(vmInstanceDao.findById(40L)).thenReturn(instanceMock); NicDnsJoinVO nicMock = @@ -1325,7 +1391,7 @@ public void testHandleNicPlugRunningVmNicFoundButZoneNullExitsGracefully() throw @Test public void testHandleVmHostnameChangedNonEmptyNicsAllZonesMissingSkipsTransactions() throws DnsProviderException { - com.cloud.vm.VMInstanceVO instanceMock = mock(com.cloud.vm.VMInstanceVO.class); + VMInstanceVO instanceMock = mock(VMInstanceVO.class); when(vmInstanceDao.findById(41L)).thenReturn(instanceMock); NicDnsJoinVO nicMock = @@ -1342,7 +1408,7 @@ public void testHandleVmHostnameChangedNonEmptyNicsAllZonesMissingSkipsTransacti @Test public void testHandleVmCreateEventNonEmptyNicsAllZonesMissingSkipsSync() throws DnsProviderException { - com.cloud.vm.VMInstanceVO instanceMock = mock(com.cloud.vm.VMInstanceVO.class); + VMInstanceVO instanceMock = mock(VMInstanceVO.class); when(vmInstanceDao.findById(42L)).thenReturn(instanceMock); NicDnsJoinVO nicMock = @@ -1369,11 +1435,10 @@ public void testVmRenameSubscriberInvalidPayloadIsSwallowed() { public void testVmRenameSubscriberMissingInstanceIdSwallowsNpe() { DnsProviderManagerImpl.VmRenameActionSubscriber subscriber = manager.new VmRenameActionSubscriber(); - java.util.Map event = new java.util.HashMap<>(); - event.put(org.apache.cloudstack.api.ApiConstants.EVENT_TYPE, - com.cloud.event.EventTypes.EVENT_VM_UPDATE); - event.put(org.apache.cloudstack.api.ApiConstants.HOST_NAME, "newvm"); - event.put(org.apache.cloudstack.api.ApiConstants.OLD_HOST_NAME, "oldvm"); + Map event = new HashMap<>(); + event.put(ApiConstants.EVENT_TYPE, EventTypes.EVENT_VM_UPDATE); + event.put(ApiConstants.HOST_NAME, "newvm"); + event.put(ApiConstants.OLD_HOST_NAME, "oldvm"); // INSTANCE_ID intentionally absent → (long) null → NullPointerException → caught internally subscriber.onPublishMessage("sender", "topic", event); verify(vmInstanceDao, never()).findById(anyLong()); @@ -1390,7 +1455,7 @@ public void testHandleVmCreateEventInstanceNullExitsEarly() throws DnsProviderEx @Test public void testHandleVmCreateEventFullSyncNoCollision() throws Exception { - com.cloud.vm.VMInstanceVO instanceMock = mock(com.cloud.vm.VMInstanceVO.class); + VMInstanceVO instanceMock = mock(VMInstanceVO.class); when(instanceMock.getHostName()).thenReturn("myvm"); when(vmInstanceDao.findById(51L)).thenReturn(instanceMock); @@ -1408,12 +1473,12 @@ public void testHandleVmCreateEventFullSyncNoCollision() throws Exception { when(nicDnsJoinDao.listActiveByVmIdZoneAndDnsRecord(eq(51L), eq(ZONE_ID), anyString())) .thenReturn(Collections.emptyList()); - try (MockedStatic txMock = - Mockito.mockStatic(com.cloud.utils.db.Transaction.class)) { - txMock.when(() -> com.cloud.utils.db.Transaction.execute( - any(com.cloud.utils.db.TransactionCallbackWithExceptionNoReturn.class))) + try (MockedStatic txMock = + Mockito.mockStatic(Transaction.class)) { + txMock.when(() -> Transaction.execute( + any(TransactionCallbackWithExceptionNoReturn.class))) .thenAnswer(invocation -> { - com.cloud.utils.db.TransactionCallbackWithExceptionNoReturn cb = + TransactionCallbackWithExceptionNoReturn cb = invocation.getArgument(0); try { cb.doInTransactionWithoutResult(null); } catch (Exception e) { throw new RuntimeException(e); } @@ -1423,14 +1488,14 @@ public void testHandleVmCreateEventFullSyncNoCollision() throws Exception { manager.handleVmCreateEvent(51L); verify(nicDetailsDao).addDetail(anyLong(), - eq(org.apache.cloudstack.api.ApiConstants.NIC_DNS_NAME), anyString(), eq(true)); + eq(ApiConstants.NIC_DNS_NAME), anyString(), eq(true)); verify(dnsProviderMock, times(2)).deleteRecord(eq(serverVO), eq(zoneVO), any(DnsRecord.class)); } } @Test public void testHandleVmCreateEventCollisionSkipsAddDetail() throws Exception { - com.cloud.vm.VMInstanceVO instanceMock = mock(com.cloud.vm.VMInstanceVO.class); + VMInstanceVO instanceMock = mock(VMInstanceVO.class); when(instanceMock.getHostName()).thenReturn("myvm"); when(vmInstanceDao.findById(52L)).thenReturn(instanceMock); @@ -1447,17 +1512,17 @@ public void testHandleVmCreateEventCollisionSkipsAddDetail() throws Exception { when(colliding.getInstanceId()).thenReturn(999L); when(nicDnsJoinDao.findActiveByDnsRecordAndZone(eq(ZONE_ID), anyString())).thenReturn(colliding); - try (MockedStatic txMock = - Mockito.mockStatic(com.cloud.utils.db.Transaction.class); - MockedStatic aeMock = - Mockito.mockStatic(com.cloud.event.ActionEventUtils.class)) { - aeMock.when(() -> com.cloud.event.ActionEventUtils.onActionEvent( + try (MockedStatic txMock = + Mockito.mockStatic(Transaction.class); + MockedStatic aeMock = + Mockito.mockStatic(ActionEventUtils.class)) { + aeMock.when(() -> ActionEventUtils.onActionEvent( anyLong(), anyLong(), anyLong(), anyString(), anyString(), anyLong(), anyString())) .thenReturn(1L); - txMock.when(() -> com.cloud.utils.db.Transaction.execute( - any(com.cloud.utils.db.TransactionCallbackWithExceptionNoReturn.class))) + txMock.when(() -> Transaction.execute( + any(TransactionCallbackWithExceptionNoReturn.class))) .thenAnswer(invocation -> { - com.cloud.utils.db.TransactionCallbackWithExceptionNoReturn cb = + TransactionCallbackWithExceptionNoReturn cb = invocation.getArgument(0); try { cb.doInTransactionWithoutResult(null); } catch (Exception e) { throw new RuntimeException(e); } @@ -1482,7 +1547,7 @@ public void testHandleVmHostnameChangedInstanceNullExitsEarly() throws DnsProvid @Test public void testHandleVmHostnameChangedFqdnUnchangedSkipsNic() throws DnsProviderException { - com.cloud.vm.VMInstanceVO instanceMock = mock(com.cloud.vm.VMInstanceVO.class); + VMInstanceVO instanceMock = mock(VMInstanceVO.class); when(vmInstanceDao.findById(61L)).thenReturn(instanceMock); NicDnsJoinVO nicMock = @@ -1503,7 +1568,7 @@ public void testHandleVmHostnameChangedFqdnUnchangedSkipsNic() throws DnsProvide @Test public void testHandleVmHostnameChangedFullRenamePath() throws Exception { - com.cloud.vm.VMInstanceVO instanceMock = mock(com.cloud.vm.VMInstanceVO.class); + VMInstanceVO instanceMock = mock(VMInstanceVO.class); when(vmInstanceDao.findById(62L)).thenReturn(instanceMock); NicDnsJoinVO nicMock = @@ -1521,12 +1586,12 @@ public void testHandleVmHostnameChangedFullRenamePath() throws Exception { when(nicDnsJoinDao.listActiveByVmIdZoneAndDnsRecord(eq(62L), eq(ZONE_ID), anyString())) .thenReturn(Collections.emptyList()); - try (MockedStatic txMock = - Mockito.mockStatic(com.cloud.utils.db.Transaction.class)) { - txMock.when(() -> com.cloud.utils.db.Transaction.execute( - any(com.cloud.utils.db.TransactionCallbackWithExceptionNoReturn.class))) + try (MockedStatic txMock = + Mockito.mockStatic(Transaction.class)) { + txMock.when(() -> Transaction.execute( + any(TransactionCallbackWithExceptionNoReturn.class))) .thenAnswer(invocation -> { - com.cloud.utils.db.TransactionCallbackWithExceptionNoReturn cb = + TransactionCallbackWithExceptionNoReturn cb = invocation.getArgument(0); try { cb.doInTransactionWithoutResult(null); } catch (Exception e) { throw new RuntimeException(e); } @@ -1537,10 +1602,10 @@ public void testHandleVmHostnameChangedFullRenamePath() throws Exception { // Tx1: old URL removed from nic_details verify(nicDetailsDao).removeDetail(anyLong(), - eq(org.apache.cloudstack.api.ApiConstants.NIC_DNS_NAME)); + eq(ApiConstants.NIC_DNS_NAME)); // Tx2: new URL written to nic_details verify(nicDetailsDao).addDetail(anyLong(), - eq(org.apache.cloudstack.api.ApiConstants.NIC_DNS_NAME), anyString(), eq(true)); + eq(ApiConstants.NIC_DNS_NAME), anyString(), eq(true)); // deleteRecord called for both old-sync (A+AAAA) and new-sync (A+AAAA) = 4 total verify(dnsProviderMock, times(4)).deleteRecord(eq(serverVO), eq(zoneVO), any(DnsRecord.class)); } @@ -1548,7 +1613,7 @@ public void testHandleVmHostnameChangedFullRenamePath() throws Exception { @Test public void testHandleVmHostnameChangedCollisionOnNewUrlSkipsAddDetail() { - com.cloud.vm.VMInstanceVO instanceMock = mock(com.cloud.vm.VMInstanceVO.class); + VMInstanceVO instanceMock = mock(VMInstanceVO.class); when(vmInstanceDao.findById(63L)).thenReturn(instanceMock); NicDnsJoinVO nicMock = @@ -1568,17 +1633,17 @@ public void testHandleVmHostnameChangedCollisionOnNewUrlSkipsAddDetail() { when(nicDnsJoinDao.listActiveByVmIdZoneAndDnsRecord(eq(63L), eq(ZONE_ID), anyString())) .thenReturn(Collections.emptyList()); - try (MockedStatic txMock = - Mockito.mockStatic(com.cloud.utils.db.Transaction.class); - MockedStatic aeMock = - Mockito.mockStatic(com.cloud.event.ActionEventUtils.class)) { - aeMock.when(() -> com.cloud.event.ActionEventUtils.onActionEvent( + try (MockedStatic txMock = + Mockito.mockStatic(Transaction.class); + MockedStatic aeMock = + Mockito.mockStatic(ActionEventUtils.class)) { + aeMock.when(() -> ActionEventUtils.onActionEvent( anyLong(), anyLong(), anyLong(), anyString(), anyString(), anyLong(), anyString())) .thenReturn(1L); - txMock.when(() -> com.cloud.utils.db.Transaction.execute( - any(com.cloud.utils.db.TransactionCallbackWithExceptionNoReturn.class))) + txMock.when(() -> Transaction.execute( + any(TransactionCallbackWithExceptionNoReturn.class))) .thenAnswer(invocation -> { - com.cloud.utils.db.TransactionCallbackWithExceptionNoReturn cb = + TransactionCallbackWithExceptionNoReturn cb = invocation.getArgument(0); try { cb.doInTransactionWithoutResult(null); } catch (Exception e) { throw new RuntimeException(e); }