mirror of https://github.com/status-im/consul.git
commit
e1a5d537d4
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
|
@ -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())
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
```
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue