Browse Source

Add REST scaffolding for node shutdown API (#70697)

This commit adds the rest endpoints for the node shutdown API. These APIs are behind the
`es.shutdown_feature_flag_enabled` feature flag for now, as development is ongoing.

Currently these APIs do not do anything, returning immediately. We plan to implement them for real
in subsequent work.

Relates to #70338
Lee Hinman 4 years ago
parent
commit
5764a188a9
17 changed files with 701 additions and 0 deletions
  1. 1 0
      docs/build.gradle
  2. 1 0
      gradle/run.gradle
  3. 16 0
      x-pack/plugin/shutdown/build.gradle
  4. 61 0
      x-pack/plugin/shutdown/src/main/java/org/elasticsearch/xpack/shutdown/DeleteShutdownNodeAction.java
  5. 75 0
      x-pack/plugin/shutdown/src/main/java/org/elasticsearch/xpack/shutdown/GetShutdownStatusAction.java
  6. 60 0
      x-pack/plugin/shutdown/src/main/java/org/elasticsearch/xpack/shutdown/PutShutdownNodeAction.java
  7. 38 0
      x-pack/plugin/shutdown/src/main/java/org/elasticsearch/xpack/shutdown/RestDeleteShutdownNodeAction.java
  8. 42 0
      x-pack/plugin/shutdown/src/main/java/org/elasticsearch/xpack/shutdown/RestGetShutdownStatusAction.java
  9. 38 0
      x-pack/plugin/shutdown/src/main/java/org/elasticsearch/xpack/shutdown/RestPutShutdownNodeAction.java
  10. 71 0
      x-pack/plugin/shutdown/src/main/java/org/elasticsearch/xpack/shutdown/ShutdownPlugin.java
  11. 60 0
      x-pack/plugin/shutdown/src/main/java/org/elasticsearch/xpack/shutdown/TransportDeleteShutdownNodeAction.java
  12. 62 0
      x-pack/plugin/shutdown/src/main/java/org/elasticsearch/xpack/shutdown/TransportGetShutdownStatusAction.java
  13. 60 0
      x-pack/plugin/shutdown/src/main/java/org/elasticsearch/xpack/shutdown/TransportPutShutdownNodeAction.java
  14. 16 0
      x-pack/plugin/shutdown/src/test/java/org/elasticsearch/xpack/shutdown/ShutdownTests.java
  15. 31 0
      x-pack/plugin/src/test/resources/rest-api-spec/api/shutdown.delete_node.json
  16. 34 0
      x-pack/plugin/src/test/resources/rest-api-spec/api/shutdown.get_node.json
  17. 35 0
      x-pack/plugin/src/test/resources/rest-api-spec/api/shutdown.put_node.json

+ 1 - 0
docs/build.gradle

@@ -44,6 +44,7 @@ testClusters.matching { it.name == "integTest"}.configureEach {
     setting 'xpack.license.self_generated.type', 'trial'
     setting 'indices.lifecycle.history_index_enabled', 'false'
     systemProperty 'es.rollup_v2_feature_flag_enabled', 'true'
+    systemProperty 'es.shutdown_feature_flag_enabled', 'true'
     keystorePassword 'keystore-password'
   }
 

+ 1 - 0
gradle/run.gradle

@@ -25,6 +25,7 @@ testClusters {
       setting 'xpack.security.enabled', 'true'
       keystore 'bootstrap.password', 'password'
       user username: 'elastic-admin', password: 'elastic-password', role: 'superuser'
+      systemProperty 'es.shutdown_feature_flag_enabled', 'true'
     }
   }
 }

+ 16 - 0
x-pack/plugin/shutdown/build.gradle

@@ -0,0 +1,16 @@
+apply plugin: 'elasticsearch.esplugin'
+
+esplugin {
+    name 'x-pack-shutdown'
+    description 'Elasticsearch Expanded Pack Plugin - Shutdown'
+    classname 'org.elasticsearch.xpack.shutdown.ShutdownPlugin'
+    extendedPlugins = ['x-pack-core']
+}
+archivesBaseName = 'x-pack-shutdown'
+
+dependencies {
+    compileOnly project(path: xpackModule('core'))
+    testImplementation(testArtifact(project(xpackModule('core'))))
+}
+
+addQaCheckDependencies()

+ 61 - 0
x-pack/plugin/shutdown/src/main/java/org/elasticsearch/xpack/shutdown/DeleteShutdownNodeAction.java

@@ -0,0 +1,61 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+package org.elasticsearch.xpack.shutdown;
+
+import org.elasticsearch.action.ActionRequestValidationException;
+import org.elasticsearch.action.ActionType;
+import org.elasticsearch.action.support.master.AcknowledgedResponse;
+import org.elasticsearch.action.support.master.MasterNodeRequest;
+import org.elasticsearch.common.Strings;
+import org.elasticsearch.common.io.stream.StreamInput;
+import org.elasticsearch.common.io.stream.StreamOutput;
+
+import java.io.IOException;
+
+public class DeleteShutdownNodeAction extends ActionType<AcknowledgedResponse> {
+
+    public static final DeleteShutdownNodeAction INSTANCE = new DeleteShutdownNodeAction();
+    public static final String NAME = "cluster:admin/shutdown/delete";
+
+    public DeleteShutdownNodeAction() {
+        super(NAME, AcknowledgedResponse::readFrom);
+    }
+
+    public static class Request extends MasterNodeRequest<DeleteShutdownNodeAction.Request> {
+
+        private final String nodeId;
+
+        public Request(String nodeId) {
+            this.nodeId = nodeId;
+        }
+
+        public Request(StreamInput in) throws IOException {
+            this.nodeId = in.readString();
+        }
+
+        @Override
+        public void writeTo(StreamOutput out) throws IOException {
+            out.writeString(this.nodeId);
+        }
+
+        public String getNodeId() {
+            return nodeId;
+        }
+
+        @Override
+        public ActionRequestValidationException validate() {
+            if (Strings.hasText(nodeId) == false) {
+                ActionRequestValidationException arve = new ActionRequestValidationException();
+                arve.addValidationError("the node id to remove from shutdown is required");
+                return arve;
+            }
+            return null;
+        }
+    }
+
+}

+ 75 - 0
x-pack/plugin/shutdown/src/main/java/org/elasticsearch/xpack/shutdown/GetShutdownStatusAction.java

@@ -0,0 +1,75 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+package org.elasticsearch.xpack.shutdown;
+
+import org.elasticsearch.action.ActionRequestValidationException;
+import org.elasticsearch.action.ActionResponse;
+import org.elasticsearch.action.ActionType;
+import org.elasticsearch.action.support.master.MasterNodeRequest;
+import org.elasticsearch.common.io.stream.StreamInput;
+import org.elasticsearch.common.io.stream.StreamOutput;
+import org.elasticsearch.common.xcontent.ToXContentObject;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+
+import java.io.IOException;
+
+public class GetShutdownStatusAction extends ActionType<GetShutdownStatusAction.Response> {
+
+    public static final GetShutdownStatusAction INSTANCE = new GetShutdownStatusAction();
+    public static final String NAME = "cluster:admin/shutdown/get";
+
+    public GetShutdownStatusAction() {
+        super(NAME, Response::new);
+    }
+
+    public static class Request extends MasterNodeRequest<Request> {
+
+        private final String[] nodeIds;
+
+        public Request(String... nodeIds) {
+            this.nodeIds = nodeIds;
+        }
+
+        public static Request readFrom(StreamInput in) throws IOException {
+            return new Request(in.readStringArray());
+        }
+
+        @Override
+        public void writeTo(StreamOutput out) throws IOException {
+            out.writeStringArray(this.nodeIds);
+        }
+
+        public String[] getNodeIds() {
+            return nodeIds;
+        }
+
+        @Override
+        public ActionRequestValidationException validate() {
+            return null;
+        }
+    }
+
+    public static class Response extends ActionResponse implements ToXContentObject {
+
+        public Response(StreamInput in) throws IOException {
+
+        }
+
+        @Override
+        public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+            builder.startObject();
+            builder.endObject();
+            return builder;
+        }
+
+        @Override
+        public void writeTo(StreamOutput out) throws IOException {
+
+        }
+    }
+}

+ 60 - 0
x-pack/plugin/shutdown/src/main/java/org/elasticsearch/xpack/shutdown/PutShutdownNodeAction.java

@@ -0,0 +1,60 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+package org.elasticsearch.xpack.shutdown;
+
+import org.elasticsearch.action.ActionRequestValidationException;
+import org.elasticsearch.action.ActionType;
+import org.elasticsearch.action.support.master.AcknowledgedResponse;
+import org.elasticsearch.action.support.master.MasterNodeRequest;
+import org.elasticsearch.common.Strings;
+import org.elasticsearch.common.io.stream.StreamInput;
+import org.elasticsearch.common.io.stream.StreamOutput;
+
+import java.io.IOException;
+
+public class PutShutdownNodeAction extends ActionType<AcknowledgedResponse> {
+
+    public static final PutShutdownNodeAction INSTANCE = new PutShutdownNodeAction();
+    public static final String NAME = "cluster:admin/shutdown/create";
+
+    public PutShutdownNodeAction() {
+        super(NAME, AcknowledgedResponse::readFrom);
+    }
+
+    public static class Request extends MasterNodeRequest<Request> {
+
+        private final String nodeId;
+
+        public Request(String nodeId) {
+            this.nodeId = nodeId;
+        }
+
+        public Request(StreamInput in) throws IOException {
+            this.nodeId = in.readString();
+        }
+
+        @Override
+        public void writeTo(StreamOutput out) throws IOException {
+            out.writeString(this.nodeId);
+        }
+
+        public String getNodeId() {
+            return nodeId;
+        }
+
+        @Override
+        public ActionRequestValidationException validate() {
+            if (Strings.hasText(nodeId) == false) {
+                ActionRequestValidationException arve = new ActionRequestValidationException();
+                arve.addValidationError("the node id to shutdown is required");
+                return arve;
+            }
+            return null;
+        }
+    }
+}

+ 38 - 0
x-pack/plugin/shutdown/src/main/java/org/elasticsearch/xpack/shutdown/RestDeleteShutdownNodeAction.java

@@ -0,0 +1,38 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+package org.elasticsearch.xpack.shutdown;
+
+import org.elasticsearch.client.node.NodeClient;
+import org.elasticsearch.rest.BaseRestHandler;
+import org.elasticsearch.rest.RestRequest;
+import org.elasticsearch.rest.action.RestToXContentListener;
+
+import java.util.List;
+
+public class RestDeleteShutdownNodeAction extends BaseRestHandler {
+
+    @Override
+    public String getName() {
+        return "delete_shutdown_node";
+    }
+
+    @Override
+    public List<Route> routes() {
+        return List.of(new Route(RestRequest.Method.DELETE, "/_nodes/{nodeId}/shutdown"));
+    }
+
+    @Override
+    protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) {
+        String nodeId = request.param("nodeId");
+        return channel -> client.execute(
+            DeleteShutdownNodeAction.INSTANCE,
+            new DeleteShutdownNodeAction.Request(nodeId),
+            new RestToXContentListener<>(channel)
+        );
+    }
+}

+ 42 - 0
x-pack/plugin/shutdown/src/main/java/org/elasticsearch/xpack/shutdown/RestGetShutdownStatusAction.java

@@ -0,0 +1,42 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+package org.elasticsearch.xpack.shutdown;
+
+import org.elasticsearch.client.node.NodeClient;
+import org.elasticsearch.common.Strings;
+import org.elasticsearch.rest.BaseRestHandler;
+import org.elasticsearch.rest.RestRequest;
+import org.elasticsearch.rest.action.RestToXContentListener;
+
+import java.util.List;
+
+public class RestGetShutdownStatusAction extends BaseRestHandler {
+
+    @Override
+    public String getName() {
+        return "get_shutdown_status";
+    }
+
+    @Override
+    public List<Route> routes() {
+        return List.of(
+            new Route(RestRequest.Method.GET, "/_nodes/{nodeId}/shutdown"),
+            new Route(RestRequest.Method.GET, "/_nodes/shutdown")
+        );
+    }
+
+    @Override
+    protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) {
+        String[] nodeIds = Strings.commaDelimitedListToStringArray(request.param("nodeId"));
+        return channel -> client.execute(
+            GetShutdownStatusAction.INSTANCE,
+            new GetShutdownStatusAction.Request(nodeIds),
+            new RestToXContentListener<>(channel)
+        );
+    }
+}

+ 38 - 0
x-pack/plugin/shutdown/src/main/java/org/elasticsearch/xpack/shutdown/RestPutShutdownNodeAction.java

@@ -0,0 +1,38 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+package org.elasticsearch.xpack.shutdown;
+
+import org.elasticsearch.client.node.NodeClient;
+import org.elasticsearch.rest.BaseRestHandler;
+import org.elasticsearch.rest.RestRequest;
+import org.elasticsearch.rest.action.RestToXContentListener;
+
+import java.util.List;
+
+public class RestPutShutdownNodeAction extends BaseRestHandler {
+
+    @Override
+    public String getName() {
+        return "put_shutdown_node";
+    }
+
+    @Override
+    public List<Route> routes() {
+        return List.of(new Route(RestRequest.Method.PUT, "/_nodes/{nodeId}/shutdown"));
+    }
+
+    @Override
+    protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) {
+        String nodeId = request.param("nodeId");
+        return channel -> client.execute(
+            PutShutdownNodeAction.INSTANCE,
+            new PutShutdownNodeAction.Request(nodeId),
+            new RestToXContentListener<>(channel)
+        );
+    }
+}

+ 71 - 0
x-pack/plugin/shutdown/src/main/java/org/elasticsearch/xpack/shutdown/ShutdownPlugin.java

@@ -0,0 +1,71 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+package org.elasticsearch.xpack.shutdown;
+
+import org.elasticsearch.action.ActionRequest;
+import org.elasticsearch.action.ActionResponse;
+import org.elasticsearch.action.support.master.AcknowledgedResponse;
+import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
+import org.elasticsearch.cluster.node.DiscoveryNodes;
+import org.elasticsearch.common.settings.ClusterSettings;
+import org.elasticsearch.common.settings.IndexScopedSettings;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.common.settings.SettingsFilter;
+import org.elasticsearch.plugins.ActionPlugin;
+import org.elasticsearch.plugins.Plugin;
+import org.elasticsearch.rest.RestController;
+import org.elasticsearch.rest.RestHandler;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Supplier;
+
+public class ShutdownPlugin extends Plugin implements ActionPlugin {
+
+    public static final boolean SHUTDOWN_FEATURE_FLAG_ENABLED = "true".equals(System.getProperty("es.shutdown_feature_flag_enabled"));
+
+    public static boolean isEnabled() {
+        return SHUTDOWN_FEATURE_FLAG_ENABLED;
+    }
+
+    @Override
+    public List<ActionHandler<? extends ActionRequest, ? extends ActionResponse>> getActions() {
+        if (isEnabled() == false) {
+            return Collections.emptyList();
+        }
+        ActionHandler<PutShutdownNodeAction.Request, AcknowledgedResponse> putShutdown = new ActionHandler<>(
+            PutShutdownNodeAction.INSTANCE,
+            TransportPutShutdownNodeAction.class
+        );
+        ActionHandler<DeleteShutdownNodeAction.Request, AcknowledgedResponse> deleteShutdown = new ActionHandler<>(
+            DeleteShutdownNodeAction.INSTANCE,
+            TransportDeleteShutdownNodeAction.class
+        );
+        ActionHandler<GetShutdownStatusAction.Request, GetShutdownStatusAction.Response> getStatus = new ActionHandler<>(
+            GetShutdownStatusAction.INSTANCE,
+            TransportGetShutdownStatusAction.class
+        );
+        return Arrays.asList(putShutdown, deleteShutdown, getStatus);
+    }
+
+    @Override
+    public List<RestHandler> getRestHandlers(
+        Settings settings,
+        RestController restController,
+        ClusterSettings clusterSettings,
+        IndexScopedSettings indexScopedSettings,
+        SettingsFilter settingsFilter,
+        IndexNameExpressionResolver indexNameExpressionResolver,
+        Supplier<DiscoveryNodes> nodesInCluster
+    ) {
+        if (isEnabled() == false) {
+            return Collections.emptyList();
+        }
+        return Arrays.asList(new RestPutShutdownNodeAction(), new RestDeleteShutdownNodeAction(), new RestGetShutdownStatusAction());
+    }
+}

+ 60 - 0
x-pack/plugin/shutdown/src/main/java/org/elasticsearch/xpack/shutdown/TransportDeleteShutdownNodeAction.java

@@ -0,0 +1,60 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+package org.elasticsearch.xpack.shutdown;
+
+import org.elasticsearch.action.ActionListener;
+import org.elasticsearch.action.support.ActionFilters;
+import org.elasticsearch.action.support.master.AcknowledgedResponse;
+import org.elasticsearch.action.support.master.AcknowledgedTransportMasterNodeAction;
+import org.elasticsearch.cluster.ClusterState;
+import org.elasticsearch.cluster.block.ClusterBlockException;
+import org.elasticsearch.cluster.block.ClusterBlockLevel;
+import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
+import org.elasticsearch.cluster.service.ClusterService;
+import org.elasticsearch.common.inject.Inject;
+import org.elasticsearch.tasks.Task;
+import org.elasticsearch.threadpool.ThreadPool;
+import org.elasticsearch.transport.TransportService;
+
+public class TransportDeleteShutdownNodeAction extends AcknowledgedTransportMasterNodeAction<DeleteShutdownNodeAction.Request> {
+    @Inject
+    public TransportDeleteShutdownNodeAction(
+        TransportService transportService,
+        ClusterService clusterService,
+        ThreadPool threadPool,
+        ActionFilters actionFilters,
+        IndexNameExpressionResolver indexNameExpressionResolver
+    ) {
+        super(
+            DeleteShutdownNodeAction.NAME,
+            transportService,
+            clusterService,
+            threadPool,
+            actionFilters,
+            DeleteShutdownNodeAction.Request::new,
+            indexNameExpressionResolver,
+            ThreadPool.Names.SAME
+        );
+    }
+
+    @Override
+    protected void masterOperation(
+        Task task,
+        DeleteShutdownNodeAction.Request request,
+        ClusterState state,
+        ActionListener<AcknowledgedResponse> listener
+    ) throws Exception {
+        // TODO: implement me!
+        listener.onResponse(AcknowledgedResponse.of(true));
+    }
+
+    @Override
+    protected ClusterBlockException checkBlock(DeleteShutdownNodeAction.Request request, ClusterState state) {
+        return state.blocks().globalBlockedException(ClusterBlockLevel.METADATA_WRITE);
+    }
+}

+ 62 - 0
x-pack/plugin/shutdown/src/main/java/org/elasticsearch/xpack/shutdown/TransportGetShutdownStatusAction.java

@@ -0,0 +1,62 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+package org.elasticsearch.xpack.shutdown;
+
+import org.elasticsearch.action.ActionListener;
+import org.elasticsearch.action.support.ActionFilters;
+import org.elasticsearch.action.support.master.TransportMasterNodeAction;
+import org.elasticsearch.cluster.ClusterState;
+import org.elasticsearch.cluster.block.ClusterBlockException;
+import org.elasticsearch.cluster.block.ClusterBlockLevel;
+import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
+import org.elasticsearch.cluster.service.ClusterService;
+import org.elasticsearch.common.inject.Inject;
+import org.elasticsearch.tasks.Task;
+import org.elasticsearch.threadpool.ThreadPool;
+import org.elasticsearch.transport.TransportService;
+
+public class TransportGetShutdownStatusAction extends TransportMasterNodeAction<
+    GetShutdownStatusAction.Request,
+    GetShutdownStatusAction.Response> {
+    @Inject
+    public TransportGetShutdownStatusAction(
+        TransportService transportService,
+        ClusterService clusterService,
+        ThreadPool threadPool,
+        ActionFilters actionFilters,
+        IndexNameExpressionResolver indexNameExpressionResolver
+    ) {
+        super(
+            GetShutdownStatusAction.NAME,
+            transportService,
+            clusterService,
+            threadPool,
+            actionFilters,
+            GetShutdownStatusAction.Request::readFrom,
+            indexNameExpressionResolver,
+            GetShutdownStatusAction.Response::new,
+            ThreadPool.Names.SAME
+        );
+    }
+
+    @Override
+    protected void masterOperation(
+        Task task,
+        GetShutdownStatusAction.Request request,
+        ClusterState state,
+        ActionListener<GetShutdownStatusAction.Response> listener
+    ) throws Exception {
+        // TODO: implement me!
+        listener.onResponse(new GetShutdownStatusAction.Response(null));
+    }
+
+    @Override
+    protected ClusterBlockException checkBlock(GetShutdownStatusAction.Request request, ClusterState state) {
+        return state.blocks().globalBlockedException(ClusterBlockLevel.METADATA_WRITE);
+    }
+}

+ 60 - 0
x-pack/plugin/shutdown/src/main/java/org/elasticsearch/xpack/shutdown/TransportPutShutdownNodeAction.java

@@ -0,0 +1,60 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+package org.elasticsearch.xpack.shutdown;
+
+import org.elasticsearch.action.ActionListener;
+import org.elasticsearch.action.support.ActionFilters;
+import org.elasticsearch.action.support.master.AcknowledgedResponse;
+import org.elasticsearch.action.support.master.AcknowledgedTransportMasterNodeAction;
+import org.elasticsearch.cluster.ClusterState;
+import org.elasticsearch.cluster.block.ClusterBlockException;
+import org.elasticsearch.cluster.block.ClusterBlockLevel;
+import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
+import org.elasticsearch.cluster.service.ClusterService;
+import org.elasticsearch.common.inject.Inject;
+import org.elasticsearch.tasks.Task;
+import org.elasticsearch.threadpool.ThreadPool;
+import org.elasticsearch.transport.TransportService;
+
+public class TransportPutShutdownNodeAction extends AcknowledgedTransportMasterNodeAction<PutShutdownNodeAction.Request> {
+    @Inject
+    public TransportPutShutdownNodeAction(
+        TransportService transportService,
+        ClusterService clusterService,
+        ThreadPool threadPool,
+        ActionFilters actionFilters,
+        IndexNameExpressionResolver indexNameExpressionResolver
+    ) {
+        super(
+            PutShutdownNodeAction.NAME,
+            transportService,
+            clusterService,
+            threadPool,
+            actionFilters,
+            PutShutdownNodeAction.Request::new,
+            indexNameExpressionResolver,
+            ThreadPool.Names.SAME
+        );
+    }
+
+    @Override
+    protected void masterOperation(
+        Task task,
+        PutShutdownNodeAction.Request request,
+        ClusterState state,
+        ActionListener<AcknowledgedResponse> listener
+    ) throws Exception {
+        // TODO: implement me!
+        listener.onResponse(AcknowledgedResponse.of(true));
+    }
+
+    @Override
+    protected ClusterBlockException checkBlock(PutShutdownNodeAction.Request request, ClusterState state) {
+        return state.blocks().globalBlockedException(ClusterBlockLevel.METADATA_WRITE);
+    }
+}

+ 16 - 0
x-pack/plugin/shutdown/src/test/java/org/elasticsearch/xpack/shutdown/ShutdownTests.java

@@ -0,0 +1,16 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+package org.elasticsearch.xpack.shutdown;
+
+import org.elasticsearch.test.ESTestCase;
+
+public class ShutdownTests extends ESTestCase {
+    public void testIt() {
+        // TODO: implement tests
+    }
+}

+ 31 - 0
x-pack/plugin/src/test/resources/rest-api-spec/api/shutdown.delete_node.json

@@ -0,0 +1,31 @@
+{
+  "shutdown.delete_node":{
+    "documentation":{
+      "url":"https://www.elastic.co/guide/en/elasticsearch/reference/current",
+      "description":"Removes a node from the shutdown list"
+    },
+    "stability":"experimental",
+    "visibility":"public",
+    "headers":{
+      "accept": [ "application/json"],
+      "content_type": ["application/json"]
+    },
+    "url":{
+      "paths":[
+        {
+          "path":"/_nodes/{node_id}/shutdown",
+          "methods":[
+            "DELETE"
+          ],
+          "parts":{
+            "node_id":{
+              "type":"string",
+              "description":"The node id of node to be removed from the shutdown state"
+            }
+          }
+        }
+      ]
+    },
+    "params":{}
+  }
+}

+ 34 - 0
x-pack/plugin/src/test/resources/rest-api-spec/api/shutdown.get_node.json

@@ -0,0 +1,34 @@
+{
+  "shutdown.get_node":{
+    "documentation":{
+      "url":"https://www.elastic.co/guide/en/elasticsearch/reference/current",
+      "description":"Retrieve status of a node or nodes that are currently marked as shutting down"
+    },
+    "stability":"experimental",
+    "visibility":"public",
+    "headers":{
+      "accept": [ "application/json"],
+      "content_type": ["application/json"]
+    },
+    "url":{
+      "paths":[
+        {
+          "path":"/_nodes/shutdown",
+          "methods":["GET"],
+          "parts":{}
+        },
+        {
+          "path":"/_nodes/{node_id}/shutdown",
+          "methods":["GET"],
+          "parts":{
+            "node_id":{
+              "type":"string",
+              "description":"Which node for which to retrieve the shutdown status"
+            }
+          }
+        }
+      ]
+    },
+    "params":{}
+  }
+}

+ 35 - 0
x-pack/plugin/src/test/resources/rest-api-spec/api/shutdown.put_node.json

@@ -0,0 +1,35 @@
+{
+  "shutdown.put_node":{
+    "documentation":{
+      "url":"https://www.elastic.co/guide/en/elasticsearch/reference/current",
+      "description":"Adds a node to be shut down"
+    },
+    "stability":"experimental",
+    "visibility":"public",
+    "headers":{
+      "accept": [ "application/json"],
+      "content_type": ["application/json"]
+    },
+    "url":{
+      "paths":[
+        {
+          "path":"/_nodes/{node_id}/shutdown",
+          "methods":[
+            "PUT"
+          ],
+          "parts":{
+            "node_id":{
+              "type":"string",
+              "description":"The node id of node to be shut down"
+            }
+          }
+        }
+      ]
+    },
+    "params":{},
+    "body":{
+      "description":"The shutdown type definition to register",
+      "required": true
+    }
+  }
+}