Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG/CHANGELOG-3.7.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@


Previous change logs can be found at [CHANGELOG-3.6](https://github.com/etcd-io/etcd/blob/main/CHANGELOG/CHANGELOG-3.6.md).

---
Expand All @@ -13,6 +12,7 @@ Previous change logs can be found at [CHANGELOG-3.6](https://github.com/etcd-io/
### etcd server

- [Update go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc to v0.61.0 and replaced the deprecated `UnaryServerInterceptor` and `StreamServerInterceptor` with `NewServerHandler`](https://github.com/etcd-io/etcd/pull/20017)
- [Add Support for Unix Socket endpoints](https://github.com/etcd-io/etcd/pull/19760)

### Package `pkg`

Expand Down
5 changes: 4 additions & 1 deletion server/embed/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -963,7 +963,7 @@
}
if err := checkHostURLs(cfg.AdvertiseClientUrls); err != nil {
addrs := cfg.getAdvertiseClientURLs()
return fmt.Errorf(`--advertise-client-urls %q must be "host:port" (%w)`, strings.Join(addrs, ","), err)
return fmt.Errorf(`--advertise-client-urls %q must be in the format "host:port", "unix:/path/to/socket" or "unixs:/path/to/socket" (%w)`, strings.Join(addrs, ","), err)

Check warning on line 966 in server/embed/config.go

View check run for this annotation

Codecov / codecov/patch

server/embed/config.go#L966

Added line #L966 was not covered by tests
}
// Check if conflicting flags are passed.
nSet := 0
Expand Down Expand Up @@ -1355,6 +1355,9 @@

func checkHostURLs(urls []url.URL) error {
for _, url := range urls {
if url.Scheme == "unix" || url.Scheme == "unixs" {
continue
}
host, _, err := net.SplitHostPort(url.Host)
if err != nil {
return err
Expand Down
63 changes: 63 additions & 0 deletions server/embed/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -784,3 +784,66 @@ func TestMatchNewConfigAddFlags(t *testing.T) {
t.Errorf("Diff: %s", diff)
}
}

func TestCheckHostURLs(t *testing.T) {
tests := []struct {
name string
urls []url.URL
wantErr bool
}{
{
name: "valid HTTP URLs",
urls: []url.URL{
{Scheme: "http", Host: "127.0.0.1:2379"},
{Scheme: "http", Host: "localhost:2379"},
},
wantErr: false,
},
{
name: "valid HTTPS URLs",
urls: []url.URL{
{Scheme: "https", Host: "127.0.0.1:2379"},
{Scheme: "https", Host: "localhost:2379"},
},
wantErr: false,
},
{
name: "valid Unix socket URLs",
urls: []url.URL{
{Scheme: "unix", Host: "", Path: "/tmp/etcd.sock"},
{Scheme: "unixs", Host: "", Path: "/tmp/etcd-secure.sock"},
},
wantErr: false,
},
{
name: "empty host in URL",
urls: []url.URL{
{Scheme: "http", Host: ""},
},
wantErr: true,
},
{
name: "invalid host format",
urls: []url.URL{
{Scheme: "http", Host: "invalid_host"},
},
wantErr: true,
},
{
name: "missing port in host",
urls: []url.URL{
{Scheme: "http", Host: "127.0.0.1"},
},
wantErr: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := checkHostURLs(tt.urls)
if (err != nil) != tt.wantErr {
t.Errorf("checkHostURLs() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
52 changes: 52 additions & 0 deletions tests/e2e/etcd_config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -735,3 +735,55 @@ func TestV2DeprecationEnforceDefaultValue(t *testing.T) {
})
}
}

func TestEtcdAdvertiseClientUnix(t *testing.T) {
e2e.SkipInShortMode(t)

// Create a temporary directory for the data directory
dataDir := t.TempDir()
socketDir := t.TempDir()
unixSocket := fmt.Sprintf("unix://%s/etcd-client.sock", socketDir)

// Start etcd with AdvertiseClientUrls set to a unix socket
proc, err := e2e.SpawnCmd(
[]string{
e2e.BinPath.Etcd,
"--data-dir", dataDir,
"--name", "etcd1",
"--listen-client-urls", unixSocket,
"--advertise-client-urls", unixSocket,
}, nil,
)
require.NoError(t, err)
defer func() {
_ = proc.Stop()
_ = proc.Close()
}()

// Wait for the process to be ready
require.NoError(t, e2e.WaitReadyExpectProc(t.Context(), proc, e2e.EtcdServerReadyLines))

// Write a key/value pair using etcdctl with unix socket
putArgs := []string{
e2e.BinPath.Etcdctl,
"--endpoints", unixSocket,
"put", "foo", "bar",
}
putProc, err := e2e.SpawnCmd(putArgs, nil)
require.NoError(t, err)
require.NoError(t, e2e.WaitReadyExpectProc(t.Context(), putProc, []string{"OK"}))
require.NoError(t, putProc.Stop())
_ = putProc.Close()

// Read the key back using etcdctl with unix socket
getArgs := []string{
e2e.BinPath.Etcdctl,
"--endpoints", unixSocket,
"get", "foo",
}
getProc, err := e2e.SpawnCmd(getArgs, nil)
require.NoError(t, err)
require.NoError(t, e2e.WaitReadyExpectProc(t.Context(), getProc, []string{"foo", "bar"}))
require.NoError(t, getProc.Stop())
_ = getProc.Close()
}