From e7b52d35d4c93af0b9fe17f16b59af503ab4228d Mon Sep 17 00:00:00 2001 From: freddygv Date: Thu, 11 Jun 2020 23:30:21 -0600 Subject: [PATCH] Create HTTP endpoint --- agent/catalog_endpoint.go | 40 ++++++ agent/catalog_endpoint_test.go | 223 +++++++++++++++++++++++++++++++++ agent/http_register.go | 1 + 3 files changed, 264 insertions(+) diff --git a/agent/catalog_endpoint.go b/agent/catalog_endpoint.go index 0b90ee2fc8..20d212010f 100644 --- a/agent/catalog_endpoint.go +++ b/agent/catalog_endpoint.go @@ -414,3 +414,43 @@ RETRY_ONCE: []metrics.Label{{Name: "node", Value: s.nodeName()}}) return &out.NodeServices, nil } + +func (s *HTTPServer) CatalogGatewayServices(resp http.ResponseWriter, req *http.Request) (interface{}, error) { + metrics.IncrCounterWithLabels([]string{"client", "api", "catalog_gateway_services"}, 1, + []metrics.Label{{Name: "node", Value: s.nodeName()}}) + + var args structs.ServiceSpecificRequest + + if err := s.parseEntMetaNoWildcard(req, &args.EnterpriseMeta); err != nil { + return nil, err + } + if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done { + return nil, nil + } + + // Pull out the gateway's service name + args.ServiceName = strings.TrimPrefix(req.URL.Path, "/v1/catalog/gateway-services/") + if args.ServiceName == "" { + resp.WriteHeader(http.StatusBadRequest) + fmt.Fprint(resp, "Missing gateway name") + return nil, nil + } + + // Make the RPC request + var out structs.IndexedGatewayServices + defer setMeta(resp, &out.QueryMeta) +RETRY_ONCE: + if err := s.agent.RPC("Catalog.GatewayServices", &args, &out); err != nil { + metrics.IncrCounterWithLabels([]string{"client", "rpc", "error", "catalog_gateway_services"}, 1, + []metrics.Label{{Name: "node", Value: s.nodeName()}}) + return nil, err + } + if args.QueryOptions.AllowStale && args.MaxStaleDuration > 0 && args.MaxStaleDuration < out.LastContact { + args.AllowStale = false + args.MaxStaleDuration = 0 + goto RETRY_ONCE + } + out.ConsistencyLevel = args.QueryOptions.ConsistencyLevel() + + return out.Services, nil +} diff --git a/agent/catalog_endpoint_test.go b/agent/catalog_endpoint_test.go index f4bc487a0a..1b4b7bd141 100644 --- a/agent/catalog_endpoint_test.go +++ b/agent/catalog_endpoint_test.go @@ -2,6 +2,7 @@ package agent import ( "fmt" + "github.com/hashicorp/consul/api" "net/http" "net/http/httptest" "net/url" @@ -1320,3 +1321,225 @@ func TestCatalogNodeServices_WanTranslation(t *testing.T) { require.Equal(t, ns2.Address, "127.0.0.1") require.Equal(t, ns2.Port, 8080) } + +func TestCatalog_GatewayServices_Terminating(t *testing.T) { + t.Parallel() + a := NewTestAgent(t, "") + defer a.Shutdown() + + testrpc.WaitForTestAgent(t, a.RPC, "dc1") + + // Register a terminating gateway + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: "127.0.0.1", + Service: &structs.NodeService{ + Kind: structs.ServiceKindTerminatingGateway, + Service: "terminating", + Port: 443, + }, + } + + var out struct{} + assert.NoError(t, a.RPC("Catalog.Register", &args, &out)) + + // Register two services the gateway will route to + args = structs.TestRegisterRequest(t) + args.Service.Service = "redis" + args.Check = &structs.HealthCheck{ + Name: "redis", + Status: api.HealthPassing, + ServiceID: args.Service.Service, + } + assert.NoError(t, a.RPC("Catalog.Register", &args, &out)) + + args = structs.TestRegisterRequest(t) + args.Service.Service = "api" + args.Check = &structs.HealthCheck{ + Name: "api", + Status: api.HealthPassing, + ServiceID: args.Service.Service, + } + assert.NoError(t, a.RPC("Catalog.Register", &args, &out)) + + // Associate the gateway and api/redis services + entryArgs := &structs.ConfigEntryRequest{ + Op: structs.ConfigEntryUpsert, + Datacenter: "dc1", + Entry: &structs.TerminatingGatewayConfigEntry{ + Kind: "terminating-gateway", + Name: "terminating", + Services: []structs.LinkedService{ + { + Name: "api", + CAFile: "api/ca.crt", + CertFile: "api/client.crt", + KeyFile: "api/client.key", + SNI: "my-domain", + }, + { + Name: "*", + CAFile: "ca.crt", + CertFile: "client.crt", + KeyFile: "client.key", + SNI: "my-alt-domain", + }, + }, + }, + } + var entryResp bool + assert.NoError(t, a.RPC("ConfigEntry.Apply", &entryArgs, &entryResp)) + + retry.Run(t, func(r *retry.R) { + req, _ := http.NewRequest("GET", "/v1/catalog/gateway-services/terminating", nil) + resp := httptest.NewRecorder() + obj, err := a.srv.CatalogGatewayServices(resp, req) + assert.NoError(r, err) + + header := resp.Header().Get("X-Consul-Index") + if header == "" || header == "0" { + r.Fatalf("Bad: %v", header) + } + + gatewayServices := obj.(structs.GatewayServices) + + expect := structs.GatewayServices{ + { + Service: structs.NewServiceID("api", nil), + Gateway: structs.NewServiceID("terminating", nil), + GatewayKind: structs.ServiceKindTerminatingGateway, + CAFile: "api/ca.crt", + CertFile: "api/client.crt", + KeyFile: "api/client.key", + SNI: "my-domain", + }, + { + Service: structs.NewServiceID("redis", nil), + Gateway: structs.NewServiceID("terminating", nil), + GatewayKind: structs.ServiceKindTerminatingGateway, + CAFile: "ca.crt", + CertFile: "client.crt", + KeyFile: "client.key", + SNI: "my-alt-domain", + FromWildcard: true, + }, + } + + // Ignore raft index for equality + for _, s := range gatewayServices { + s.RaftIndex = structs.RaftIndex{} + } + assert.Equal(r, expect, gatewayServices) + }) +} + +func TestCatalog_GatewayServices_Ingress(t *testing.T) { + t.Parallel() + a := NewTestAgent(t, "") + defer a.Shutdown() + + testrpc.WaitForTestAgent(t, a.RPC, "dc1") + + // Register an ingress gateway + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: "127.0.0.1", + Service: &structs.NodeService{ + Kind: structs.ServiceKindTerminatingGateway, + Service: "ingress", + Port: 444, + }, + } + + var out struct{} + require.NoError(t, a.RPC("Catalog.Register", &args, &out)) + + // Register two services the gateway will route to + args = structs.TestRegisterRequest(t) + args.Service.Service = "redis" + args.Check = &structs.HealthCheck{ + Name: "redis", + Status: api.HealthPassing, + ServiceID: args.Service.Service, + } + require.NoError(t, a.RPC("Catalog.Register", &args, &out)) + + args = structs.TestRegisterRequest(t) + args.Service.Service = "api" + args.Check = &structs.HealthCheck{ + Name: "api", + Status: api.HealthPassing, + ServiceID: args.Service.Service, + } + require.NoError(t, a.RPC("Catalog.Register", &args, &out)) + + // Associate the gateway and db service + entryArgs := &structs.ConfigEntryRequest{ + Op: structs.ConfigEntryUpsert, + Datacenter: "dc1", + Entry: &structs.IngressGatewayConfigEntry{ + Kind: "ingress-gateway", + Name: "ingress", + Listeners: []structs.IngressListener{ + { + Port: 8888, + Services: []structs.IngressService{ + { + Name: "api", + }, + }, + }, + { + Port: 9999, + Services: []structs.IngressService{ + { + Name: "redis", + }, + }, + }, + }, + }, + } + + var entryResp bool + require.NoError(t, a.RPC("ConfigEntry.Apply", &entryArgs, &entryResp)) + + retry.Run(t, func(r *retry.R) { + req, _ := http.NewRequest("GET", "/v1/catalog/gateway-services/ingress", nil) + resp := httptest.NewRecorder() + obj, err := a.srv.CatalogGatewayServices(resp, req) + require.NoError(r, err) + + header := resp.Header().Get("X-Consul-Index") + if header == "" || header == "0" { + r.Fatalf("Bad: %v", header) + } + + gatewayServices := obj.(structs.GatewayServices) + + expect := structs.GatewayServices{ + { + Service: structs.NewServiceID("api", nil), + Gateway: structs.NewServiceID("ingress", nil), + GatewayKind: structs.ServiceKindIngressGateway, + Protocol: "tcp", + Port: 8888, + }, + { + Service: structs.NewServiceID("redis", nil), + Gateway: structs.NewServiceID("ingress", nil), + GatewayKind: structs.ServiceKindIngressGateway, + Protocol: "tcp", + Port: 9999, + }, + } + + // Ignore raft index for equality + for _, s := range gatewayServices { + s.RaftIndex = structs.RaftIndex{} + } + require.Equal(r, expect, gatewayServices) + }) +} diff --git a/agent/http_register.go b/agent/http_register.go index ba4b8a5a94..96ee58d05a 100644 --- a/agent/http_register.go +++ b/agent/http_register.go @@ -68,6 +68,7 @@ func init() { registerEndpoint("/v1/catalog/service/", []string{"GET"}, (*HTTPServer).CatalogServiceNodes) registerEndpoint("/v1/catalog/node/", []string{"GET"}, (*HTTPServer).CatalogNodeServices) registerEndpoint("/v1/catalog/node-services/", []string{"GET"}, (*HTTPServer).CatalogNodeServiceList) + registerEndpoint("/v1/catalog/gateway-services/", []string{"GET"}, (*HTTPServer).CatalogGatewayServices) registerEndpoint("/v1/config/", []string{"GET", "DELETE"}, (*HTTPServer).Config) registerEndpoint("/v1/config", []string{"PUT"}, (*HTTPServer).ConfigApply) registerEndpoint("/v1/connect/ca/configuration", []string{"GET", "PUT"}, (*HTTPServer).ConnectCAConfiguration)