func mustParseStatement(s string) influxql.Statement { stmt, err := influxql.ParseStatement(s) if err != nil { panic(err) } return stmt }
func (s *Service) execQuery(q, cluster string) (kapacitor.DBRP, *influxdb.Response, error) { // Parse query to determine dbrp dbrp := kapacitor.DBRP{} stmt, err := influxql.ParseStatement(q) if err != nil { return dbrp, nil, err } if slct, ok := stmt.(*influxql.SelectStatement); ok && len(slct.Sources) == 1 { if m, ok := slct.Sources[0].(*influxql.Measurement); ok { dbrp.Database = m.Database dbrp.RetentionPolicy = m.RetentionPolicy } } if dbrp.Database == "" || dbrp.RetentionPolicy == "" { return dbrp, nil, errors.New("could not determine database and retention policy. Is the query fully qualified?") } if s.InfluxDBService == nil { return dbrp, nil, errors.New("InfluxDB not configured, cannot record query") } // Query InfluxDB con, err := s.InfluxDBService.NewNamedClient(cluster) if err != nil { return dbrp, nil, errors.Wrap(err, "failed to get InfluxDB client") } query := influxdb.Query{ Command: q, } resp, err := con.Query(query) if err != nil { return dbrp, nil, errors.Wrap(err, "InfluxDB query failed") } return dbrp, resp, nil }
// Parse statements that might appear valid but should return an error. // If allowed to execute, at least some of these statements would result in a panic. func TestParse_Errors(t *testing.T) { for _, tt := range []struct { tmpl string good string bad string }{ // Second argument to derivative must be duration {tmpl: `SELECT derivative(f, %s) FROM m`, good: "1h", bad: "true"}, } { good := fmt.Sprintf(tt.tmpl, tt.good) if _, err := influxql.ParseStatement(good); err != nil { t.Fatalf("statement %q should have parsed correctly but returned error: %s", good, err) } bad := fmt.Sprintf(tt.tmpl, tt.bad) if _, err := influxql.ParseStatement(bad); err == nil { t.Fatalf("statement %q should have resulted in a parse error but did not", bad) } } }
func (s *Service) processExecuteStatementRequest(buf []byte) error { // Unmarshal the request. var req ExecuteStatementRequest if err := req.UnmarshalBinary(buf); err != nil { return err } // Parse the InfluxQL statement. stmt, err := influxql.ParseStatement(req.Statement()) if err != nil { return err } return s.executeStatement(stmt, req.Database()) }
func influxTag(args []parse.Node) (parse.Tags, error) { st, err := influxql.ParseStatement(args[1].(*parse.StringNode).Text) if err != nil { return nil, err } s, ok := st.(*influxql.SelectStatement) if !ok { return nil, fmt.Errorf("influx: expected select statement") } t := make(parse.Tags, len(s.Dimensions)) for _, d := range s.Dimensions { if _, ok := d.Expr.(*influxql.Call); ok { continue } t[d.String()] = struct{}{} } return t, nil }
// ParseStatements takes a configFile and returns a slice of Statements func ParseStatements(file string) ([]statement.Statement, error) { seq := []statement.Statement{} f, err := os.Open(file) check(err) s := NewScanner(f) for { t, l := s.Scan() if t == EOF { break } _, err := influxql.ParseStatement(l) if err == nil { seq = append(seq, &statement.InfluxqlStatement{Query: l, StatementID: stressql.RandStr(10)}) } else if t == BREAK { continue } else { f := strings.NewReader(l) p := stressql.NewParser(f) s, err := p.Parse() if err != nil { return nil, err } seq = append(seq, s) } } f.Close() return seq, nil }
func TestRewriteStatement(t *testing.T) { tests := []struct { stmt string s string }{ { stmt: `SHOW FIELD KEYS`, s: `SELECT fieldKey FROM _fieldKeys`, }, { stmt: `SHOW FIELD KEYS FROM cpu`, s: `SELECT fieldKey FROM _fieldKeys WHERE _name = 'cpu'`, }, { stmt: `SHOW FIELD KEYS FROM /c.*/`, s: `SELECT fieldKey FROM _fieldKeys WHERE _name =~ /c.*/`, }, { stmt: `SHOW FIELD KEYS FROM mydb.myrp2.cpu`, s: `SELECT fieldKey FROM mydb.myrp2._fieldKeys WHERE _name = 'cpu'`, }, { stmt: `SHOW FIELD KEYS FROM mydb.myrp2./c.*/`, s: `SELECT fieldKey FROM mydb.myrp2._fieldKeys WHERE _name =~ /c.*/`, }, { stmt: `SHOW MEASUREMENTS`, s: `SELECT _name AS "name" FROM _measurements`, }, { stmt: `SHOW MEASUREMENTS WITH MEASUREMENT = cpu`, s: `SELECT _name AS "name" FROM _measurements WHERE _name = 'cpu'`, }, { stmt: `SHOW MEASUREMENTS WITH MEASUREMENT =~ /c.*/`, s: `SELECT _name AS "name" FROM _measurements WHERE _name =~ /c.*/`, }, { stmt: `SHOW MEASUREMENTS WHERE region = 'uswest'`, s: `SELECT _name AS "name" FROM _measurements WHERE region = 'uswest'`, }, { stmt: `SHOW MEASUREMENTS WITH MEASUREMENT = cpu WHERE region = 'uswest'`, s: `SELECT _name AS "name" FROM _measurements WHERE (_name = 'cpu') AND (region = 'uswest')`, }, { stmt: `SHOW SERIES`, s: `SELECT "key" FROM _series`, }, { stmt: `SHOW SERIES FROM cpu`, s: `SELECT "key" FROM _series WHERE _name = 'cpu'`, }, { stmt: `SHOW SERIES FROM mydb.myrp1.cpu`, s: `SELECT "key" FROM mydb.myrp1._series WHERE _name = 'cpu'`, }, { stmt: `SHOW SERIES FROM mydb.myrp1./c.*/`, s: `SELECT "key" FROM mydb.myrp1._series WHERE _name =~ /c.*/`, }, { stmt: `SHOW TAG KEYS`, s: `SELECT tagKey FROM _tagKeys`, }, { stmt: `SHOW TAG KEYS FROM cpu`, s: `SELECT tagKey FROM _tagKeys WHERE _name = 'cpu'`, }, { stmt: `SHOW TAG KEYS FROM /c.*/`, s: `SELECT tagKey FROM _tagKeys WHERE _name =~ /c.*/`, }, { stmt: `SHOW TAG KEYS FROM cpu WHERE region = 'uswest'`, s: `SELECT tagKey FROM _tagKeys WHERE (_name = 'cpu') AND (region = 'uswest')`, }, { stmt: `SHOW TAG KEYS FROM mydb.myrp1.cpu`, s: `SELECT tagKey FROM mydb.myrp1._tagKeys WHERE _name = 'cpu'`, }, { stmt: `SHOW TAG KEYS FROM mydb.myrp1./c.*/`, s: `SELECT tagKey FROM mydb.myrp1._tagKeys WHERE _name =~ /c.*/`, }, { stmt: `SHOW TAG KEYS FROM mydb.myrp1.cpu WHERE region = 'uswest'`, s: `SELECT tagKey FROM mydb.myrp1._tagKeys WHERE (_name = 'cpu') AND (region = 'uswest')`, }, { stmt: `SHOW TAG VALUES WITH KEY = region`, s: `SELECT _tagKey AS "key", value FROM _tags WHERE _tagKey = 'region'`, }, { stmt: `SHOW TAG VALUES FROM cpu WITH KEY = region`, s: `SELECT _tagKey AS "key", value FROM _tags WHERE (_name = 'cpu') AND (_tagKey = 'region')`, }, { stmt: `SHOW TAG VALUES FROM cpu WITH KEY IN (region, host)`, s: `SELECT _tagKey AS "key", value FROM _tags WHERE (_name = 'cpu') AND (_tagKey = 'region' OR _tagKey = 'host')`, }, { stmt: `SHOW TAG VALUES FROM mydb.myrp1.cpu WITH KEY IN (region, host)`, s: `SELECT _tagKey AS "key", value FROM mydb.myrp1._tags WHERE (_name = 'cpu') AND (_tagKey = 'region' OR _tagKey = 'host')`, }, { stmt: `SELECT value FROM cpu`, s: `SELECT value FROM cpu`, }, } for _, test := range tests { stmt, err := influxql.ParseStatement(test.stmt) if err != nil { t.Errorf("error parsing statement: %s", err) } else { stmt, err = influxql.RewriteStatement(stmt) if err != nil { t.Errorf("error rewriting statement: %s", err) } else if s := stmt.String(); s != test.s { t.Errorf("error rendering string. expected %s, actual: %s", test.s, s) } } } }
func TestRewriteStatement(t *testing.T) { tests := []struct { stmt string s string }{ { stmt: `SHOW FIELD KEYS`, s: `SELECT fieldKey, fieldType FROM _fieldKeys`, }, { stmt: `SHOW FIELD KEYS FROM cpu`, s: `SELECT fieldKey, fieldType FROM _fieldKeys WHERE _name = 'cpu'`, }, { stmt: `SHOW FIELD KEYS FROM /c.*/`, s: `SELECT fieldKey, fieldType FROM _fieldKeys WHERE _name =~ /c.*/`, }, { stmt: `SHOW FIELD KEYS FROM mydb.myrp2.cpu`, s: `SELECT fieldKey, fieldType FROM mydb.myrp2._fieldKeys WHERE _name = 'cpu'`, }, { stmt: `SHOW FIELD KEYS FROM mydb.myrp2./c.*/`, s: `SELECT fieldKey, fieldType FROM mydb.myrp2._fieldKeys WHERE _name =~ /c.*/`, }, { stmt: `SHOW SERIES`, s: `SELECT "key" FROM _series`, }, { stmt: `SHOW SERIES FROM cpu`, s: `SELECT "key" FROM _series WHERE _name = 'cpu'`, }, { stmt: `SHOW SERIES FROM mydb.myrp1.cpu`, s: `SELECT "key" FROM mydb.myrp1._series WHERE _name = 'cpu'`, }, { stmt: `SHOW SERIES FROM mydb.myrp1./c.*/`, s: `SELECT "key" FROM mydb.myrp1._series WHERE _name =~ /c.*/`, }, { stmt: `SHOW TAG KEYS`, s: `SELECT tagKey FROM _tagKeys`, }, { stmt: `SHOW TAG KEYS FROM cpu`, s: `SELECT tagKey FROM _tagKeys WHERE _name = 'cpu'`, }, { stmt: `SHOW TAG KEYS FROM /c.*/`, s: `SELECT tagKey FROM _tagKeys WHERE _name =~ /c.*/`, }, { stmt: `SHOW TAG KEYS FROM cpu WHERE region = 'uswest'`, s: `SELECT tagKey FROM _tagKeys WHERE (_name = 'cpu') AND (region = 'uswest')`, }, { stmt: `SHOW TAG KEYS FROM mydb.myrp1.cpu`, s: `SELECT tagKey FROM mydb.myrp1._tagKeys WHERE _name = 'cpu'`, }, { stmt: `SHOW TAG KEYS FROM mydb.myrp1./c.*/`, s: `SELECT tagKey FROM mydb.myrp1._tagKeys WHERE _name =~ /c.*/`, }, { stmt: `SHOW TAG KEYS FROM mydb.myrp1.cpu WHERE region = 'uswest'`, s: `SELECT tagKey FROM mydb.myrp1._tagKeys WHERE (_name = 'cpu') AND (region = 'uswest')`, }, { stmt: `SELECT value FROM cpu`, s: `SELECT value FROM cpu`, }, } for _, test := range tests { stmt, err := influxql.ParseStatement(test.stmt) if err != nil { t.Errorf("error parsing statement: %s", err) } else { stmt, err = influxql.RewriteStatement(stmt) if err != nil { t.Errorf("error rewriting statement: %s", err) } else if s := stmt.String(); s != test.s { t.Errorf("error rendering string. expected %s, actual: %s", test.s, s) } } } }
// influxQueryDuration adds time WHERE clauses to query for the given start and end durations. func influxQueryDuration(now time.Time, query, start, end, groupByInterval string) (string, error) { sd, err := opentsdb.ParseDuration(start) if err != nil { return "", err } ed, err := opentsdb.ParseDuration(end) if end == "" { ed = 0 } else if err != nil { return "", err } st, err := influxql.ParseStatement(query) if err != nil { return "", err } s, ok := st.(*influxql.SelectStatement) if !ok { return "", fmt.Errorf("influx: expected select statement") } isTime := func(n influxql.Node) bool { v, ok := n.(*influxql.VarRef) if !ok { return false } s := strings.ToLower(v.Val) return s == "time" } influxql.WalkFunc(s.Condition, func(n influxql.Node) { b, ok := n.(*influxql.BinaryExpr) if !ok { return } if isTime(b.LHS) || isTime(b.RHS) { err = fmt.Errorf("influx query must not contain time in WHERE") } }) if err != nil { return "", err } //Add New BinaryExpr for time clause startExpr := &influxql.BinaryExpr{ Op: influxql.GTE, LHS: &influxql.VarRef{Val: "time"}, RHS: &influxql.TimeLiteral{Val: now.Add(time.Duration(-sd))}, } stopExpr := &influxql.BinaryExpr{ Op: influxql.LTE, LHS: &influxql.VarRef{Val: "time"}, RHS: &influxql.TimeLiteral{Val: now.Add(time.Duration(-ed))}, } if s.Condition != nil { s.Condition = &influxql.BinaryExpr{ Op: influxql.AND, LHS: s.Condition, RHS: &influxql.BinaryExpr{ Op: influxql.AND, LHS: startExpr, RHS: stopExpr, }, } } else { s.Condition = &influxql.BinaryExpr{ Op: influxql.AND, LHS: startExpr, RHS: stopExpr, } } // parse last argument if len(groupByInterval) > 0 { gbi, err := time.ParseDuration(groupByInterval) if err != nil { return "", err } s.Dimensions = append(s.Dimensions, &influxql.Dimension{Expr: &influxql.Call{ Name: "time", Args: []influxql.Expr{&influxql.DurationLiteral{Val: gbi}}, }, }) } // emtpy aggregate windows should be purged from the result // this default resembles the opentsdb results. if s.Fill == influxql.NullFill { s.Fill = influxql.NoFill s.FillValue = nil } return s.String(), nil }
func TestService_Open_LinkSubscriptions(t *testing.T) { type clusterInfo struct { name string dbrps map[string][]string // Map of db names to list of rp that have subs subs map[string][]string } type subChanged struct { // All the ways a sub can change NoDests bool InvalidURL bool WrongProtocol bool Host bool Port bool ExtraUser bool NoUser bool WrongUser bool NoPassword bool WrongCluster bool } type partialConfig struct { configSubs map[string][]string configExSubs map[string][]string } testCases := map[string]struct { useTokens bool // First round clusters map[string]clusterInfo tokens []string createSubs []string dropSubs []string grantedTokens []tokenGrant revokedTokens []string subChanged subChanged // apply new config between rounds partialConfigs map[string]partialConfig // Second round secondClusters map[string]clusterInfo secondTokens []string secondCreateSubs []string secondDropSubs []string secondGrantedTokens []tokenGrant secondRevokedTokens []string secondSubChanged subChanged }{ "NoExisting": { useTokens: true, clusters: map[string]clusterInfo{ testClusterName: { dbrps: map[string][]string{ "db1": []string{"rpA", "rpB"}, "db2": []string{"rpC"}, }, }}, secondClusters: map[string]clusterInfo{ testClusterName: { dbrps: map[string][]string{ "db1": []string{"rpA", "rpB"}, "db2": []string{"rpC"}, }, subs: map[string][]string{ "db1": []string{"rpA", "rpB"}, "db2": []string{"rpC"}, }, }}, createSubs: []string{ `CREATE SUBSCRIPTION "` + testSubName + `" ON db1.rpA DESTINATIONS ANY '` + testDestinationWithTokenForCluster(testClusterName) + `'`, `CREATE SUBSCRIPTION "` + testSubName + `" ON db1.rpB DESTINATIONS ANY '` + testDestinationWithTokenForCluster(testClusterName) + `'`, `CREATE SUBSCRIPTION "` + testSubName + `" ON db2.rpC DESTINATIONS ANY '` + testDestinationWithTokenForCluster(testClusterName) + `'`, }, grantedTokens: []tokenGrant{ { token: randomToken, db: "db1", rp: "rpA", }, { token: randomToken, db: "db1", rp: "rpB", }, { token: randomToken, db: "db2", rp: "rpC", }, }, }, "NoExisting_NoTokens": { useTokens: false, clusters: map[string]clusterInfo{ testClusterName: { dbrps: map[string][]string{ "db1": []string{"rpA", "rpB"}, "db2": []string{"rpC"}, }, }}, secondClusters: map[string]clusterInfo{ testClusterName: { dbrps: map[string][]string{ "db1": []string{"rpA", "rpB"}, "db2": []string{"rpC"}, }, subs: map[string][]string{ "db1": []string{"rpA", "rpB"}, "db2": []string{"rpC"}, }, }}, createSubs: []string{ `CREATE SUBSCRIPTION "` + testSubName + `" ON db1.rpA DESTINATIONS ANY '` + testDestination + `'`, `CREATE SUBSCRIPTION "` + testSubName + `" ON db1.rpB DESTINATIONS ANY '` + testDestination + `'`, `CREATE SUBSCRIPTION "` + testSubName + `" ON db2.rpC DESTINATIONS ANY '` + testDestination + `'`, }, }, "Existing": { useTokens: true, clusters: map[string]clusterInfo{ testClusterName: { dbrps: map[string][]string{ "db1": []string{"rpA", "rpB"}, "db2": []string{"rpC"}, }, subs: map[string][]string{ "db1": []string{"rpA"}, }, }}, secondClusters: map[string]clusterInfo{ testClusterName: { dbrps: map[string][]string{ "db1": []string{"rpA", "rpB"}, "db2": []string{"rpC"}, }, subs: map[string][]string{ "db1": []string{"rpA", "rpB"}, "db2": []string{"rpC"}, }, }}, tokens: []string{ randomToken, }, createSubs: []string{ `CREATE SUBSCRIPTION "` + testSubName + `" ON db1.rpB DESTINATIONS ANY '` + testDestinationWithTokenForCluster(testClusterName) + `'`, `CREATE SUBSCRIPTION "` + testSubName + `" ON db2.rpC DESTINATIONS ANY '` + testDestinationWithTokenForCluster(testClusterName) + `'`, }, grantedTokens: []tokenGrant{ { token: randomToken, db: "db1", rp: "rpB", }, { token: randomToken, db: "db2", rp: "rpC", }, }, }, "NoChanges": { useTokens: true, clusters: map[string]clusterInfo{ testClusterName: { dbrps: map[string][]string{ "db1": []string{"rpA", "rpB"}, "db2": []string{"rpC"}, }, subs: map[string][]string{ "db1": []string{"rpA", "rpB"}, "db2": []string{"rpC"}, }, }}, secondClusters: map[string]clusterInfo{ testClusterName: { dbrps: map[string][]string{ "db1": []string{"rpA", "rpB"}, "db2": []string{"rpC"}, }, subs: map[string][]string{ "db1": []string{"rpA", "rpB"}, "db2": []string{"rpC"}, }, }}, tokens: []string{ randomToken, }, }, "ExtraTokens": { useTokens: true, clusters: map[string]clusterInfo{ testClusterName: { dbrps: map[string][]string{ "db1": []string{"rpA", "rpB"}, "db2": []string{"rpC"}, }, subs: map[string][]string{ "db1": []string{"rpA", "rpB"}, "db2": []string{"rpC"}, }, }, "anothercluster": { dbrps: map[string][]string{ "db10": []string{"rpZ"}, }, subs: map[string][]string{ "db10": []string{"rpZ"}, }, }}, secondClusters: map[string]clusterInfo{ testClusterName: { dbrps: map[string][]string{ "db1": []string{"rpA", "rpB"}, "db2": []string{"rpC"}, }, subs: map[string][]string{ "db1": []string{"rpA", "rpB"}, "db2": []string{"rpC"}, }, }, "anothercluster": { dbrps: map[string][]string{ "db10": []string{"rpZ"}, }, subs: map[string][]string{ "db10": []string{"rpZ"}, }, }, }, tokens: []string{ randomToken, "invalidtoken", tokenForCluster("anothercluster"), testClusterName + ";unusedtoken", }, revokedTokens: []string{ "invalidtoken", testClusterName + ";unusedtoken", }, }, "ExtraNonClusterTokens": { useTokens: true, clusters: map[string]clusterInfo{ testClusterName: { dbrps: map[string][]string{ "db1": []string{"rpA", "rpB"}, "db2": []string{"rpC"}, }, subs: map[string][]string{ "db1": []string{"rpA", "rpB"}, "db2": []string{"rpC"}, }, }, "anothercluster": { dbrps: map[string][]string{ "db10": []string{"rpZ"}, }, subs: map[string][]string{ "db10": []string{"rpZ"}, }, }}, secondClusters: map[string]clusterInfo{ testClusterName: { dbrps: map[string][]string{ "db1": []string{"rpA", "rpB"}, "db2": []string{"rpC"}, }, subs: map[string][]string{ "db1": []string{"rpA", "rpB"}, "db2": []string{"rpC"}, }, }, "anothercluster": { dbrps: map[string][]string{ "db10": []string{"rpZ"}, }, subs: map[string][]string{ "db10": []string{"rpZ"}, }, }, }, tokens: []string{ randomToken, "invalidtoken", tokenForCluster("anothercluster"), testClusterName + ";unusedtoken", tokenForCluster("nonexistantcluster"), }, revokedTokens: []string{ "invalidtoken", testClusterName + ";unusedtoken", tokenForCluster("nonexistantcluster"), }, }, "SecondDroppedDB": { useTokens: true, clusters: map[string]clusterInfo{ testClusterName: { dbrps: map[string][]string{ "db1": []string{"rpA"}, }, subs: map[string][]string{ "db1": []string{"rpA"}, }, }}, secondClusters: map[string]clusterInfo{ testClusterName: { // db1 had been dropped }}, tokens: []string{ randomToken, }, secondTokens: []string{ randomToken, }, secondRevokedTokens: []string{ randomToken, }, }, "SecondNewDB": { useTokens: true, clusters: map[string]clusterInfo{ testClusterName: { dbrps: map[string][]string{ "db1": []string{"rpA"}, }, subs: map[string][]string{ "db1": []string{"rpA"}, }, }}, secondClusters: map[string]clusterInfo{ testClusterName: { dbrps: map[string][]string{ "db1": []string{"rpA"}, "db2": []string{"rpB"}, }, subs: map[string][]string{ "db1": []string{"rpA"}, }, }}, tokens: []string{ randomToken, }, secondTokens: []string{ randomToken, }, secondCreateSubs: []string{ `CREATE SUBSCRIPTION "` + testSubName + `" ON db2.rpB DESTINATIONS ANY '` + testDestinationWithTokenForCluster(testClusterName) + `'`, }, secondGrantedTokens: []tokenGrant{{ token: randomToken, db: "db2", rp: "rpB", }}, }, "SC_NoDests": { useTokens: false, subChanged: subChanged{NoDests: true}, clusters: map[string]clusterInfo{ testClusterName: { dbrps: map[string][]string{ "db1": []string{"rpA"}, }, subs: map[string][]string{ "db1": []string{"rpA"}, }, }}, secondClusters: map[string]clusterInfo{ testClusterName: { dbrps: map[string][]string{ "db1": []string{"rpA"}, }, subs: map[string][]string{ "db1": []string{"rpA"}, }, }}, dropSubs: []string{ `DROP SUBSCRIPTION "` + testSubName + `" ON db1.rpA`, }, createSubs: []string{ `CREATE SUBSCRIPTION "` + testSubName + `" ON db1.rpA DESTINATIONS ANY '` + testDestination + `'`, }, }, "SC_InvalidURL": { useTokens: true, subChanged: subChanged{InvalidURL: true}, clusters: map[string]clusterInfo{ testClusterName: { dbrps: map[string][]string{ "db1": []string{"rpA"}, }, subs: map[string][]string{ "db1": []string{"rpA"}, }, }}, secondClusters: map[string]clusterInfo{ testClusterName: { dbrps: map[string][]string{ "db1": []string{"rpA"}, }, subs: map[string][]string{ "db1": []string{"rpA"}, }, }}, dropSubs: []string{ `DROP SUBSCRIPTION "` + testSubName + `" ON db1.rpA`, }, createSubs: []string{ `CREATE SUBSCRIPTION "` + testSubName + `" ON db1.rpA DESTINATIONS ANY '` + testDestinationWithTokenForCluster(testClusterName) + `'`, }, tokens: []string{ randomToken, }, secondTokens: []string{ randomToken, }, grantedTokens: []tokenGrant{{ token: randomToken, db: "db1", rp: "rpA", }}, }, "SC_WrongProtocol": { useTokens: false, subChanged: subChanged{WrongProtocol: true}, clusters: map[string]clusterInfo{ testClusterName: { dbrps: map[string][]string{ "db1": []string{"rpA"}, }, subs: map[string][]string{ "db1": []string{"rpA"}, }, }}, secondClusters: map[string]clusterInfo{ testClusterName: { dbrps: map[string][]string{ "db1": []string{"rpA"}, }, subs: map[string][]string{ "db1": []string{"rpA"}, }, }}, dropSubs: []string{ `DROP SUBSCRIPTION "` + testSubName + `" ON db1.rpA`, }, createSubs: []string{ `CREATE SUBSCRIPTION "` + testSubName + `" ON db1.rpA DESTINATIONS ANY '` + testDestination + `'`, }, }, "SC_Host": { useTokens: false, subChanged: subChanged{Host: true}, clusters: map[string]clusterInfo{ testClusterName: { dbrps: map[string][]string{ "db1": []string{"rpA"}, }, subs: map[string][]string{ "db1": []string{"rpA"}, }, }}, secondClusters: map[string]clusterInfo{ testClusterName: { dbrps: map[string][]string{ "db1": []string{"rpA"}, }, subs: map[string][]string{ "db1": []string{"rpA"}, }, }}, dropSubs: []string{ `DROP SUBSCRIPTION "` + testSubName + `" ON db1.rpA`, }, createSubs: []string{ `CREATE SUBSCRIPTION "` + testSubName + `" ON db1.rpA DESTINATIONS ANY '` + testDestination + `'`, }, }, "SC_Port": { useTokens: false, subChanged: subChanged{Port: true}, clusters: map[string]clusterInfo{ testClusterName: { dbrps: map[string][]string{ "db1": []string{"rpA"}, }, subs: map[string][]string{ "db1": []string{"rpA"}, }, }}, secondClusters: map[string]clusterInfo{ testClusterName: { dbrps: map[string][]string{ "db1": []string{"rpA"}, }, subs: map[string][]string{ "db1": []string{"rpA"}, }, }}, dropSubs: []string{ `DROP SUBSCRIPTION "` + testSubName + `" ON db1.rpA`, }, createSubs: []string{ `CREATE SUBSCRIPTION "` + testSubName + `" ON db1.rpA DESTINATIONS ANY '` + testDestination + `'`, }, }, "SC_ExtraUser": { useTokens: false, subChanged: subChanged{ExtraUser: true}, clusters: map[string]clusterInfo{ testClusterName: { dbrps: map[string][]string{ "db1": []string{"rpA"}, }, subs: map[string][]string{ "db1": []string{"rpA"}, }, }}, secondClusters: map[string]clusterInfo{ testClusterName: { dbrps: map[string][]string{ "db1": []string{"rpA"}, }, subs: map[string][]string{ "db1": []string{"rpA"}, }, }}, dropSubs: []string{ `DROP SUBSCRIPTION "` + testSubName + `" ON db1.rpA`, }, createSubs: []string{ `CREATE SUBSCRIPTION "` + testSubName + `" ON db1.rpA DESTINATIONS ANY '` + testDestination + `'`, }, }, "SC_NoUser": { useTokens: true, subChanged: subChanged{NoUser: true}, clusters: map[string]clusterInfo{ testClusterName: { dbrps: map[string][]string{ "db1": []string{"rpA"}, }, subs: map[string][]string{ "db1": []string{"rpA"}, }, }}, secondClusters: map[string]clusterInfo{ testClusterName: { dbrps: map[string][]string{ "db1": []string{"rpA"}, }, subs: map[string][]string{ "db1": []string{"rpA"}, }, }}, dropSubs: []string{ `DROP SUBSCRIPTION "` + testSubName + `" ON db1.rpA`, }, createSubs: []string{ `CREATE SUBSCRIPTION "` + testSubName + `" ON db1.rpA DESTINATIONS ANY '` + testDestinationWithTokenForCluster(testClusterName) + `'`, }, tokens: []string{ randomToken, }, secondTokens: []string{ randomToken, }, grantedTokens: []tokenGrant{{ token: randomToken, db: "db1", rp: "rpA", }}, }, "SC_WrongUser": { useTokens: true, subChanged: subChanged{WrongUser: true}, clusters: map[string]clusterInfo{ testClusterName: { dbrps: map[string][]string{ "db1": []string{"rpA"}, }, subs: map[string][]string{ "db1": []string{"rpA"}, }, }}, secondClusters: map[string]clusterInfo{ testClusterName: { dbrps: map[string][]string{ "db1": []string{"rpA"}, }, subs: map[string][]string{ "db1": []string{"rpA"}, }, }}, dropSubs: []string{ `DROP SUBSCRIPTION "` + testSubName + `" ON db1.rpA`, }, createSubs: []string{ `CREATE SUBSCRIPTION "` + testSubName + `" ON db1.rpA DESTINATIONS ANY '` + testDestinationWithTokenForCluster(testClusterName) + `'`, }, tokens: []string{ randomToken, }, secondTokens: []string{ randomToken, }, grantedTokens: []tokenGrant{{ token: randomToken, db: "db1", rp: "rpA", }}, }, "SC_NoPassword": { useTokens: true, subChanged: subChanged{NoPassword: true}, clusters: map[string]clusterInfo{ testClusterName: { dbrps: map[string][]string{ "db1": []string{"rpA"}, }, subs: map[string][]string{ "db1": []string{"rpA"}, }, }}, secondClusters: map[string]clusterInfo{ testClusterName: { dbrps: map[string][]string{ "db1": []string{"rpA"}, }, subs: map[string][]string{ "db1": []string{"rpA"}, }, }}, dropSubs: []string{ `DROP SUBSCRIPTION "` + testSubName + `" ON db1.rpA`, }, createSubs: []string{ `CREATE SUBSCRIPTION "` + testSubName + `" ON db1.rpA DESTINATIONS ANY '` + testDestinationWithTokenForCluster(testClusterName) + `'`, }, tokens: []string{ randomToken, }, secondTokens: []string{ randomToken, }, grantedTokens: []tokenGrant{{ token: randomToken, db: "db1", rp: "rpA", }}, }, "SC_WrongCluster": { useTokens: true, subChanged: subChanged{WrongCluster: true}, clusters: map[string]clusterInfo{ testClusterName: { dbrps: map[string][]string{ "db1": []string{"rpA"}, }, subs: map[string][]string{ "db1": []string{"rpA"}, }, }}, secondClusters: map[string]clusterInfo{ testClusterName: { dbrps: map[string][]string{ "db1": []string{"rpA"}, }, subs: map[string][]string{ "db1": []string{"rpA"}, }, }}, dropSubs: []string{ `DROP SUBSCRIPTION "` + testSubName + `" ON db1.rpA`, }, createSubs: []string{ `CREATE SUBSCRIPTION "` + testSubName + `" ON db1.rpA DESTINATIONS ANY '` + testDestinationWithTokenForCluster(testClusterName) + `'`, }, tokens: []string{ randomToken, }, secondTokens: []string{ randomToken, }, grantedTokens: []tokenGrant{{ token: randomToken, db: "db1", rp: "rpA", }}, }, "ConfigChange_NewSubs": { clusters: map[string]clusterInfo{ testClusterName: { dbrps: map[string][]string{ "db1": []string{"rpA", "rpB"}, "db2": []string{"rpC", "rpD"}, }, subs: map[string][]string{ "db1": []string{"rpA", "rpB"}, "db2": []string{"rpC", "rpD"}, }, }, }, secondClusters: map[string]clusterInfo{ testClusterName: { dbrps: map[string][]string{ "db1": []string{"rpA", "rpB"}, "db2": []string{"rpC", "rpD"}, }, subs: map[string][]string{ "db1": []string{"rpA", "rpB"}, "db2": []string{"rpC", "rpD"}, }, }, }, partialConfigs: map[string]partialConfig{ testClusterName: { configSubs: map[string][]string{ "db1": {"rpA"}, }, }, }, secondDropSubs: []string{ `DROP SUBSCRIPTION "` + testSubName + `" ON db1.rpB`, `DROP SUBSCRIPTION "` + testSubName + `" ON db2.rpC`, `DROP SUBSCRIPTION "` + testSubName + `" ON db2.rpD`, }, }, "ConfigChange_NewExcludes": { clusters: map[string]clusterInfo{ testClusterName: { dbrps: map[string][]string{ "db1": []string{"rpA", "rpB"}, "db2": []string{"rpC", "rpD"}, }, subs: map[string][]string{ "db1": []string{"rpA", "rpB"}, "db2": []string{"rpC", "rpD"}, }, }}, secondClusters: map[string]clusterInfo{ testClusterName: { dbrps: map[string][]string{ "db1": []string{"rpA", "rpB"}, "db2": []string{"rpC", "rpD"}, }, subs: map[string][]string{ "db1": []string{"rpA", "rpB"}, "db2": []string{"rpC", "rpD"}, }, }}, partialConfigs: map[string]partialConfig{ testClusterName: { configExSubs: map[string][]string{ "db1": {"rpA"}, }, }, }, secondDropSubs: []string{ `DROP SUBSCRIPTION "` + testSubName + `" ON db1.rpA`, }, }, } for testName, tc := range testCases { t.Log("starting test:", testName) log.Println("starting test:", testName) clusterNames := make([]string, 0, len(tc.clusters)) clusterNameLookup := make(map[string]int, len(tc.clusters)) i := 0 for clusterName := range tc.clusters { clusterNames = append(clusterNames, clusterName) clusterNameLookup[clusterName] = i i++ } defaultConfigs := NewDefaultTestConfigs(clusterNames) s, as, cs := NewTestService(defaultConfigs, "localhost", tc.useTokens) // Define the active vars var activeClusters map[string]clusterInfo var tokens []string var subChanges subChanged var createSubs map[string]bool var dropSubs map[string]bool var grantedTokens map[tokenGrant]bool var revokedTokens map[string]bool // Setup functions cs.QueryFunc = func(clusterName string, q influxcli.Query) (*influxcli.Response, error) { log.Println("query:", q.Command) switch { case q.Command == "SHOW DATABASES": dbs := make([][]interface{}, 0, len(activeClusters[clusterName].dbrps)) for db := range activeClusters[clusterName].dbrps { dbs = append(dbs, []interface{}{db}) } return &influxcli.Response{ Results: []influxcli.Result{{ Series: []models.Row{ { Values: dbs, }, }, }}, }, nil case strings.HasPrefix(q.Command, "SHOW RETENTION POLICIES ON"): stmt, _ := influxql.ParseStatement(q.Command) if show, ok := stmt.(*influxql.ShowRetentionPoliciesStatement); ok { var rps [][]interface{} for _, rp := range activeClusters[clusterName].dbrps[show.Database] { rps = append(rps, []interface{}{rp}) } return &influxcli.Response{ Results: []influxcli.Result{{ Series: []models.Row{ { Values: rps, }, }, }}, }, nil } return nil, fmt.Errorf("invalid show rp query: %s", q.Command) case q.Command == "SHOW SUBSCRIPTIONS": result := influxcli.Result{} for db, subs := range activeClusters[clusterName].subs { series := models.Row{ Name: db, Columns: []string{ "name", "retention_policy", "mode", "destinations", }, } for _, rp := range subs { var destinations []interface{} switch { case subChanges.NoDests: destinations = nil case subChanges.InvalidURL: destinations = []interface{}{"://broken url"} case subChanges.WrongProtocol: destinations = []interface{}{"unknown://*****:*****@localhost:9092"} case subChanges.WrongUser: destinations = []interface{}{"http://*****:*****@localhost:9092"} case subChanges.NoPassword: destinations = []interface{}{"http://~subscriber@localhost:9092"} case subChanges.WrongCluster: destinations = testDestinationsWithTokensForCluster("wrong") default: if tc.useTokens { destinations = testDestinationsWithTokensForCluster(clusterName) } else { destinations = []interface{}{testDestination} } } series.Values = append(series.Values, []interface{}{ testSubName, rp, subMode, destinations, }) } result.Series = append(result.Series, series) } return &influxcli.Response{Results: []influxcli.Result{result}}, nil case strings.HasPrefix(q.Command, "CREATE SUBSCRIPTION"): createSubs[q.Command] = true return &influxcli.Response{}, nil case strings.HasPrefix(q.Command, "DROP SUBSCRIPTION"): dropSubs[q.Command] = true return &influxcli.Response{}, nil default: msg := fmt.Sprintf("unexpected query: %s", q.Command) t.Error(msg) return nil, errors.New(msg) } } as.ListSubscriptionTokensFunc = func() ([]string, error) { ts := make([]string, len(tokens)) for i, token := range tokens { ts[i] = base64.RawURLEncoding.EncodeToString([]byte(token)) } return ts, nil } as.GrantSubscriptionAccessFunc = func(token, db, rp string) error { raw, err := base64.RawURLEncoding.DecodeString(token) if err != nil { return err } log.Println("granted token:", string(raw), db, rp) grantedTokens[tokenGrant{ token: string(raw), db: db, rp: rp, }] = true return nil } as.RevokeSubscriptionAccessFunc = func(token string) error { raw, err := base64.RawURLEncoding.DecodeString(token) if err != nil { return err } log.Println("revoked token:", string(raw)) revokedTokens[string(raw)] = true return nil } // Run first round activeClusters = tc.clusters tokens = tc.tokens subChanges = tc.subChanged createSubs = make(map[string]bool) dropSubs = make(map[string]bool) grantedTokens = make(map[tokenGrant]bool) revokedTokens = make(map[string]bool) log.Println("D! first round") if err := s.Open(); err != nil { t.Fatal(err) } defer s.Close() validate( t, testName+"-1", tc.createSubs, tc.dropSubs, tc.grantedTokens, tc.revokedTokens, createSubs, dropSubs, grantedTokens, revokedTokens, ) // Run second round activeClusters = tc.secondClusters tokens = tc.secondTokens subChanges = tc.secondSubChanged createSubs = make(map[string]bool) dropSubs = make(map[string]bool) grantedTokens = make(map[tokenGrant]bool) revokedTokens = make(map[string]bool) log.Println("D! second round") if len(tc.partialConfigs) > 0 { configs := make([]interface{}, 0, len(tc.partialConfigs)) for name, pc := range tc.partialConfigs { c := defaultConfigs[clusterNameLookup[name]] c.Subscriptions = pc.configSubs c.ExcludedSubscriptions = pc.configExSubs configs = append(configs, c) } if err := s.Update(configs); err != nil { t.Fatal(err) } } s.LinkSubscriptions() validate( t, testName+"-2", tc.secondCreateSubs, tc.secondDropSubs, tc.secondGrantedTokens, tc.secondRevokedTokens, createSubs, dropSubs, grantedTokens, revokedTokens, ) } }