Skip to content

Commit be35a5b

Browse files
committed
Add a way to create HostInfo objects for testing purposes
It is not possible to test implementations of public gocql interfaces such as HostFilter because it requires creating HostInfo objects (HostInfo has private fields). With this change, it is now possible to create HostInfo objects from system.local / system.peers rows using NewTestHostInfoFromRow(). Patch by João Reis; reviewed by James Hartig for CASSGO-71
1 parent eda74b6 commit be35a5b

File tree

6 files changed

+97
-41
lines changed

6 files changed

+97
-41
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1313
- Cleanup of deprecated elements (CASSGO-12)
1414
- Remove global NewBatch function (CASSGO-15)
1515
- Remove deprecated global logger (CASSGO-24)
16+
- HostInfo.SetHostID is no longer exported (CASSGO-71)
1617

1718
### Added
1819

@@ -23,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2324
Query.SetKeyspace(), Query.WithNowInSeconds(), Batch.SetKeyspace(), Batch.WithNowInSeconds() (CASSGO-1)
2425
- Externally-defined type registration (CASSGO-43)
2526
- Add Query and Batch to ObservedQuery and ObservedBatch (CASSGO-73)
27+
- Add way to create HostInfo objects for testing purposes (CASSGO-71)
2628

2729
### Changed
2830

conn.go

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1938,11 +1938,7 @@ func (c *Conn) awaitSchemaAgreement(ctx context.Context) (err error) {
19381938

19391939
for _, row := range rows {
19401940
var host *HostInfo
1941-
host, err = NewHostInfo(c.host.ConnectAddress(), c.session.cfg.Port)
1942-
if err != nil {
1943-
goto cont
1944-
}
1945-
host, err = c.session.hostInfoFromMap(row, host)
1941+
host, err = c.session.newHostInfoFromMap(c.host.ConnectAddress(), c.session.cfg.Port, row)
19461942
if err != nil {
19471943
goto cont
19481944
}

control.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ func hostInfo(addr string, defaultPort int) ([]*HostInfo, error) {
148148

149149
// Check if host is a literal IP address
150150
if ip := net.ParseIP(host); ip != nil {
151-
h, err := NewHostInfo(ip, port)
151+
h, err := NewHostInfoFromAddrPort(ip, port)
152152
if err != nil {
153153
return nil, err
154154
}
@@ -178,7 +178,7 @@ func hostInfo(addr string, defaultPort int) ([]*HostInfo, error) {
178178
}
179179

180180
for _, ip := range ips {
181-
h, err := NewHostInfo(ip, port)
181+
h, err := NewHostInfoFromAddrPort(ip, port)
182182
if err != nil {
183183
return nil, err
184184
}

host_source.go

Lines changed: 64 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,8 @@ func (c cassVersion) nodeUpDelay() time.Duration {
155155
return 10 * time.Second
156156
}
157157

158+
// HostInfo represents a server Host/Node. You can create a HostInfo object with either NewHostInfoFromAddrPort or
159+
// NewTestHostInfoFromRow.
158160
type HostInfo struct {
159161
// TODO(zariel): reduce locking maybe, not all values will change, but to ensure
160162
// that we are thread safe use a mutex to access all fields.
@@ -181,9 +183,12 @@ type HostInfo struct {
181183
tokens []string
182184
}
183185

184-
// NewHostInfo creates HostInfo with provided connectAddress and port.
186+
// NewHostInfoFromAddrPort creates HostInfo with provided connectAddress and port.
185187
// It returns an error if addr is invalid.
186-
func NewHostInfo(addr net.IP, port int) (*HostInfo, error) {
188+
//
189+
// If you're looking for a way to create a HostInfo object with more than just an address and port for
190+
// testing purposes then you can use NewTestHostInfoFromRow
191+
func NewHostInfoFromAddrPort(addr net.IP, port int) (*HostInfo, error) {
187192
if !validIpAddr(addr) {
188193
return nil, errors.New("invalid host address")
189194
}
@@ -251,7 +256,7 @@ func (h *HostInfo) nodeToNodeAddress() net.IP {
251256
return net.IPv4zero
252257
}
253258

254-
// Returns the address that should be used to connect to the host.
259+
// ConnectAddress Returns the address that should be used to connect to the host.
255260
// If you wish to override this, use an AddressTranslator
256261
func (h *HostInfo) ConnectAddress() net.IP {
257262
h.mu.RLock()
@@ -305,7 +310,7 @@ func (h *HostInfo) HostID() string {
305310
return h.hostId
306311
}
307312

308-
func (h *HostInfo) SetHostID(hostID string) {
313+
func (h *HostInfo) setHostID(hostID string) {
309314
h.mu.Lock()
310315
defer h.mu.Unlock()
311316
h.hostId = hostID
@@ -492,17 +497,42 @@ func checkSystemSchema(control *controlConn) (bool, error) {
492497
return true, nil
493498
}
494499

495-
func (s *Session) newHostInfoFromMap(addr net.IP, port int, row map[string]interface{}) (*HostInfo, error) {
496-
return s.hostInfoFromMap(row, &HostInfo{connectAddress: addr, port: port})
497-
}
498-
499500
// Given a map that represents a row from either system.local or system.peers
500501
// return as much information as we can in *HostInfo
501-
func (s *Session) hostInfoFromMap(row map[string]interface{}, host *HostInfo) (*HostInfo, error) {
502+
func (s *Session) newHostInfoFromMap(addr net.IP, port int, row map[string]interface{}) (*HostInfo, error) {
503+
return newHostInfoFromRow(s, addr, port, row)
504+
}
505+
506+
// NewTestHostInfoFromRow creates a new HostInfo object from a system.peers or system.local row. The port
507+
// defaults to 9042.
508+
//
509+
// You can create a HostInfo object for testing purposes using this function:
510+
//
511+
// Example usage:
512+
//
513+
// row := map[string]interface{}{
514+
// "broadcast_address": net.ParseIP("10.0.0.1"),
515+
// "listen_address": net.ParseIP("10.0.0.1"),
516+
// "rpc_address": net.ParseIP("10.0.0.1"),
517+
// "peer": net.ParseIP("10.0.0.1"), // system.peers only
518+
// "data_center": "dc1",
519+
// "rack": "rack1",
520+
// "host_id": MustRandomUUID(), // can also use ParseUUID("550e8400-e29b-41d4-a716-446655440000")
521+
// "release_version": "4.0.0",
522+
// "native_port": 9042,
523+
// }
524+
// host, err := NewTestHostInfoFromRow(row)
525+
func NewTestHostInfoFromRow(row map[string]interface{}) (*HostInfo, error) {
526+
return newHostInfoFromRow(nil, nil, 9042, row)
527+
}
528+
529+
func newHostInfoFromRow(s *Session, defaultAddr net.IP, defaultPort int, row map[string]interface{}) (*HostInfo, error) {
502530
const assertErrorMsg = "Assertion failed for %s, type was %T"
503531
var ok bool
504532

505-
// Default to our connected port if the cluster doesn't have port information
533+
host := &HostInfo{connectAddress: defaultAddr, port: defaultPort}
534+
535+
// Process all fields from the row
506536
for key, value := range row {
507537
switch key {
508538
case "data_center":
@@ -606,18 +636,34 @@ func (s *Session) hostInfoFromMap(row map[string]interface{}, host *HostInfo) (*
606636
}
607637
host.schemaVersion = schemaVersion.String()
608638
}
609-
// TODO(thrawn01): Add 'port'? once CASSANDRA-7544 is complete
610-
// Not sure what the port field will be called until the JIRA issue is complete
611639
}
612640

613-
ip, port := s.cfg.translateAddressPort(host.ConnectAddress(), host.port, s.logger)
614-
if !validIpAddr(ip) {
615-
return nil, fmt.Errorf("invalid host address (before translation: %v:%v, after translation: %v:%v)", host.ConnectAddress(), host.port, ip.String(), port)
641+
// Determine the connect address from available addresses
642+
if validIpAddr(host.rpcAddress) {
643+
host.connectAddress = host.rpcAddress
644+
} else if validIpAddr(host.preferredIP) {
645+
host.connectAddress = host.preferredIP
646+
} else if validIpAddr(host.broadcastAddress) {
647+
host.connectAddress = host.broadcastAddress
648+
} else if validIpAddr(host.peer) {
649+
host.connectAddress = host.peer
616650
}
617-
host.connectAddress = ip
618-
host.port = port
619651

620-
return host, nil
652+
if s != nil && s.cfg.AddressTranslator != nil {
653+
ip, port := s.cfg.translateAddressPort(host.ConnectAddress(), host.port, s.logger)
654+
if !validIpAddr(ip) {
655+
return nil, fmt.Errorf("invalid host address (before translation: %v:%v, after translation: %v:%v)", host.ConnectAddress(), host.port, ip.String(), port)
656+
}
657+
host.connectAddress = ip
658+
host.port = port
659+
}
660+
661+
if validIpAddr(host.connectAddress) {
662+
host.hostname = host.connectAddress.String()
663+
return host, nil
664+
} else {
665+
return nil, errors.New("invalid host address")
666+
}
621667
}
622668

623669
func (s *Session) hostInfoFromIter(iter *Iter, connectAddress net.IP, defaultPort int) (*HostInfo, error) {

hostpool/hostpool_test.go

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -35,21 +35,33 @@ func TestHostPolicy_HostPool(t *testing.T) {
3535
policy := HostPoolHostPolicy(hostpool.New(nil))
3636

3737
//hosts := []*gocql.HostInfo{
38-
// {hostId: "0", connectAddress: net.IPv4(10, 0, 0, 0)},
39-
// {hostId: "1", connectAddress: net.IPv4(10, 0, 0, 1)},
38+
// {hostId: "f1935733-af5f-4995-bd1e-94a7a3e67bfd", connectAddress: net.ParseIP("10.0.0.0")},
39+
// {hostId: "93ca4489-b322-4fda-b5a5-12d4436271df", connectAddress: net.ParseIP("10.0.0.1")},
4040
//}
41+
firstHostId, err1 := gocql.ParseUUID("f1935733-af5f-4995-bd1e-94a7a3e67bfd")
42+
secondHostId, err2 := gocql.ParseUUID("93ca4489-b322-4fda-b5a5-12d4436271df")
4143

42-
firstHost, err := gocql.NewHostInfo(net.IPv4(10, 0, 0, 0), 9042)
44+
if err1 != nil || err2 != nil {
45+
t.Fatal(err1, err2)
46+
}
47+
48+
firstHost, err := gocql.NewTestHostInfoFromRow(
49+
map[string]interface{}{
50+
"peer": net.ParseIP("10.0.0.0"),
51+
"native_port": 9042,
52+
"host_id": firstHostId})
4353
if err != nil {
4454
t.Errorf("Error creating first host: %v", err)
4555
}
46-
firstHost.SetHostID("0")
4756

48-
secHost, err := gocql.NewHostInfo(net.IPv4(10, 0, 0, 1), 9042)
57+
secHost, err := gocql.NewTestHostInfoFromRow(
58+
map[string]interface{}{
59+
"peer": net.ParseIP("10.0.0.1"),
60+
"native_port": 9042,
61+
"host_id": secondHostId})
4962
if err != nil {
5063
t.Errorf("Error creating second host: %v", err)
5164
}
52-
secHost.SetHostID("1")
5365
hosts := []*gocql.HostInfo{firstHost, secHost}
5466
// Using set host to control the ordering of the hosts as calling "AddHost" iterates the map
5567
// which will result in an unpredictable ordering
@@ -59,26 +71,26 @@ func TestHostPolicy_HostPool(t *testing.T) {
5971
// interleaved iteration should always increment the host
6072
iter := policy.Pick(nil)
6173
actualA := iter()
62-
if actualA.Info().HostID() != "0" {
63-
t.Errorf("Expected hosts[0] but was hosts[%s]", actualA.Info().HostID())
74+
if actualA.Info().HostID() != firstHostId.String() {
75+
t.Errorf("Expected first host id but was %s", actualA.Info().HostID())
6476
}
6577
actualA.Mark(nil)
6678

6779
actualB := iter()
68-
if actualB.Info().HostID() != "1" {
69-
t.Errorf("Expected hosts[1] but was hosts[%s]", actualB.Info().HostID())
80+
if actualB.Info().HostID() != secondHostId.String() {
81+
t.Errorf("Expected second host id but was %s", actualB.Info().HostID())
7082
}
7183
actualB.Mark(fmt.Errorf("error"))
7284

7385
actualC := iter()
74-
if actualC.Info().HostID() != "0" {
75-
t.Errorf("Expected hosts[0] but was hosts[%s]", actualC.Info().HostID())
86+
if actualC.Info().HostID() != firstHostId.String() {
87+
t.Errorf("Expected first host id but was %s", actualC.Info().HostID())
7688
}
7789
actualC.Mark(nil)
7890

7991
actualD := iter()
80-
if actualD.Info().HostID() != "0" {
81-
t.Errorf("Expected hosts[0] but was hosts[%s]", actualD.Info().HostID())
92+
if actualD.Info().HostID() != firstHostId.String() {
93+
t.Errorf("Expected first host id but was %s", actualD.Info().HostID())
8294
}
8395
actualD.Mark(nil)
8496
}

session.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ func (s *Session) init() error {
268268
// by internal logic.
269269
// Associate random UUIDs here with all hosts missing this information.
270270
if len(host.HostID()) == 0 {
271-
host.SetHostID(MustRandomUUID().String())
271+
host.setHostID(MustRandomUUID().String())
272272
}
273273
}
274274

@@ -2143,7 +2143,7 @@ type ObservedQuery struct {
21432143
// Rows is not used in batch queries and remains at the default value
21442144
Rows int
21452145

2146-
// Host is the informations about the host that performed the query
2146+
// Host is the information about the host that performed the query
21472147
Host *HostInfo
21482148

21492149
// The metrics per this host

0 commit comments

Comments
 (0)