github.com/datastax/go-cassandra-native-protocol@v0.0.0-20220706104457-5e8aad05cf90/client/system.go (about)

     1  // Copyright 2020 DataStax
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package client
    16  
    17  import (
    18  	"bytes"
    19  	"net"
    20  	"strings"
    21  
    22  	"github.com/rs/zerolog/log"
    23  
    24  	"github.com/datastax/go-cassandra-native-protocol/datatype"
    25  	"github.com/datastax/go-cassandra-native-protocol/frame"
    26  	"github.com/datastax/go-cassandra-native-protocol/message"
    27  	"github.com/datastax/go-cassandra-native-protocol/primitive"
    28  )
    29  
    30  // Creates a new RequestHandler to handle queries to system tables (system.local and system.peers).
    31  func NewSystemTablesHandler(cluster string, datacenter string) RequestHandler {
    32  	return func(request *frame.Frame, conn *CqlServerConnection, _ RequestHandlerContext) (response *frame.Frame) {
    33  		if query, ok := request.Body.Message.(*message.Query); ok {
    34  			q := strings.TrimSpace(strings.ToLower(query.Query))
    35  			q = strings.Join(strings.Fields(q), " ") // remove extra whitespace
    36  			if strings.HasPrefix(q, "select * from system.local") {
    37  				log.Debug().Msgf("%v: [system tables handler]: returning full system.local", conn)
    38  				response = fullSystemLocal(cluster, datacenter, request, conn)
    39  			} else if strings.HasPrefix(q, "select schema_version from system.local") {
    40  				log.Debug().Msgf("%v: [system tables handler]: returning schema_version", conn)
    41  				response = schemaVersion(request)
    42  			} else if strings.HasPrefix(q, "select cluster_name from system.local") {
    43  				log.Debug().Msgf("%v: [system tables handler]: returning cluster_name", conn)
    44  				response = clusterName(cluster, request)
    45  			} else if strings.Contains(q, "from system.peers") {
    46  				log.Debug().Msgf("%v: [system tables handler]: returning empty system.peers", conn)
    47  				response = emptySystemPeers(request)
    48  			}
    49  		}
    50  		return
    51  	}
    52  }
    53  
    54  var (
    55  	keyColumn              = &message.ColumnMetadata{Keyspace: "system", Table: "local", Name: "key", Type: datatype.Varchar}
    56  	broadcastAddressColumn = &message.ColumnMetadata{Keyspace: "system", Table: "local", Name: "broadcast_address", Type: datatype.Inet}
    57  	clusterNameColumn      = &message.ColumnMetadata{Keyspace: "system", Table: "local", Name: "cluster_name", Type: datatype.Varchar}
    58  	cqlVersionColumn       = &message.ColumnMetadata{Keyspace: "system", Table: "local", Name: "cql_version", Type: datatype.Varchar}
    59  	datacenterColumn       = &message.ColumnMetadata{Keyspace: "system", Table: "local", Name: "data_center", Type: datatype.Varchar}
    60  	hostIdColumn           = &message.ColumnMetadata{Keyspace: "system", Table: "local", Name: "host_id", Type: datatype.Uuid}
    61  	listenAddressColumn    = &message.ColumnMetadata{Keyspace: "system", Table: "local", Name: "listen_address", Type: datatype.Inet}
    62  	partitionerColumn      = &message.ColumnMetadata{Keyspace: "system", Table: "local", Name: "partitioner", Type: datatype.Varchar}
    63  	rackColumn             = &message.ColumnMetadata{Keyspace: "system", Table: "local", Name: "rack", Type: datatype.Varchar}
    64  	releaseVersionColumn   = &message.ColumnMetadata{Keyspace: "system", Table: "local", Name: "release_version", Type: datatype.Varchar}
    65  	rpcAddressColumn       = &message.ColumnMetadata{Keyspace: "system", Table: "local", Name: "rpc_address", Type: datatype.Inet}
    66  	schemaVersionColumn    = &message.ColumnMetadata{Keyspace: "system", Table: "local", Name: "schema_version", Type: datatype.Uuid}
    67  	tokensColumn           = &message.ColumnMetadata{Keyspace: "system", Table: "local", Name: "tokens", Type: datatype.NewSet(datatype.Varchar)}
    68  )
    69  
    70  // These columns are a subset of the total columns returned by OSS C* 3.11.2, and contain all the information that
    71  // drivers need in order to establish the cluster topology and determine its characteristics.
    72  var systemLocalColumns = []*message.ColumnMetadata{
    73  	keyColumn,
    74  	broadcastAddressColumn,
    75  	clusterNameColumn,
    76  	cqlVersionColumn,
    77  	datacenterColumn,
    78  	hostIdColumn,
    79  	listenAddressColumn,
    80  	partitionerColumn,
    81  	rackColumn,
    82  	releaseVersionColumn,
    83  	rpcAddressColumn,
    84  	schemaVersionColumn,
    85  	tokensColumn,
    86  }
    87  
    88  var (
    89  	keyValue            = message.Column("local")
    90  	cqlVersionValue     = message.Column("3.4.4")
    91  	hostIdValue         = message.Column{0xC0, 0xD1, 0xD2, 0x1E, 0xBB, 0x01, 0x41, 0x96, 0x86, 0xDB, 0xBC, 0x31, 0x7B, 0xC1, 0x79, 0x6A}
    92  	partitionerValue    = message.Column("org.apache.cassandra.dht.Murmur3Partitioner")
    93  	rackValue           = message.Column("rack1")
    94  	releaseVersionValue = message.Column("3.11.2")
    95  	schemaVersionValue  = message.Column{0xC0, 0xD1, 0xD2, 0x1E, 0xBB, 0x01, 0x41, 0x96, 0x86, 0xDB, 0xBC, 0x31, 0x7B, 0xC1, 0x79, 0x6A}
    96  )
    97  
    98  func systemLocalRow(cluster string, datacenter string, addr net.Addr, version primitive.ProtocolVersion) message.Row {
    99  	addrBuf := &bytes.Buffer{}
   100  	// TODO replace the serialization code below when data type serialization is implemented
   101  	inetAddr := addr.(*net.TCPAddr).IP
   102  	if inetAddr.To4() != nil {
   103  		addrBuf.Write(inetAddr.To4())
   104  	} else {
   105  		addrBuf.Write(inetAddr)
   106  	}
   107  	// emulate {'-9223372036854775808'} (entire ring)
   108  	tokensBuf := &bytes.Buffer{}
   109  	if version >= primitive.ProtocolVersion3 {
   110  		_ = primitive.WriteInt(1, tokensBuf)
   111  		_ = primitive.WriteInt(int32(len("-9223372036854775808")), tokensBuf)
   112  	} else {
   113  		_ = primitive.WriteShort(1, tokensBuf)
   114  		_ = primitive.WriteShort(uint16(len("-9223372036854775808")), tokensBuf)
   115  	}
   116  	tokensBuf.WriteString("-9223372036854775808")
   117  	return message.Row{
   118  		keyValue,
   119  		addrBuf.Bytes(),
   120  		message.Column(cluster),
   121  		cqlVersionValue,
   122  		message.Column(datacenter),
   123  		hostIdValue,
   124  		addrBuf.Bytes(),
   125  		partitionerValue,
   126  		rackValue,
   127  		releaseVersionValue,
   128  		addrBuf.Bytes(),
   129  		schemaVersionValue,
   130  		tokensBuf.Bytes(),
   131  	}
   132  }
   133  
   134  func fullSystemLocal(cluster string, datacenter string, request *frame.Frame, conn *CqlServerConnection) *frame.Frame {
   135  	systemLocalRow := systemLocalRow(cluster, datacenter, conn.LocalAddr(), request.Header.Version)
   136  	msg := &message.RowsResult{
   137  		Metadata: &message.RowsMetadata{
   138  			ColumnCount: int32(len(systemLocalColumns)),
   139  			Columns:     systemLocalColumns,
   140  		},
   141  		Data: message.RowSet{systemLocalRow},
   142  	}
   143  	return frame.NewFrame(request.Header.Version, request.Header.StreamId, msg)
   144  }
   145  
   146  func schemaVersion(request *frame.Frame) *frame.Frame {
   147  	msg := &message.RowsResult{
   148  		Metadata: &message.RowsMetadata{
   149  			ColumnCount: 1,
   150  			Columns:     []*message.ColumnMetadata{schemaVersionColumn},
   151  		},
   152  		Data: message.RowSet{message.Row{schemaVersionValue}},
   153  	}
   154  	return frame.NewFrame(request.Header.Version, request.Header.StreamId, msg)
   155  }
   156  
   157  func clusterName(cluster string, request *frame.Frame) *frame.Frame {
   158  	msg := &message.RowsResult{
   159  		Metadata: &message.RowsMetadata{
   160  			ColumnCount: 1,
   161  			Columns:     []*message.ColumnMetadata{clusterNameColumn},
   162  		},
   163  		Data: message.RowSet{message.Row{message.Column(cluster)}},
   164  	}
   165  	return frame.NewFrame(request.Header.Version, request.Header.StreamId, msg)
   166  }
   167  
   168  func emptySystemPeers(request *frame.Frame) *frame.Frame {
   169  	msg := &message.RowsResult{
   170  		Metadata: &message.RowsMetadata{ColumnCount: 0},
   171  		Data:     message.RowSet{},
   172  	}
   173  	return frame.NewFrame(request.Header.Version, request.Header.StreamId, msg)
   174  }