Skip to content

Commit

Permalink
Merge pull request #138 from HadesArchitect/#136
Browse files Browse the repository at this point in the history
  • Loading branch information
futuarmo authored May 19, 2022
2 parents 4cae508 + b3d7dd2 commit 5342f11
Show file tree
Hide file tree
Showing 9 changed files with 496 additions and 501 deletions.
265 changes: 100 additions & 165 deletions backend/datasource.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ type CassandraDatasource struct {
session *gocql.Session
}

func NewDataSource() *CassandraDatasource {
func NewDataSource(settings backend.DataSourceInstanceSettings) (*CassandraDatasource, error) {
ds := &CassandraDatasource{
logger: logger,
}
Expand All @@ -39,7 +39,60 @@ func NewDataSource() *CassandraDatasource {

ds.resourceHandler = httpadapter.New(mux)

return ds
if settings.URL == "" {
return nil, errors.New("host field cannot be empty, please fill it with a proper value")
}

options, err := ds.getRequestOptions(settings.JSONData)
if err != nil {
ds.logger.Error("Failed to parse connection parameters", "Message", err)
return nil, errors.New("failed to parse connection parameters, please inspect Grafana server log for details")
}

hosts := strings.Split(settings.URL, ";")
ds.logger.Debug("Connecting...", "hosts", settings.URL)
cluster := gocql.NewCluster(hosts...)

if options.Timeout != nil {
cluster.Timeout = time.Duration(*options.Timeout) * time.Second
}

consistency, err := parseConsistency(options.Consistency)
if err != nil {
ds.logger.Error("Failed to parse consistency", "Message", err, "Consistency", options.Consistency)
return nil, errors.New("failed to parse consistency, please inspect Grafana server log for details")
}

cluster.Consistency = consistency
cluster.Keyspace = options.Keyspace
cluster.Authenticator = gocql.PasswordAuthenticator{
Username: options.User,
Password: settings.DecryptedSecureJSONData["password"],
}
cluster.DisableInitialHostLookup = true // AWS Specific Required

if options.UseCustomTLS {
ds.logger.Debug("Setting TLS Configuration...")

tlsConfig, err := PrepareTLSCfg(options.CertPath, options.RootPath, options.CaPath, options.AllowInsecureTLS)
if err != nil {
ds.logger.Error("Failed to create TLS config", "Message", err)
return nil, errors.New("failed to create TLS config, please inspect Grafana server log for details")
}

cluster.SslOpts = &gocql.SslOptions{Config: tlsConfig}
}

session, err := cluster.CreateSession()
if err != nil {
ds.logger.Error("Failed to create session", "Message", err)

return nil, errors.New("failed to create Cassandra session, please inspect Grafana server log for details")
}

ds.session = session

return ds, nil
}

func (ds *CassandraDatasource) handleKeyspaces(rw http.ResponseWriter, req *http.Request) {
Expand Down Expand Up @@ -76,7 +129,7 @@ func (ds *CassandraDatasource) handleTables(rw http.ResponseWriter, req *http.Re
}

func (ds *CassandraDatasource) handleColumns(rw http.ResponseWriter, req *http.Request) {
ds.logger.Info("Process 'columns' request")
ds.logger.Debug("Process 'columns' request")

ctx := httpadapter.PluginConfigFromContext(req.Context())

Expand Down Expand Up @@ -127,100 +180,69 @@ func (ds *CassandraDatasource) HandleMetricQueries(ctx context.Context, req *bac
}

func (ds *CassandraDatasource) handleMetricQuery(ctx *backend.PluginContext, query backend.DataQuery) backend.DataResponse {
err := ds.connectIfNeeded(ctx)
if err != nil {
ds.logger.Warn("Failed to connect", "Message", err)
return dataResponse(data.Frames{}, errors.New("Failed to connect to server, please inspect Grafana server log for details"))
}

frames, err := ds.metricQuery(&query)

return dataResponse(frames, err)
}

func (ds *CassandraDatasource) getConnected(ctx *backend.PluginContext, getData func() ([]string, error)) ([]string, error) {
err := ds.connectIfNeeded(ctx)
if err != nil {
ds.logger.Warn("Failed to connect", "Message", err)

return nil, errors.New("Failed to connect to server, please inspect Grafana server log for details")
}

return getData()
return dataResponse(ds.metricQuery(&query))
}

func (ds *CassandraDatasource) getColumns(ctx *backend.PluginContext, keyspace, table, needType string) ([]string, error) {
getColumns := func() ([]string, error) {
if keyspace == "" {
return nil, errors.New("Keyspace is not set")
}
if keyspace == "" {
return nil, errors.New("keyspace is not set")
}

if table == "" {
return nil, errors.New("Table is not set")
}
if table == "" {
return nil, errors.New("table is not set")
}

keyspaceMetadata, err := ds.session.KeyspaceMetadata(keyspace)
if err != nil {
ds.logger.Error("Failed to retrieve keyspace metadata", "Message", err, "Keyspace", keyspace)
keyspaceMetadata, err := ds.session.KeyspaceMetadata(keyspace)
if err != nil {
ds.logger.Error("Failed to retrieve keyspace metadata", "Message", err, "Keyspace", keyspace)

return nil, fmt.Errorf("Failed to retrieve keyspace metadata, please inspect Grafana server log for details")
}
return nil, fmt.Errorf("failed to retrieve keyspace metadata, please inspect Grafana server log for details")
}

tableMetadata, ok := keyspaceMetadata.Tables[table]
if !ok {
return nil, fmt.Errorf("Table '%s' not found", table)
}
tableMetadata, ok := keyspaceMetadata.Tables[table]
if !ok {
return nil, fmt.Errorf("table '%s' not found", table)
}

var columns []string = make([]string, 0)
for name, column := range tableMetadata.Columns {
if column.Type.Type().String() == needType {
columns = append(columns, name)
}
var columns []string = make([]string, 0)
for name, column := range tableMetadata.Columns {
if column.Type.Type().String() == needType {
columns = append(columns, name)
}

return columns, nil
}

return ds.getConnected(ctx, getColumns)
return columns, nil
}

func (ds *CassandraDatasource) getTables(ctx *backend.PluginContext, keyspace string) ([]string, error) {
getTables := func() ([]string, error) {
if keyspace == "" {
return nil, errors.New("Keyspace is not set")
}

keyspaceMetadata, err := ds.session.KeyspaceMetadata(keyspace)
if err != nil {
ds.logger.Error("Failed to retrieve keyspace metadata", "Message", err, "Keyspace", keyspace)
if keyspace == "" {
return nil, errors.New("keyspace is not set")
}

return nil, errors.New("Failed to retrieve keyspace metadata")
}
keyspaceMetadata, err := ds.session.KeyspaceMetadata(keyspace)
if err != nil {
ds.logger.Error("Failed to retrieve keyspace metadata", "Message", err, "Keyspace", keyspace)

var tables []string = make([]string, 0)
for name, _ := range keyspaceMetadata.Tables {
tables = append(tables, name)
}
return nil, errors.New("failed to retrieve keyspace metadata")
}

return tables, nil
var tables []string = make([]string, 0)
for name := range keyspaceMetadata.Tables {
tables = append(tables, name)
}

return ds.getConnected(ctx, getTables)
return tables, nil
}

func (ds *CassandraDatasource) getKeyspaces(ctx *backend.PluginContext) ([]string, error) {
getKeyspaces := func() ([]string, error) {
keyspaces, err := ds.processor.processKeyspacesQuery(ds)
if err != nil {
ds.logger.Error("Failed to get keyspaces list", "Message", err)

return nil, errors.New("Failed to get keyspaces list")
}
keyspaces, err := ds.processor.processKeyspacesQuery(ds)
if err != nil {
ds.logger.Error("Failed to get keyspaces list", "Message", err)

return keyspaces, nil
return nil, errors.New("failed to get keyspaces list")
}

return ds.getConnected(ctx, getKeyspaces)
return keyspaces, nil
}

func (ds *CassandraDatasource) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
Expand All @@ -231,9 +253,9 @@ func (ds *CassandraDatasource) CheckHealth(ctx context.Context, req *backend.Che
}, nil
}

err := ds.connect(&req.PluginContext)
err := ds.session.Query("SELECT keyspace_name FROM system_schema.keyspaces;").Exec()
if err != nil {
ds.logger.Warn("Failed to connect", "Message", err)
ds.logger.Error("Failed to connect", "Message", err)

return &backend.CheckHealthResult{
Status: backend.HealthStatusError,
Expand Down Expand Up @@ -262,14 +284,14 @@ func (ds *CassandraDatasource) metricQuery(query *backend.DataQuery) (data.Frame
if err != nil {
ds.logger.Error("Failed to parse queries", "Message", err)

return nil, errors.New("Failed to parse queries, please inspect Grafana server log for details")
return nil, errors.New("failed to parse queries, please inspect Grafana server log for details")
}

if cassQuery.RawQuery {
return ds.processor.processRawMetricQuery(cassQuery.Target, ds)
} else {
from, to := timeRangeToStr(query.TimeRange)
ds.logger.Debug(fmt.Sprintf("Timeframe from: %s to %s\n", from, to))
ds.logger.Debug("Timeframe", "From", from, "To", to)

preparedQuery := ds.builder.prepareStrictMetricQuery(&cassQuery, from, to)

Expand All @@ -283,100 +305,13 @@ func (ds *CassandraDatasource) getRequestOptions(jsonData []byte) (DataSourceOpt
if err != nil {
ds.logger.Error("Failed to parse request", "Message", err)

return options, errors.New("Failed to parse request, please inspect Grafana server log for details")
return options, errors.New("failed to parse request, please inspect Grafana server log for details")
}
return options, nil
}

func (ds *CassandraDatasource) connectIfNeeded(context *backend.PluginContext) error {
if ds.session != nil {
return nil
}

return ds.connect(context)
}

func (ds *CassandraDatasource) connect(context *backend.PluginContext) error {
hosts := strings.Split(context.DataSourceInstanceSettings.URL, ";")

err := ds.tryToConnect(hosts, context)
if err != nil {
ds.logger.Error("Failed to connect", "Message", err)

return errors.New("Failed to connect, please inspect Grafana server log for details")
}

return nil
}

func (ds *CassandraDatasource) tryToConnect(hosts []string, context *backend.PluginContext) error {
options, err := ds.getRequestOptions(context.DataSourceInstanceSettings.JSONData)
if err != nil {
ds.logger.Error("Failed to parse connection parameters", "Message", err)

return errors.New("Failed to parse connection parameters, please inspect Grafana server log for details")
}

ds.logger.Debug("Connecting", "Hosts", hosts)

cluster := gocql.NewCluster(hosts...)

if options.Timeout != nil {
cluster.Timeout = time.Duration(*options.Timeout) * time.Second
}

consistency, err := parseConsistency(options.Consistency)
if err != nil {
ds.logger.Error("Failed to parse consistency", "Message", err, "Consistency", options.Consistency)

return errors.New("Failed to parse consistency, please inspect Grafana server log for details")
}

cluster.Consistency = consistency
cluster.Keyspace = options.Keyspace
cluster.Authenticator = gocql.PasswordAuthenticator{
Username: options.User,
Password: context.DataSourceInstanceSettings.DecryptedSecureJSONData["password"],
}
cluster.DisableInitialHostLookup = true // AWS Specific Required

if options.UseCustomTLS {
ds.logger.Debug("Setting TLS Configuration...")

tlsConfig, err := PrepareTLSCfg(options.CertPath, options.RootPath, options.CaPath, options.AllowInsecureTLS)
if err != nil {
ds.logger.Error("Failed to create TLS config", "Message", err)

return errors.New("Failed to create TLS config, please inspect Grafana server log for details")
}

cluster.SslOpts = &gocql.SslOptions{Config: tlsConfig}
}

session, err := cluster.CreateSession()
if err != nil {
ds.logger.Warn("Failed to create session", "Message", err)

return errors.New("Failed to create Cassandra session, please inspect Grafana server log for details")
}

ds.session = session

return nil
}

func parseConsistency(consistencyStr string) (consistency gocql.Consistency, err error) {
defer func() {
if r := recover(); r != nil {
var ok bool
err, ok = r.(error)
if !ok {
err = fmt.Errorf("failed to parse consistency \"%s\": %v", consistencyStr, r)
}
}
}()

consistency = gocql.ParseConsistency(consistencyStr)
consistency, err = gocql.ParseConsistencyWrapper(consistencyStr)
return
}

Expand Down
1 change: 0 additions & 1 deletion backend/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ module github.com/HadesArchitect/GrafanaCassandraDatasource/backend
go 1.15

require (
github.com/bitly/go-simplejson v0.5.0 // indirect
github.com/gocql/gocql v0.0.0-20201215165327-e49edf966d90
github.com/grafana/grafana-plugin-model v0.0.0-20200514130833-df1eb6bdf4c5
github.com/grafana/grafana-plugin-sdk-go v0.93.0
Expand Down
2 changes: 0 additions & 2 deletions backend/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 h1:mXoPYz/Ul5HYEDvkta6I8/rnYM5gSdSV2tJ6XbZuEtY=
github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k=
github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y=
github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
Expand Down
Loading

0 comments on commit 5342f11

Please sign in to comment.