diff --git a/agent/http_oss_test.go b/agent/http_oss_test.go index d2e04419b7..34084895b8 100644 --- a/agent/http_oss_test.go +++ b/agent/http_oss_test.go @@ -3,94 +3,96 @@ package agent import ( "fmt" "net/http" + "net/http/httptest" "strings" "testing" "github.com/hashicorp/consul/logger" ) +var expectedEndpoints = []struct { + methods, uri string +}{ + {"OPTIONS,PUT", "/v1/acl/bootstrap"}, + {"OPTIONS,PUT", "/v1/acl/create"}, + {"OPTIONS,PUT", "/v1/acl/update"}, + {"OPTIONS,PUT", "/v1/acl/destroy/"}, + {"OPTIONS,GET", "/v1/acl/info/"}, + {"OPTIONS,PUT", "/v1/acl/clone/"}, + {"OPTIONS,GET", "/v1/acl/list"}, + {"OPTIONS,GET", "/v1/acl/replication"}, + {"OPTIONS,PUT", "/v1/agent/token/"}, + {"OPTIONS,GET", "/v1/agent/self"}, + {"OPTIONS,GET", "/v1/agent/members"}, + {"OPTIONS,PUT", "/v1/agent/check/deregister/"}, + {"OPTIONS,PUT", "/v1/agent/check/fail/"}, + {"OPTIONS,PUT", "/v1/agent/check/pass/"}, + {"OPTIONS,PUT", "/v1/agent/check/register"}, + {"OPTIONS,PUT", "/v1/agent/check/update/"}, + {"OPTIONS,PUT", "/v1/agent/check/warn/"}, + {"OPTIONS,GET", "/v1/agent/checks"}, + {"OPTIONS,PUT", "/v1/agent/force-leave/"}, + {"OPTIONS,PUT", "/v1/agent/join/"}, + {"OPTIONS,PUT", "/v1/agent/leave"}, + {"OPTIONS,PUT", "/v1/agent/maintenance"}, + {"OPTIONS,GET", "/v1/agent/metrics"}, + // {"GET", "/v1/agent/monitor"}, // requires LogWriter. Hangs if LogWriter is provided + {"OPTIONS,PUT", "/v1/agent/reload"}, + {"OPTIONS,PUT", "/v1/agent/service/deregister/"}, + {"OPTIONS,PUT", "/v1/agent/service/maintenance/"}, + {"OPTIONS,PUT", "/v1/agent/service/register"}, + {"OPTIONS,GET", "/v1/agent/services"}, + {"OPTIONS,GET", "/v1/catalog/datacenters"}, + {"OPTIONS,PUT", "/v1/catalog/deregister"}, + {"OPTIONS,GET", "/v1/catalog/node/"}, + {"OPTIONS,GET", "/v1/catalog/nodes"}, + {"OPTIONS,PUT", "/v1/catalog/register"}, + {"OPTIONS,GET", "/v1/catalog/service/"}, + {"OPTIONS,GET", "/v1/catalog/services"}, + {"OPTIONS,GET", "/v1/coordinate/datacenters"}, + {"OPTIONS,GET", "/v1/coordinate/nodes"}, + {"OPTIONS,GET", "/v1/coordinate/node/"}, + {"OPTIONS,PUT", "/v1/event/fire/"}, + {"OPTIONS,GET", "/v1/event/list"}, + {"OPTIONS,GET", "/v1/health/checks/"}, + {"OPTIONS,GET", "/v1/health/node/"}, + {"OPTIONS,GET", "/v1/health/service/"}, + {"OPTIONS,GET", "/v1/health/state/"}, + {"OPTIONS,GET", "/v1/internal/ui/node/"}, + {"OPTIONS,GET", "/v1/internal/ui/nodes"}, + {"OPTIONS,GET", "/v1/internal/ui/services"}, + {"OPTIONS,GET,PUT,DELETE", "/v1/kv/"}, + {"OPTIONS,GET,PUT", "/v1/operator/autopilot/configuration"}, + {"OPTIONS,GET", "/v1/operator/autopilot/health"}, + {"OPTIONS,GET,POST,PUT,DELETE", "/v1/operator/keyring"}, + {"OPTIONS,GET", "/v1/operator/raft/configuration"}, + {"OPTIONS,DELETE", "/v1/operator/raft/peer"}, + {"OPTIONS,GET,POST", "/v1/query"}, + {"OPTIONS,GET,PUT,DELETE", "/v1/query/"}, + {"OPTIONS,GET", "/v1/query/xxx/execute"}, + {"OPTIONS,GET", "/v1/query/xxx/explain"}, + {"OPTIONS,PUT", "/v1/session/create"}, + {"OPTIONS,PUT", "/v1/session/destroy/"}, + {"OPTIONS,GET", "/v1/session/info/"}, + {"OPTIONS,GET", "/v1/session/list"}, + {"OPTIONS,GET", "/v1/session/node/"}, + {"OPTIONS,PUT", "/v1/session/renew/"}, + {"OPTIONS,GET,PUT", "/v1/snapshot"}, + {"OPTIONS,GET", "/v1/status/leader"}, + // {"GET", "/v1/status/peers"},// hangs + {"OPTIONS,PUT", "/v1/txn"}, +} + func TestHTTPAPI_MethodNotAllowed_OSS(t *testing.T) { - tests := []struct { - methods, uri string - }{ - {"PUT", "/v1/acl/bootstrap"}, - {"PUT", "/v1/acl/create"}, - {"PUT", "/v1/acl/update"}, - {"PUT", "/v1/acl/destroy/"}, - {"GET", "/v1/acl/info/"}, - {"PUT", "/v1/acl/clone/"}, - {"GET", "/v1/acl/list"}, - {"GET", "/v1/acl/replication"}, - {"PUT", "/v1/agent/token/"}, - {"GET", "/v1/agent/self"}, - {"GET", "/v1/agent/members"}, - {"PUT", "/v1/agent/check/deregister/"}, - {"PUT", "/v1/agent/check/fail/"}, - {"PUT", "/v1/agent/check/pass/"}, - {"PUT", "/v1/agent/check/register"}, - {"PUT", "/v1/agent/check/update/"}, - {"PUT", "/v1/agent/check/warn/"}, - {"GET", "/v1/agent/checks"}, - {"PUT", "/v1/agent/force-leave/"}, - {"PUT", "/v1/agent/join/"}, - {"PUT", "/v1/agent/leave"}, - {"PUT", "/v1/agent/maintenance"}, - {"GET", "/v1/agent/metrics"}, - // {"GET", "/v1/agent/monitor"}, // requires LogWriter. Hangs if LogWriter is provided - {"PUT", "/v1/agent/reload"}, - {"PUT", "/v1/agent/service/deregister/"}, - {"PUT", "/v1/agent/service/maintenance/"}, - {"PUT", "/v1/agent/service/register"}, - {"GET", "/v1/agent/services"}, - {"GET", "/v1/catalog/datacenters"}, - {"PUT", "/v1/catalog/deregister"}, - {"GET", "/v1/catalog/node/"}, - {"GET", "/v1/catalog/nodes"}, - {"PUT", "/v1/catalog/register"}, - {"GET", "/v1/catalog/service/"}, - {"GET", "/v1/catalog/services"}, - {"GET", "/v1/coordinate/datacenters"}, - {"GET", "/v1/coordinate/nodes"}, - {"GET", "/v1/coordinate/node/"}, - {"PUT", "/v1/event/fire/"}, - {"GET", "/v1/event/list"}, - {"GET", "/v1/health/checks/"}, - {"GET", "/v1/health/node/"}, - {"GET", "/v1/health/service/"}, - {"GET", "/v1/health/state/"}, - {"GET", "/v1/internal/ui/node/"}, - {"GET", "/v1/internal/ui/nodes"}, - {"GET", "/v1/internal/ui/services"}, - {"GET PUT DELETE", "/v1/kv/"}, - {"GET PUT", "/v1/operator/autopilot/configuration"}, - {"GET", "/v1/operator/autopilot/health"}, - {"GET POST PUT DELETE", "/v1/operator/keyring"}, - {"GET", "/v1/operator/raft/configuration"}, - {"DELETE", "/v1/operator/raft/peer"}, - {"GET POST", "/v1/query"}, - {"GET PUT DELETE", "/v1/query/"}, - {"GET", "/v1/query/xxx/execute"}, - {"GET", "/v1/query/xxx/explain"}, - {"PUT", "/v1/session/create"}, - {"PUT", "/v1/session/destroy/"}, - {"GET", "/v1/session/info/"}, - {"GET", "/v1/session/list"}, - {"GET", "/v1/session/node/"}, - {"PUT", "/v1/session/renew/"}, - {"GET PUT", "/v1/snapshot"}, - {"GET", "/v1/status/leader"}, - // {"GET", "/v1/status/peers"},// hangs - {"PUT", "/v1/txn"}, - } a := NewTestAgent(t.Name(), `acl_datacenter = "dc1"`) a.Agent.LogWriter = logger.NewLogWriter(512) defer a.Shutdown() - all := []string{"GET", "PUT", "POST", "DELETE", "HEAD"} + all := []string{"GET", "PUT", "POST", "DELETE", "HEAD", "OPTIONS"} client := http.Client{} - for _, tt := range tests { + for _, tt := range expectedEndpoints { for _, m := range all { t.Run(m+" "+tt.uri, func(t *testing.T) { uri := fmt.Sprintf("http://%s%s", a.HTTPAddr(), tt.uri) @@ -111,3 +113,29 @@ func TestHTTPAPI_MethodNotAllowed_OSS(t *testing.T) { } } } + +func TestHTTPAPI_OptionMethod_OSS(t *testing.T) { + a := NewTestAgent(t.Name(), `acl_datacenter = "dc1"`) + a.Agent.LogWriter = logger.NewLogWriter(512) + defer a.Shutdown() + + for _, tt := range expectedEndpoints { + t.Run("OPTIONS "+tt.uri, func(t *testing.T) { + uri := fmt.Sprintf("http://%s%s", a.HTTPAddr(), tt.uri) + req, _ := http.NewRequest("OPTIONS", uri, nil) + resp := httptest.NewRecorder() + a.srv.Handler.ServeHTTP(resp, req) + + if resp.Code != http.StatusOK { + t.Fatalf("options request: got status code %d want %d", resp.Code, http.StatusOK) + } + + optionsStr := resp.Header().Get("Allow") + if optionsStr == "" { + t.Fatalf("options request: got empty 'Allow' header") + } else if optionsStr != tt.methods { + t.Fatalf("options request: got 'Allow' header value of %s want %s", optionsStr, tt.methods) + } + }) + } +} diff --git a/agent/http_test.go b/agent/http_test.go index dbec2fb8fa..23c90014ef 100644 --- a/agent/http_test.go +++ b/agent/http_test.go @@ -714,42 +714,6 @@ func TestEnableWebUI(t *testing.T) { } } -func TestHttpOptions(t *testing.T) { - - req, _ := http.NewRequest("OPTIONS", "/v1/query", nil) - resp := httptest.NewRecorder() - a := NewTestAgent(t.Name(), "") - a.srv.Handler.ServeHTTP(resp, req) - - // Check the result - if resp.Code != 200 { - t.Fatalf("should handle options method") - } - optionsStr := resp.Header().Get("Allow") - if optionsStr == "" { - t.Fatalf("options method should set 'Allow' header") - } else if optionsStr != "OPTIONS,GET,POST" { - t.Fatalf("options method should set 'Allow' header correctly") - } -} - -func TestMethodNotAllowed(t *testing.T) { - req, _ := http.NewRequest("PATH", "/v1/kv/", nil) - resp := httptest.NewRecorder() - a := NewTestAgent(t.Name(), "") - a.srv.Handler.ServeHTTP(resp, req) - - if resp.Code != 405 { - t.Fatalf("should reject an unsupported method with 405") - } - optionsStr := resp.Header().Get("Allow") - if optionsStr == "" { - t.Fatalf("405 error should set 'Allow' header") - } else if optionsStr != "OPTIONS,GET,PUT,DELETE" { - t.Fatalf("options method should set 'Allow' header correctly") - } -} - // assertIndex tests that X-Consul-Index is set and non-zero func assertIndex(t *testing.T, resp *httptest.ResponseRecorder) { header := resp.Header().Get("X-Consul-Index")