Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RPC and HTTP interfaces fully generically-sockified so Unix is supported... #587

Merged
merged 7 commits into from
Jan 15, 2015
43 changes: 35 additions & 8 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ import (
"encoding/json"
"fmt"
"io"
"net"
"net/http"
"net/url"
"os"
"strconv"
"strings"
"time"
)

Expand Down Expand Up @@ -111,11 +114,17 @@ type Config struct {

// DefaultConfig returns a default configuration for the client
func DefaultConfig() *Config {
return &Config{
config := &Config{
Address: "127.0.0.1:8500",
Scheme: "http",
HttpClient: http.DefaultClient,
}

if len(os.Getenv("CONSUL_HTTP_ADDR")) > 0 {
config.Address = os.Getenv("CONSUL_HTTP_ADDR")
}

return config
}

// Client provides a client to the Consul API
Expand All @@ -128,7 +137,11 @@ func NewClient(config *Config) (*Client, error) {
// bootstrap the config
defConfig := DefaultConfig()

if len(config.Address) == 0 {
switch {
case len(config.Address) != 0:
case len(os.Getenv("CONSUL_HTTP_ADDR")) > 0:
config.Address = os.Getenv("CONSUL_HTTP_ADDR")
default:
config.Address = defConfig.Address
}

Expand All @@ -140,6 +153,16 @@ func NewClient(config *Config) (*Client, error) {
config.HttpClient = defConfig.HttpClient
}

if strings.HasPrefix(config.Address, "unix://") {
shortStr := strings.TrimPrefix(config.Address, "unix://")
t := &http.Transport{}
t.Dial = func(_, _ string) (net.Conn, error) {
return net.Dial("unix", shortStr)
}
config.HttpClient.Transport = t
config.Address = shortStr
}

client := &Client{
config: *config,
}
Expand Down Expand Up @@ -206,9 +229,6 @@ func (r *request) toHTTP() (*http.Request, error) {
// Encode the query parameters
r.url.RawQuery = r.params.Encode()

// Get the url sring
urlRaw := r.url.String()

// Check if we should encode the body
if r.body == nil && r.obj != nil {
if b, err := encodeBody(r.obj); err != nil {
Expand All @@ -219,14 +239,21 @@ func (r *request) toHTTP() (*http.Request, error) {
}

// Create the HTTP request
req, err := http.NewRequest(r.method, urlRaw, r.body)
req, err := http.NewRequest(r.method, r.url.RequestURI(), r.body)
if err != nil {
return nil, err
}

req.URL.Host = r.url.Host
req.URL.Scheme = r.url.Scheme
req.Host = r.url.Host

// Setup auth
if err == nil && r.config.HttpAuth != nil {
if r.config.HttpAuth != nil {
req.SetBasicAuth(r.config.HttpAuth.Username, r.config.HttpAuth.Password)
}

return req, err
return req, nil
}

// newRequest is used to create a new request
Expand Down
115 changes: 80 additions & 35 deletions api/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package api

import (
crand "crypto/rand"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
Expand All @@ -13,27 +14,50 @@ import (
"github.com/hashicorp/consul/testutil"
)

var consulConfig = `{
"ports": {
"dns": 19000,
"http": 18800,
"rpc": 18600,
"serf_lan": 18200,
"serf_wan": 18400,
"server": 18000
},
"data_dir": "%s",
"bootstrap": true,
"log_level": "debug",
"server": true
}`

type testServer struct {
pid int
dataDir string
configFile string
}

type testPortConfig struct {
DNS int `json:"dns,omitempty"`
HTTP int `json:"http,omitempty"`
RPC int `json:"rpc,omitempty"`
SerfLan int `json:"serf_lan,omitempty"`
SerfWan int `json:"serf_wan,omitempty"`
Server int `json:"server,omitempty"`
}

type testAddressConfig struct {
HTTP string `json:"http,omitempty"`
}

type testServerConfig struct {
Bootstrap bool `json:"bootstrap,omitempty"`
Server bool `json:"server,omitempty"`
DataDir string `json:"data_dir,omitempty"`
LogLevel string `json:"log_level,omitempty"`
Addresses *testAddressConfig `json:"addresses,omitempty"`
Ports testPortConfig `json:"ports,omitempty"`
}

func defaultConfig() *testServerConfig {
return &testServerConfig{
Bootstrap: true,
Server: true,
LogLevel: "debug",
Ports: testPortConfig{
DNS: 19000,
HTTP: 18800,
RPC: 18600,
SerfLan: 18200,
SerfWan: 18400,
Server: 18000,
},
}
}

func (s *testServer) stop() {
defer os.RemoveAll(s.dataDir)
defer os.RemoveAll(s.configFile)
Expand All @@ -45,6 +69,10 @@ func (s *testServer) stop() {
}

func newTestServer(t *testing.T) *testServer {
return newTestServerWithConfig(t, func(c *testServerConfig) {})
}

func newTestServerWithConfig(t *testing.T, cb func(c *testServerConfig)) *testServer {
if path, err := exec.LookPath("consul"); err != nil || path == "" {
t.Log("consul not found on $PATH, skipping")
t.SkipNow()
Expand All @@ -66,8 +94,18 @@ func newTestServer(t *testing.T) *testServer {
if err != nil {
t.Fatalf("err: %s", err)
}
configContent := fmt.Sprintf(consulConfig, dataDir)
if _, err := configFile.WriteString(configContent); err != nil {

consulConfig := defaultConfig()
consulConfig.DataDir = dataDir

cb(consulConfig)

configContent, err := json.Marshal(consulConfig)
if err != nil {
t.Fatalf("err: %s", err)
}

if _, err := configFile.Write(configContent); err != nil {
t.Fatalf("err: %s", err)
}
configFile.Close()
Expand All @@ -80,10 +118,32 @@ func newTestServer(t *testing.T) *testServer {
t.Fatalf("err: %s", err)
}

return &testServer{
pid: cmd.Process.Pid,
dataDir: dataDir,
configFile: configFile.Name(),
}
}

func makeClient(t *testing.T) (*Client, *testServer) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably have a DefaultConfig()-esque method instead of having a global variable and resetting the address for each test that uses it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kept it that way since that's how it was defined before. I'll change it to a DefaultConfig method.

return makeClientWithConfig(t, func(c *Config) {
c.Address = "127.0.0.1:18800"
}, func(c *testServerConfig) {})
}

func makeClientWithConfig(t *testing.T, clientConfig func(c *Config), serverConfig func(c *testServerConfig)) (*Client, *testServer) {
server := newTestServerWithConfig(t, serverConfig)
conf := DefaultConfig()
clientConfig(conf)
client, err := NewClient(conf)
if err != nil {
t.Fatalf("err: %v", err)
}

// Allow the server some time to start, and verify we have a leader.
client := new(http.Client)
testutil.WaitForResult(func() (bool, error) {
resp, err := client.Get("http://127.0.0.1:18800/v1/catalog/nodes")
req := client.newRequest("GET", "/v1/catalog/nodes")
_, resp, err := client.doRequest(req)
if err != nil {
return false, err
}
Expand All @@ -102,21 +162,6 @@ func newTestServer(t *testing.T) *testServer {
t.Fatalf("err: %s", err)
})

return &testServer{
pid: cmd.Process.Pid,
dataDir: dataDir,
configFile: configFile.Name(),
}
}

func makeClient(t *testing.T) (*Client, *testServer) {
server := newTestServer(t)
conf := DefaultConfig()
conf.Address = "127.0.0.1:18800"
client, err := NewClient(conf)
if err != nil {
t.Fatalf("err: %v", err)
}
return client, server
}

Expand Down Expand Up @@ -205,7 +250,7 @@ func TestRequestToHTTP(t *testing.T) {
if req.Method != "DELETE" {
t.Fatalf("bad: %v", req)
}
if req.URL.String() != "http://127.0.0.1:18800/v1/kv/foo?dc=foo" {
if req.URL.RequestURI() != "/v1/kv/foo?dc=foo" {
t.Fatalf("bad: %v", req)
}
}
Expand Down
47 changes: 46 additions & 1 deletion api/status_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package api

import (
"io/ioutil"
"os/user"
"runtime"
"testing"
)

func TestStatusLeader(t *testing.T) {
func TestStatusLeaderTCP(t *testing.T) {
c, s := makeClient(t)
defer s.stop()

Expand All @@ -19,6 +22,48 @@ func TestStatusLeader(t *testing.T) {
}
}

func TestStatusLeaderUnix(t *testing.T) {
if runtime.GOOS == "windows" {
t.SkipNow()
}

tempdir, err := ioutil.TempDir("", "consul-test-")
if err != nil {
t.Fatal("Could not create a working directory")
}

socket := "unix://" + tempdir + "/unix-http-test.sock"

clientConfig := func(c *Config) {
c.Address = socket
}

serverConfig := func(c *testServerConfig) {
user, err := user.Current()
if err != nil {
t.Fatal("Could not get current user")
}

if c.Addresses == nil {
c.Addresses = &testAddressConfig{}
}
c.Addresses.HTTP = socket + ";" + user.Uid + ";" + user.Gid + ";640"
}

c, s := makeClientWithConfig(t, clientConfig, serverConfig)
defer s.stop()

status := c.Status()

leader, err := status.Leader()
if err != nil {
t.Fatalf("err: %v", err)
}
if leader == "" {
t.Fatalf("Expected leader")
}
}

func TestStatusPeers(t *testing.T) {
c, s := makeClient(t)
defer s.stop()
Expand Down
33 changes: 32 additions & 1 deletion command/agent/agent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ import (
"io"
"io/ioutil"
"os"
"os/user"
"path/filepath"
"reflect"
"runtime"
"sync/atomic"
"testing"
"time"
Expand Down Expand Up @@ -123,7 +125,7 @@ func TestAgentStartStop(t *testing.T) {
}
}

func TestAgent_RPCPing(t *testing.T) {
func TestAgent_RPCPingTCP(t *testing.T) {
dir, agent := makeAgent(t, nextConfig())
defer os.RemoveAll(dir)
defer agent.Shutdown()
Expand All @@ -134,6 +136,35 @@ func TestAgent_RPCPing(t *testing.T) {
}
}

func TestAgent_RPCPingUnix(t *testing.T) {
if runtime.GOOS == "windows" {
t.SkipNow()
}

nextConf := nextConfig()

tempdir, err := ioutil.TempDir("", "consul-test-")
if err != nil {
t.Fatal("Could not create a working directory")
}

user, err := user.Current()
if err != nil {
t.Fatal("Could not get current user")
}

nextConf.Addresses.RPC = "unix://" + tempdir + "/unix-rpc-test.sock;" + user.Uid + ";" + user.Gid + ";640"

dir, agent := makeAgent(t, nextConf)
defer os.RemoveAll(dir)
defer agent.Shutdown()

var out struct{}
if err := agent.RPC("Status.Ping", struct{}{}, &out); err != nil {
t.Fatalf("err: %v", err)
}
}

func TestAgent_AddService(t *testing.T) {
dir, agent := makeAgent(t, nextConfig())
defer os.RemoveAll(dir)
Expand Down
Loading