IGNITE-17415 Fix node resolves obsolete addresses from the previously restarted and killed nodes (#10170)

diff --git a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/internal/TcpDiscoveryNode.java b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/internal/TcpDiscoveryNode.java
index 869df96..a64d4c8 100644
--- a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/internal/TcpDiscoveryNode.java
+++ b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/internal/TcpDiscoveryNode.java
@@ -82,7 +82,7 @@
 
     /** */
     @GridToStringInclude
-    private Collection<InetSocketAddress> sockAddrs;
+    private volatile Collection<InetSocketAddress> sockAddrs;
 
     /** */
     @GridToStringInclude
@@ -408,6 +408,9 @@
      * @return Addresses that could be used by discovery.
      */
     public Collection<InetSocketAddress> socketAddresses() {
+        if (this.sockAddrs == null)
+            sockAddrs = U.toSocketAddresses(this, discPort);
+
         return sockAddrs;
     }
 
@@ -611,8 +614,6 @@
         hostNames = U.readCollection(in);
         discPort = in.readInt();
 
-        sockAddrs = U.toSocketAddresses(this, discPort);
-
         Object consistentIdAttr = attrs.get(ATTR_NODE_CONSISTENT_ID);
 
         // Cluster metrics
@@ -660,9 +661,7 @@
      * Only purpose of this constructor is creating node which contains necessary data to store on disc only
      * @param node to copy data from
      */
-    public TcpDiscoveryNode(
-        ClusterNode node
-    ) {
+    public TcpDiscoveryNode(ClusterNode node) {
         this.id = node.id();
         this.consistentId = node.consistentId();
         this.addrs = node.addresses();
diff --git a/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryDeadNodeAddressResolvingTest.java b/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryDeadNodeAddressResolvingTest.java
new file mode 100644
index 0000000..e07aee8
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryDeadNodeAddressResolvingTest.java
@@ -0,0 +1,120 @@
+/*
+ * 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.ignite.spi.discovery.tcp;
+
+import java.util.Collection;
+import java.util.Optional;
+import org.apache.ignite.cluster.ClusterNode;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.internal.IgniteEx;
+import org.apache.ignite.internal.managers.discovery.GridDiscoveryManager;
+import org.apache.ignite.spi.discovery.tcp.internal.TcpDiscoveryNode;
+import org.apache.ignite.testframework.GridTestUtils;
+import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
+import org.junit.Test;
+
+/**
+ * Tests that {@link TcpDiscoveryNode} that has already left cluster,
+ * doesn't have resolved socket addresses in toplogy history that a new joining node receives.
+ */
+public class TcpDiscoveryDeadNodeAddressResolvingTest extends GridCommonAbstractTest {
+    /** {@inheritDoc} */
+    @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception {
+        IgniteConfiguration configuration = super.getConfiguration(igniteInstanceName);
+
+        configuration.setConsistentId(igniteInstanceName);
+
+        return configuration;
+    }
+
+    /**
+     * Tests that a new node that joins the topology and receives topology history,
+     * doesn't resolve addresses of nodes that already left the topology.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void test() throws Exception {
+        startGrid(0);
+
+        IgniteEx cli = startClientGrid(1);
+        String consistentId = (String)cli.configuration().getConsistentId();
+
+        startGrid(2);
+
+        cli.close();
+
+        IgniteEx grid3 = startGrid(3);
+
+        GridDiscoveryManager discovery = grid3.context().discovery();
+        // Only grid0.
+        checkNoNode(discovery, 1, consistentId);
+
+        // grid1 must be in topology snapshots but as it has already left the cluster, its address should not be resolved.
+        checkSockAddrsNull(discovery, 2, consistentId);
+        checkSockAddrsNull(discovery, 3, consistentId);
+
+        // grid1 will not be present in these topology snapshots.
+        checkNoNode(discovery, 4, consistentId);
+        checkNoNode(discovery, 5, consistentId);
+    }
+
+    /**
+     * Checks that there is no node by consistent id in the topology history snapshot.
+     *
+     * @param disco Discovery manager.
+     * @param topology Topology version.
+     * @param consistentId Node consistent id.
+     */
+    private void checkNoNode(GridDiscoveryManager disco, int topology, String consistentId) {
+        assertFalse(findNode(disco, topology, consistentId).isPresent());
+    }
+
+    /**
+     * Checks that {@link TcpDiscoveryNode} from the topology snapshot doesn't have resolved socket addresses.
+     *
+     * @param disco Discovery manager.
+     * @param topology Topology version.
+     * @param consistentId Node consistent id.
+     * @throws Exception If failed.
+     */
+    private void checkSockAddrsNull(GridDiscoveryManager disco, int topology, String consistentId) throws Exception {
+        Optional<ClusterNode> node = findNode(disco, topology, consistentId);
+
+        assertTrue(node.isPresent());
+
+        ClusterNode clusterNode = node.get();
+
+        Object sockAddrs = GridTestUtils.getFieldValue(clusterNode, "sockAddrs");
+        assertNull(sockAddrs);
+    }
+
+    /**
+     * Finds node by consistent id in the topology history snapshot.
+     *
+     * @param disco Discovery manager.
+     * @param topology Topology version.
+     * @param consistentId Node consistent id.
+     * @return Node optional.
+     */
+    private Optional<ClusterNode> findNode(GridDiscoveryManager disco, int topology, String consistentId) {
+        Collection<ClusterNode> nodes = disco.topology(topology);
+
+        return nodes.stream().filter(node -> node.consistentId().equals(consistentId)).findFirst();
+    }
+}
diff --git a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSpiDiscoverySelfTestSuite.java b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSpiDiscoverySelfTestSuite.java
index 3757ce7..1264c16 100644
--- a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSpiDiscoverySelfTestSuite.java
+++ b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSpiDiscoverySelfTestSuite.java
@@ -42,6 +42,7 @@
 import org.apache.ignite.spi.discovery.tcp.TcpDiscoveryClientSuspensionSelfTest;
 import org.apache.ignite.spi.discovery.tcp.TcpDiscoveryConcurrentStartTest;
 import org.apache.ignite.spi.discovery.tcp.TcpDiscoveryCoordinatorFailureTest;
+import org.apache.ignite.spi.discovery.tcp.TcpDiscoveryDeadNodeAddressResolvingTest;
 import org.apache.ignite.spi.discovery.tcp.TcpDiscoveryFailedJoinTest;
 import org.apache.ignite.spi.discovery.tcp.TcpDiscoveryIpFinderCleanerTest;
 import org.apache.ignite.spi.discovery.tcp.TcpDiscoveryIpFinderFailureTest;
@@ -180,7 +181,9 @@
 
     DiscoverySpiDataExchangeTest.class,
 
-    TcpDiscoveryIpFinderFailureTest.class
+    TcpDiscoveryIpFinderFailureTest.class,
+
+    TcpDiscoveryDeadNodeAddressResolvingTest.class
 })
 public class IgniteSpiDiscoverySelfTestSuite {
     /** */
diff --git a/modules/web/src/test/java/org/apache/ignite/internal/websession/WebSessionSelfTest.java b/modules/web/src/test/java/org/apache/ignite/internal/websession/WebSessionSelfTest.java
index 8701312..f58bd04 100644
--- a/modules/web/src/test/java/org/apache/ignite/internal/websession/WebSessionSelfTest.java
+++ b/modules/web/src/test/java/org/apache/ignite/internal/websession/WebSessionSelfTest.java
@@ -59,8 +59,10 @@
 import org.apache.ignite.testframework.GridTestUtils;
 import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
 import org.eclipse.jetty.security.HashLoginService;
+import org.eclipse.jetty.security.SecurityHandler;
 import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.util.security.Constraint;
 import org.eclipse.jetty.webapp.WebAppContext;
 import org.jetbrains.annotations.Nullable;
 import org.junit.Ignore;
@@ -856,7 +858,6 @@
         final AtomicBoolean stop = new AtomicBoolean();
 
         IgniteInternalFuture<?> restarterFut = GridTestUtils.runMultiThreadedAsync(new Callable<Object>() {
-            @SuppressWarnings("BusyWait")
             @Override public Object call() throws Exception {
                 Random rnd = new Random();
 
@@ -1017,7 +1018,10 @@
         hashLoginService.setName("Test Realm");
         createRealm();
         hashLoginService.setConfig("/tmp/realm.properties");
-        ctx.getSecurityHandler().setLoginService(hashLoginService);
+        SecurityHandler securityHandler = ctx.getSecurityHandler();
+        // DefaultAuthenticatorFactory doesn't default to basic auth anymore.
+        securityHandler.setAuthMethod(Constraint.__BASIC_AUTH);
+        securityHandler.setLoginService(hashLoginService);
 
         srv.setHandler(ctx);