diff --git a/docs/modules/operation/pages/deep-dive/provisioning/scalability.adoc b/docs/modules/operation/pages/deep-dive/provisioning/scalability.adoc index 2e18ef59ba0e..d8848b0c0688 100644 --- a/docs/modules/operation/pages/deep-dive/provisioning/scalability.adoc +++ b/docs/modules/operation/pages/deep-dive/provisioning/scalability.adoc @@ -89,3 +89,11 @@ For a large number of devices, you may want to set the `scan-interval` to `0` to NOTE: Even if `scan-interval` is set to `0`, new nodes added to a requisition are scanned during import. You can also disable this scan by setting `scan-interval` to `-1`. +=== Tuning DNS reverse lookups + +During the provisioning process hostnames are determined for each interface IP address by DNS reverse lookups. +A thread pool is used to allow concurrent DNS lookups. +You can use the property `org.opennms.netmgt.provision.dns.client.rpc.threadCount` to configure the thread pool's fixed size (default 64). + +NOTE: To completely disable DNS reverse lookups, set the property `org.opennms.provisiond.reverseResolveRequisitionIpInterfaceHostnames` to `false`. + diff --git a/opennms-base-assembly/src/main/filtered/etc/opennms.properties b/opennms-base-assembly/src/main/filtered/etc/opennms.properties index 450e4910f180..51a9f5b1fda0 100644 --- a/opennms-base-assembly/src/main/filtered/etc/opennms.properties +++ b/opennms-base-assembly/src/main/filtered/etc/opennms.properties @@ -864,6 +864,13 @@ org.opennms.newts.nan_on_counter_wrap=true # By default this property is not used and empty. # org.opennms.netmgt.search.info= -# # ###### Mate Expressions ###### +# ###### Mate Expressions ###### # Limit of recursion depth while evaluating mate expressions # org.opennms.mate.maxRecursionDepth=8 + +# ###### DNS tuning options ###### +# A thread pool is used to allow concurrent DNS lookups on OpenNMS Minions. +# org.opennms.netmgt.provision.dns.client.rpc.threadCount=64 + +# By default hostnames are determined for a node's IP addresses during the provisioning's audit phase. +# org.opennms.provisiond.reverseResolveRequisitionIpInterfaceHostnames=true diff --git a/opennms-provision/opennms-detectorclient-rpc/src/main/java/org/opennms/netmgt/provision/dns/client/rpc/DnsLookupClientRpcModule.java b/opennms-provision/opennms-detectorclient-rpc/src/main/java/org/opennms/netmgt/provision/dns/client/rpc/DnsLookupClientRpcModule.java index 7c8454ea2ecc..1adffa4d4847 100644 --- a/opennms-provision/opennms-detectorclient-rpc/src/main/java/org/opennms/netmgt/provision/dns/client/rpc/DnsLookupClientRpcModule.java +++ b/opennms-provision/opennms-detectorclient-rpc/src/main/java/org/opennms/netmgt/provision/dns/client/rpc/DnsLookupClientRpcModule.java @@ -31,6 +31,8 @@ import java.net.InetAddress; import java.net.UnknownHostException; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import org.opennms.core.rpc.xml.AbstractXmlRpcModule; import org.opennms.core.utils.InetAddressUtils; @@ -44,8 +46,14 @@ public class DnsLookupClientRpcModule extends AbstractXmlRpcModule execute(DnsLookupRequestDTO request) { - final CompletableFuture future = new CompletableFuture<>(); - try { - final InetAddress addr = InetAddressUtils.addr(request.getHostRequest()); - final DnsLookupResponseDTO dto = new DnsLookupResponseDTO(); - final QueryType queryType = request.getQueryType(); - if (queryType.equals(QueryType.LOOKUP)) { - dto.setHostResponse(addr.getHostAddress()); - } else if (queryType.equals(QueryType.REVERSE_LOOKUP)) { - // Attempt to retrieve the fully qualified domain name for this IP address - String hostName = addr.getCanonicalHostName(); - if (InetAddressUtils.str(addr).equals(hostName)) { - // The given host name matches the textual representation of - // the IP address, which means that the reverse lookup failed - // NMS-9356: InetAddress#getCanonicalHostName requires PTR records - // to have a corresponding A record in order to succeed, so we - // try using dnsjava's implementation to work around this - try { - hostName = Address.getHostName(addr); - } catch (UnknownHostException e) { - LOG.warn("Failed to retrieve the fully qualified domain name for {}. " - + "Using the textual representation of the IP address.", addr); + return CompletableFuture.supplyAsync(() -> { + final InetAddress addr = InetAddressUtils.addr(request.getHostRequest()); + final DnsLookupResponseDTO dto = new DnsLookupResponseDTO(); + final QueryType queryType = request.getQueryType(); + if (queryType.equals(QueryType.LOOKUP)) { + dto.setHostResponse(addr.getHostAddress()); + } else if (queryType.equals(QueryType.REVERSE_LOOKUP)) { + // Attempt to retrieve the fully qualified domain name for this IP address + String hostName = addr.getCanonicalHostName(); + if (InetAddressUtils.str(addr).equals(hostName)) { + // The given host name matches the textual representation of + // the IP address, which means that the reverse lookup failed + // NMS-9356: InetAddress#getCanonicalHostName requires PTR records + // to have a corresponding A record in order to succeed, so we + // try using dnsjava's implementation to work around this + try { + hostName = Address.getHostName(addr); + } catch (UnknownHostException e) { + LOG.warn("Failed to retrieve the fully qualified domain name for {}. " + + "Using the textual representation of the IP address.", addr); + } } + dto.setHostResponse(hostName); } - dto.setHostResponse(hostName); - } - future.complete(dto); - } catch (Exception e) { - future.completeExceptionally(e); - } - return future; + return dto; + }, this.executorService); } - } diff --git a/opennms-provision/opennms-detectorclient-rpc/src/main/resources/META-INF/opennms/applicationContext-rpc-dns.xml b/opennms-provision/opennms-detectorclient-rpc/src/main/resources/META-INF/opennms/applicationContext-rpc-dns.xml index 7f7c49e2f8ff..571bfbf2c205 100644 --- a/opennms-provision/opennms-detectorclient-rpc/src/main/resources/META-INF/opennms/applicationContext-rpc-dns.xml +++ b/opennms-provision/opennms-detectorclient-rpc/src/main/resources/META-INF/opennms/applicationContext-rpc-dns.xml @@ -11,7 +11,9 @@ - + + + diff --git a/opennms-provision/opennms-detectorclient-rpc/src/main/resources/OSGI-INF/blueprint/blueprint.xml b/opennms-provision/opennms-detectorclient-rpc/src/main/resources/OSGI-INF/blueprint/blueprint.xml index bc76f981de68..6d226454eaa2 100644 --- a/opennms-provision/opennms-detectorclient-rpc/src/main/resources/OSGI-INF/blueprint/blueprint.xml +++ b/opennms-provision/opennms-detectorclient-rpc/src/main/resources/OSGI-INF/blueprint/blueprint.xml @@ -11,6 +11,12 @@ http://aries.apache.org/schemas/blueprint-ext/blueprint-ext-1.5.xsd "> + + + + + + @@ -20,7 +26,9 @@ - + + + diff --git a/opennms-provision/opennms-provisiond/src/main/java/org/opennms/netmgt/provision/service/DefaultHostnameResolver.java b/opennms-provision/opennms-provisiond/src/main/java/org/opennms/netmgt/provision/service/DefaultHostnameResolver.java index 5e905d7fffc3..2cae58094db3 100644 --- a/opennms-provision/opennms-provisiond/src/main/java/org/opennms/netmgt/provision/service/DefaultHostnameResolver.java +++ b/opennms-provision/opennms-provisiond/src/main/java/org/opennms/netmgt/provision/service/DefaultHostnameResolver.java @@ -31,7 +31,6 @@ import java.net.InetAddress; import java.util.Objects; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; import org.opennms.core.utils.InetAddressUtils; import org.opennms.netmgt.provision.LocationAwareDnsLookupClient; @@ -49,16 +48,16 @@ public DefaultHostnameResolver(LocationAwareDnsLookupClient locationAwareDnsLook } @Override - public String getHostname(final InetAddress addr, final String location) { + public CompletableFuture getHostnameAsync(final InetAddress addr, final String location) { LOG.debug("Performing reverse lookup on {} at location {}", addr, location); - final CompletableFuture future = m_locationAwareDnsLookupClient.reverseLookup(addr, location); - try { - final String result = future.get(); - LOG.debug("Reverse lookup returned {} for {} at location {}", result, addr, location); - return result; - } catch (InterruptedException | ExecutionException e) { - LOG.warn("Reverse lookup failed for {} at location {}. Using IP address as hostname.", addr, location); - return InetAddressUtils.str(addr); - } + return m_locationAwareDnsLookupClient.reverseLookup(addr, location).handle((result, e) -> { + if (e == null) { + LOG.debug("Reverse lookup returned {} for {} at location {}", result, addr, location); + return result; + } else { + LOG.warn("Reverse lookup failed for {} at location {}. Using IP address as hostname.", addr, location); + return InetAddressUtils.str(addr); + } + }); } } diff --git a/opennms-provision/opennms-provisiond/src/main/java/org/opennms/netmgt/provision/service/HostnameResolver.java b/opennms-provision/opennms-provisiond/src/main/java/org/opennms/netmgt/provision/service/HostnameResolver.java index 9247a043887d..1b3b4043d9ca 100644 --- a/opennms-provision/opennms-provisiond/src/main/java/org/opennms/netmgt/provision/service/HostnameResolver.java +++ b/opennms-provision/opennms-provisiond/src/main/java/org/opennms/netmgt/provision/service/HostnameResolver.java @@ -29,7 +29,18 @@ package org.opennms.netmgt.provision.service; import java.net.InetAddress; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; public interface HostnameResolver { - public String getHostname(final InetAddress addr, final String location); + + default String getHostname(final InetAddress addr, final String location) { + try { + return getHostnameAsync(addr, location).get(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + } + + CompletableFuture getHostnameAsync(final InetAddress addr, final String location); } diff --git a/opennms-provision/opennms-provisiond/src/main/java/org/opennms/netmgt/provision/service/RequisitionAccountant.java b/opennms-provision/opennms-provisiond/src/main/java/org/opennms/netmgt/provision/service/RequisitionAccountant.java index fde97a35432b..27fb25066095 100644 --- a/opennms-provision/opennms-provisiond/src/main/java/org/opennms/netmgt/provision/service/RequisitionAccountant.java +++ b/opennms-provision/opennms-provisiond/src/main/java/org/opennms/netmgt/provision/service/RequisitionAccountant.java @@ -28,23 +28,35 @@ package org.opennms.netmgt.provision.service; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.CompletableFuture; + import org.opennms.netmgt.provision.persist.AbstractRequisitionVisitor; import org.opennms.netmgt.provision.persist.OnmsAssetRequisition; import org.opennms.netmgt.provision.persist.OnmsInterfaceMetaDataRequisition; import org.opennms.netmgt.provision.persist.OnmsIpInterfaceRequisition; -import org.opennms.netmgt.provision.persist.OnmsNodeMetaDataRequisition; import org.opennms.netmgt.provision.persist.OnmsMonitoredServiceRequisition; import org.opennms.netmgt.provision.persist.OnmsNodeCategoryRequisition; +import org.opennms.netmgt.provision.persist.OnmsNodeMetaDataRequisition; import org.opennms.netmgt.provision.persist.OnmsNodeRequisition; import org.opennms.netmgt.provision.persist.OnmsServiceMetaDataRequisition; +import org.opennms.netmgt.provision.persist.requisition.Requisition; import org.opennms.netmgt.provision.service.operations.ImportOperationsManager; import org.opennms.netmgt.provision.service.operations.SaveOrUpdateOperation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class RequisitionAccountant extends AbstractRequisitionVisitor { - private final ImportOperationsManager m_opsMgr; + private static final Logger LOG = LoggerFactory.getLogger(RequisitionAccountant.class); + + private final ImportOperationsManager m_opsMgr; private SaveOrUpdateOperation m_currentOp; private String monitorKey; - + + private final Set> m_dnsLookups = Collections.synchronizedSet(new HashSet<>()); + /** *

Constructor for RequisitionAccountant.

* @@ -70,8 +82,8 @@ public void completeNode(OnmsNodeRequisition nodeReq) { /** {@inheritDoc} */ @Override public void visitInterface(OnmsIpInterfaceRequisition ifaceReq) { - m_currentOp.foundInterface(ifaceReq.getIpAddr(), ifaceReq.getDescr(), ifaceReq.getSnmpPrimary(), ifaceReq.getManaged(), ifaceReq.getStatus()); - + m_currentOp.foundInterface(ifaceReq.getIpAddr(), ifaceReq.getDescr(), ifaceReq.getSnmpPrimary(), ifaceReq.getManaged(), ifaceReq.getStatus(), m_dnsLookups); + LOG.debug("{} DNS lookups scheduled, {} DNS lookups completed", dnsLookupsTotal(), dnsLookupsCompleted()); } /** {@inheritDoc} */ @@ -106,4 +118,25 @@ public void visitInterfaceMetaData(OnmsInterfaceMetaDataRequisition metaDataReq) public void visitServiceMetaData(OnmsServiceMetaDataRequisition metaDataReq) { m_currentOp.foundServiceMetaData(metaDataReq.getContext(), metaDataReq.getKey(), metaDataReq.getValue()); } + + int dnsLookupsCompleted() { + return (int) m_dnsLookups.stream().filter(f -> f.isDone()).count(); + } + + int dnsLookupsPending() { + return dnsLookupsTotal() - dnsLookupsCompleted(); + } + + int dnsLookupsTotal() { + return m_dnsLookups.size(); + } + + @Override + public void completeModelImport(Requisition req) { + LOG.debug("Waiting for {} scheduled DNS lookups, {} DNS lookups pending", dnsLookupsTotal(), dnsLookupsPending()); + + m_dnsLookups.stream().map(CompletableFuture::join); + + LOG.debug("All {} scheduled DNS lookups completed", dnsLookupsTotal()); + } } diff --git a/opennms-provision/opennms-provisiond/src/main/java/org/opennms/netmgt/provision/service/operations/SaveOrUpdateOperation.java b/opennms-provision/opennms-provisiond/src/main/java/org/opennms/netmgt/provision/service/operations/SaveOrUpdateOperation.java index 8a06b4c8f301..01f2c51b20da 100644 --- a/opennms-provision/opennms-provisiond/src/main/java/org/opennms/netmgt/provision/service/operations/SaveOrUpdateOperation.java +++ b/opennms-provision/opennms-provisiond/src/main/java/org/opennms/netmgt/provision/service/operations/SaveOrUpdateOperation.java @@ -29,8 +29,9 @@ package org.opennms.netmgt.provision.service.operations; import java.net.InetAddress; +import java.util.Set; +import java.util.concurrent.CompletableFuture; -import org.opennms.core.utils.InetAddressUtils; import org.opennms.netmgt.dao.api.MonitoringLocationDao; import org.opennms.netmgt.model.OnmsCategory; import org.opennms.netmgt.model.OnmsIpInterface; @@ -99,8 +100,9 @@ public ScanManager getScanManager() { * @param primaryType a {@link InterfaceSnmpPrimaryType} object. * @param managed a boolean. * @param status a int. + * @param dnsLookups the list of DNS lookup futures. */ - public void foundInterface(InetAddress addr, Object descr, final PrimaryType primaryType, boolean managed, int status) { + public void foundInterface(InetAddress addr, Object descr, final PrimaryType primaryType, boolean managed, int status, final Set> dnsLookups) { if (addr == null) { LOG.error("Found interface on node {} with an empty/invalid ipaddr! Ignoring!", m_node.getLabel()); @@ -111,10 +113,6 @@ public void foundInterface(InetAddress addr, Object descr, final PrimaryType pri m_currentInterface.setIsManaged(status == 3 ? "U" : "M"); m_currentInterface.setIsSnmpPrimary(primaryType); - if (addr != null && System.getProperty("org.opennms.provisiond.reverseResolveRequisitionIpInterfaceHostnames", "true").equalsIgnoreCase("true")) { - m_currentInterface.setIpHostName(getProvisionService().getHostnameResolver().getHostname(addr, m_node.getLocation().getLocationName())); - } - if (PrimaryType.PRIMARY.equals(primaryType)) { if (addr != null) { m_scanManager = new ScanManager(getProvisionService().getLocationAwareSnmpClient(), addr); @@ -124,6 +122,10 @@ public void foundInterface(InetAddress addr, Object descr, final PrimaryType pri //FIXME: verify this doesn't conflict with constructor. The constructor already adds this //interface to the node. m_node.addIpInterface(m_currentInterface); + + if (System.getProperty("org.opennms.provisiond.reverseResolveRequisitionIpInterfaceHostnames", "true").equalsIgnoreCase("true")) { + dnsLookups.add(getProvisionService().getHostnameResolver().getHostnameAsync(addr, m_node.getLocation().getLocationName()).thenAccept(s -> m_node.getInterfaceWithAddress(addr).setIpHostName(s))); + } } /** diff --git a/opennms-provision/opennms-provisiond/src/test/java/org/opennms/netmgt/provision/service/NewSuspectScanIT.java b/opennms-provision/opennms-provisiond/src/test/java/org/opennms/netmgt/provision/service/NewSuspectScanIT.java index dc200d2a5892..19c81a7ea344 100644 --- a/opennms-provision/opennms-provisiond/src/test/java/org/opennms/netmgt/provision/service/NewSuspectScanIT.java +++ b/opennms-provision/opennms-provisiond/src/test/java/org/opennms/netmgt/provision/service/NewSuspectScanIT.java @@ -35,9 +35,9 @@ import java.net.InetAddress; import java.util.List; import java.util.Properties; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; -import com.fasterxml.jackson.databind.ObjectMapper; import org.joda.time.Duration; import org.junit.After; import org.junit.Before; @@ -79,7 +79,6 @@ import org.opennms.netmgt.provision.persist.foreignsource.PluginConfig; import org.opennms.netmgt.provision.persist.requisition.Requisition; import org.opennms.netmgt.provision.persist.requisition.RequisitionNode; -import org.opennms.netmgt.provision.service.operations.ProvisionMonitor; import org.opennms.netmgt.xml.event.Event; import org.opennms.test.JUnitConfigurationEnvironment; import org.springframework.beans.factory.InitializingBean; @@ -440,8 +439,8 @@ public void testRescanWithChangingDns() throws Exception { assertEquals(0, getSnmpInterfaceDao().countAll()); m_provisionService.setHostnameResolver(new HostnameResolver() { - @Override public String getHostname(final InetAddress addr, final String location) { - return "oldNodeLabel"; + @Override public CompletableFuture getHostnameAsync(final InetAddress addr, final String location) { + return CompletableFuture.completedFuture("oldNodeLabel"); } }); @@ -482,8 +481,8 @@ public void testRescanWithChangingDns() throws Exception { assertEquals(0, getSnmpInterfaceDao().countAll()); m_provisionService.setHostnameResolver(new HostnameResolver() { - @Override public String getHostname(final InetAddress addr, final String location) { - return "newNodeLabel"; + @Override public CompletableFuture getHostnameAsync(final InetAddress addr, final String location) { + return CompletableFuture.completedFuture("newNodeLabel"); } }); @@ -514,8 +513,8 @@ public void testWithNoDnsOnRescan() throws Exception { assertEquals(0, getSnmpInterfaceDao().countAll()); m_provisionService.setHostnameResolver(new HostnameResolver() { - @Override public String getHostname(final InetAddress addr, final String location) { - return "oldNodeLabel"; + @Override public CompletableFuture getHostnameAsync(final InetAddress addr, final String location) { + return CompletableFuture.completedFuture("oldNodeLabel"); } }); @@ -555,8 +554,8 @@ public void testWithNoDnsOnRescan() throws Exception { assertEquals(0, getSnmpInterfaceDao().countAll()); m_provisionService.setHostnameResolver(new HostnameResolver() { - @Override public String getHostname(final InetAddress addr, final String location) { - return null; + @Override public CompletableFuture getHostnameAsync(final InetAddress addr, final String location) { + return CompletableFuture.completedFuture(null); } }); diff --git a/opennms-provision/opennms-provisiond/src/test/java/org/opennms/netmgt/provision/service/Nms5414IT.java b/opennms-provision/opennms-provisiond/src/test/java/org/opennms/netmgt/provision/service/Nms5414IT.java index bc930a76dca4..149619f8cfba 100644 --- a/opennms-provision/opennms-provisiond/src/test/java/org/opennms/netmgt/provision/service/Nms5414IT.java +++ b/opennms-provision/opennms-provisiond/src/test/java/org/opennms/netmgt/provision/service/Nms5414IT.java @@ -32,6 +32,7 @@ import java.net.InetAddress; import java.util.List; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; @@ -107,9 +108,8 @@ public void setUp() { mfsr.putDefaultForeignSource(fs); m_provisioner.getProvisionService().setForeignSourceRepository(mfsr); m_provisioner.getProvisionService().setHostnameResolver(new HostnameResolver() { - @Override - public String getHostname(InetAddress addr, String location) { - return "opennms-com"; + @Override public CompletableFuture getHostnameAsync(final InetAddress addr, final String location) { + return CompletableFuture.completedFuture("opennms-com"); } }); } diff --git a/opennms-provision/opennms-provisiond/src/test/java/org/opennms/netmgt/provision/service/NodeLocationChangeIT.java b/opennms-provision/opennms-provisiond/src/test/java/org/opennms/netmgt/provision/service/NodeLocationChangeIT.java index ad925163fe76..41c2738507f8 100644 --- a/opennms-provision/opennms-provisiond/src/test/java/org/opennms/netmgt/provision/service/NodeLocationChangeIT.java +++ b/opennms-provision/opennms-provisiond/src/test/java/org/opennms/netmgt/provision/service/NodeLocationChangeIT.java @@ -33,6 +33,7 @@ import java.net.InetAddress; import java.util.List; +import java.util.concurrent.CompletableFuture; import org.joda.time.Duration; import org.junit.Before; @@ -147,9 +148,8 @@ public void setUp() throws Exception { m_provisionService.setForeignSourceRepository(m_foreignSourceRepository); m_provisionService.setHostnameResolver(new HostnameResolver() { - @Override - public String getHostname(InetAddress addr, String location) { - return "opennms-com"; + @Override public CompletableFuture getHostnameAsync(final InetAddress addr, final String location) { + return CompletableFuture.completedFuture("opennms-com"); } }); diff --git a/opennms-provision/opennms-provisiond/src/test/java/org/opennms/netmgt/provision/service/RequisitionAccountantTest.java b/opennms-provision/opennms-provisiond/src/test/java/org/opennms/netmgt/provision/service/RequisitionAccountantTest.java new file mode 100644 index 000000000000..1525da0d7922 --- /dev/null +++ b/opennms-provision/opennms-provisiond/src/test/java/org/opennms/netmgt/provision/service/RequisitionAccountantTest.java @@ -0,0 +1,165 @@ +/******************************************************************************* + * This file is part of OpenNMS(R). + * + * Copyright (C) 2023 The OpenNMS Group, Inc. + * OpenNMS(R) is Copyright (C) 1999-2023 The OpenNMS Group, Inc. + * + * OpenNMS(R) is a registered trademark of The OpenNMS Group, Inc. + * + * OpenNMS(R) is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * OpenNMS(R) is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with OpenNMS(R). If not, see: + * http://www.gnu.org/licenses/ + * + * For more information contact: + * OpenNMS(R) Licensing + * http://www.opennms.org/ + * http://www.opennms.com/ + *******************************************************************************/ + +package org.opennms.netmgt.provision.service; + +import static org.awaitility.Awaitility.await; + +import java.net.InetAddress; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +import org.junit.Assert; +import org.junit.Test; +import org.mockito.Mockito; +import org.opennms.core.utils.InetAddressUtils; +import org.opennms.netmgt.model.PrimaryType; +import org.opennms.netmgt.provision.persist.requisition.Requisition; +import org.opennms.netmgt.provision.persist.requisition.RequisitionInterface; +import org.opennms.netmgt.provision.persist.requisition.RequisitionNode; +import org.opennms.netmgt.provision.service.operations.ImportOperationsManager; +import org.opennms.netmgt.provision.service.operations.InsertOperation; +import org.opennms.netmgt.snmp.proxy.LocationAwareSnmpClient; + +import com.google.common.collect.Lists; + +public class RequisitionAccountantTest { + private boolean blocked = false; + + private RequisitionAccountant createRequisitionAccountant() { + final ProvisionService provisionService = Mockito.mock(ProvisionService.class); + final LocationAwareSnmpClient locationAwareSnmpClient = Mockito.mock(LocationAwareSnmpClient.class); + Mockito.when(provisionService.getLocationAwareSnmpClient()).thenReturn(locationAwareSnmpClient); + Mockito.when(provisionService.getHostnameResolver()).thenReturn(new HostnameResolver() { + @Override + public CompletableFuture getHostnameAsync(InetAddress addr, String location) { + return CompletableFuture.supplyAsync(()-> { + while (blocked) { + try { + Thread.sleep(250); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + return location + InetAddressUtils.str(addr); + }); + } + }); + + final ImportOperationsManager importOperationsManager = Mockito.mock(ImportOperationsManager.class); + Mockito.when(importOperationsManager.foundNode("node1", "node1", "MINION", "", "", "monitorKey")).thenReturn(new InsertOperation("foreignSource", "node1", "node1", "MINION", "","", provisionService, "monitorKey")); + Mockito.when(importOperationsManager.foundNode("node2", "node2", "MINION", "", "", "monitorKey")).thenReturn(new InsertOperation("foreignSource", "node2", "node1", "MINION", "","", provisionService, "monitorKey")); + Mockito.when(importOperationsManager.foundNode("node3", "node3", "MINION", "", "", "monitorKey")).thenReturn(new InsertOperation("foreignSource", "node3", "node1", "MINION", "","", provisionService, "monitorKey")); + + return new RequisitionAccountant(importOperationsManager, "monitorKey"); + } + + private RequisitionNode createNode(final String foreignId, final String ... ipAddresses) { + final RequisitionNode requisitionNode = new RequisitionNode(); + requisitionNode.setLocation("MINION"); + requisitionNode.setNodeLabel(foreignId); + requisitionNode.setForeignId(foreignId); + requisitionNode.setBuilding(""); + requisitionNode.setCity(""); + + final List requisitionInterfaces = new ArrayList<>(); + + for(final String ipAddress : ipAddresses) { + final RequisitionInterface requisitionInterface = new RequisitionInterface(); + requisitionInterface.setIpAddr(ipAddress); + requisitionInterface.setManaged(true); + requisitionInterface.setStatus(1); + requisitionInterface.setSnmpPrimary(requisitionInterfaces.size() == 0 ? PrimaryType.PRIMARY : PrimaryType.SECONDARY); + requisitionInterfaces.add(requisitionInterface); + } + + requisitionNode.setInterfaces(requisitionInterfaces); + + return requisitionNode; + } + + private Requisition createRequisition() { + final Requisition requisition = new Requisition(); + requisition.setForeignSource("foreignSource"); + requisition.setDate(new Date()); + + requisition.setNodes(Lists.newArrayList( + createNode("node1", "10.32.29.11"), + createNode("node2", "10.32.30.22", "10.32.29.22"), + createNode("node3", "10.32.29.33", "10.32.30.33", "10.32.28.33") + )); + + return requisition; + } + + @Test + public void testRequisitionAccountant() { + blocked = true; + + // create requisition with three nodes, first with one, second with two, third with three interfaces + final Requisition requisition = createRequisition(); + + // create visitor + final RequisitionAccountant requisitionAccountant = createRequisitionAccountant(); + + // no DNS lookups scheduled since visit() wasn't called + Assert.assertEquals(0, requisitionAccountant.dnsLookupsTotal()); + Assert.assertEquals(0, requisitionAccountant.dnsLookupsPending()); + Assert.assertEquals(0, requisitionAccountant.dnsLookupsCompleted()); + + final CompletableFuture completableFuture = CompletableFuture.runAsync(new Runnable() { + @Override + public void run() { + requisition.visit(requisitionAccountant); + } + }); + + // wait for lookups to be scheduled + await().atMost(1, TimeUnit.MINUTES).until(() -> { + return requisitionAccountant.dnsLookupsTotal() == 6; + }); + + // check for all six lookups + Assert.assertEquals(6, requisitionAccountant.dnsLookupsTotal()); + Assert.assertEquals(6, requisitionAccountant.dnsLookupsPending()); + Assert.assertEquals(0, requisitionAccountant.dnsLookupsCompleted()); + + // unblock + blocked = false; + + // wait till lookups are completed + await().atMost(1, TimeUnit.MINUTES).until(() -> requisitionAccountant.dnsLookupsPending() == 0); + + Assert.assertEquals(6, requisitionAccountant.dnsLookupsTotal()); + Assert.assertEquals(0, requisitionAccountant.dnsLookupsPending()); + Assert.assertEquals(6, requisitionAccountant.dnsLookupsCompleted()); + } +} diff --git a/opennms-provision/opennms-provisiond/src/test/java/org/opennms/netmgt/provision/service/operations/SaveOrUpdateOperationTest.java b/opennms-provision/opennms-provisiond/src/test/java/org/opennms/netmgt/provision/service/operations/SaveOrUpdateOperationTest.java new file mode 100644 index 000000000000..b9c5d33117c1 --- /dev/null +++ b/opennms-provision/opennms-provisiond/src/test/java/org/opennms/netmgt/provision/service/operations/SaveOrUpdateOperationTest.java @@ -0,0 +1,146 @@ +/******************************************************************************* + * This file is part of OpenNMS(R). + * + * Copyright (C) 2023 The OpenNMS Group, Inc. + * OpenNMS(R) is Copyright (C) 1999-2023 The OpenNMS Group, Inc. + * + * OpenNMS(R) is a registered trademark of The OpenNMS Group, Inc. + * + * OpenNMS(R) is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * OpenNMS(R) is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with OpenNMS(R). If not, see: + * http://www.gnu.org/licenses/ + * + * For more information contact: + * OpenNMS(R) Licensing + * http://www.opennms.org/ + * http://www.opennms.com/ + *******************************************************************************/ + +package org.opennms.netmgt.provision.service.operations; + +import static org.awaitility.Awaitility.await; + +import java.net.InetAddress; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +import org.junit.Assert; +import org.junit.Test; +import org.mockito.Mockito; +import org.opennms.core.utils.InetAddressUtils; +import org.opennms.netmgt.model.PrimaryType; +import org.opennms.netmgt.provision.service.HostnameResolver; +import org.opennms.netmgt.provision.service.ProvisionService; +import org.opennms.netmgt.snmp.proxy.LocationAwareSnmpClient; + +public class SaveOrUpdateOperationTest { + private boolean blocked = true; + + private ProvisionService createProvisioningService() { + final ProvisionService provisionService = Mockito.mock(ProvisionService.class); + final LocationAwareSnmpClient locationAwareSnmpClient = Mockito.mock(LocationAwareSnmpClient.class); + Mockito.when(provisionService.getLocationAwareSnmpClient()).thenReturn(locationAwareSnmpClient); + Mockito.when(provisionService.getHostnameResolver()).thenReturn(new HostnameResolver() { + @Override + public CompletableFuture getHostnameAsync(InetAddress addr, String location) { + return CompletableFuture.supplyAsync(() -> { + while (blocked) { + try { + Thread.sleep(250); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + return location + "-" + InetAddressUtils.str(addr); + }); + } + }); + + return provisionService; + } + + @Test + public void testParallelNameResolution() throws InterruptedException { + blocked = true; + + final ProvisionService provisionService = createProvisioningService(); + final Set> completableFutures = new HashSet<>(); + + // insert first node with three interfaces + final SaveOrUpdateOperation insertOperation1 = new InsertOperation("foreignSource", "foreignId1", "nodeLabel1", "MINION", "", "", provisionService, "monitorKey"); + insertOperation1.foundInterface(InetAddressUtils.addr("10.32.29.12"), "Interface #1", PrimaryType.PRIMARY, true, 1, completableFutures); + insertOperation1.foundInterface(InetAddressUtils.addr("10.32.29.13"), "Interface #2", PrimaryType.SECONDARY, true, 1, completableFutures); + insertOperation1.foundInterface(InetAddressUtils.addr("10.32.29.14"), "Interface #3", PrimaryType.SECONDARY, true, 1, completableFutures); + + // insert second node with two interfaces + final SaveOrUpdateOperation insertOperation2 = new InsertOperation("foreignSource", "foreignId2", "nodeLabel2", "MINION", "", "", provisionService, "monitorKey"); + insertOperation2.foundInterface(InetAddressUtils.addr("10.32.30.101"), "Interface #1", PrimaryType.PRIMARY, true, 1, completableFutures); + insertOperation2.foundInterface(InetAddressUtils.addr("10.32.30.102"), "Interface #2", PrimaryType.SECONDARY, true, 1, completableFutures); + + // blocked, so five pending lookups + Assert.assertEquals(5, completableFutures.size()); + Assert.assertEquals(5, completableFutures.stream().filter(f -> !f.isDone()).count()); + + // unblock + blocked = false; + + // wait, till all lookups are completed + await().atMost(1, TimeUnit.MINUTES).until(() -> completableFutures.stream().filter(f -> f.isDone()).count() == 5); + + // all done + Assert.assertEquals(5, completableFutures.size()); + + // check that the correct hostnames are set + Assert.assertEquals("MINION-10.32.29.12", insertOperation1.getNode().getIpInterfaceByIpAddress("10.32.29.12").getIpHostName()); + Assert.assertEquals("MINION-10.32.29.13", insertOperation1.getNode().getIpInterfaceByIpAddress("10.32.29.13").getIpHostName()); + Assert.assertEquals("MINION-10.32.29.14", insertOperation1.getNode().getIpInterfaceByIpAddress("10.32.29.14").getIpHostName()); + Assert.assertEquals("MINION-10.32.30.101", insertOperation2.getNode().getIpInterfaceByIpAddress("10.32.30.101").getIpHostName()); + Assert.assertEquals("MINION-10.32.30.102", insertOperation2.getNode().getIpInterfaceByIpAddress("10.32.30.102").getIpHostName()); + } + + @Test + public void testNameResolutionFlag() { + blocked = false; + final ProvisionService provisionService = createProvisioningService(); + final Set> completableFutures = new HashSet<>(); + + Assert.assertEquals(0, completableFutures.size()); + + // insert a node with one interface + final SaveOrUpdateOperation insertOperation1 = new InsertOperation("foreignSource", "foreignId1", "nodeLabel1", "MINION", "", "", provisionService, "monitorKey"); + insertOperation1.foundInterface(InetAddressUtils.addr("10.32.29.12"), "Interface #1", PrimaryType.PRIMARY, true, 1, completableFutures); + Assert.assertEquals(1, completableFutures.size()); + + // insert a node with interface without address + final SaveOrUpdateOperation insertOperation2 = new InsertOperation("foreignSource", "foreignId2", "nodeLabel2", "MINION", "", "", provisionService, "monitorKey"); + insertOperation2.foundInterface(null, "Interface #1", PrimaryType.PRIMARY, true, 1, completableFutures); + Assert.assertEquals(1, completableFutures.size()); + + // set system property to false + System.setProperty("org.opennms.provisiond.reverseResolveRequisitionIpInterfaceHostnames", "false"); + + // insert another node with one interface + final SaveOrUpdateOperation insertOperation3 = new InsertOperation("foreignSource", "foreignId3", "nodeLabel3", "MINION", "", "", provisionService, "monitorKey"); + insertOperation3.foundInterface(InetAddressUtils.addr("10.32.29.32"), "Interface #1", PrimaryType.PRIMARY, true, 1, completableFutures); + Assert.assertEquals(1, completableFutures.size()); + + await().atMost(1, TimeUnit.MINUTES).until(() -> completableFutures.stream().filter(f -> f.isDone()).count() == 1); + + // check for the right hostname + Assert.assertEquals("MINION-10.32.29.12", insertOperation1.getNode().getPrimaryInterface().getIpHostName()); + Assert.assertEquals(0, insertOperation2.getNode().getIpInterfaces().size()); + Assert.assertEquals(null, insertOperation3.getNode().getPrimaryInterface().getIpHostName()); + } +}