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

Refactor UNIX domain socket #612

Merged
merged 9 commits into from
Jan 16, 2015
Merged
25 changes: 11 additions & 14 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,8 @@ func DefaultConfig() *Config {
HttpClient: http.DefaultClient,
}

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

return config
Expand All @@ -137,11 +137,7 @@ func NewClient(config *Config) (*Client, error) {
// bootstrap the config
defConfig := DefaultConfig()

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

Expand All @@ -153,14 +149,15 @@ 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)
if parts := strings.SplitN(config.Address, "unix://", 2); len(parts) == 2 {
config.HttpClient = &http.Client{
Transport: &http.Transport{
Dial: func(_, _ string) (net.Conn, error) {
return net.Dial("unix", parts[1])
},
},
}
config.HttpClient.Transport = t
config.Address = shortStr
config.Address = parts[1]
}

client := &Client{
Expand Down
51 changes: 47 additions & 4 deletions api/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"net/http"
"os"
"os/exec"
"path/filepath"
"runtime"
"testing"
"time"

Expand Down Expand Up @@ -42,6 +44,10 @@ type testServerConfig struct {
Ports testPortConfig `json:"ports,omitempty"`
}

// Callback functions for modifying config
type configCallback func(c *Config)
type serverConfigCallback func(c *testServerConfig)

func defaultConfig() *testServerConfig {
return &testServerConfig{
Bootstrap: true,
Expand Down Expand Up @@ -72,7 +78,7 @@ func newTestServer(t *testing.T) *testServer {
return newTestServerWithConfig(t, func(c *testServerConfig) {})
}

func newTestServerWithConfig(t *testing.T, cb func(c *testServerConfig)) *testServer {
func newTestServerWithConfig(t *testing.T, cb serverConfigCallback) *testServer {
if path, err := exec.LookPath("consul"); err != nil || path == "" {
t.Log("consul not found on $PATH, skipping")
t.SkipNow()
Expand Down Expand Up @@ -131,15 +137,20 @@ func makeClient(t *testing.T) (*Client, *testServer) {
}, func(c *testServerConfig) {})
}

func makeClientWithConfig(t *testing.T, clientConfig func(c *Config), serverConfig func(c *testServerConfig)) (*Client, *testServer) {
server := newTestServerWithConfig(t, serverConfig)
func makeClientWithConfig(t *testing.T, cb1 configCallback, cb2 serverConfigCallback) (*Client, *testServer) {
// Make client config
conf := DefaultConfig()
clientConfig(conf)
cb1(conf)

// Create client
client, err := NewClient(conf)
if err != nil {
t.Fatalf("err: %v", err)
}

// Create server
server := newTestServerWithConfig(t, cb2)

// Allow the server some time to start, and verify we have a leader.
testutil.WaitForResult(func() (bool, error) {
req := client.newRequest("GET", "/v1/catalog/nodes")
Expand Down Expand Up @@ -278,3 +289,35 @@ func TestParseQueryMeta(t *testing.T) {
t.Fatalf("Bad: %v", qm)
}
}

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

tempDir, err := ioutil.TempDir("", "consul")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.RemoveAll(tempDir)
socket := filepath.Join(tempDir, "test.sock")

c, s := makeClientWithConfig(t, func(c *Config) {
c.Address = "unix://" + socket
}, func(c *testServerConfig) {
c.Addresses = &testAddressConfig{
HTTP: "unix://" + socket,
}
})
defer s.stop()

agent := c.Agent()

info, err := agent.Self()
if err != nil {
t.Fatalf("err: %s", err)
}
if info["Config"]["NodeName"] == "" {
t.Fatalf("bad: %v", info)
}
}
47 changes: 1 addition & 46 deletions api/status_test.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
package api

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

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

Expand All @@ -22,48 +19,6 @@ func TestStatusLeaderTCP(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
7 changes: 7 additions & 0 deletions command/agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ const (

// Path to save local agent checks
checksDir = "checks"

// errSocketFileExists is the human-friendly error message displayed when
// trying to bind a socket to an existing file.
errSocketFileExists = "A file exists at the requested socket path %q. " +
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we surround the path with quotes or a special character? If the path has spaces it it, it might be difficult to distinguish.

Copy link
Member Author

Choose a reason for hiding this comment

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

%q's got your back, jack :) http://play.golang.org/p/V6PfAl5PWn

%q  a double-quoted string safely escaped with Go syntax

"If Consul was not shut down properly, the socket file may " +
"be left behind. If the path looks correct, remove the file " +
"and try again."
)

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

func TestAgent_RPCPingTCP(t *testing.T) {
func TestAgent_RPCPing(t *testing.T) {
dir, agent := makeAgent(t, nextConfig())
defer os.RemoveAll(dir)
defer agent.Shutdown()
Expand All @@ -136,35 +134,6 @@ func TestAgent_RPCPingTCP(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
17 changes: 6 additions & 11 deletions command/agent/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -295,9 +295,12 @@ func (c *Command) setupAgent(config *Config, logOutput io.Writer, logWriter *log
return err
}

if _, ok := rpcAddr.(*net.UnixAddr); ok {
// Remove the socket if it exists, or we'll get a bind error
_ = os.Remove(rpcAddr.String())
// Error if we are trying to bind a domain socket to an existing path
if path, ok := unixSocketAddr(config.Addresses.RPC); ok {
if _, err := os.Stat(path); err == nil || !os.IsNotExist(err) {
c.Ui.Output(fmt.Sprintf(errSocketFileExists, path))
return fmt.Errorf(errSocketFileExists, path)
}
}

rpcListener, err := net.Listen(rpcAddr.Network(), rpcAddr.String())
Expand All @@ -307,14 +310,6 @@ func (c *Command) setupAgent(config *Config, logOutput io.Writer, logWriter *log
return err
}

if _, ok := rpcAddr.(*net.UnixAddr); ok {
if err := adjustUnixSocketPermissions(config.Addresses.RPC); err != nil {
agent.Shutdown()
c.Ui.Error(fmt.Sprintf("Error adjusting Unix socket permissions: %s", err))
return err
}
}

// Start the IPC layer
c.Ui.Output("Starting Consul agent RPC...")
c.rpcServer = NewAgentRPC(agent, rpcListener, logOutput, logWriter)
Expand Down
Loading