From ead9c42abcff7b89a67041fc02d0212152c2c22d Mon Sep 17 00:00:00 2001 From: Armon Dadgar Date: Tue, 24 Dec 2013 11:55:14 -0800 Subject: [PATCH] Finish implementing the HTTP catalog interface --- command/agent/catalog_endpoint.go | 110 +++++++++++++++++-- command/agent/catalog_endpoint_test.go | 143 +++++++++++++++++++++++++ command/agent/http.go | 5 + 3 files changed, 248 insertions(+), 10 deletions(-) diff --git a/command/agent/catalog_endpoint.go b/command/agent/catalog_endpoint.go index 047adcc3e9..5aa0b90ecf 100644 --- a/command/agent/catalog_endpoint.go +++ b/command/agent/catalog_endpoint.go @@ -4,18 +4,9 @@ import ( "fmt" "github.com/hashicorp/consul/consul/structs" "net/http" + "strings" ) -/* -* /v1/catalog/register : Registers a new service -* /v1/catalog/deregister : Deregisters a service or node -* /v1/catalog/datacenters : Lists known datacenters -* /v1/catalog/nodes : Lists nodes in a given DC -* /v1/catalog/services : Lists services in a given DC -* /v1/catalog/service// : Lists the nodes in a given service -* /v1/catalog/node// : Lists the services provided by a node - */ - func (s *HTTPServer) CatalogRegister(resp http.ResponseWriter, req *http.Request) (interface{}, error) { var args structs.RegisterRequest if err := decodeBody(req, &args); err != nil { @@ -37,6 +28,27 @@ func (s *HTTPServer) CatalogRegister(resp http.ResponseWriter, req *http.Request return true, nil } +func (s *HTTPServer) CatalogDeregister(resp http.ResponseWriter, req *http.Request) (interface{}, error) { + var args structs.DeregisterRequest + if err := decodeBody(req, &args); err != nil { + resp.WriteHeader(400) + resp.Write([]byte(fmt.Sprintf("Request decode failed: %v", err))) + return nil, nil + } + + // Setup the default DC if not provided + if args.Datacenter == "" { + args.Datacenter = s.agent.config.Datacenter + } + + // Forward to the servers + var out struct{} + if err := s.agent.RPC("Catalog.Deregister", &args, &out); err != nil { + return nil, err + } + return true, nil +} + func (s *HTTPServer) CatalogDatacenters(resp http.ResponseWriter, req *http.Request) (interface{}, error) { var out []string if err := s.agent.RPC("Catalog.ListDatacenters", struct{}{}, &out); err != nil { @@ -60,3 +72,81 @@ func (s *HTTPServer) CatalogNodes(resp http.ResponseWriter, req *http.Request) ( } return out, nil } + +func (s *HTTPServer) CatalogServices(resp http.ResponseWriter, req *http.Request) (interface{}, error) { + // Set default DC + dc := s.agent.config.Datacenter + + // Check for other DC + if other := req.URL.Query().Get("dc"); other != "" { + dc = other + } + + var out structs.Services + if err := s.agent.RPC("Catalog.ListServices", dc, &out); err != nil { + return nil, err + } + return out, nil +} + +func (s *HTTPServer) CatalogServiceNodes(resp http.ResponseWriter, req *http.Request) (interface{}, error) { + // Set default DC + args := structs.ServiceNodesRequest{ + Datacenter: s.agent.config.Datacenter, + } + + // Check for other DC + params := req.URL.Query() + if other := params.Get("dc"); other != "" { + args.Datacenter = other + } + + // Check for a tag + if _, ok := params["tag"]; ok { + args.ServiceTag = params.Get("tag") + args.TagFilter = true + } + + // Pull out the service name + args.ServiceName = strings.TrimPrefix(req.URL.Path, "/v1/catalog/service/") + if args.ServiceName == "" { + resp.WriteHeader(400) + resp.Write([]byte("Missing service name")) + return nil, nil + } + + // Make the RPC request + var out structs.ServiceNodes + if err := s.agent.RPC("Catalog.ServiceNodes", &args, &out); err != nil { + return nil, err + } + return out, nil +} + +func (s *HTTPServer) CatalogNodeServices(resp http.ResponseWriter, req *http.Request) (interface{}, error) { + // Set default Datacenter + args := structs.NodeServicesRequest{ + Datacenter: s.agent.config.Datacenter, + } + + // Check for other DC + params := req.URL.Query() + if other := params.Get("dc"); other != "" { + args.Datacenter = other + } + + // Pull out the node name + args.Node = strings.TrimPrefix(req.URL.Path, "/v1/catalog/node/") + if args.Node == "" { + resp.WriteHeader(400) + resp.Write([]byte("Missing node name")) + return nil, nil + } + + // Make the RPC request + var out structs.NodeServices + if err := s.agent.RPC("Catalog.NodeServices", &args, &out); err != nil { + return nil, err + } + return out, nil +} diff --git a/command/agent/catalog_endpoint_test.go b/command/agent/catalog_endpoint_test.go index 1649383e3e..a68c55914d 100644 --- a/command/agent/catalog_endpoint_test.go +++ b/command/agent/catalog_endpoint_test.go @@ -39,6 +39,36 @@ func TestCatalogRegister(t *testing.T) { } } +func TestCatalogDeregister(t *testing.T) { + dir, srv := makeHTTPServer(t) + defer os.RemoveAll(dir) + defer srv.Shutdown() + defer srv.agent.Shutdown() + + // Wait for a leader + time.Sleep(100 * time.Millisecond) + + // Register node + req, err := http.NewRequest("GET", "/v1/catalog/deregister", nil) + if err != nil { + t.Fatalf("err: %v", err) + } + args := &structs.DeregisterRequest{ + Node: "foo", + } + req.Body = encodeReq(args) + + obj, err := srv.CatalogDeregister(nil, req) + if err != nil { + t.Fatalf("err: %v", err) + } + + res := obj.(bool) + if res != true { + t.Fatalf("bad: %v", res) + } +} + func TestCatalogDatacenters(t *testing.T) { dir, srv := makeHTTPServer(t) defer os.RemoveAll(dir) @@ -94,3 +124,116 @@ func TestCatalogNodes(t *testing.T) { t.Fatalf("bad: %v", obj) } } + +func TestCatalogServices(t *testing.T) { + dir, srv := makeHTTPServer(t) + defer os.RemoveAll(dir) + defer srv.Shutdown() + defer srv.agent.Shutdown() + + // Wait for a leader + time.Sleep(100 * time.Millisecond) + + // Register node + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: "127.0.0.1", + ServiceName: "api", + } + var out struct{} + if err := srv.agent.RPC("Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + + req, err := http.NewRequest("GET", "/v1/catalog/services?dc=dc1", nil) + if err != nil { + t.Fatalf("err: %v", err) + } + + obj, err := srv.CatalogServices(nil, req) + if err != nil { + t.Fatalf("err: %v", err) + } + + services := obj.(structs.Services) + if len(services) != 1 { + t.Fatalf("bad: %v", obj) + } +} + +func TestCatalogServiceNodes(t *testing.T) { + dir, srv := makeHTTPServer(t) + defer os.RemoveAll(dir) + defer srv.Shutdown() + defer srv.agent.Shutdown() + + // Wait for a leader + time.Sleep(100 * time.Millisecond) + + // Register node + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: "127.0.0.1", + ServiceName: "api", + ServiceTag: "a", + } + var out struct{} + if err := srv.agent.RPC("Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + + req, err := http.NewRequest("GET", "/v1/catalog/service/api?tag=a", nil) + if err != nil { + t.Fatalf("err: %v", err) + } + + obj, err := srv.CatalogServiceNodes(nil, req) + if err != nil { + t.Fatalf("err: %v", err) + } + + nodes := obj.(structs.ServiceNodes) + if len(nodes) != 1 { + t.Fatalf("bad: %v", obj) + } +} + +func TestCatalogNodeServices(t *testing.T) { + dir, srv := makeHTTPServer(t) + defer os.RemoveAll(dir) + defer srv.Shutdown() + defer srv.agent.Shutdown() + + // Wait for a leader + time.Sleep(100 * time.Millisecond) + + // Register node + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: "127.0.0.1", + ServiceName: "api", + ServiceTag: "a", + } + var out struct{} + if err := srv.agent.RPC("Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + + req, err := http.NewRequest("GET", "/v1/catalog/node/foo?dc=dc1", nil) + if err != nil { + t.Fatalf("err: %v", err) + } + + obj, err := srv.CatalogNodeServices(nil, req) + if err != nil { + t.Fatalf("err: %v", err) + } + + services := obj.(structs.NodeServices) + if len(services) != 1 { + t.Fatalf("bad: %v", obj) + } +} diff --git a/command/agent/http.go b/command/agent/http.go index dd71d813f3..b66262aa6d 100644 --- a/command/agent/http.go +++ b/command/agent/http.go @@ -55,8 +55,13 @@ func (s *HTTPServer) registerHandlers() { s.mux.HandleFunc("/v1/status/leader", s.wrap(s.StatusLeader)) s.mux.HandleFunc("/v1/status/peers", s.wrap(s.StatusPeers)) + s.mux.HandleFunc("/v1/catalog/register", s.wrap(s.CatalogRegister)) + s.mux.HandleFunc("/v1/catalog/deregister", s.wrap(s.CatalogDeregister)) s.mux.HandleFunc("/v1/catalog/datacenters", s.wrap(s.CatalogDatacenters)) s.mux.HandleFunc("/v1/catalog/nodes", s.wrap(s.CatalogNodes)) + s.mux.HandleFunc("/v1/catalog/services", s.wrap(s.CatalogServices)) + s.mux.HandleFunc("/v1/catalog/service/", s.wrap(s.CatalogServiceNodes)) + s.mux.HandleFunc("/v1/catalog/node/", s.wrap(s.CatalogNodeServices)) } // wrap is used to wrap functions to make them more convenient