Skip to content

Commit aa8e727

Browse files
committed
issue-1774: Function to retrieve info about client connections was added
1 parent 953e0df commit aa8e727

File tree

2 files changed

+174
-10
lines changed

2 files changed

+174
-10
lines changed

session.go

Lines changed: 100 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -800,6 +800,95 @@ func (s *Session) MapExecuteBatchCAS(batch *Batch, dest map[string]interface{})
800800
return applied, iter, iter.err
801801
}
802802

803+
// connectionType is a custom type that represents the different stages
804+
// of a client connection in a Cassandra cluster. It is used to filter and categorize
805+
// connections based on their current state.
806+
type connectionType string
807+
808+
const (
809+
Ready connectionType = "ready"
810+
Connecting connectionType = "connecting"
811+
Idle connectionType = "idle"
812+
Closed connectionType = "closed"
813+
Failed connectionType = "failed"
814+
)
815+
816+
// ClientConnection represents a client connection to a Cassandra node. It holds detailed
817+
// information about the connection, including the client address, connection stage, driver details,
818+
// and various configuration options.
819+
type ClientConnection struct {
820+
Address string
821+
Port int
822+
ConnectionStage string
823+
DriverName string
824+
DriverVersion string
825+
Hostname string
826+
KeyspaceName *string
827+
ProtocolVersion int
828+
RequestCount int
829+
SSLCipherSuite *string
830+
SSLEnabled bool
831+
SSLProtocol *string
832+
Username string
833+
}
834+
835+
// RetrieveClientConnections retrieves a list of client connections from the
836+
// `system_views.clients` table based on the specified connection type. The function
837+
// queries the Cassandra database for connections with a given `connection_stage` and
838+
// scans the results into a slice of `ClientConnection` structs. It handles nullable
839+
// fields and returns the list of connections or an error if the operation fails.
840+
func (s *Session) RetrieveClientConnections(connectionType connectionType) ([]*ClientConnection, error) {
841+
const stmt = `
842+
SELECT address, port, connection_stage, driver_name, driver_version,
843+
hostname, keyspace_name, protocol_version, request_count,
844+
ssl_cipher_suite, ssl_enabled, ssl_protocol, username
845+
FROM system_views.clients
846+
WHERE connection_stage = ?`
847+
848+
iter := s.control.query(stmt, connectionType)
849+
if iter.NumRows() == 0 {
850+
return nil, ErrConnectionsDoNotExist
851+
}
852+
defer iter.Close()
853+
854+
var connections []*ClientConnection
855+
for {
856+
conn := &ClientConnection{}
857+
858+
// Variables to hold nullable fields
859+
var keyspaceName, sslCipherSuite, sslProtocol *string
860+
861+
if !iter.Scan(
862+
&conn.Address,
863+
&conn.Port,
864+
&conn.ConnectionStage,
865+
&conn.DriverName,
866+
&conn.DriverVersion,
867+
&conn.Hostname,
868+
&keyspaceName,
869+
&conn.ProtocolVersion,
870+
&conn.RequestCount,
871+
&sslCipherSuite,
872+
&conn.SSLEnabled,
873+
&sslProtocol,
874+
&conn.Username,
875+
) {
876+
if err := iter.Close(); err != nil {
877+
return nil, err
878+
}
879+
break
880+
}
881+
882+
conn.KeyspaceName = keyspaceName
883+
conn.SSLCipherSuite = sslCipherSuite
884+
conn.SSLProtocol = sslProtocol
885+
886+
connections = append(connections, conn)
887+
}
888+
889+
return connections, nil
890+
}
891+
803892
type hostMetrics struct {
804893
// Attempts is count of how many times this query has been attempted for this host.
805894
// An attempt is either a retry or fetching next page of results.
@@ -2279,16 +2368,17 @@ func (e Error) Error() string {
22792368
}
22802369

22812370
var (
2282-
ErrNotFound = errors.New("not found")
2283-
ErrUnavailable = errors.New("unavailable")
2284-
ErrUnsupported = errors.New("feature not supported")
2285-
ErrTooManyStmts = errors.New("too many statements")
2286-
ErrUseStmt = errors.New("use statements aren't supported. Please see https://github.com/apache/cassandra-gocql-driver for explanation.")
2287-
ErrSessionClosed = errors.New("session has been closed")
2288-
ErrNoConnections = errors.New("gocql: no hosts available in the pool")
2289-
ErrNoKeyspace = errors.New("no keyspace provided")
2290-
ErrKeyspaceDoesNotExist = errors.New("keyspace does not exist")
2291-
ErrNoMetadata = errors.New("no metadata available")
2371+
ErrNotFound = errors.New("not found")
2372+
ErrUnavailable = errors.New("unavailable")
2373+
ErrUnsupported = errors.New("feature not supported")
2374+
ErrTooManyStmts = errors.New("too many statements")
2375+
ErrUseStmt = errors.New("use statements aren't supported. Please see https://github.com/gocql/gocql for explanation.")
2376+
ErrSessionClosed = errors.New("session has been closed")
2377+
ErrNoConnections = errors.New("gocql: no hosts available in the pool")
2378+
ErrNoKeyspace = errors.New("no keyspace provided")
2379+
ErrKeyspaceDoesNotExist = errors.New("keyspace does not exist")
2380+
ErrConnectionsDoNotExist = errors.New("connections do not exist")
2381+
ErrNoMetadata = errors.New("no metadata available")
22922382
)
22932383

22942384
type ErrProtocol struct{ error }

session_test.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,3 +347,77 @@ func TestIsUseStatement(t *testing.T) {
347347
}
348348
}
349349
}
350+
351+
func TestRetrieveClientConnections(t *testing.T) {
352+
testCases := []struct {
353+
name string
354+
connectionType connectionType
355+
expectedResult []*ClientConnection
356+
expectError bool
357+
}{
358+
{
359+
name: "Valid ready connections",
360+
connectionType: Ready,
361+
expectedResult: []*ClientConnection{
362+
{
363+
Address: "127.0.0.1",
364+
Port: 9042,
365+
ConnectionStage: "ready",
366+
DriverName: "gocql",
367+
DriverVersion: "v1.0.0",
368+
Hostname: "localhost",
369+
KeyspaceName: nil,
370+
ProtocolVersion: 4,
371+
RequestCount: 10,
372+
SSLCipherSuite: nil,
373+
SSLEnabled: true,
374+
SSLProtocol: nil,
375+
Username: "user1",
376+
},
377+
},
378+
expectError: false,
379+
},
380+
{
381+
name: "No connections found",
382+
connectionType: Closed,
383+
expectedResult: nil,
384+
expectError: true,
385+
},
386+
}
387+
388+
for _, tc := range testCases {
389+
t.Run(tc.name, func(t *testing.T) {
390+
session := &Session{
391+
control: &controlConn{},
392+
}
393+
394+
results, err := session.RetrieveClientConnections(tc.connectionType)
395+
396+
if tc.expectError {
397+
if err == nil {
398+
t.Fatalf("expected an error but got none")
399+
}
400+
} else {
401+
if err != nil {
402+
t.Fatalf("unexpected error: %v", err)
403+
}
404+
if !compareClientConnections(results, tc.expectedResult) {
405+
t.Fatalf("expected result %+v, got %+v", tc.expectedResult, results)
406+
}
407+
}
408+
})
409+
}
410+
}
411+
412+
// Helper function to compare two slices of ClientConnection pointers
413+
func compareClientConnections(a, b []*ClientConnection) bool {
414+
if len(a) != len(b) {
415+
return false
416+
}
417+
for i := range a {
418+
if *a[i] != *b[i] {
419+
return false
420+
}
421+
}
422+
return true
423+
}

0 commit comments

Comments
 (0)