Merge pull request #625 from hashicorp/f-maintcmd

New "maint" command
This commit is contained in:
Armon Dadgar 2015-01-22 11:56:49 -08:00
commit e1a5d537d4
12 changed files with 574 additions and 29 deletions

View File

@ -278,9 +278,10 @@ func (a *Agent) ForceLeave(node string) error {
// EnableServiceMaintenance toggles service maintenance mode on
// for the given service ID.
func (a *Agent) EnableServiceMaintenance(serviceID string) error {
func (a *Agent) EnableServiceMaintenance(serviceID, reason string) error {
r := a.c.newRequest("PUT", "/v1/agent/service/maintenance/"+serviceID)
r.params.Set("enable", "true")
r.params.Set("reason", reason)
_, resp, err := requireOK(a.c.doRequest(r))
if err != nil {
return err
@ -304,9 +305,10 @@ func (a *Agent) DisableServiceMaintenance(serviceID string) error {
// EnableNodeMaintenance toggles node maintenance mode on for the
// agent we are connected to.
func (a *Agent) EnableNodeMaintenance() error {
func (a *Agent) EnableNodeMaintenance(reason string) error {
r := a.c.newRequest("PUT", "/v1/agent/maintenance")
r.params.Set("enable", "true")
r.params.Set("reason", reason)
_, resp, err := requireOK(a.c.doRequest(r))
if err != nil {
return err

View File

@ -318,7 +318,7 @@ func TestServiceMaintenance(t *testing.T) {
}
// Enable maintenance mode
if err := agent.EnableServiceMaintenance("redis"); err != nil {
if err := agent.EnableServiceMaintenance("redis", "broken"); err != nil {
t.Fatalf("err: %s", err)
}
@ -331,7 +331,7 @@ func TestServiceMaintenance(t *testing.T) {
for _, check := range checks {
if strings.Contains(check.CheckID, "maintenance") {
found = true
if check.Status != "critical" {
if check.Status != "critical" || check.Notes != "broken" {
t.Fatalf("bad: %#v", checks)
}
}
@ -364,7 +364,7 @@ func TestNodeMaintenance(t *testing.T) {
agent := c.Agent()
// Enable maintenance mode
if err := agent.EnableNodeMaintenance(); err != nil {
if err := agent.EnableNodeMaintenance("broken"); err != nil {
t.Fatalf("err: %s", err)
}
@ -377,7 +377,7 @@ func TestNodeMaintenance(t *testing.T) {
for _, check := range checks {
if strings.Contains(check.CheckID, "maintenance") {
found = true
if check.Status != "critical" {
if check.Status != "critical" || check.Notes != "broken" {
t.Fatalf("bad: %#v", checks)
}
}

View File

@ -27,6 +27,12 @@ const (
// The ID of the faux health checks for maintenance mode
serviceMaintCheckPrefix = "_service_maintenance"
nodeMaintCheckID = "_node_maintenance"
// Default reasons for node/service maintenance mode
defaultNodeMaintReason = "Maintenance mode is enabled for this node, " +
"but no reason was provided. This is a default message."
defaultServiceMaintReason = "Maintenance mode is enabled for this " +
"service, but no reason was provided. This is a default message."
)
/*
@ -1020,7 +1026,7 @@ func serviceMaintCheckID(serviceID string) string {
// EnableServiceMaintenance will register a false health check against the given
// service ID with critical status. This will exclude the service from queries.
func (a *Agent) EnableServiceMaintenance(serviceID string) error {
func (a *Agent) EnableServiceMaintenance(serviceID, reason string) error {
service, ok := a.state.Services()[serviceID]
if !ok {
return fmt.Errorf("No service registered with ID %q", serviceID)
@ -1032,18 +1038,23 @@ func (a *Agent) EnableServiceMaintenance(serviceID string) error {
return nil
}
// Use default notes if no reason provided
if reason == "" {
reason = defaultServiceMaintReason
}
// Create and register the critical health check
check := &structs.HealthCheck{
Node: a.config.NodeName,
CheckID: checkID,
Name: "Service Maintenance Mode",
Notes: "Maintenance mode is enabled for this service",
Notes: reason,
ServiceID: service.ID,
ServiceName: service.Service,
Status: structs.HealthCritical,
}
a.AddCheck(check, nil, true)
a.logger.Printf("[INFO] agent: service %q entered maintenance mode", serviceID)
a.logger.Printf("[INFO] agent: Service %q entered maintenance mode", serviceID)
return nil
}
@ -1063,28 +1074,33 @@ func (a *Agent) DisableServiceMaintenance(serviceID string) error {
// Deregister the maintenance check
a.RemoveCheck(checkID, true)
a.logger.Printf("[INFO] agent: service %q left maintenance mode", serviceID)
a.logger.Printf("[INFO] agent: Service %q left maintenance mode", serviceID)
return nil
}
// EnableNodeMaintenance places a node into maintenance mode.
func (a *Agent) EnableNodeMaintenance() {
func (a *Agent) EnableNodeMaintenance(reason string) {
// Ensure node maintenance is not already enabled
if _, ok := a.state.Checks()[nodeMaintCheckID]; ok {
return
}
// Use a default notes value
if reason == "" {
reason = defaultNodeMaintReason
}
// Create and register the node maintenance check
check := &structs.HealthCheck{
Node: a.config.NodeName,
CheckID: nodeMaintCheckID,
Name: "Node Maintenance Mode",
Notes: "Maintenance mode is enabled for this node",
Notes: reason,
Status: structs.HealthCritical,
}
a.AddCheck(check, nil, true)
a.logger.Printf("[INFO] agent: node entered maintenance mode")
a.logger.Printf("[INFO] agent: Node entered maintenance mode")
}
// DisableNodeMaintenance removes a node from maintenance mode
@ -1093,5 +1109,5 @@ func (a *Agent) DisableNodeMaintenance() {
return
}
a.RemoveCheck(nodeMaintCheckID, true)
a.logger.Printf("[INFO] agent: node left maintenance mode")
a.logger.Printf("[INFO] agent: Node left maintenance mode")
}

View File

@ -220,17 +220,21 @@ func (s *HTTPServer) AgentServiceMaintenance(resp http.ResponseWriter, req *http
}
if enable {
if err = s.agent.EnableServiceMaintenance(serviceID); err != nil {
reason := params.Get("reason")
if err = s.agent.EnableServiceMaintenance(serviceID, reason); err != nil {
resp.WriteHeader(404)
resp.Write([]byte(err.Error()))
return nil, nil
}
} else {
if err = s.agent.DisableServiceMaintenance(serviceID); err != nil {
resp.WriteHeader(404)
resp.Write([]byte(err.Error()))
return nil, nil
}
}
return nil, err
return nil, nil
}
func (s *HTTPServer) AgentNodeMaintenance(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
@ -257,7 +261,7 @@ func (s *HTTPServer) AgentNodeMaintenance(resp http.ResponseWriter, req *http.Re
}
if enable {
s.agent.EnableNodeMaintenance()
s.agent.EnableNodeMaintenance(params.Get("reason"))
} else {
s.agent.DisableNodeMaintenance()
}

View File

@ -542,8 +542,8 @@ func TestHTTPAgent_ServiceMaintenanceEndpoint_BadRequest(t *testing.T) {
// Fails when bad service ID provided
req, _ = http.NewRequest("PUT", "/v1/agent/service/maintenance/_nope_?enable=true", nil)
resp = httptest.NewRecorder()
if _, err := srv.AgentServiceMaintenance(resp, req); err == nil {
t.Fatalf("should have errored")
if _, err := srv.AgentServiceMaintenance(resp, req); err != nil {
t.Fatalf("err: %s", err)
}
if resp.Code != 404 {
t.Fatalf("expected 404, got %d", resp.Code)
@ -566,7 +566,7 @@ func TestHTTPAgent_EnableServiceMaintenance(t *testing.T) {
}
// Force the service into maintenance mode
req, _ := http.NewRequest("PUT", "/v1/agent/service/maintenance/test?enable=true", nil)
req, _ := http.NewRequest("PUT", "/v1/agent/service/maintenance/test?enable=true&reason=broken", nil)
resp := httptest.NewRecorder()
if _, err := srv.AgentServiceMaintenance(resp, req); err != nil {
t.Fatalf("err: %s", err)
@ -577,9 +577,15 @@ func TestHTTPAgent_EnableServiceMaintenance(t *testing.T) {
// Ensure the maintenance check was registered
checkID := serviceMaintCheckID("test")
if _, ok := srv.agent.state.Checks()[checkID]; !ok {
check, ok := srv.agent.state.Checks()[checkID]
if !ok {
t.Fatalf("should have registered maintenance check")
}
// Ensure the reason was set in notes
if check.Notes != "broken" {
t.Fatalf("bad: %#v", check)
}
}
func TestHTTPAgent_DisableServiceMaintenance(t *testing.T) {
@ -598,7 +604,7 @@ func TestHTTPAgent_DisableServiceMaintenance(t *testing.T) {
}
// Force the service into maintenance mode
if err := srv.agent.EnableServiceMaintenance("test"); err != nil {
if err := srv.agent.EnableServiceMaintenance("test", ""); err != nil {
t.Fatalf("err: %s", err)
}
@ -653,7 +659,8 @@ func TestHTTPAgent_EnableNodeMaintenance(t *testing.T) {
defer srv.agent.Shutdown()
// Force the node into maintenance mode
req, _ := http.NewRequest("PUT", "/v1/agent/self/maintenance?enable=true", nil)
req, _ := http.NewRequest(
"PUT", "/v1/agent/self/maintenance?enable=true&reason=broken", nil)
resp := httptest.NewRecorder()
if _, err := srv.AgentNodeMaintenance(resp, req); err != nil {
t.Fatalf("err: %s", err)
@ -663,9 +670,15 @@ func TestHTTPAgent_EnableNodeMaintenance(t *testing.T) {
}
// Ensure the maintenance check was registered
if _, ok := srv.agent.state.Checks()[nodeMaintCheckID]; !ok {
check, ok := srv.agent.state.Checks()[nodeMaintCheckID]
if !ok {
t.Fatalf("should have registered maintenance check")
}
// Ensure the reason was set in notes
if check.Notes != "broken" {
t.Fatalf("bad: %#v", check)
}
}
func TestHTTPAgent_DisableNodeMaintenance(t *testing.T) {
@ -675,7 +688,7 @@ func TestHTTPAgent_DisableNodeMaintenance(t *testing.T) {
defer srv.agent.Shutdown()
// Force the node into maintenance mode
srv.agent.EnableNodeMaintenance()
srv.agent.EnableNodeMaintenance("")
// Leave maintenance mode
req, _ := http.NewRequest("PUT", "/v1/agent/self/maintenance?enable=false", nil)

View File

@ -916,16 +916,22 @@ func TestAgent_ServiceMaintenanceMode(t *testing.T) {
}
// Enter maintenance mode for the service
if err := agent.EnableServiceMaintenance("redis"); err != nil {
if err := agent.EnableServiceMaintenance("redis", "broken"); err != nil {
t.Fatalf("err: %s", err)
}
// Make sure the critical health check was added
checkID := serviceMaintCheckID("redis")
if _, ok := agent.state.Checks()[checkID]; !ok {
check, ok := agent.state.Checks()[checkID]
if !ok {
t.Fatalf("should have registered critical maintenance check")
}
// Ensure the reason was set in notes
if check.Notes != "broken" {
t.Fatalf("bad: %#v", check)
}
// Leave maintenance mode
if err := agent.DisableServiceMaintenance("redis"); err != nil {
t.Fatalf("err: %s", err)
@ -935,6 +941,20 @@ func TestAgent_ServiceMaintenanceMode(t *testing.T) {
if _, ok := agent.state.Checks()[checkID]; ok {
t.Fatalf("should have deregistered maintenance check")
}
// Enter service maintenance mode without providing a reason
if err := agent.EnableServiceMaintenance("redis", ""); err != nil {
t.Fatalf("err: %s", err)
}
// Ensure the check was registered with the default notes
check, ok = agent.state.Checks()[checkID]
if !ok {
t.Fatalf("should have registered critical check")
}
if check.Notes != defaultServiceMaintReason {
t.Fatalf("bad: %#v", check)
}
}
func TestAgent_NodeMaintenanceMode(t *testing.T) {
@ -944,13 +964,19 @@ func TestAgent_NodeMaintenanceMode(t *testing.T) {
defer agent.Shutdown()
// Enter maintenance mode for the node
agent.EnableNodeMaintenance()
agent.EnableNodeMaintenance("broken")
// Make sure the critical health check was added
if _, ok := agent.state.Checks()[nodeMaintCheckID]; !ok {
check, ok := agent.state.Checks()[nodeMaintCheckID]
if !ok {
t.Fatalf("should have registered critical node check")
}
// Ensure the reason was set in notes
if check.Notes != "broken" {
t.Fatalf("bad: %#v", check)
}
// Leave maintenance mode
agent.DisableNodeMaintenance()
@ -958,4 +984,16 @@ func TestAgent_NodeMaintenanceMode(t *testing.T) {
if _, ok := agent.state.Checks()[nodeMaintCheckID]; ok {
t.Fatalf("should have deregistered critical node check")
}
// Enter maintenance mode without passing a reason
agent.EnableNodeMaintenance("")
// Make sure the check was registered with the default note
check, ok = agent.state.Checks()[nodeMaintCheckID]
if !ok {
t.Fatalf("should have registered critical node check")
}
if check.Notes != defaultNodeMaintReason {
t.Fatalf("bad: %#v", check)
}
}

176
command/maint.go Normal file
View File

@ -0,0 +1,176 @@
package command
import (
"flag"
"fmt"
"strings"
"github.com/hashicorp/consul/api"
"github.com/mitchellh/cli"
)
// MaintCommand is a Command implementation that enables or disables
// node or service maintenance mode.
type MaintCommand struct {
Ui cli.Ui
}
func (c *MaintCommand) Help() string {
helpText := `
Usage: consul maint [options]
Places a node or service into maintenance mode. During maintenance mode,
the node or service will be excluded from all queries through the DNS
or API interfaces, effectively taking it out of the pool of available
nodes. This is done by registering an additional critical health check.
When enabling maintenance mode for a node or service, you may optionally
specify a reason string. This string will appear in the "Notes" field
of the critical health check which is registered against the node or
service. If no reason is provided, a default value will be used.
Maintenance mode is persistent, and will be restored in the event of an
agent restart. It is therefore required to disable maintenance mode on
a given node or service before it will be placed back into the pool.
By default, we operate on the node as a whole. By specifying the
"-service" argument, this behavior can be changed to enable or disable
only a specific service.
If no arguments are given, the agent's maintenance status will be shown.
This will return blank if nothing is currently under maintenance.
Options:
-enable Enable maintenance mode.
-disable Disable maintenance mode.
-reason=<string> Text string describing the maintenance reason
-service=<serviceID> Control maintenance mode for a specific service ID
-token="" ACL token to use. Defaults to that of agent.
-http-addr=127.0.0.1:8500 HTTP address of the Consul agent.
`
return strings.TrimSpace(helpText)
}
func (c *MaintCommand) Run(args []string) int {
var enable bool
var disable bool
var reason string
var serviceID string
var token string
cmdFlags := flag.NewFlagSet("maint", flag.ContinueOnError)
cmdFlags.Usage = func() { c.Ui.Output(c.Help()) }
cmdFlags.BoolVar(&enable, "enable", false, "enable maintenance mode")
cmdFlags.BoolVar(&disable, "disable", false, "disable maintenance mode")
cmdFlags.StringVar(&reason, "reason", "", "maintenance reason")
cmdFlags.StringVar(&serviceID, "service", "", "service maintenance")
cmdFlags.StringVar(&token, "token", "", "")
httpAddr := HTTPAddrFlag(cmdFlags)
if err := cmdFlags.Parse(args); err != nil {
return 1
}
// Ensure we don't have conflicting args
if enable && disable {
c.Ui.Error("Only one of -enable or -disable may be provided")
return 1
}
if !enable && reason != "" {
c.Ui.Error("Reason may only be provided with -enable")
return 1
}
if !enable && !disable && serviceID != "" {
c.Ui.Error("Service requires either -enable or -disable")
return 1
}
// Create and test the HTTP client
conf := api.DefaultConfig()
conf.Address = *httpAddr
conf.Token = token
client, err := api.NewClient(conf)
if err != nil {
c.Ui.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err))
return 1
}
a := client.Agent()
nodeName, err := a.NodeName()
if err != nil {
c.Ui.Error(fmt.Sprintf("Error querying Consul agent: %s", err))
return 1
}
if !enable && !disable {
// List mode - list nodes/services in maintenance mode
checks, err := a.Checks()
if err != nil {
c.Ui.Error(fmt.Sprintf("Error getting checks: %s", err))
return 1
}
for _, check := range checks {
if check.CheckID == "_node_maintenance" {
c.Ui.Output("Node:")
c.Ui.Output(" Name: " + nodeName)
c.Ui.Output(" Reason: " + check.Notes)
c.Ui.Output("")
} else if strings.HasPrefix(check.CheckID, "_service_maintenance:") {
c.Ui.Output("Service:")
c.Ui.Output(" ID: " + check.ServiceID)
c.Ui.Output(" Reason: " + check.Notes)
c.Ui.Output("")
}
}
return 0
}
if enable {
// Enable node maintenance
if serviceID == "" {
if err := a.EnableNodeMaintenance(reason); err != nil {
c.Ui.Error(fmt.Sprintf("Error enabling node maintenance: %s", err))
return 1
}
c.Ui.Output("Node maintenance is now enabled")
return 0
}
// Enable service maintenance
if err := a.EnableServiceMaintenance(serviceID, reason); err != nil {
c.Ui.Error(fmt.Sprintf("Error enabling service maintenance: %s", err))
return 1
}
c.Ui.Output(fmt.Sprintf("Service maintenance is now enabled for %q", serviceID))
return 0
}
if disable {
// Disable node maintenance
if serviceID == "" {
if err := a.DisableNodeMaintenance(); err != nil {
c.Ui.Error(fmt.Sprintf("Error disabling node maintenance: %s", err))
return 1
}
c.Ui.Output("Node maintenance is now disabled")
return 0
}
// Disable service maintenance
if err := a.DisableServiceMaintenance(serviceID); err != nil {
c.Ui.Error(fmt.Sprintf("Error disabling service maintenance: %s", err))
return 1
}
c.Ui.Output(fmt.Sprintf("Service maintenance is now disabled for %q", serviceID))
return 0
}
return 0
}
func (c *MaintCommand) Synopsis() string {
return "Controls node or service maintenance mode"
}

210
command/maint_test.go Normal file
View File

@ -0,0 +1,210 @@
package command
import (
"strings"
"testing"
"github.com/hashicorp/consul/consul/structs"
"github.com/mitchellh/cli"
)
func TestMaintCommand_implements(t *testing.T) {
var _ cli.Command = &MaintCommand{}
}
func TestMaintCommandRun_ConflictingArgs(t *testing.T) {
ui := new(cli.MockUi)
c := &MaintCommand{Ui: ui}
if code := c.Run([]string{"-enable", "-disable"}); code != 1 {
t.Fatalf("expected return code 1, got %d", code)
}
if code := c.Run([]string{"-disable", "-reason=broken"}); code != 1 {
t.Fatalf("expected return code 1, got %d", code)
}
if code := c.Run([]string{"-reason=broken"}); code != 1 {
t.Fatalf("expected return code 1, got %d", code)
}
if code := c.Run([]string{"-service=redis"}); code != 1 {
t.Fatalf("expected return code 1, got %d", code)
}
}
func TestMaintCommandRun_NoArgs(t *testing.T) {
a1 := testAgent(t)
defer a1.Shutdown()
// Register the service and put it into maintenance mode
service := &structs.NodeService{
ID: "test",
Service: "test",
}
if err := a1.agent.AddService(service, nil, false); err != nil {
t.Fatalf("err: %v", err)
}
if err := a1.agent.EnableServiceMaintenance("test", "broken 1"); err != nil {
t.Fatalf("err: %s", err)
}
// Enable node maintenance
a1.agent.EnableNodeMaintenance("broken 2")
// Run consul maint with no args (list mode)
ui := new(cli.MockUi)
c := &MaintCommand{Ui: ui}
args := []string{"-http-addr=" + a1.httpAddr}
code := c.Run(args)
if code != 0 {
t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String())
}
// Ensure the service shows up in the list
out := ui.OutputWriter.String()
if !strings.Contains(out, "test") {
t.Fatalf("bad:\n%s", out)
}
if !strings.Contains(out, "broken 1") {
t.Fatalf("bad:\n%s", out)
}
// Ensure the node shows up in the list
if !strings.Contains(out, a1.config.NodeName) {
t.Fatalf("bad:\n%s", out)
}
if !strings.Contains(out, "broken 2") {
t.Fatalf("bad:\n%s", out)
}
}
func TestMaintCommandRun_EnableNodeMaintenance(t *testing.T) {
a1 := testAgent(t)
defer a1.Shutdown()
ui := new(cli.MockUi)
c := &MaintCommand{Ui: ui}
args := []string{
"-http-addr=" + a1.httpAddr,
"-enable",
"-reason=broken",
}
code := c.Run(args)
if code != 0 {
t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String())
}
if !strings.Contains(ui.OutputWriter.String(), "now enabled") {
t.Fatalf("bad: %#v", ui.OutputWriter.String())
}
}
func TestMaintCommandRun_DisableNodeMaintenance(t *testing.T) {
a1 := testAgent(t)
defer a1.Shutdown()
ui := new(cli.MockUi)
c := &MaintCommand{Ui: ui}
args := []string{
"-http-addr=" + a1.httpAddr,
"-disable",
}
code := c.Run(args)
if code != 0 {
t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String())
}
if !strings.Contains(ui.OutputWriter.String(), "now disabled") {
t.Fatalf("bad: %#v", ui.OutputWriter.String())
}
}
func TestMaintCommandRun_EnableServiceMaintenance(t *testing.T) {
a1 := testAgent(t)
defer a1.Shutdown()
// Register the service
service := &structs.NodeService{
ID: "test",
Service: "test",
}
if err := a1.agent.AddService(service, nil, false); err != nil {
t.Fatalf("err: %v", err)
}
ui := new(cli.MockUi)
c := &MaintCommand{Ui: ui}
args := []string{
"-http-addr=" + a1.httpAddr,
"-enable",
"-service=test",
"-reason=broken",
}
code := c.Run(args)
if code != 0 {
t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String())
}
if !strings.Contains(ui.OutputWriter.String(), "now enabled") {
t.Fatalf("bad: %#v", ui.OutputWriter.String())
}
}
func TestMaintCommandRun_DisableServiceMaintenance(t *testing.T) {
a1 := testAgent(t)
defer a1.Shutdown()
// Register the service
service := &structs.NodeService{
ID: "test",
Service: "test",
}
if err := a1.agent.AddService(service, nil, false); err != nil {
t.Fatalf("err: %v", err)
}
ui := new(cli.MockUi)
c := &MaintCommand{Ui: ui}
args := []string{
"-http-addr=" + a1.httpAddr,
"-disable",
"-service=test",
}
code := c.Run(args)
if code != 0 {
t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String())
}
if !strings.Contains(ui.OutputWriter.String(), "now disabled") {
t.Fatalf("bad: %#v", ui.OutputWriter.String())
}
}
func TestMaintCommandRun_ServiceMaintenance_NoService(t *testing.T) {
a1 := testAgent(t)
defer a1.Shutdown()
ui := new(cli.MockUi)
c := &MaintCommand{Ui: ui}
args := []string{
"-http-addr=" + a1.httpAddr,
"-enable",
"-service=redis",
"-reason=broken",
}
code := c.Run(args)
if code != 1 {
t.Fatalf("expected response code 1, got %d", code)
}
if !strings.Contains(ui.ErrorWriter.String(), "No service registered") {
t.Fatalf("bad: %#v", ui.ErrorWriter.String())
}
}

View File

@ -76,6 +76,12 @@ func init() {
}, nil
},
"maint": func() (cli.Command, error) {
return &command.MaintCommand{
Ui: ui,
}, nil
},
"members": func() (cli.Command, error) {
return &command.MembersCommand{
Ui: ui,

View File

@ -195,6 +195,10 @@ persistent and will be automatically restored on agent restart.
The `?enable` flag is required, and its value must be `true` (to enter
maintenance mode), or `false` (to resume normal operation).
The `?reason` flag is optional, and can contain a text string explaining the
reason for placing the node into maintenance mode. If no reason is provided,
a default value will be used instead.
The return code is 200 on success.
### <a name="agent_join"></a> /v1/agent/join/\<address\>
@ -355,4 +359,8 @@ on agent restart.
The `?enable` flag is required, and its value must be `true` (to enter
maintenance mode), or `false` (to resume normal operation).
The `?reason` flag is optional, and can contain a text string explaining the
reason for placing the service into maintenance mode. If no reason is provided,
a default value will be used instead.
The return code is 200 on success.

View File

@ -0,0 +1,68 @@
---
layout: "docs"
page_title: "Commands: Maint"
sidebar_current: "docs-commands-maint"
description: >
The `maint` command provides control of both service and node maintenance mode
---
# Consul Maint
Command: `consul maint`
The `maint` command provides control of both service and node maintenance mode.
Using the command, it is possible to mark a service provided by a node or the
node as a whole as "under maintenance". In this mode of operation, the service
or node will not appear in DNS query results, or API results. This effectively
takes the service or node out of the pool of available "healthy" nodes.
Under the hood, maintenance mode is activated by registering a health check in
critical status against a node or service, and deactivated by deregistering the
health check.
## Usage
Usage: `consul maint [options]`
All of the command line arguments are optional.
The list of available flags are:
* `-enable` - Enable maintenance mode on a given service or node. If
combined with the `-service` flag, we operate on a specific service ID.
Otherwise, node maintenance mode is enabled.
* `-disable` - Disable maintenance mode on a given service or node. If
combined with the `-service` flag, we operate on a specific service ID.
Otherwise, node maintenance mode is disabled.
* `-reason` - An optional reason for placing the node or service into
maintenance mode. If provided, this reason will be visible in the newly-
registered critical check's "Notes" field.
* `-service` - An optional service ID to control node maintenance mode for. By
providing this flag, the `-enable` and `-disable` flags functionality is
modified to operate on the given service ID.
* `-token` - ACL token to use. Defaults to that of agent.
* `-http-addr` - Address to the HTTP server of the agent you want to contact
to send this command. If this isn't specified, the command will contact
"127.0.0.1:8500" which is the default HTTP address of a Consul agent.
## List mode
If neither `-enable` nor `-disable` are passed, the `maint` command will
switch to "list mode", displaying any current maintenances. This may return
blank if nothing is currently under maintenance. The output will look like:
```
$ consul maint
Node:
Name: node1.local
Reason: This node is broken.
Service:
ID: redis
Reason: Redis is currently offline.
```

View File

@ -91,6 +91,10 @@
<a href="/docs/commands/lock.html">lock</a>
</li>
<li<%= sidebar_current("docs-commands-maint") %>>
<a href="/docs/commands/maint.html">maint</a>
</li>
<li<%= sidebar_current("docs-commands-members") %>>
<a href="/docs/commands/members.html">members</a>
</li>