Skip to content

Commit b2e98d5

Browse files
committed
issue-1774: Function to retrieve info about client connections was added
1 parent 34fdeeb commit b2e98d5

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
@@ -780,6 +780,95 @@ func (s *Session) MapExecuteBatchCAS(batch *Batch, dest map[string]interface{})
780780
return applied, iter, iter.err
781781
}
782782

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

22612350
var (
2262-
ErrNotFound = errors.New("not found")
2263-
ErrUnavailable = errors.New("unavailable")
2264-
ErrUnsupported = errors.New("feature not supported")
2265-
ErrTooManyStmts = errors.New("too many statements")
2266-
ErrUseStmt = errors.New("use statements aren't supported. Please see https://github.com/gocql/gocql for explanation.")
2267-
ErrSessionClosed = errors.New("session has been closed")
2268-
ErrNoConnections = errors.New("gocql: no hosts available in the pool")
2269-
ErrNoKeyspace = errors.New("no keyspace provided")
2270-
ErrKeyspaceDoesNotExist = errors.New("keyspace does not exist")
2271-
ErrNoMetadata = errors.New("no metadata available")
2351+
ErrNotFound = errors.New("not found")
2352+
ErrUnavailable = errors.New("unavailable")
2353+
ErrUnsupported = errors.New("feature not supported")
2354+
ErrTooManyStmts = errors.New("too many statements")
2355+
ErrUseStmt = errors.New("use statements aren't supported. Please see https://github.com/gocql/gocql for explanation.")
2356+
ErrSessionClosed = errors.New("session has been closed")
2357+
ErrNoConnections = errors.New("gocql: no hosts available in the pool")
2358+
ErrNoKeyspace = errors.New("no keyspace provided")
2359+
ErrKeyspaceDoesNotExist = errors.New("keyspace does not exist")
2360+
ErrConnectionsDoNotExist = errors.New("connections do not exist")
2361+
ErrNoMetadata = errors.New("no metadata available")
22722362
)
22732363

22742364
type ErrProtocol struct{ error }

session_test.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,3 +323,77 @@ func TestIsUseStatement(t *testing.T) {
323323
}
324324
}
325325
}
326+
327+
func TestRetrieveClientConnections(t *testing.T) {
328+
testCases := []struct {
329+
name string
330+
connectionType connectionType
331+
expectedResult []*ClientConnection
332+
expectError bool
333+
}{
334+
{
335+
name: "Valid ready connections",
336+
connectionType: Ready,
337+
expectedResult: []*ClientConnection{
338+
{
339+
Address: "127.0.0.1",
340+
Port: 9042,
341+
ConnectionStage: "ready",
342+
DriverName: "gocql",
343+
DriverVersion: "v1.0.0",
344+
Hostname: "localhost",
345+
KeyspaceName: nil,
346+
ProtocolVersion: 4,
347+
RequestCount: 10,
348+
SSLCipherSuite: nil,
349+
SSLEnabled: true,
350+
SSLProtocol: nil,
351+
Username: "user1",
352+
},
353+
},
354+
expectError: false,
355+
},
356+
{
357+
name: "No connections found",
358+
connectionType: Closed,
359+
expectedResult: nil,
360+
expectError: true,
361+
},
362+
}
363+
364+
for _, tc := range testCases {
365+
t.Run(tc.name, func(t *testing.T) {
366+
session := &Session{
367+
control: &controlConn{},
368+
}
369+
370+
results, err := session.RetrieveClientConnections(tc.connectionType)
371+
372+
if tc.expectError {
373+
if err == nil {
374+
t.Fatalf("expected an error but got none")
375+
}
376+
} else {
377+
if err != nil {
378+
t.Fatalf("unexpected error: %v", err)
379+
}
380+
if !compareClientConnections(results, tc.expectedResult) {
381+
t.Fatalf("expected result %+v, got %+v", tc.expectedResult, results)
382+
}
383+
}
384+
})
385+
}
386+
}
387+
388+
// Helper function to compare two slices of ClientConnection pointers
389+
func compareClientConnections(a, b []*ClientConnection) bool {
390+
if len(a) != len(b) {
391+
return false
392+
}
393+
for i := range a {
394+
if *a[i] != *b[i] {
395+
return false
396+
}
397+
}
398+
return true
399+
}

0 commit comments

Comments
 (0)