You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cassandra.apache.org by ja...@apache.org on 2025/06/26 14:36:44 UTC
(cassandra-gocql-driver) branch trunk updated: CASSGO-6 Accept peers with empty rack
This is an automated email from the ASF dual-hosted git repository.
jameshartig pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/cassandra-gocql-driver.git
The following commit(s) were added to refs/heads/trunk by this push:
new f400b491 CASSGO-6 Accept peers with empty rack
f400b491 is described below
commit f400b49138fe84ce980a2ea3e17d5d21232e284e
Author: James Hartig <ja...@apache.org>
AuthorDate: Thu Jun 19 00:32:15 2025 +0000
CASSGO-6 Accept peers with empty rack
This fixes #1706.
Patch by James Hartig for CASSGO-6; reviewed by João Reis for CASSGO-6
---
CHANGELOG.md | 1 +
cassandra_test.go | 105 +++++++++++++++++++++++++++++++++++++
control.go | 7 ++-
host_source.go | 120 +++++++++++++++++++++++++++++++-----------
host_source_test.go | 148 ++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 349 insertions(+), 32 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6262875b..227e729d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -62,6 +62,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Don't panic in MapExecuteBatchCAS if no `[applied]` column is returned (CASSGO-42)
- Fix deadlock in refresh debouncer stop (CASSGO-41)
- Endless query execution fix (CASSGO-50)
+- Accept peers with empty rack (CASSGO-6)
## [1.7.0] - 2024-09-23
diff --git a/cassandra_test.go b/cassandra_test.go
index 2613f17d..b92c3cd6 100644
--- a/cassandra_test.go
+++ b/cassandra_test.go
@@ -4013,3 +4013,108 @@ func TestRoutingKeyCacheUsesOverriddenKeyspace(t *testing.T) {
session.Query("DROP KEYSPACE IF EXISTS gocql_test_routing_key_cache").Exec()
}
+
+func TestHostInfoFromIter(t *testing.T) {
+ session := createSession(t)
+ defer session.Close()
+
+ err := createTable(session, `CREATE TABLE IF NOT EXISTS gocql_test.system_peers(
+ peer inet PRIMARY KEY,
+ data_center text,
+ host_id uuid,
+ preferred_ip inet,
+ rack text,
+ release_version text,
+ rpc_address inet,
+ schema_version uuid,
+ tokens set<text>
+ )`)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ id1 := MustRandomUUID()
+ err = session.Query(
+ "INSERT INTO gocql_test.system_peers (peer, data_center, host_id, rack, release_version, rpc_address, tokens) VALUES (?, ?, ?, ?, ?, ?, ?)",
+ net.ParseIP("10.0.0.1"),
+ "dc1",
+ id1,
+ "rack1",
+ "4.0.0",
+ net.ParseIP("10.0.0.2"),
+ []string{"0", "1"},
+ ).Exec()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ id2 := MustRandomUUID()
+ err = session.Query(
+ "INSERT INTO gocql_test.system_peers (peer, data_center, host_id, release_version, rpc_address, tokens) VALUES (?, ?, ?, ?, ?, ?)",
+ net.ParseIP("10.0.0.2"),
+ "dc2",
+ id2,
+ "4.0.0",
+ net.ParseIP("10.0.0.3"),
+ []string{"0", "1"},
+ ).Exec()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ iter := session.Query("SELECT * FROM gocql_test.system_peers WHERE data_center='dc1' ALLOW FILTERING").Iter()
+
+ h, err := session.hostInfoFromIter(iter, nil, 9042)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !isValidPeer(h) {
+ t.Errorf("expected %+v to be a valid peer", h)
+ }
+ if addr := h.ConnectAddressAndPort(); addr != "10.0.0.2:9042" {
+ t.Errorf("unexpected connect address: %s != '10.0.0.2:9042'", addr)
+ }
+ if h.HostID() != id1.String() {
+ t.Errorf("unexpected hostID %s != %s", h.HostID(), id1.String())
+ }
+ if h.Version().String() != "v4.0.0" {
+ t.Errorf("unexpected version %s != v4.0.0", h.Version().String())
+ }
+ if h.Rack() != "rack1" {
+ t.Errorf("unexpected rack %s != 'rack1'", h.Rack())
+ }
+ if h.DataCenter() != "dc1" {
+ t.Errorf("unexpected data center %s != 'dc1'", h.DataCenter())
+ }
+ if h.missingRack {
+ t.Errorf("unexpected missing rack")
+ }
+
+ iter = session.Query("SELECT * FROM gocql_test.system_peers WHERE data_center='dc2' ALLOW FILTERING").Iter()
+
+ h, err = session.hostInfoFromIter(iter, nil, 9042)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if isValidPeer(h) {
+ t.Errorf("expected %+v to be an invalid peer", h)
+ }
+ if addr := h.ConnectAddressAndPort(); addr != "10.0.0.3:9042" {
+ t.Errorf("unexpected connect address: %s != '10.0.0.3:9042'", addr)
+ }
+ if h.HostID() != id2.String() {
+ t.Errorf("unexpected hostID %s != %s", h.HostID(), id2.String())
+ }
+ if h.Version().String() != "v4.0.0" {
+ t.Errorf("unexpected version %s != v4.0.0", h.Version().String())
+ }
+ if h.Rack() != "" {
+ t.Errorf("unexpected rack %s != ''", h.Rack())
+ }
+ if h.DataCenter() != "dc2" {
+ t.Errorf("unexpected data center %s != 'dc2'", h.DataCenter())
+ }
+ if !h.missingRack {
+ t.Errorf("unexpected non-missing rack")
+ }
+}
diff --git a/control.go b/control.go
index fa30cd05..b521c41d 100644
--- a/control.go
+++ b/control.go
@@ -322,7 +322,12 @@ func (c *controlConn) setupConn(conn *Conn, sessionInit bool) error {
iter := conn.querySystemLocal(context.TODO())
host, err := c.session.hostInfoFromIter(iter, conn.host.connectAddress, conn.r.RemoteAddr().(*net.TCPAddr).Port)
if err != nil {
- return err
+ // just cleanup
+ iter.Close()
+ return fmt.Errorf("could not retrieve control host info: %w", err)
+ }
+ if host == nil {
+ return errors.New("could not retrieve control host info: query returned 0 rows")
}
var exists bool
diff --git a/host_source.go b/host_source.go
index fbc9134d..3ee3dd07 100644
--- a/host_source.go
+++ b/host_source.go
@@ -171,6 +171,7 @@ type HostInfo struct {
port int
dataCenter string
rack string
+ missingRack bool
hostId string
workload string
graph bool
@@ -413,8 +414,9 @@ func (h *HostInfo) update(from *HostInfo) {
if h.dataCenter == "" {
h.dataCenter = from.dataCenter
}
- if h.rack == "" {
+ if h.missingRack {
h.rack = from.rack
+ h.missingRack = from.missingRack
}
if h.hostId == "" {
h.hostId = from.hostId
@@ -530,7 +532,7 @@ func newHostInfoFromRow(s *Session, defaultAddr net.IP, defaultPort int, row map
const assertErrorMsg = "Assertion failed for %s, type was %T"
var ok bool
- host := &HostInfo{connectAddress: defaultAddr, port: defaultPort}
+ host := &HostInfo{connectAddress: defaultAddr, port: defaultPort, missingRack: true}
// Process all fields from the row
for key, value := range row {
@@ -541,14 +543,30 @@ func newHostInfoFromRow(s *Session, defaultAddr net.IP, defaultPort int, row map
return nil, fmt.Errorf(assertErrorMsg, "data_center", value)
}
case "rack":
- host.rack, ok = value.(string)
+ rack, ok := value.(*string)
if !ok {
- return nil, fmt.Errorf(assertErrorMsg, "rack", value)
+ if rack, ok := value.(string); !ok {
+ return nil, fmt.Errorf(assertErrorMsg, "rack", value)
+ } else {
+ host.rack = rack
+ host.missingRack = false
+ }
+ } else if rack != nil {
+ host.rack = *rack
+ host.missingRack = false
}
case "host_id":
hostId, ok := value.(UUID)
if !ok {
- return nil, fmt.Errorf(assertErrorMsg, "host_id", value)
+ if str, ok := value.(string); ok {
+ var err error
+ hostId, err = ParseUUID(str)
+ if err != nil {
+ return nil, fmt.Errorf("failed to parse host_id: %w", err)
+ }
+ } else {
+ return nil, fmt.Errorf(assertErrorMsg, "host_id", value)
+ }
}
host.hostId = hostId.String()
case "release_version":
@@ -560,7 +578,11 @@ func newHostInfoFromRow(s *Session, defaultAddr net.IP, defaultPort int, row map
case "peer":
ip, ok := value.(net.IP)
if !ok {
- return nil, fmt.Errorf(assertErrorMsg, "peer", value)
+ if str, ok := value.(string); ok {
+ ip = net.ParseIP(str)
+ } else {
+ return nil, fmt.Errorf(assertErrorMsg, "peer", value)
+ }
}
host.peer = ip
case "cluster_name":
@@ -576,31 +598,51 @@ func newHostInfoFromRow(s *Session, defaultAddr net.IP, defaultPort int, row map
case "broadcast_address":
ip, ok := value.(net.IP)
if !ok {
- return nil, fmt.Errorf(assertErrorMsg, "broadcast_address", value)
+ if str, ok := value.(string); ok {
+ ip = net.ParseIP(str)
+ } else {
+ return nil, fmt.Errorf(assertErrorMsg, "broadcast_address", value)
+ }
}
host.broadcastAddress = ip
case "preferred_ip":
ip, ok := value.(net.IP)
if !ok {
- return nil, fmt.Errorf(assertErrorMsg, "preferred_ip", value)
+ if str, ok := value.(string); ok {
+ ip = net.ParseIP(str)
+ } else {
+ return nil, fmt.Errorf(assertErrorMsg, "preferred_ip", value)
+ }
}
host.preferredIP = ip
case "rpc_address":
ip, ok := value.(net.IP)
if !ok {
- return nil, fmt.Errorf(assertErrorMsg, "rpc_address", value)
+ if str, ok := value.(string); ok {
+ ip = net.ParseIP(str)
+ } else {
+ return nil, fmt.Errorf(assertErrorMsg, "rpc_address", value)
+ }
}
host.rpcAddress = ip
case "native_address":
ip, ok := value.(net.IP)
if !ok {
- return nil, fmt.Errorf(assertErrorMsg, "native_address", value)
+ if str, ok := value.(string); ok {
+ ip = net.ParseIP(str)
+ } else {
+ return nil, fmt.Errorf(assertErrorMsg, "native_address", value)
+ }
}
host.rpcAddress = ip
case "listen_address":
ip, ok := value.(net.IP)
if !ok {
- return nil, fmt.Errorf(assertErrorMsg, "listen_address", value)
+ if str, ok := value.(string); ok {
+ ip = net.ParseIP(str)
+ } else {
+ return nil, fmt.Errorf(assertErrorMsg, "listen_address", value)
+ }
}
host.listenAddress = ip
case "native_port":
@@ -666,18 +708,23 @@ func newHostInfoFromRow(s *Session, defaultAddr net.IP, defaultPort int, row map
}
}
+// this will return nil, nil if there were no rows left in the Iter
func (s *Session) hostInfoFromIter(iter *Iter, connectAddress net.IP, defaultPort int) (*HostInfo, error) {
- rows, err := iter.SliceMap()
- if err != nil {
- // TODO(zariel): make typed error
- return nil, err
+ // TODO: switch this to a new iterator method once CASSGO-36 is solved
+ m := map[string]interface{}{
+ // we set rack to a double pointer so we can know if it's NULL or not since
+ // we need to be able to filter out NULL rack hosts but not empty string hosts
+ // see CASSGO-6
+ "rack": new(*string),
}
-
- if len(rows) == 0 {
- return nil, errors.New("query returned 0 rows")
+ if !iter.MapScan(m) {
+ if err := iter.Close(); err != nil {
+ return nil, err
+ }
+ return nil, nil
}
- host, err := s.newHostInfoFromMap(connectAddress, defaultPort, rows[0])
+ host, err := s.newHostInfoFromMap(connectAddress, defaultPort, m)
if err != nil {
return nil, err
}
@@ -700,8 +747,13 @@ func (r *ringDescriber) getLocalHostInfo() (*HostInfo, error) {
host, err := r.session.hostInfoFromIter(iter, nil, r.session.cfg.Port)
if err != nil {
+ // just cleanup
+ iter.Close()
return nil, fmt.Errorf("could not retrieve local host info: %w", err)
}
+ if host == nil {
+ return nil, errors.New("could not retrieve local host info: query returned 0 rows")
+ }
return host, nil
}
@@ -711,7 +763,6 @@ func (r *ringDescriber) getClusterPeerInfo(localHost *HostInfo) ([]*HostInfo, er
return nil, errNoControl
}
- var peers []*HostInfo
iter := r.session.control.withConnHost(func(ch *connHost) *Iter {
return ch.conn.querySystemPeers(context.TODO(), localHost.version)
})
@@ -720,18 +771,25 @@ func (r *ringDescriber) getClusterPeerInfo(localHost *HostInfo) ([]*HostInfo, er
return nil, errNoControl
}
- rows, err := iter.SliceMap()
- if err != nil {
- // TODO(zariel): make typed error
- return nil, fmt.Errorf("unable to fetch peer host info: %s", err)
- }
-
- for _, row := range rows {
+ var peers []*HostInfo
+ for {
// extract all available info about the peer
- host, err := r.session.newHostInfoFromMap(nil, r.session.cfg.Port, row)
+ host, err := r.session.hostInfoFromIter(iter, nil, r.session.cfg.Port)
if err != nil {
- return nil, err
- } else if !isValidPeer(host) {
+ // if the error came from the iterator then return it, otherwise ignore
+ // and warn
+ if iterErr := iter.Close(); iterErr != nil {
+ return nil, fmt.Errorf("unable to fetch peer host info: %s", iterErr)
+ }
+ // skip over peers that we couldn't parse
+ r.session.logger.Warning("Failed to parse peer this host will be ignored.", newLogFieldError("err", err))
+ continue
+ }
+ // if nil then none left
+ if host == nil {
+ break
+ }
+ if !isValidPeer(host) {
// If it's not a valid peer
r.session.logger.Warning("Found invalid peer "+
"likely due to a gossip or snitch issue, this host will be ignored.", newLogFieldStringer("host", host))
@@ -749,7 +807,7 @@ func isValidPeer(host *HostInfo) bool {
return !(len(host.RPCAddress()) == 0 ||
host.hostId == "" ||
host.dataCenter == "" ||
- host.rack == "" ||
+ host.missingRack ||
len(host.tokens) == 0)
}
diff --git a/host_source_test.go b/host_source_test.go
index e2454be5..07086e68 100644
--- a/host_source_test.go
+++ b/host_source_test.go
@@ -85,6 +85,153 @@ func TestCassVersionBefore(t *testing.T) {
}
+func TestNewHostInfoFromRow(t *testing.T) {
+ id := MustRandomUUID()
+ row := map[string]interface{}{
+ "broadcast_address": "10.0.0.1",
+ "listen_address": net.ParseIP("10.0.0.2"),
+ "rpc_address": net.ParseIP("10.0.0.3"),
+ "data_center": "dc",
+ "rack": "",
+ "host_id": id,
+ "release_version": "4.0.0",
+ "native_port": 9042,
+ "tokens": []string{"0", "1"},
+ }
+ s := &Session{}
+ h, err := newHostInfoFromRow(s, nil, 0, row)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !isValidPeer(h) {
+ t.Errorf("expected %+v to be a valid peer", h)
+ }
+ if addr := h.ConnectAddressAndPort(); addr != "10.0.0.3:9042" {
+ t.Errorf("unexpected connect address: %s != '10.0.0.3:9042'", addr)
+ }
+ if h.HostID() != id.String() {
+ t.Errorf("unexpected hostID %s != %s", h.HostID(), id.String())
+ }
+ if h.Version().String() != "v4.0.0" {
+ t.Errorf("unexpected version %s != v4.0.0", h.Version().String())
+ }
+ if h.Rack() != "" {
+ t.Errorf("unexpected rack %s != ''", h.Rack())
+ }
+ if h.DataCenter() != "dc" {
+ t.Errorf("unexpected data center %s != 'dc'", h.DataCenter())
+ }
+
+ row = map[string]interface{}{
+ "broadcast_address": "10.0.0.1",
+ "listen_address": net.ParseIP("10.0.0.2"),
+ "preferred_ip": "10.0.0.4",
+ "data_center": "dc",
+ "rack": "rack",
+ "host_id": id,
+ "release_version": "4.0.0",
+ "native_port": 9042,
+ "tokens": []string{"0", "1"},
+ }
+ h, err = newHostInfoFromRow(s, nil, 0, row)
+ if err != nil {
+ t.Fatal(err)
+ }
+ // missing rpc_address
+ if isValidPeer(h) {
+ t.Errorf("expected %+v to be an invalid peer", h)
+ }
+ if addr := h.ConnectAddressAndPort(); addr != "10.0.0.4:9042" {
+ t.Errorf("unexpected connect address: %s != '10.0.0.4:9042'", addr)
+ }
+ if h.Rack() != "rack" {
+ t.Errorf("unexpected rack %s != 'rack'", h.Rack())
+ }
+
+ row = map[string]interface{}{
+ "broadcast_address": "10.0.0.1",
+ "data_center": "dc",
+ "rack": "rack",
+ "host_id": id,
+ "native_port": 9042,
+ "tokens": []string{"0", "1"},
+ }
+ h, err = newHostInfoFromRow(s, nil, 0, row)
+ if err != nil {
+ t.Fatal(err)
+ }
+ // missing rpc_address
+ if isValidPeer(h) {
+ t.Errorf("expected %+v to be an invalid peer", h)
+ }
+ if addr := h.ConnectAddressAndPort(); addr != "10.0.0.1:9042" {
+ t.Errorf("unexpected connect address: %s != '10.0.0.1:9042'", addr)
+ }
+
+ row = map[string]interface{}{
+ "rpc_address": "10.0.0.2",
+ "data_center": "dc",
+ "rack": "rack",
+ "host_id": id,
+ "tokens": []string{"0", "1"},
+ }
+ s = &Session{
+ cfg: ClusterConfig{
+ AddressTranslator: AddressTranslatorFunc(func(addr net.IP, port int) (net.IP, int) {
+ if !addr.Equal(net.ParseIP("10.0.0.2")) {
+ t.Errorf("unexpected ip sent to translator: %s != '10.0.0.2'", addr.String())
+ }
+ if port != 9042 {
+ t.Errorf("unexpected port sent to translator: %d != 9042", port)
+ }
+ return net.ParseIP("10.0.0.5"), 9043
+ }),
+ },
+ logger: &defaultLogger{},
+ }
+ h, err = newHostInfoFromRow(s, nil, 9042, row)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !isValidPeer(h) {
+ t.Errorf("expected %+v to be a valid peer", h)
+ }
+ if addr := h.ConnectAddressAndPort(); addr != "10.0.0.5:9043" {
+ t.Errorf("unexpected connect address: %s != '10.0.0.5:9043'", addr)
+ }
+
+ // missing rack
+ row = map[string]interface{}{
+ "rpc_address": "10.0.0.2",
+ "data_center": "dc",
+ "host_id": id,
+ "tokens": []string{"0", "1"},
+ }
+ h, err = newHostInfoFromRow(nil, nil, 9042, row)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if isValidPeer(h) {
+ t.Errorf("expected %+v to be an invalid peer", h)
+ }
+ if h.Rack() != "" {
+ t.Errorf("unexpected rack %s != ''", h.Rack())
+ }
+
+ // inavlid ip
+ row = map[string]interface{}{
+ "rpc_address": net.ParseIP("0.0.0.0"),
+ "data_center": "dc",
+ "rack": "rack",
+ "host_id": id,
+ "tokens": []string{"0", "1"},
+ }
+ _, err = newHostInfoFromRow(nil, nil, 9042, row)
+ if err == nil {
+ t.Error("expected invalid ip to error")
+ }
+}
+
func TestIsValidPeer(t *testing.T) {
host := &HostInfo{
rpcAddress: net.ParseIP("0.0.0.0"),
@@ -99,6 +246,7 @@ func TestIsValidPeer(t *testing.T) {
}
host.rack = ""
+ host.missingRack = true
if isValidPeer(host) {
t.Errorf("expected %+v to NOT be a valid peer", host)
}
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cassandra.apache.org
For additional commands, e-mail: commits-help@cassandra.apache.org