IGNITE-12811: Cancel service command (#7565)

diff --git a/modules/core/src/main/java/org/apache/ignite/internal/ServiceMXBeanImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/ServiceMXBeanImpl.java
new file mode 100644
index 0000000..7ee7104
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/ServiceMXBeanImpl.java
@@ -0,0 +1,67 @@
+/*
+ * 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;
+
+import org.apache.ignite.IgniteCompute;
+import org.apache.ignite.IgniteException;
+import org.apache.ignite.IgniteLogger;
+import org.apache.ignite.internal.cluster.IgniteClusterImpl;
+import org.apache.ignite.internal.util.typedef.internal.A;
+import org.apache.ignite.internal.visor.VisorTaskArgument;
+import org.apache.ignite.internal.visor.service.VisorCancelServiceTask;
+import org.apache.ignite.internal.visor.service.VisorCancelServiceTaskArg;
+import org.apache.ignite.mxbean.ServiceMXBean;
+
+/**
+ * ServiceMXBean implementation.
+ */
+public class ServiceMXBeanImpl implements ServiceMXBean {
+    /** Kernal context. */
+    private final GridKernalContext ctx;
+
+    /** Logger. */
+    private final IgniteLogger log;
+
+    /**
+     * @param ctx Context.
+     */
+    public ServiceMXBeanImpl(GridKernalContext ctx) {
+        this.ctx = ctx;
+        this.log = ctx.log(ServiceMXBeanImpl.class);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void cancel(String name) {
+        A.notNull(name, "name");
+
+        if (log.isInfoEnabled())
+            log.info("Canceling service[name=" + name + ']');
+
+        try {
+            IgniteClusterImpl cluster = ctx.cluster().get();
+
+            IgniteCompute compute = cluster.compute();
+
+            compute.execute(new VisorCancelServiceTask(),
+                new VisorTaskArgument<>(ctx.localNodeId(), new VisorCancelServiceTaskArg(name), false));
+        }
+        catch (IgniteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/commandline/query/KillCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/commandline/query/KillCommand.java
index a86e341..0d5e577 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/commandline/query/KillCommand.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/commandline/query/KillCommand.java
@@ -25,22 +25,25 @@
 import org.apache.ignite.internal.commandline.Command;
 import org.apache.ignite.internal.commandline.CommandArgIterator;
 import org.apache.ignite.internal.commandline.CommandLogger;
+import org.apache.ignite.internal.visor.service.VisorCancelServiceTask;
+import org.apache.ignite.internal.visor.service.VisorCancelServiceTaskArg;
+import org.apache.ignite.mxbean.ServiceMXBean;
 import org.apache.ignite.internal.visor.compute.VisorComputeCancelSessionTask;
 import org.apache.ignite.internal.visor.compute.VisorComputeCancelSessionTaskArg;
 import org.apache.ignite.lang.IgniteUuid;
 import org.apache.ignite.mxbean.ComputeMXBean;
-import org.apache.ignite.mxbean.TransactionsMXBean;
 
 import static org.apache.ignite.internal.commandline.CommandList.KILL;
 import static org.apache.ignite.internal.commandline.TaskExecutor.executeTaskByNameOnNode;
+import static org.apache.ignite.internal.commandline.query.KillSubcommand.SERVICE;
 import static org.apache.ignite.internal.commandline.query.KillSubcommand.COMPUTE;
 
 /**
  * control.sh kill command.
  *
  * @see KillSubcommand
+ * @see ServiceMXBean
  * @see ComputeMXBean
- * @see TransactionsMXBean
  */
 public class KillCommand implements Command<Object> {
     /** Command argument. */
@@ -93,6 +96,13 @@
 
                 break;
 
+            case SERVICE:
+                taskArgs = new VisorCancelServiceTaskArg(argIter.nextArg("Expected service name."));
+
+                taskName = VisorCancelServiceTask.class.getName();
+
+                break;
+
             default:
                 throw new IllegalArgumentException("Unknown kill subcommand: " + cmd);
         }
@@ -106,6 +116,11 @@
 
         Command.usage(log, "Kill compute task by session id:", KILL, params, COMPUTE.toString(),
             "session_id");
+
+        params.clear();
+        params.put("name", "Service name.");
+
+        Command.usage(log, "Kill service by name:", KILL, params, SERVICE.toString(), "name");
     }
 
     /** {@inheritDoc} */
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/commandline/query/KillSubcommand.java b/modules/core/src/main/java/org/apache/ignite/internal/commandline/query/KillSubcommand.java
index 4b220ea..9ad7d7a 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/commandline/query/KillSubcommand.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/commandline/query/KillSubcommand.java
@@ -18,14 +18,19 @@
 package org.apache.ignite.internal.commandline.query;
 
 import org.apache.ignite.mxbean.ComputeMXBean;
+import org.apache.ignite.mxbean.ServiceMXBean;
 
 /**
  * Subcommands of the kill command.
  *
  * @see KillCommand
  * @see ComputeMXBean
+ * @see ServiceMXBean
  */
 public enum KillSubcommand {
     /** Kill compute task. */
-    COMPUTE
+    COMPUTE,
+
+    /** Kill service. */
+    SERVICE
 }
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/managers/IgniteMBeansManager.java b/modules/core/src/main/java/org/apache/ignite/internal/managers/IgniteMBeansManager.java
index babf847..3fae961 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/managers/IgniteMBeansManager.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/managers/IgniteMBeansManager.java
@@ -30,6 +30,7 @@
 import org.apache.ignite.internal.ComputeMXBeanImpl;
 import org.apache.ignite.internal.GridKernalContextImpl;
 import org.apache.ignite.internal.IgniteKernal;
+import org.apache.ignite.internal.ServiceMXBeanImpl;
 import org.apache.ignite.internal.StripedExecutorMXBeanAdapter;
 import org.apache.ignite.internal.ThreadPoolMXBeanAdapter;
 import org.apache.ignite.internal.TransactionMetricsMxBeanImpl;
@@ -51,6 +52,7 @@
 import org.apache.ignite.mxbean.FailureHandlingMxBean;
 import org.apache.ignite.mxbean.IgniteMXBean;
 import org.apache.ignite.mxbean.MetricsMxBean;
+import org.apache.ignite.mxbean.ServiceMXBean;
 import org.apache.ignite.mxbean.StripedExecutorMXBean;
 import org.apache.ignite.mxbean.ThreadPoolMXBean;
 import org.apache.ignite.mxbean.TransactionMetricsMxBean;
@@ -153,6 +155,10 @@
         ComputeMXBean computeMXBean = new ComputeMXBeanImpl(ctx);
         registerMBean("Compute", computeMXBean.getClass().getSimpleName(), computeMXBean, ComputeMXBean.class);
 
+        // Service management
+        ServiceMXBean serviceMXBean = new ServiceMXBeanImpl(ctx);
+        registerMBean("Service", serviceMXBean.getClass().getSimpleName(), serviceMXBean, ServiceMXBean.class);
+
         // Data storage
         DataStorageMXBean dataStorageMXBean = new DataStorageMXBeanImpl(ctx);
         registerMBean("DataStorage", dataStorageMXBean.getClass().getSimpleName(), dataStorageMXBean, DataStorageMXBean.class);
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/sql/SqlKeyword.java b/modules/core/src/main/java/org/apache/ignite/internal/sql/SqlKeyword.java
index b772b2d..88afb90 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/sql/SqlKeyword.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/sql/SqlKeyword.java
@@ -35,6 +35,9 @@
     /** Keyword: COMPUTE_TASK. */
     public static final String COMPUTE = "COMPUTE";
 
+    /** Keyword: SERVICE. */
+    public static final String SERVICE = "SERVICE";
+
     /** Keyword: ALTER. */
     public static final String ALTER = "ALTER";
 
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/sql/SqlParser.java b/modules/core/src/main/java/org/apache/ignite/internal/sql/SqlParser.java
index e0fd335..aa7a24a 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/sql/SqlParser.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/sql/SqlParser.java
@@ -30,6 +30,7 @@
 import org.apache.ignite.internal.sql.command.SqlDropUserCommand;
 import org.apache.ignite.internal.sql.command.SqlKillComputeTaskCommand;
 import org.apache.ignite.internal.sql.command.SqlKillQueryCommand;
+import org.apache.ignite.internal.sql.command.SqlKillServiceCommand;
 import org.apache.ignite.internal.sql.command.SqlRollbackTransactionCommand;
 import org.apache.ignite.internal.sql.command.SqlSetStreamingCommand;
 import org.jetbrains.annotations.Nullable;
@@ -50,6 +51,7 @@
 import static org.apache.ignite.internal.sql.SqlKeyword.QUERY;
 import static org.apache.ignite.internal.sql.SqlKeyword.REVOKE;
 import static org.apache.ignite.internal.sql.SqlKeyword.ROLLBACK;
+import static org.apache.ignite.internal.sql.SqlKeyword.SERVICE;
 import static org.apache.ignite.internal.sql.SqlKeyword.SET;
 import static org.apache.ignite.internal.sql.SqlKeyword.SHOW;
 import static org.apache.ignite.internal.sql.SqlKeyword.SPATIAL;
@@ -294,6 +296,9 @@
 
                 case COMPUTE:
                     return new SqlKillComputeTaskCommand().parse(lex);
+
+                case SERVICE:
+                    return new SqlKillServiceCommand().parse(lex);
             }
         }
 
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/sql/command/SqlKillServiceCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/sql/command/SqlKillServiceCommand.java
new file mode 100644
index 0000000..2f22594
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/sql/command/SqlKillServiceCommand.java
@@ -0,0 +1,63 @@
+/*
+ * 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.sql.command;
+
+import org.apache.ignite.internal.sql.SqlLexer;
+import org.apache.ignite.internal.sql.SqlLexerTokenType;
+import org.apache.ignite.internal.sql.SqlParserUtils;
+import org.apache.ignite.mxbean.ServiceMXBean;
+import org.apache.ignite.spi.systemview.view.ServiceView;
+
+/**
+ * KILL SERVICE command.
+ *
+ * @see ServiceMXBean#cancel(String)
+ * @see ServiceView#name()
+ */
+public class SqlKillServiceCommand implements SqlCommand {
+    /** Service name. */
+    private String name;
+
+    /** {@inheritDoc} */
+    @Override public SqlCommand parse(SqlLexer lex) {
+        if (lex.shift()) {
+            if (lex.tokenType() == SqlLexerTokenType.STRING) {
+                name = lex.token();
+
+                return this;
+            }
+        }
+
+        throw SqlParserUtils.error(lex, "Expected service name.");
+    }
+
+    /** {@inheritDoc} */
+    @Override public String schemaName() {
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void schemaName(String schemaName) {
+        // No-op.
+    }
+
+    /** @return Service name. */
+    public String getName() {
+        return name;
+    }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/mxbean/ServiceMXBean.java b/modules/core/src/main/java/org/apache/ignite/mxbean/ServiceMXBean.java
new file mode 100644
index 0000000..4897d21
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/mxbean/ServiceMXBean.java
@@ -0,0 +1,34 @@
+/*
+ * 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.mxbean;
+
+import org.apache.ignite.spi.systemview.view.ServiceView;
+
+/**
+ * Service MXBean interface.
+ */
+public interface ServiceMXBean {
+    /**
+     * @param name Service name.
+     * @see ServiceView#name()
+     */
+    @MXBeanDescription("Kills service by the name.")
+    public void cancel(
+        @MXBeanParameter(name = "name", description = "Service name.") String name
+    );
+}
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/commandline/CommandHandlerParsingTest.java b/modules/core/src/test/java/org/apache/ignite/internal/commandline/CommandHandlerParsingTest.java
index d28d6cb..d884ac5 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/commandline/CommandHandlerParsingTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/commandline/CommandHandlerParsingTest.java
@@ -529,6 +529,9 @@
 
         assertParseArgsThrows("Invalid UUID string: not_a_uuid", IllegalArgumentException.class,
             "--kill", "compute", "not_a_uuid");
+
+        // Service command format errors.
+        assertParseArgsThrows("Expected service name.", "--kill", "service");
     }
 
     /**
diff --git a/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassTest_help.output b/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassTest_help.output
index 9060abb..e83571c 100644
--- a/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassTest_help.output
+++ b/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassTest_help.output
@@ -74,6 +74,12 @@
     Parameters:
       session_id  - Session identifier.
 
+  Kill service by name:
+    control.(sh|bat) --kill SERVICE name
+
+    Parameters:
+      name  - Service name.
+
 By default commands affecting the cluster require interactive confirmation.
 Use --yes option to disable it.
 
diff --git a/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassWithSSLTest_help.output b/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassWithSSLTest_help.output
index 9060abb..e83571c 100644
--- a/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassWithSSLTest_help.output
+++ b/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassWithSSLTest_help.output
@@ -74,6 +74,12 @@
     Parameters:
       session_id  - Session identifier.
 
+  Kill service by name:
+    control.(sh|bat) --kill SERVICE name
+
+    Parameters:
+      name  - Service name.
+
 By default commands affecting the cluster require interactive confirmation.
 Use --yes option to disable it.
 
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/CommandProcessor.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/CommandProcessor.java
index a2f2595..676b0b4 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/CommandProcessor.java
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/CommandProcessor.java
@@ -52,6 +52,7 @@
 import org.apache.ignite.internal.GridKernalContext;
 import org.apache.ignite.internal.GridTopic;
 import org.apache.ignite.internal.IgniteInternalFuture;
+import org.apache.ignite.internal.ServiceMXBeanImpl;
 import org.apache.ignite.internal.managers.communication.GridIoPolicy;
 import org.apache.ignite.internal.managers.eventstorage.GridLocalEventListener;
 import org.apache.ignite.internal.processors.bulkload.BulkLoadAckClientParameters;
@@ -100,6 +101,7 @@
 import org.apache.ignite.internal.sql.command.SqlIndexColumn;
 import org.apache.ignite.internal.sql.command.SqlKillComputeTaskCommand;
 import org.apache.ignite.internal.sql.command.SqlKillQueryCommand;
+import org.apache.ignite.internal.sql.command.SqlKillServiceCommand;
 import org.apache.ignite.internal.sql.command.SqlRollbackTransactionCommand;
 import org.apache.ignite.internal.sql.command.SqlSetStreamingCommand;
 import org.apache.ignite.internal.util.future.GridFinishedFuture;
@@ -413,6 +415,8 @@
                 processKillQueryCommand((SqlKillQueryCommand) cmdNative);
             else if (cmdNative instanceof SqlKillComputeTaskCommand)
                 processKillComputeTaskCommand((SqlKillComputeTaskCommand) cmdNative);
+            else if (cmdNative instanceof SqlKillServiceCommand)
+                processKillServiceTaskCommand((SqlKillServiceCommand) cmdNative);
             else
                 processTxCommand(cmdNative, params);
         }
@@ -501,6 +505,15 @@
     }
 
     /**
+     * Process kill service command.
+     *
+     * @param cmd Command.
+     */
+    private void processKillServiceTaskCommand(SqlKillServiceCommand cmd) {
+        new ServiceMXBeanImpl(ctx).cancel(cmd.getName());
+    }
+
+    /**
      * Run DDL statement.
      *
      * @param sql Original SQL.
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/QueryParser.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/QueryParser.java
index da84eec..1a391b8 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/QueryParser.java
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/QueryParser.java
@@ -71,6 +71,7 @@
 import org.apache.ignite.internal.sql.command.SqlDropUserCommand;
 import org.apache.ignite.internal.sql.command.SqlKillComputeTaskCommand;
 import org.apache.ignite.internal.sql.command.SqlKillQueryCommand;
+import org.apache.ignite.internal.sql.command.SqlKillServiceCommand;
 import org.apache.ignite.internal.sql.command.SqlRollbackTransactionCommand;
 import org.apache.ignite.internal.sql.command.SqlSetStreamingCommand;
 import org.apache.ignite.internal.util.GridBoundedConcurrentLinkedHashMap;
@@ -91,7 +92,7 @@
     /** A pattern for commands having internal implementation in Ignite. */
     private static final Pattern INTERNAL_CMD_RE = Pattern.compile(
         "^(create|drop)\\s+index|^alter\\s+table|^copy|^set|^begin|^commit|^rollback|^(create|alter|drop)\\s+user" +
-            "|^kill\\s+(query|compute)|show|help|grant|revoke",
+            "|^kill\\s+(query|compute|service)|show|help|grant|revoke",
         Pattern.CASE_INSENSITIVE);
 
     /** Indexing. */
@@ -266,7 +267,8 @@
                 || nativeCmd instanceof SqlAlterUserCommand
                 || nativeCmd instanceof SqlDropUserCommand
                 || nativeCmd instanceof SqlKillQueryCommand
-                || nativeCmd instanceof SqlKillComputeTaskCommand)
+                || nativeCmd instanceof SqlKillComputeTaskCommand
+                || nativeCmd instanceof SqlKillServiceCommand)
             )
                 return null;
 
diff --git a/modules/indexing/src/test/java/org/apache/ignite/util/KillCommandsCommandShTest.java b/modules/indexing/src/test/java/org/apache/ignite/util/KillCommandsCommandShTest.java
index 34493c1..2489394 100644
--- a/modules/indexing/src/test/java/org/apache/ignite/util/KillCommandsCommandShTest.java
+++ b/modules/indexing/src/test/java/org/apache/ignite/util/KillCommandsCommandShTest.java
@@ -25,6 +25,7 @@
 
 import static org.apache.ignite.internal.commandline.CommandHandler.EXIT_CODE_OK;
 import static org.apache.ignite.util.KillCommandsTests.doTestCancelComputeTask;
+import static org.apache.ignite.util.KillCommandsTests.doTestCancelService;
 
 /** Tests cancel of user created entities via control.sh. */
 public class KillCommandsCommandShTest extends GridCommandHandlerClusterByClassAbstractTest {
@@ -58,6 +59,16 @@
         });
     }
 
+    /** @throws Exception If failed. */
+    @Test
+    public void testCancelService() throws Exception {
+        doTestCancelService(client, client, srvs.get(0), name -> {
+            int res = execute("--kill", "service", name);
+
+            assertEquals(EXIT_CODE_OK, res);
+        });
+    }
+
     /** */
     @Test
     public void testCancelUnknownComputeTask() {
@@ -65,4 +76,12 @@
 
         assertEquals(EXIT_CODE_OK, res);
     }
+
+    /** */
+    @Test
+    public void testCancelUnknownService() {
+        int res = execute("--kill", "service", "unknown");
+
+        assertEquals(EXIT_CODE_OK, res);
+    }
 }
diff --git a/modules/indexing/src/test/java/org/apache/ignite/util/KillCommandsMXBeanTest.java b/modules/indexing/src/test/java/org/apache/ignite/util/KillCommandsMXBeanTest.java
index 28f61fc..5ca33fa 100644
--- a/modules/indexing/src/test/java/org/apache/ignite/util/KillCommandsMXBeanTest.java
+++ b/modules/indexing/src/test/java/org/apache/ignite/util/KillCommandsMXBeanTest.java
@@ -23,11 +23,14 @@
 import org.apache.ignite.internal.IgniteEx;
 import org.apache.ignite.lang.IgniteUuid;
 import org.apache.ignite.mxbean.ComputeMXBean;
+import org.apache.ignite.internal.ServiceMXBeanImpl;
+import org.apache.ignite.mxbean.ServiceMXBean;
 import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
 import org.junit.Test;
 
 import static org.apache.ignite.cluster.ClusterState.ACTIVE;
 import static org.apache.ignite.util.KillCommandsTests.doTestCancelComputeTask;
+import static org.apache.ignite.util.KillCommandsTests.doTestCancelService;
 
 /** Tests cancel of user created entities via JMX. */
 public class KillCommandsMXBeanTest extends GridCommonAbstractTest {
@@ -46,6 +49,9 @@
     /** */
     private static ComputeMXBean computeMBean;
 
+    /** */
+    private static ServiceMXBean svcMxBean;
+
     /** {@inheritDoc} */
     @Override protected void beforeTestsStarted() throws Exception {
         startGridsMultiThreaded(NODES_CNT);
@@ -62,13 +68,21 @@
 
         computeMBean = getMxBean(killCli.name(), "Compute",
             ComputeMXBeanImpl.class.getSimpleName(), ComputeMXBean.class);
+
+        svcMxBean = getMxBean(killCli.name(), "Service",
+            ServiceMXBeanImpl.class.getSimpleName(), ServiceMXBean.class);
     }
 
     /** @throws Exception If failed. */
     @Test
     public void testCancelComputeTask() throws Exception {
-        doTestCancelComputeTask(startCli, srvs, sessId ->
-            computeMBean.cancel(sessId));
+        doTestCancelComputeTask(startCli, srvs, sessId -> computeMBean.cancel(sessId));
+    }
+
+    /** @throws Exception If failed. */
+    @Test
+    public void testCancelService() throws Exception {
+        doTestCancelService(startCli, killCli, srvs.get(0), name -> svcMxBean.cancel(name));
     }
 
     /** */
@@ -76,4 +90,10 @@
     public void testCancelUnknownComputeTask() {
         computeMBean.cancel(IgniteUuid.randomUuid().toString());
     }
+
+    /** */
+    @Test
+    public void testCancelUnknownService() {
+        svcMxBean.cancel("unknown");
+    }
 }
diff --git a/modules/indexing/src/test/java/org/apache/ignite/util/KillCommandsSQLTest.java b/modules/indexing/src/test/java/org/apache/ignite/util/KillCommandsSQLTest.java
index a009124..2a5cc8e 100644
--- a/modules/indexing/src/test/java/org/apache/ignite/util/KillCommandsSQLTest.java
+++ b/modules/indexing/src/test/java/org/apache/ignite/util/KillCommandsSQLTest.java
@@ -30,7 +30,9 @@
 import static org.apache.ignite.internal.processors.cache.index.AbstractSchemaSelfTest.queryProcessor;
 import static org.apache.ignite.internal.sql.SqlKeyword.COMPUTE;
 import static org.apache.ignite.internal.sql.SqlKeyword.KILL;
+import static org.apache.ignite.internal.sql.SqlKeyword.SERVICE;
 import static org.apache.ignite.util.KillCommandsTests.doTestCancelComputeTask;
+import static org.apache.ignite.util.KillCommandsTests.doTestCancelService;
 
 /** Tests cancel of user created entities via SQL. */
 public class KillCommandsSQLTest extends GridCommonAbstractTest {
@@ -41,6 +43,9 @@
     public static final String KILL_COMPUTE_QRY = KILL + " " + COMPUTE;
 
     /** */
+    public static final String KILL_SVC_QRY = KILL + " " + SERVICE;
+
+    /** */
     private static List<IgniteEx> srvs;
 
     /** Client that starts tasks. */
@@ -70,12 +75,25 @@
         doTestCancelComputeTask(startCli, srvs, sessId -> execute(killCli, KILL_COMPUTE_QRY + " '" + sessId + "'"));
     }
 
+    /** @throws Exception If failed. */
+    @Test
+    public void testCancelService() throws Exception {
+        doTestCancelService(startCli, killCli, srvs.get(0),
+            name -> execute(srvs.get(0), KILL_SVC_QRY + " '" + name + "'"));
+    }
+
     /** */
     @Test
     public void testCancelUnknownComputeTask() {
         execute(killCli, KILL_COMPUTE_QRY + " '" + IgniteUuid.randomUuid() + "'");
     }
 
+    /** */
+    @Test
+    public void testCancelUnknownService() {
+        execute(killCli, KILL_SVC_QRY + " 'unknown'");
+    }
+
     /**
      * Execute query on given node.
      *
diff --git a/modules/indexing/src/test/java/org/apache/ignite/util/KillCommandsTests.java b/modules/indexing/src/test/java/org/apache/ignite/util/KillCommandsTests.java
index 3ca620e..7a0af7f 100644
--- a/modules/indexing/src/test/java/org/apache/ignite/util/KillCommandsTests.java
+++ b/modules/indexing/src/test/java/org/apache/ignite/util/KillCommandsTests.java
@@ -24,16 +24,26 @@
 import org.apache.ignite.IgniteException;
 import org.apache.ignite.internal.IgniteEx;
 import org.apache.ignite.lang.IgniteFuture;
+import org.apache.ignite.services.Service;
+import org.apache.ignite.services.ServiceConfiguration;
+import org.apache.ignite.services.ServiceContext;
+import org.apache.ignite.spi.systemview.view.ServiceView;
+import org.apache.ignite.spi.systemview.view.SystemView;
 
+import static org.apache.ignite.internal.processors.service.IgniteServiceProcessor.SVCS_VIEW;
 import static org.apache.ignite.testframework.GridTestUtils.assertThrowsWithCause;
 import static org.apache.ignite.testframework.GridTestUtils.waitForCondition;
 import static org.apache.ignite.util.KillCommandsSQLTest.execute;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
 /**
  * General tests for the cancel command.
  */
 class KillCommandsTests {
+    /** Service name. */
+    public static final String SVC_NAME = "my-svc";
+
     /** Operations timeout. */
     public static final int TIMEOUT = 10_000;
 
@@ -92,4 +102,70 @@
             computeLatch.countDown();
         }
     }
+
+    /**
+     * Test cancel of the service.
+     *
+     * @param startCli Client node to start service.
+     * @param killCli Client node to kill service.
+     * @param srv Server node.
+     * @param svcCanceler Service cancel closure.
+     */
+    public static void doTestCancelService(IgniteEx startCli, IgniteEx killCli, IgniteEx srv,
+        Consumer<String> svcCanceler) throws Exception {
+        ServiceConfiguration scfg = new ServiceConfiguration();
+
+        scfg.setName(SVC_NAME);
+        scfg.setMaxPerNodeCount(1);
+        scfg.setNodeFilter(srv.cluster().predicate());
+        scfg.setService(new TestServiceImpl());
+
+        startCli.services().deploy(scfg);
+
+        SystemView<ServiceView> svcView = srv.context().systemView().view(SVCS_VIEW);
+        SystemView<ServiceView> killCliSvcView = killCli.context().systemView().view(SVCS_VIEW);
+
+        boolean res = waitForCondition(() -> svcView.size() == 1 && killCliSvcView.size() == 1, TIMEOUT);
+
+        assertTrue(res);
+
+        TestService svc = startCli.services().serviceProxy(SVC_NAME, TestService.class, true);
+
+        assertNotNull(svc);
+
+        svcCanceler.accept(SVC_NAME);
+
+        res = waitForCondition(() -> svcView.size() == 0, TIMEOUT);
+
+        assertTrue(res);
+    }
+
+    /** */
+    public interface TestService extends Service {
+        /** */
+        public void doTheJob();
+    }
+
+    /** */
+    public static class TestServiceImpl implements TestService {
+        /** {@inheritDoc} */
+        @Override public void cancel(ServiceContext ctx) {
+            // No-op.
+        }
+
+        /** {@inheritDoc} */
+        @Override public void init(ServiceContext ctx) {
+            // No-op.
+        }
+
+        /** {@inheritDoc} */
+        @Override public void execute(ServiceContext ctx) {
+            // No-op.
+        }
+
+        /** {@inheritDoc} */
+        @Override public void doTheJob() {
+            // No-op.
+        }
+    }
 }