This is an automated email from the ASF dual-hosted git repository.

nizhikov pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ignite.git


The following commit(s) were added to refs/heads/master by this push:
     new 66a4a2d876a IGNITE-23226 Services: deployment failed but service 
descriptor stay (#11634)
66a4a2d876a is described below

commit 66a4a2d876aad528ee1be35d8711441ecdfdf97b
Author: Vladislav Novikov <[email protected]>
AuthorDate: Tue Nov 19 17:27:01 2024 +0300

    IGNITE-23226 Services: deployment failed but service descriptor stay 
(#11634)
---
 .../processors/service/IgniteServiceProcessor.java |  42 ++-
 .../IgniteServiceDeploymentFailureTest.java        | 326 +++++++++++++++++++++
 .../testsuites/IgniteServiceGridTestSuite.java     |   2 +
 3 files changed, 364 insertions(+), 6 deletions(-)

diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/service/IgniteServiceProcessor.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/service/IgniteServiceProcessor.java
index b3e504e9551..636b7df03aa 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/service/IgniteServiceProcessor.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/service/IgniteServiceProcessor.java
@@ -1579,6 +1579,16 @@ public class IgniteServiceProcessor extends 
GridProcessorAdapter implements Igni
 
         try {
             updateServicesMap(deployedServices, fullTops);
+
+            for (Map.Entry<IgniteUuid, Map<UUID, Integer>> e : 
fullTops.entrySet()) {
+                // Checking if there are successful deployments.
+                // If none, service not deployed and must be removed from 
descriptors.
+                if (e.getValue().entrySet().stream().allMatch(nodeTop -> 
nodeTop.getValue() == 0)) {
+                    removeFromServicesMap(registeredServices, 
registeredServicesByName, e.getKey());
+
+                    removeFromServicesMap(deployedServices, 
deployedServicesByName, e.getKey());
+                }
+            }
         }
         finally {
             leaveBusy();
@@ -1632,11 +1642,9 @@ public class IgniteServiceProcessor extends 
GridProcessorAdapter implements Igni
             });
 
             depActions.servicesToUndeploy().forEach((srvcId, desc) -> {
-                ServiceInfo rmv = deployedServices.remove(srvcId);
+                ServiceInfo rmv = removeFromServicesMap(deployedServices, 
deployedServicesByName, srvcId);
 
-                assert rmv != null && rmv == desc : "Concurrent map 
modification.";
-
-                deployedServicesByName.remove(rmv.name());
+                assert rmv == desc : "Concurrent map modification.";
             });
         }
         finally {
@@ -1863,8 +1871,7 @@ public class IgniteServiceProcessor extends 
GridProcessorAdapter implements Igni
                 }
             }
             else if (req instanceof ServiceUndeploymentRequest) {
-                ServiceInfo rmv = registeredServices.remove(reqSrvcId);
-                registeredServicesByName.remove(oldDesc.name());
+                ServiceInfo rmv = removeFromServicesMap(registeredServices, 
registeredServicesByName, reqSrvcId);
 
                 assert oldDesc == rmv : "Concurrent map modification.";
 
@@ -2015,6 +2022,29 @@ public class IgniteServiceProcessor extends 
GridProcessorAdapter implements Igni
         });
     }
 
+    /**
+     * Remove service record from service map and corresponding services by 
name map.
+     *
+     * @param srvcsMap Services map.
+     * @param srvcsByNameMap Services by name map.
+     * @param srvcId Service id.
+     *
+     * @return Removed service descriptor.
+     * */
+    private ServiceInfo removeFromServicesMap(
+        Map<IgniteUuid, ServiceInfo> srvcsMap,
+        Map<String, ServiceInfo> srvcsByNameMap,
+        IgniteUuid srvcId
+    ) {
+        ServiceInfo desc = srvcsMap.remove(srvcId);
+
+        assert desc != null : "Concurrent map modification.";
+
+        srvcsByNameMap.remove(desc.name());
+
+        return desc;
+    }
+
     /**
      * Enters busy state.
      *
diff --git 
a/modules/core/src/test/java/org/apache/ignite/internal/processors/service/IgniteServiceDeploymentFailureTest.java
 
b/modules/core/src/test/java/org/apache/ignite/internal/processors/service/IgniteServiceDeploymentFailureTest.java
new file mode 100644
index 00000000000..1263cac4f1c
--- /dev/null
+++ 
b/modules/core/src/test/java/org/apache/ignite/internal/processors/service/IgniteServiceDeploymentFailureTest.java
@@ -0,0 +1,326 @@
+/*
+ * 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.internal.processors.service;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.UUID;
+import java.util.function.Predicate;
+
+import org.apache.ignite.Ignite;
+import org.apache.ignite.Ignition;
+import org.apache.ignite.cluster.ClusterNode;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.failure.StopNodeFailureHandler;
+import org.apache.ignite.internal.GridKernalContext;
+import org.apache.ignite.internal.IgniteEx;
+import org.apache.ignite.internal.IgniteServicesImpl;
+import org.apache.ignite.lang.IgnitePredicate;
+import org.apache.ignite.services.Service;
+import org.apache.ignite.services.ServiceConfiguration;
+import org.apache.ignite.services.ServiceDeploymentException;
+import org.apache.ignite.services.ServiceDescriptor;
+import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Test;
+
+import static 
org.apache.ignite.testframework.GridTestUtils.assertThrowsWithCause;
+import static org.apache.ignite.testframework.GridTestUtils.getFieldValue;
+import static org.apache.ignite.testframework.GridTestUtils.waitForCondition;
+
+/**
+ * Test class for verifying the behavior of Ignite service deployment when
+ * specified service classes are not found or when service initialization 
fails.
+ */
+public class IgniteServiceDeploymentFailureTest extends GridCommonAbstractTest 
{
+    /** */
+    private static final String NODE_FILTER_CLS_NAME = 
"org.apache.ignite.tests.p2p.ExcludeNodeFilter";
+
+    /** */
+    private static final String NOOP_SERVICE_NAME = "NoopService";
+
+    /** */
+    private static final String INIT_THROWING_SERVICE_NAME = 
"InitThrowingService";
+
+    /** */
+    private static final int SERVER_NODES_CNT = 5;
+
+    /** */
+    private static final int CLIENT_NODES_CNT = 4;
+
+    /** */
+    private static final int TIMEOUT = 10_000;
+
+    /** */
+    private static final String DEPLOYED_SERVICE_MUST_BE_PRESENTED_IN_CLUSTER 
= "Deployed service must be presented in cluster";
+
+    /** */
+    private static final String 
FAILED_SERVICE_SHOULD_NOT_BE_PRESENT_IN_THE_CLUSTER =
+        "Service descriptors whose deployment has failed should not be present 
in the cluster";
+
+    /** */
+    public static final String REGISTERED_SERVICES_BY_NAME = 
"registeredServicesByName";
+
+    /** */
+    public static final String DEPLOYED_SERVICES = "deployedServices";
+
+    /** */
+    public static final String DEPLOYED_SERVICES_BY_NAME = 
"deployedServicesByName";
+
+    /** */
+    private static ClassLoader extClsLdr;
+
+    /** {@inheritDoc} */
+    @Override protected IgniteConfiguration getConfiguration(String 
igniteInstanceName) throws Exception {
+        IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName);
+
+        cfg.setPeerClassLoadingEnabled(false);
+        cfg.setFailureHandler(new StopNodeFailureHandler());
+
+        return cfg;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void beforeTest() throws Exception {
+        super.beforeTest();
+
+        extClsLdr = getExternalClassLoader();
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void afterTest() throws Exception {
+        stopAllGrids();
+
+        super.afterTest();
+
+        extClsLdr = null;
+    }
+
+    /**
+     * Tests that deploying a service with a missing class causes a 
ServiceDeploymentException.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testFailWhenClassNotFound() throws Exception {
+        startGrid();
+
+        IgniteEx cli = startClientGrid(1);
+
+        ServiceConfiguration svcCfg = new ServiceConfiguration()
+            .setName(NOOP_SERVICE_NAME)
+            .setService(new NoopService())
+            
.setNodeFilter(((Class<IgnitePredicate<ClusterNode>>)extClsLdr.loadClass(NODE_FILTER_CLS_NAME))
+                .getConstructor(UUID.class)
+                .newInstance(cli.configuration().getNodeId()))
+            .setTotalCount(1);
+
+        assertThrowsWithCause(() -> cli.services().deploy(svcCfg), 
ServiceDeploymentException.class);
+
+        assertTrue(waitForCondition(() -> 
noDescriptorInClusterForService(NOOP_SERVICE_NAME), TIMEOUT));
+    }
+
+    /**
+     * Tests that service descriptors are clear after an attempt of deploying 
a service which throws an exception
+     * during initialization. Dynamic configuration for services is used.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testFailedServiceDescriptorsDynamicConfiguration() throws 
Exception {
+        final int maxNoopSrvcMaxPerNodeCnt = 4;
+
+        startGrids(SERVER_NODES_CNT);
+
+        IgniteEx client = startClientGrid(SERVER_NODES_CNT);
+
+        for (int i = 1; i < CLIENT_NODES_CNT; i++)
+            startClientGrid(SERVER_NODES_CNT + i);
+
+        ServiceConfiguration noopSrvcCfg = new ServiceConfiguration()
+            .setName(NOOP_SERVICE_NAME)
+            .setService(new NoopService())
+            .setTotalCount(SERVER_NODES_CNT * maxNoopSrvcMaxPerNodeCnt)
+            .setMaxPerNodeCount(2);
+
+        // Deploying NoopService - should be completed successfully.
+        client.services().deploy(noopSrvcCfg);
+
+        // Check that the expected number of instances has been deployed.
+        assertEquals(
+            DEPLOYED_SERVICE_MUST_BE_PRESENTED_IN_CLUSTER,
+            2 * SERVER_NODES_CNT,
+            totalInstancesCount(client, NOOP_SERVICE_NAME)
+        );
+
+        // Deploy InitThrowingService that throws an exception in init - 
should not be deployed.
+        assertThrowsWithCause(
+            () -> client.services().deploy(new ServiceConfiguration()
+                .setName(INIT_THROWING_SERVICE_NAME)
+                .setService(new InitThrowingService())
+                .setTotalCount(10)
+                .setMaxPerNodeCount(2)),
+            ServiceDeploymentException.class
+        );
+
+        assertTrue(
+            FAILED_SERVICE_SHOULD_NOT_BE_PRESENT_IN_THE_CLUSTER,
+            waitForCondition(() -> 
noDescriptorInClusterForService(INIT_THROWING_SERVICE_NAME), TIMEOUT)
+        );
+
+        assertEquals(
+            DEPLOYED_SERVICE_MUST_BE_PRESENTED_IN_CLUSTER,
+            2 * SERVER_NODES_CNT,
+            totalInstancesCount(client, NOOP_SERVICE_NAME)
+        );
+
+        client.services().cancel(NOOP_SERVICE_NAME);
+
+        // Deploy some additional NoopService instances.
+        noopSrvcCfg.setMaxPerNodeCount(maxNoopSrvcMaxPerNodeCnt);
+
+        client.services().deploy(noopSrvcCfg);
+
+        assertEquals(
+            DEPLOYED_SERVICE_MUST_BE_PRESENTED_IN_CLUSTER,
+            SERVER_NODES_CNT * maxNoopSrvcMaxPerNodeCnt,
+            totalInstancesCount(client, NOOP_SERVICE_NAME)
+        );
+    }
+
+    /**
+     * Tests that service descriptors are clear after an attempt of deploying 
a service which throws an exception
+     * during initialization. Static configuration for services is used.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testFailedServiceDescriptorsStaticConfiguration() throws 
Exception {
+        final int noopSrvcTotalCnt = 20;
+
+        startGrids(SERVER_NODES_CNT - 1);
+
+        for (int i = 0; i < CLIENT_NODES_CNT; i++)
+            startClientGrid(SERVER_NODES_CNT + i);
+
+        IgniteEx ign = startGrid(
+            getConfiguration().setServiceConfiguration(
+                new ServiceConfiguration()
+                    .setName(NOOP_SERVICE_NAME)
+                    .setService(new NoopService())
+                    .setTotalCount(noopSrvcTotalCnt),
+                new ServiceConfiguration()
+                    .setName(INIT_THROWING_SERVICE_NAME)
+                    .setService(new InitThrowingService())
+                    .setTotalCount(20)
+            )
+        );
+
+        assertTrue(
+            FAILED_SERVICE_SHOULD_NOT_BE_PRESENT_IN_THE_CLUSTER,
+            waitForCondition(() -> 
noDescriptorInClusterForService(INIT_THROWING_SERVICE_NAME), TIMEOUT)
+        );
+
+        assertEquals(
+            DEPLOYED_SERVICE_MUST_BE_PRESENTED_IN_CLUSTER,
+            noopSrvcTotalCnt,
+            totalInstancesCount(ign, NOOP_SERVICE_NAME)
+        );
+    }
+
+    /**
+     * @param srvcName Service name.
+     */
+    private static boolean noDescriptorInClusterForService(String srvcName) {
+        return Ignition.allGrids().stream().allMatch(
+            node -> 
node.services().serviceDescriptors().stream().noneMatch(filterByName(srvcName)) 
&&
+                    getServicesMap(node, 
REGISTERED_SERVICES_BY_NAME).values().stream().noneMatch(filterByName(srvcName))
 &&
+                    getServicesMap(node, 
DEPLOYED_SERVICES).values().stream().noneMatch(filterByName(srvcName)) &&
+                    getServicesMap(node, 
DEPLOYED_SERVICES_BY_NAME).values().stream().noneMatch(filterByName(srvcName))
+        );
+    }
+
+    /**
+     * Retrieves the total instances count of the service.
+     *
+     * @param igniteEx Ignite instance.
+     * @param srvcName Service name.
+     * @return Total instances count of the service named {@code srvcName} 
from the given {@code igniteEx} instance.
+     */
+    private static int totalInstancesCount(IgniteEx igniteEx, String srvcName) 
{
+        int registeredCnt = 
totalInstancesCount(igniteEx.services().serviceDescriptors(), srvcName);
+
+        assertEquals(registeredCnt, 
totalInstancesCount(getServicesMap(igniteEx, 
REGISTERED_SERVICES_BY_NAME).values(), srvcName));
+        assertEquals(registeredCnt, 
totalInstancesCount(getServicesMap(igniteEx, DEPLOYED_SERVICES).values(), 
srvcName));
+        assertEquals(registeredCnt, 
totalInstancesCount(getServicesMap(igniteEx, 
DEPLOYED_SERVICES_BY_NAME).values(), srvcName));
+        
+        return registeredCnt;
+    }
+
+    /**
+     * @param descs Descriptors.
+     * @param srvcName Service name.
+     * @return Total instances count of the service named {@code srvcName} 
from the given service descriptors {@code descs}.
+     */
+    private static <T> int totalInstancesCount(Collection<? extends 
ServiceDescriptor> descs, String srvcName) {
+        return descs.stream()
+            .filter(filterByName(srvcName))
+            .flatMap(desc -> desc.topologySnapshot().values().stream())
+            .mapToInt(Integer::intValue).sum();
+    }
+
+    /**
+     * Filters descriptors of services with given name.
+     *
+     * @param srvcName Service name.
+     */
+    private static @NotNull Predicate<ServiceDescriptor> filterByName(String 
srvcName) {
+        return desc -> desc.name().equals(srvcName);
+    }
+
+    /**
+     * @param node Node.
+     * @param mapName Map name.
+     */
+    private static <T> Map<T, ServiceInfo> getServicesMap(Ignite node, String 
mapName) {
+        return getFieldValue(
+            ((GridKernalContext)getFieldValue(node.services(), 
IgniteServicesImpl.class, "ctx")).service(),
+            IgniteServiceProcessor.class,
+            mapName
+        );
+    }
+
+    /**
+     * Service that throws an exception in init.
+     */
+    private static class InitThrowingService implements Service {
+        /** {@inheritDoc} */
+        @Override public void init() throws Exception {
+            throw new Exception("Service init exception");
+        }
+    }
+
+    /**
+     * NoopService for testing.
+     */
+    private static class NoopService implements Service {
+        /** {@inheritDoc} */
+        @Override public void init() throws Exception {
+            // No-op.
+        }
+    }
+}
diff --git 
a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteServiceGridTestSuite.java
 
b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteServiceGridTestSuite.java
index a5ee7fe1bba..6b6a2716b8e 100644
--- 
a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteServiceGridTestSuite.java
+++ 
b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteServiceGridTestSuite.java
@@ -46,6 +46,7 @@ import 
org.apache.ignite.internal.processors.service.IgniteServiceDeployment2Cla
 import 
org.apache.ignite.internal.processors.service.IgniteServiceDeploymentClassLoadingDefaultMarshallerTest;
 import 
org.apache.ignite.internal.processors.service.IgniteServiceDeploymentClassLoadingJdkMarshallerTest;
 import 
org.apache.ignite.internal.processors.service.IgniteServiceDeploymentClassLoadingOptimizedMarshallerTest;
+import 
org.apache.ignite.internal.processors.service.IgniteServiceDeploymentFailureTest;
 import 
org.apache.ignite.internal.processors.service.IgniteServiceDynamicCachesSelfTest;
 import 
org.apache.ignite.internal.processors.service.IgniteServiceProxyTimeoutInitializedTest;
 import 
org.apache.ignite.internal.processors.service.IgniteServiceReassignmentTest;
@@ -106,6 +107,7 @@ import org.junit.runners.Suite;
     IgniteServiceDeployment2ClassLoadersDefaultMarshallerTest.class,
     IgniteServiceDeployment2ClassLoadersJdkMarshallerTest.class,
     IgniteServiceDeployment2ClassLoadersOptimizedMarshallerTest.class,
+    IgniteServiceDeploymentFailureTest.class,
 
     GridServiceDeploymentExceptionPropagationTest.class,
     ServiceDeploymentProcessingOnCoordinatorLeftTest.class,

Reply via email to