From 54cf820e9115d8908de072c2e6a696e4bf1d3882 Mon Sep 17 00:00:00 2001 From: John Shahid Date: Thu, 6 Feb 2014 14:08:35 -0500 Subject: [PATCH 01/15] limit the number of keys in the delete batch. --- src/datastore/leveldb_datastore.go | 11 +++++++++ src/integration/benchmark_test.go | 37 ++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/src/datastore/leveldb_datastore.go b/src/datastore/leveldb_datastore.go index 2eed8a7c7fb..370f9993e22 100644 --- a/src/datastore/leveldb_datastore.go +++ b/src/datastore/leveldb_datastore.go @@ -621,12 +621,23 @@ func (self *LevelDbDatastore) deleteRangeOfSeriesCommon(database, series string, } } } + count := 0 for it = it; it.Valid(); it.Next() { k := it.Key() if len(k) < 16 || !bytes.Equal(k[:8], field.Id) || bytes.Compare(k[8:16], endTimeBytes) == 1 { break } wb.Delete(k) + count++ + // delete every one million keys which is approximately 24 megabytes + if count == ONE_MEGABYTE { + err = self.db.Write(self.writeOptions, wb) + if err != nil { + return err + } + wb.Clear() + count = 0 + } endKey = k } err = self.db.Write(self.writeOptions, wb) diff --git a/src/integration/benchmark_test.go b/src/integration/benchmark_test.go index f23e10729d8..486fdae346b 100644 --- a/src/integration/benchmark_test.go +++ b/src/integration/benchmark_test.go @@ -935,6 +935,43 @@ func (self *IntegrationSuite) TestDeleteQuery(c *C) { } } +func (self *IntegrationSuite) TestLargeDeletes(c *C) { + numberOfPoints := 2 * 1024 * 1024 + points := []interface{}{} + for i := 0; i < numberOfPoints; i++ { + points = append(points, []interface{}{i}) + } + pointsString, _ := json.Marshal(points) + err := self.server.WriteData(fmt.Sprintf(` +[ + { + "name": "test_large_deletes", + "columns": ["val1"], + "points":%s + } +]`, string(pointsString))) + c.Assert(err, IsNil) + bs, err := self.server.RunQuery("select count(val1) from test_large_deletes", "m") + c.Assert(err, IsNil) + data := []*h.SerializedSeries{} + err = json.Unmarshal(bs, &data) + c.Assert(data, HasLen, 1) + c.Assert(data[0].Points, HasLen, 1) + c.Assert(data[0].Points[0][1], Equals, float64(numberOfPoints)) + + query := "delete from test_large_deletes" + _, err = self.server.RunQuery(query, "m") + c.Assert(err, IsNil) + + // this shouldn't return any data + bs, err = self.server.RunQuery("select count(val1) from test_large_deletes", "m") + c.Assert(err, IsNil) + data = []*h.SerializedSeries{} + err = json.Unmarshal(bs, &data) + c.Assert(err, IsNil) + c.Assert(data, HasLen, 0) +} + func (self *IntegrationSuite) TestReading(c *C) { if !*benchmark { c.Skip("Benchmarking is disabled") From e9990940e42448854b2cdbc0ad891e116a1b2846 Mon Sep 17 00:00:00 2001 From: Paul Dix Date: Thu, 6 Feb 2014 14:34:29 -0500 Subject: [PATCH 02/15] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d3998c6e050..38d630bd57e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -229,4 +229,6 @@ ### Bugfixes +- Ensure large deletes don't take too much memory + ### Features From a86e6ed7a44a8e6a99cd46a54b13cc885605a0da Mon Sep 17 00:00:00 2001 From: John Shahid Date: Mon, 10 Feb 2014 14:04:14 -0500 Subject: [PATCH 03/15] build protobuf before running the tests --- Makefile.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.in b/Makefile.in index fe1c8945b2a..c8568ffecde 100644 --- a/Makefile.in +++ b/Makefile.in @@ -161,7 +161,7 @@ endif timeout = 10m GOTEST_OPTS += -test.timeout=$(timeout) -test: test_dependencies parser +test: test_dependencies parser protobuf $(GO) test $(packages) $(GOTEST_OPTS) coverage: test_dependencies From 2ea95aadda03d4a60e547a0002d9735129ce8106 Mon Sep 17 00:00:00 2001 From: John Shahid Date: Mon, 10 Feb 2014 14:12:21 -0500 Subject: [PATCH 04/15] fix #240. Selecting columns with dots in their names don't work Don't always assume that column names with dots have the format `table_name.column_name`. Instead, we check whether `table_name` exists and if it doesn't we treat the full name as a column name. --- src/integration/benchmark_test.go | 35 +++++++++++++++++++++++++++++++ src/parser/parser_test.go | 18 ++++++++++++++++ src/parser/query_api.go | 18 ++++++++++++++++ 3 files changed, 71 insertions(+) diff --git a/src/integration/benchmark_test.go b/src/integration/benchmark_test.go index 486fdae346b..cc8ddff22ba 100644 --- a/src/integration/benchmark_test.go +++ b/src/integration/benchmark_test.go @@ -1004,6 +1004,41 @@ func (self *IntegrationSuite) TestReading(c *C) { } } +func (self *IntegrationSuite) TestReadingWhenColumnHasDot(c *C) { + err := self.server.WriteData(` +[ + { + "name": "test_column_names_with_dots", + "columns": ["first.name", "last.name"], + "points": [["paul", "dix"], ["john", "shahid"]] + } +]`) + c.Assert(err, IsNil) + + for name, expected := range map[string]map[string]bool{ + "first.name": map[string]bool{"paul": true, "john": true}, + "last.name": map[string]bool{"dix": true, "shahid": true}, + } { + q := fmt.Sprintf("select %s from test_column_names_with_dots", name) + + bs, err := self.server.RunQuery(q, "m") + c.Assert(err, IsNil) + + data := []*h.SerializedSeries{} + err = json.Unmarshal(bs, &data) + c.Assert(err, IsNil) + + c.Assert(data, HasLen, 1) + c.Assert(data[0].Columns, HasLen, 3) // time, sequence number and the requested columns + c.Assert(data[0].Columns[2], Equals, name) + names := map[string]bool{} + for _, p := range data[0].Points { + names[p[2].(string)] = true + } + c.Assert(names, DeepEquals, expected) + } +} + func (self *IntegrationSuite) TestSinglePointSelect(c *C) { err := self.server.WriteData(` [ diff --git a/src/parser/parser_test.go b/src/parser/parser_test.go index 3d8decaf219..571940d581d 100644 --- a/src/parser/parser_test.go +++ b/src/parser/parser_test.go @@ -73,6 +73,24 @@ func (self *QueryParserSuite) TestParseDeleteQueryWithEndTime(c *C) { c.Assert(q.GetEndTime(), Equals, time.Unix(1389040522, 0).UTC()) } +func (self *QueryParserSuite) TestParseSelectQueryWithDotInColumnName(c *C) { + query := "select patient.first.name from foo" + queries, err := ParseQuery(query) + c.Assert(err, IsNil) + + c.Assert(queries, HasLen, 1) + + _q := queries[0] + + c.Assert(_q.SelectQuery, NotNil) + + q := _q.SelectQuery + + for _, columns := range q.GetReferencedColumns() { + c.Assert(columns, DeepEquals, []string{"patient.first.name"}) + } +} + func (self *QueryParserSuite) TestParseDropSeries(c *C) { query := "drop series foobar" queries, err := ParseQuery(query) diff --git a/src/parser/query_api.go b/src/parser/query_api.go index 3636ac5638b..f9f331747d7 100644 --- a/src/parser/query_api.go +++ b/src/parser/query_api.go @@ -158,6 +158,24 @@ func (self *SelectQuery) GetReferencedColumns() map[*Value][]string { delete(mapping, name) } + if len(mapping) == 0 { + return returnedMapping + } + + // if `mapping` still have some mappings, then we have mistaken a + // column name with dots with a prefix.column, see issue #240 + for prefix, columnNames := range mapping { + for _, columnName := range columnNames { + for table, columns := range returnedMapping { + if len(returnedMapping[table]) > 1 && returnedMapping[table][0] == "*" { + continue + } + returnedMapping[table] = append(columns, prefix+"."+columnName) + } + } + delete(mapping, prefix) + } + return returnedMapping } From 7d3b31b885a25757fa4a73f2b2c7d44422c30094 Mon Sep 17 00:00:00 2001 From: Paul Dix Date: Mon, 10 Feb 2014 14:21:25 -0500 Subject: [PATCH 05/15] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 38d630bd57e..8e9671623a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -230,5 +230,6 @@ ### Bugfixes - Ensure large deletes don't take too much memory +- [Issue #240](https://github.com/influxdb/influxdb/pull/240). Unable to query against columns with `.` in the name. ### Features From 6d44c5d394a1a65ad58128bd2bf9377ab6cdd2b9 Mon Sep 17 00:00:00 2001 From: Todd Persen Date: Mon, 10 Feb 2014 17:10:05 -0500 Subject: [PATCH 06/15] Clean up variable naming. --- src/api/http/api_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/api/http/api_test.go b/src/api/http/api_test.go index 53ad494d445..05b67ab230c 100644 --- a/src/api/http/api_test.go +++ b/src/api/http/api_test.go @@ -753,10 +753,10 @@ func (self *ApiSuite) TestDatabasesIndex(c *C) { defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) c.Assert(err, IsNil) - users := []*coordinator.Database{} - err = json.Unmarshal(body, &users) + databases := []*coordinator.Database{} + err = json.Unmarshal(body, &databases) c.Assert(err, IsNil) - c.Assert(users, DeepEquals, []*coordinator.Database{&coordinator.Database{"db1", uint8(1)}, &coordinator.Database{"db2", uint8(1)}}) + c.Assert(databases, DeepEquals, []*coordinator.Database{&coordinator.Database{"db1", uint8(1)}, &coordinator.Database{"db2", uint8(1)}}) } } @@ -771,10 +771,10 @@ func (self *ApiSuite) TestBasicAuthentication(c *C) { body, err := ioutil.ReadAll(resp.Body) c.Assert(err, IsNil) c.Assert(resp.StatusCode, Equals, libhttp.StatusOK) - users := []*coordinator.Database{} - err = json.Unmarshal(body, &users) + databases := []*coordinator.Database{} + err = json.Unmarshal(body, &databases) c.Assert(err, IsNil) - c.Assert(users, DeepEquals, []*coordinator.Database{&coordinator.Database{"db1", 1}, &coordinator.Database{"db2", 1}}) + c.Assert(databases, DeepEquals, []*coordinator.Database{&coordinator.Database{"db1", 1}, &coordinator.Database{"db2", 1}}) } func (self *ApiSuite) TestContinuousQueryOperations(c *C) { From 5d30d2ff37b335d930700a7b8b1cf68cd87358d4 Mon Sep 17 00:00:00 2001 From: Todd Persen Date: Mon, 10 Feb 2014 17:10:48 -0500 Subject: [PATCH 07/15] Add API call to return user details. --- src/api/http/api.go | 23 +++++++++++++++++++++++ src/api/http/api_test.go | 14 ++++++++++++++ src/api/http/mock_user_manager_test.go | 24 ++++++++++++++++++++++++ src/coordinator/coordinator.go | 13 +++++++++++++ src/coordinator/coordinator_test.go | 11 +++++++++++ src/coordinator/interface.go | 1 + 6 files changed, 86 insertions(+) diff --git a/src/api/http/api.go b/src/api/http/api.go index 98e366a1aea..414db80c829 100644 --- a/src/api/http/api.go +++ b/src/api/http/api.go @@ -119,6 +119,7 @@ func (self *HttpServer) Serve(listener net.Listener) { self.registerEndpoint(p, "get", "/db/:db/authenticate", self.authenticateDbUser) self.registerEndpoint(p, "get", "/db/:db/users", self.listDbUsers) self.registerEndpoint(p, "post", "/db/:db/users", self.createDbUser) + self.registerEndpoint(p, "get", "/db/:db/users/:user", self.showDbUser) self.registerEndpoint(p, "del", "/db/:db/users/:user", self.deleteDbUser) self.registerEndpoint(p, "post", "/db/:db/users/:user", self.updateDbUser) @@ -693,6 +694,11 @@ type User struct { Name string `json:"username"` } +type UserDetail struct { + Name string `json:"username"` + IsAdmin bool `json:"isAdmin"` +} + type NewContinuousQuery struct { Query string `json:"query"` } @@ -855,6 +861,23 @@ func (self *HttpServer) listDbUsers(w libhttp.ResponseWriter, r *libhttp.Request }) } +func (self *HttpServer) showDbUser(w libhttp.ResponseWriter, r *libhttp.Request) { + db := r.URL.Query().Get(":db") + username := r.URL.Query().Get(":user") + + self.tryAsDbUserAndClusterAdmin(w, r, func(u common.User) (int, interface{}) { + user, err := self.userManager.GetDbUser(u, db, username) + if err != nil { + return errorToStatusCode(err), err.Error() + } + + userDetail := &UserDetail{username, user.IsDbAdmin(db)} + fmt.Println(userDetail) + + return libhttp.StatusOK, userDetail + }) +} + func (self *HttpServer) createDbUser(w libhttp.ResponseWriter, r *libhttp.Request) { body, err := ioutil.ReadAll(r.Body) if err != nil { diff --git a/src/api/http/api_test.go b/src/api/http/api_test.go index 05b67ab230c..c33c5af7a46 100644 --- a/src/api/http/api_test.go +++ b/src/api/http/api_test.go @@ -744,6 +744,20 @@ func (self *ApiSuite) TestDbUsersIndex(c *C) { c.Assert(users, DeepEquals, []*User{&User{"db_user1"}}) } +func (self *ApiSuite) TestDbUserShow(c *C) { + url := self.formatUrl("/db/db1/users/db_user1?u=root&p=root") + resp, err := libhttp.Get(url) + c.Assert(err, IsNil) + c.Assert(resp.Header.Get("content-type"), Equals, "application/json") + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + c.Assert(err, IsNil) + userDetail := &UserDetail{} + err = json.Unmarshal(body, &userDetail) + c.Assert(err, IsNil) + c.Assert(userDetail, DeepEquals, &UserDetail{"db_user1", false}) +} + func (self *ApiSuite) TestDatabasesIndex(c *C) { for _, path := range []string{"/db?u=root&p=root", "/db?u=root&p=root"} { url := self.formatUrl(path) diff --git a/src/api/http/mock_user_manager_test.go b/src/api/http/mock_user_manager_test.go index 3aeddb88b34..fa2c357de66 100644 --- a/src/api/http/mock_user_manager_test.go +++ b/src/api/http/mock_user_manager_test.go @@ -12,6 +12,16 @@ type Operation struct { isAdmin bool } +type MockUser struct { + common.User + Name string + IsAdmin bool +} + +func (self MockUser) IsDbAdmin(_ string) bool { + return self.IsAdmin +} + type MockUserManager struct { dbUsers map[string][]string clusterAdmins []string @@ -29,6 +39,7 @@ func (self *MockUserManager) AuthenticateDbUser(db, username, password string) ( return nil, nil } + func (self *MockUserManager) AuthenticateClusterAdmin(username, password string) (common.User, error) { if username == "fail_auth" { return nil, fmt.Errorf("Invalid username/password") @@ -40,6 +51,7 @@ func (self *MockUserManager) AuthenticateClusterAdmin(username, password string) return nil, nil } + func (self *MockUserManager) CreateClusterAdminUser(request common.User, username string) error { if username == "" { return fmt.Errorf("Invalid empty username") @@ -48,14 +60,17 @@ func (self *MockUserManager) CreateClusterAdminUser(request common.User, usernam self.ops = append(self.ops, &Operation{"cluster_admin_add", username, "", false}) return nil } + func (self *MockUserManager) DeleteClusterAdminUser(requester common.User, username string) error { self.ops = append(self.ops, &Operation{"cluster_admin_del", username, "", false}) return nil } + func (self *MockUserManager) ChangeClusterAdminPassword(requester common.User, username, password string) error { self.ops = append(self.ops, &Operation{"cluster_admin_passwd", username, password, false}) return nil } + func (self *MockUserManager) CreateDbUser(request common.User, db, username string) error { if username == "" { return fmt.Errorf("Invalid empty username") @@ -64,21 +79,30 @@ func (self *MockUserManager) CreateDbUser(request common.User, db, username stri self.ops = append(self.ops, &Operation{"db_user_add", username, "", false}) return nil } + func (self *MockUserManager) DeleteDbUser(requester common.User, db, username string) error { self.ops = append(self.ops, &Operation{"db_user_del", username, "", false}) return nil } + func (self *MockUserManager) ChangeDbUserPassword(requester common.User, db, username, password string) error { self.ops = append(self.ops, &Operation{"db_user_passwd", username, password, false}) return nil } + func (self *MockUserManager) SetDbAdmin(requester common.User, db, username string, isAdmin bool) error { self.ops = append(self.ops, &Operation{"db_user_admin", username, "", isAdmin}) return nil } + func (self *MockUserManager) ListClusterAdmins(requester common.User) ([]string, error) { return self.clusterAdmins, nil } + func (self *MockUserManager) ListDbUsers(requester common.User, db string) ([]string, error) { return self.dbUsers[db], nil } + +func (self *MockUserManager) GetDbUser(requester common.User, db, username string) (common.User, error) { + return MockUser{Name: username, IsAdmin: false}, nil +} diff --git a/src/coordinator/coordinator.go b/src/coordinator/coordinator.go index a874f3dfe67..8929e16cd2c 100644 --- a/src/coordinator/coordinator.go +++ b/src/coordinator/coordinator.go @@ -1233,6 +1233,19 @@ func (self *CoordinatorImpl) ListDbUsers(requester common.User, db string) ([]st return self.clusterConfiguration.GetDbUsers(db), nil } +func (self *CoordinatorImpl) GetDbUser(requester common.User, db string, username string) (common.User, error) { + if !requester.IsClusterAdmin() && !requester.IsDbAdmin(db) { + return nil, common.NewAuthorizationError("Insufficient permissions") + } + + dbUsers := self.clusterConfiguration.dbUsers[db] + if dbUsers == nil || dbUsers[username] == nil { + return nil, fmt.Errorf("Invalid username %s", username) + } + + return dbUsers[username], nil +} + func (self *CoordinatorImpl) ChangeDbUserPassword(requester common.User, db, username, password string) error { if !requester.IsClusterAdmin() && !requester.IsDbAdmin(db) && !(requester.GetDb() == db && requester.GetName() == username) { return common.NewAuthorizationError("Insufficient permissions") diff --git a/src/coordinator/coordinator_test.go b/src/coordinator/coordinator_test.go index 59e7047fe80..46ba8151dcd 100644 --- a/src/coordinator/coordinator_test.go +++ b/src/coordinator/coordinator_test.go @@ -367,6 +367,17 @@ func (self *CoordinatorSuite) TestAdminOperations(c *C) { c.Assert(u.IsClusterAdmin(), Equals, false) c.Assert(u.IsDbAdmin("db1"), Equals, false) + // can get properties of db users + dbUser, err := coordinator.GetDbUser(root, "db1", "db_user") + c.Assert(err, IsNil) + c.Assert(dbUser, NotNil) + c.Assert(dbUser.GetName(), Equals, "db_user") + c.Assert(dbUser.IsDbAdmin("db1"), Equals, false) + + dbUser, err = coordinator.GetDbUser(root, "db1", "invalid_user") + c.Assert(err, NotNil) + c.Assert(err, ErrorMatches, "Invalid username invalid_user") + // can make db users db admins c.Assert(coordinator.SetDbAdmin(root, "db1", "db_user", true), IsNil) u, err = coordinator.AuthenticateDbUser("db1", "db_user", "db_pass") diff --git a/src/coordinator/interface.go b/src/coordinator/interface.go index 14f2c301f7f..c28027818c6 100644 --- a/src/coordinator/interface.go +++ b/src/coordinator/interface.go @@ -56,6 +56,7 @@ type UserManager interface { ChangeDbUserPassword(requester common.User, db, username, password string) error // list cluster admins. only a cluster admin or the db admin can list the db users ListDbUsers(requester common.User, db string) ([]string, error) + GetDbUser(requester common.User, db, username string) (common.User, error) // make user a db admin for 'db'. It's an error if the requester // isn't a db admin or cluster admin or if user isn't a db user // for the given db From e9d3623226e1bed2387a365df1cde71c0d18dd77 Mon Sep 17 00:00:00 2001 From: Todd Persen Date: Mon, 10 Feb 2014 17:15:08 -0500 Subject: [PATCH 08/15] Change `username` to `name`. --- src/api/http/api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/http/api.go b/src/api/http/api.go index 414db80c829..28c19255415 100644 --- a/src/api/http/api.go +++ b/src/api/http/api.go @@ -695,7 +695,7 @@ type User struct { } type UserDetail struct { - Name string `json:"username"` + Name string `json:"name"` IsAdmin bool `json:"isAdmin"` } From 7e2405921d64f06d79c19cc213486ce4b0262a55 Mon Sep 17 00:00:00 2001 From: Paul Dix Date: Mon, 10 Feb 2014 17:26:45 -0500 Subject: [PATCH 09/15] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e9671623a6..01f4dd78244 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -233,3 +233,5 @@ - [Issue #240](https://github.com/influxdb/influxdb/pull/240). Unable to query against columns with `.` in the name. ### Features + +- [Issue #243](https://github.com/influxdb/influxdb/issues/243). Should have endpoint to GET a user's attributes. \ No newline at end of file From 3c0c605124b674da7dc54dfdba063a1f139f3c68 Mon Sep 17 00:00:00 2001 From: Todd Persen Date: Mon, 10 Feb 2014 22:03:28 -0500 Subject: [PATCH 10/15] Fix #246. Add user details to index endpoint. --- src/api/http/api.go | 11 +++--- src/api/http/api_test.go | 8 ++-- src/api/http/mock_user_manager_test.go | 48 ++++++++++++++++++++---- src/coordinator/cluster_configuration.go | 8 ++-- src/coordinator/coordinator.go | 4 +- src/coordinator/coordinator_test.go | 13 ++++--- src/coordinator/interface.go | 2 +- 7 files changed, 66 insertions(+), 28 deletions(-) diff --git a/src/api/http/api.go b/src/api/http/api.go index 28c19255415..cfe7ca224f1 100644 --- a/src/api/http/api.go +++ b/src/api/http/api.go @@ -848,14 +848,14 @@ func (self *HttpServer) listDbUsers(w libhttp.ResponseWriter, r *libhttp.Request db := r.URL.Query().Get(":db") self.tryAsDbUserAndClusterAdmin(w, r, func(u common.User) (int, interface{}) { - names, err := self.userManager.ListDbUsers(u, db) + dbUsers, err := self.userManager.ListDbUsers(u, db) if err != nil { return errorToStatusCode(err), err.Error() } - users := make([]*User, 0, len(names)) - for _, name := range names { - users = append(users, &User{name}) + users := make([]*UserDetail, 0, len(dbUsers)) + for _, dbUser := range dbUsers { + users = append(users, &UserDetail{dbUser.GetName(), dbUser.IsDbAdmin(db)}) } return libhttp.StatusOK, users }) @@ -871,8 +871,7 @@ func (self *HttpServer) showDbUser(w libhttp.ResponseWriter, r *libhttp.Request) return errorToStatusCode(err), err.Error() } - userDetail := &UserDetail{username, user.IsDbAdmin(db)} - fmt.Println(userDetail) + userDetail := &UserDetail{user.GetName(), user.IsDbAdmin(db)} return libhttp.StatusOK, userDetail }) diff --git a/src/api/http/api_test.go b/src/api/http/api_test.go index c33c5af7a46..7e6e8d9d431 100644 --- a/src/api/http/api_test.go +++ b/src/api/http/api_test.go @@ -178,9 +178,10 @@ func (self *ApiSuite) SetUpSuite(c *C) { }, }, } + self.manager = &MockUserManager{ clusterAdmins: []string{"root"}, - dbUsers: map[string][]string{"db1": []string{"db_user1"}}, + dbUsers: map[string]map[string]MockDbUser{"db1": map[string]MockDbUser{"db_user1": {Name: "db_user1", IsAdmin: false}}}, } self.engine = &MockEngine{} dir := c.MkDir() @@ -738,10 +739,11 @@ func (self *ApiSuite) TestDbUsersIndex(c *C) { defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) c.Assert(err, IsNil) - users := []*User{} + users := []*UserDetail{} err = json.Unmarshal(body, &users) c.Assert(err, IsNil) - c.Assert(users, DeepEquals, []*User{&User{"db_user1"}}) + c.Assert(users, HasLen, 1) + c.Assert(users[0], DeepEquals, &UserDetail{"db_user1", false}) } func (self *ApiSuite) TestDbUserShow(c *C) { diff --git a/src/api/http/mock_user_manager_test.go b/src/api/http/mock_user_manager_test.go index fa2c357de66..82c3930d0f5 100644 --- a/src/api/http/mock_user_manager_test.go +++ b/src/api/http/mock_user_manager_test.go @@ -12,18 +12,41 @@ type Operation struct { isAdmin bool } -type MockUser struct { - common.User +type MockDbUser struct { Name string IsAdmin bool } -func (self MockUser) IsDbAdmin(_ string) bool { +func (self MockDbUser) GetName() string { + return self.Name +} + +func (self MockDbUser) IsDeleted() bool { + return false +} + +func (self MockDbUser) IsClusterAdmin() bool { + return false +} + +func (self MockDbUser) IsDbAdmin(_ string) bool { return self.IsAdmin } +func (self MockDbUser) GetDb() string { + return "" +} + +func (self MockDbUser) HasWriteAccess(_ string) bool { + return true +} + +func (self MockDbUser) HasReadAccess(_ string) bool { + return true +} + type MockUserManager struct { - dbUsers map[string][]string + dbUsers map[string]map[string]MockDbUser clusterAdmins []string ops []*Operation } @@ -99,10 +122,21 @@ func (self *MockUserManager) ListClusterAdmins(requester common.User) ([]string, return self.clusterAdmins, nil } -func (self *MockUserManager) ListDbUsers(requester common.User, db string) ([]string, error) { - return self.dbUsers[db], nil +func (self *MockUserManager) ListDbUsers(requester common.User, db string) ([]common.User, error) { + dbUsers := self.dbUsers[db] + users := make([]common.User, 0, len(dbUsers)) + for _, user := range dbUsers { + users = append(users, user) + } + + return users, nil } func (self *MockUserManager) GetDbUser(requester common.User, db, username string) (common.User, error) { - return MockUser{Name: username, IsAdmin: false}, nil + dbUsers := self.dbUsers[db] + if dbUser, ok := dbUsers[username]; ok { + return MockDbUser{Name: dbUser.GetName(), IsAdmin: dbUser.IsDbAdmin(db)}, nil + } else { + return nil, fmt.Errorf("'%s' is not a valid username for database '%s'", username, db) + } } diff --git a/src/coordinator/cluster_configuration.go b/src/coordinator/cluster_configuration.go index 32d27ad9b14..b5b3bf78f07 100644 --- a/src/coordinator/cluster_configuration.go +++ b/src/coordinator/cluster_configuration.go @@ -420,15 +420,17 @@ func (self *ClusterConfiguration) GetContinuousQueries(db string) []*ContinuousQ return self.continuousQueries[db] } -func (self *ClusterConfiguration) GetDbUsers(db string) (names []string) { +func (self *ClusterConfiguration) GetDbUsers(db string) []*dbUser { self.usersLock.RLock() defer self.usersLock.RUnlock() dbUsers := self.dbUsers[db] + users := make([]*dbUser, 0, len(dbUsers)) for name, _ := range dbUsers { - names = append(names, name) + fmt.Println(name) + users = append(users, dbUsers[name]) } - return + return users } func (self *ClusterConfiguration) GetDbUser(db, username string) *dbUser { diff --git a/src/coordinator/coordinator.go b/src/coordinator/coordinator.go index 8929e16cd2c..8a2399c81f3 100644 --- a/src/coordinator/coordinator.go +++ b/src/coordinator/coordinator.go @@ -1225,7 +1225,7 @@ func (self *CoordinatorImpl) DeleteDbUser(requester common.User, db, username st return self.raftServer.SaveDbUser(user) } -func (self *CoordinatorImpl) ListDbUsers(requester common.User, db string) ([]string, error) { +func (self *CoordinatorImpl) ListDbUsers(requester common.User, db string) ([]*dbUser, error) { if !requester.IsClusterAdmin() && !requester.IsDbAdmin(db) { return nil, common.NewAuthorizationError("Insufficient permissions") } @@ -1233,7 +1233,7 @@ func (self *CoordinatorImpl) ListDbUsers(requester common.User, db string) ([]st return self.clusterConfiguration.GetDbUsers(db), nil } -func (self *CoordinatorImpl) GetDbUser(requester common.User, db string, username string) (common.User, error) { +func (self *CoordinatorImpl) GetDbUser(requester common.User, db string, username string) (*dbUser, error) { if !requester.IsClusterAdmin() && !requester.IsDbAdmin(db) { return nil, common.NewAuthorizationError("Insufficient permissions") } diff --git a/src/coordinator/coordinator_test.go b/src/coordinator/coordinator_test.go index 46ba8151dcd..190d10e4893 100644 --- a/src/coordinator/coordinator_test.go +++ b/src/coordinator/coordinator_test.go @@ -387,7 +387,9 @@ func (self *CoordinatorSuite) TestAdminOperations(c *C) { // can list db users dbUsers, err := coordinator.ListDbUsers(root, "db1") c.Assert(err, IsNil) - c.Assert(dbUsers, DeepEquals, []string{"db_user"}) + c.Assert(dbUsers, HasLen, 1) + c.Assert(dbUsers[0].GetName(), Equals, "db_user") + c.Assert(dbUsers[0].IsDbAdmin("db1"), Equals, true) // can delete cluster admins and db users c.Assert(coordinator.DeleteDbUser(root, "db1", "db_user"), IsNil) @@ -487,11 +489,10 @@ func (self *CoordinatorSuite) TestDbAdminOperations(c *C) { // can get db users admins, err := coordinator.ListDbUsers(dbUser, "db1") c.Assert(err, IsNil) - adminsSet := map[string]bool{} - for _, admin := range admins { - adminsSet[admin] = true - } - c.Assert(adminsSet, DeepEquals, map[string]bool{"db_user": true, "db_user2": true}) + c.Assert(admins[0].GetName(), Equals, "db_user") + c.Assert(admins[0].IsDbAdmin("db1"), Equals, true) + c.Assert(admins[1].GetName(), Equals, "db_user2") + c.Assert(admins[1].IsDbAdmin("db1"), Equals, true) // cannot create db users for a different db c.Assert(coordinator.CreateDbUser(dbUser, "db2", "db_user"), NotNil) diff --git a/src/coordinator/interface.go b/src/coordinator/interface.go index c28027818c6..b6685e2d4ed 100644 --- a/src/coordinator/interface.go +++ b/src/coordinator/interface.go @@ -55,7 +55,7 @@ type UserManager interface { // Change db user's password. It's an error if requester isn't a cluster admin or db admin ChangeDbUserPassword(requester common.User, db, username, password string) error // list cluster admins. only a cluster admin or the db admin can list the db users - ListDbUsers(requester common.User, db string) ([]string, error) + ListDbUsers(requester common.User, db string) ([]common.User, error) GetDbUser(requester common.User, db, username string) (common.User, error) // make user a db admin for 'db'. It's an error if the requester // isn't a db admin or cluster admin or if user isn't a db user From 2eb922cf2f15e0d308c037bb36daa1c5bc406fe7 Mon Sep 17 00:00:00 2001 From: Todd Persen Date: Thu, 13 Feb 2014 17:47:37 -0500 Subject: [PATCH 11/15] User common.User interface instead of coordinator.dbUser --- src/coordinator/cluster_configuration.go | 10 +++++----- src/coordinator/coordinator.go | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/coordinator/cluster_configuration.go b/src/coordinator/cluster_configuration.go index b5b3bf78f07..23238002e7c 100644 --- a/src/coordinator/cluster_configuration.go +++ b/src/coordinator/cluster_configuration.go @@ -420,20 +420,20 @@ func (self *ClusterConfiguration) GetContinuousQueries(db string) []*ContinuousQ return self.continuousQueries[db] } -func (self *ClusterConfiguration) GetDbUsers(db string) []*dbUser { +func (self *ClusterConfiguration) GetDbUsers(db string) []common.User { self.usersLock.RLock() defer self.usersLock.RUnlock() dbUsers := self.dbUsers[db] - users := make([]*dbUser, 0, len(dbUsers)) + users := make([]common.User, 0, len(dbUsers)) for name, _ := range dbUsers { - fmt.Println(name) - users = append(users, dbUsers[name]) + dbUser := dbUsers[name] + users = append(users, dbUser) } return users } -func (self *ClusterConfiguration) GetDbUser(db, username string) *dbUser { +func (self *ClusterConfiguration) GetDbUser(db, username string) common.User { self.usersLock.RLock() defer self.usersLock.RUnlock() diff --git a/src/coordinator/coordinator.go b/src/coordinator/coordinator.go index 8a2399c81f3..41bab48ac48 100644 --- a/src/coordinator/coordinator.go +++ b/src/coordinator/coordinator.go @@ -1225,7 +1225,7 @@ func (self *CoordinatorImpl) DeleteDbUser(requester common.User, db, username st return self.raftServer.SaveDbUser(user) } -func (self *CoordinatorImpl) ListDbUsers(requester common.User, db string) ([]*dbUser, error) { +func (self *CoordinatorImpl) ListDbUsers(requester common.User, db string) ([]common.User, error) { if !requester.IsClusterAdmin() && !requester.IsDbAdmin(db) { return nil, common.NewAuthorizationError("Insufficient permissions") } @@ -1233,7 +1233,7 @@ func (self *CoordinatorImpl) ListDbUsers(requester common.User, db string) ([]*d return self.clusterConfiguration.GetDbUsers(db), nil } -func (self *CoordinatorImpl) GetDbUser(requester common.User, db string, username string) (*dbUser, error) { +func (self *CoordinatorImpl) GetDbUser(requester common.User, db string, username string) (common.User, error) { if !requester.IsClusterAdmin() && !requester.IsDbAdmin(db) { return nil, common.NewAuthorizationError("Insufficient permissions") } From 1c4860710342618bf75fd7511959ab06c6f188c7 Mon Sep 17 00:00:00 2001 From: John Shahid Date: Mon, 10 Feb 2014 14:48:51 -0500 Subject: [PATCH 12/15] kill a file that we're not using anymore --- VERSION | 1 - 1 file changed, 1 deletion(-) delete mode 100644 VERSION diff --git a/VERSION b/VERSION deleted file mode 100644 index d4720952217..00000000000 --- a/VERSION +++ /dev/null @@ -1 +0,0 @@ -0.0.2.dev From 6ef724c522ccbae7a84d07ab602e9773919419e8 Mon Sep 17 00:00:00 2001 From: John Shahid Date: Fri, 14 Feb 2014 13:08:41 -0500 Subject: [PATCH 13/15] move the parse duration code in its own func --- src/parser/query_api.go | 66 +++++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 32 deletions(-) diff --git a/src/parser/query_api.go b/src/parser/query_api.go index f9f331747d7..a45931090c8 100644 --- a/src/parser/query_api.go +++ b/src/parser/query_api.go @@ -226,6 +226,39 @@ func parseTimeString(t string) (time.Time, error) { return time.Parse("2006-01-02", t) } +func parseTimeDuration(value string) (int64, error) { + parsedFloat, err := strconv.ParseFloat(value[:len(value)-1], 64) + if err != nil { + return 0, err + } + + switch value[len(value)-1] { + case 'u': + return int64(parsedFloat * float64(time.Microsecond)), nil + case 's': + return int64(parsedFloat * float64(time.Second)), nil + case 'm': + return int64(parsedFloat * float64(time.Minute)), nil + case 'h': + return int64(parsedFloat * float64(time.Hour)), nil + case 'd': + return int64(parsedFloat * 24 * float64(time.Hour)), nil + case 'w': + return int64(parsedFloat * 7 * 24 * float64(time.Hour)), nil + } + + lastChar := value[len(value)-1] + if !unicode.IsDigit(rune(lastChar)) && lastChar != '.' { + return 0, fmt.Errorf("Invalid character '%c'", lastChar) + } + + if value[len(value)-2] != '.' { + extraDigit := float64(lastChar - '0') + parsedFloat = parsedFloat*10 + extraDigit + } + return int64(parsedFloat), nil +} + // parse time expressions, e.g. now() - 1d func parseTime(value *Value) (int64, error) { if value.Type != ValueExpression { @@ -242,38 +275,7 @@ func parseTime(value *Value) (int64, error) { return t.UnixNano(), err } - name := value.Name - - parsedFloat, err := strconv.ParseFloat(name[:len(name)-1], 64) - if err != nil { - return 0, err - } - - switch name[len(name)-1] { - case 'u': - return int64(parsedFloat * float64(time.Microsecond)), nil - case 's': - return int64(parsedFloat * float64(time.Second)), nil - case 'm': - return int64(parsedFloat * float64(time.Minute)), nil - case 'h': - return int64(parsedFloat * float64(time.Hour)), nil - case 'd': - return int64(parsedFloat * 24 * float64(time.Hour)), nil - case 'w': - return int64(parsedFloat * 7 * 24 * float64(time.Hour)), nil - } - - lastChar := name[len(name)-1] - if !unicode.IsDigit(rune(lastChar)) && lastChar != '.' { - return 0, fmt.Errorf("Invalid character '%c'", lastChar) - } - - if name[len(name)-2] != '.' { - extraDigit := float64(lastChar - '0') - parsedFloat = parsedFloat*10 + extraDigit - } - return int64(parsedFloat), nil + return parseTimeDuration(value.Name) } leftValue, err := parseTime(value.Elems[0]) From 177c976370829d826b308f312deb319313036657 Mon Sep 17 00:00:00 2001 From: John Shahid Date: Fri, 14 Feb 2014 13:47:21 -0500 Subject: [PATCH 14/15] move ParseTimeDuration to common and make it public --- src/common/helpers.go | 35 +++++++++++++++++++++++++++++++++++ src/parser/query_api.go | 36 +----------------------------------- 2 files changed, 36 insertions(+), 35 deletions(-) diff --git a/src/common/helpers.go b/src/common/helpers.go index 0ae4a7f9a9c..8652e4691e5 100644 --- a/src/common/helpers.go +++ b/src/common/helpers.go @@ -8,9 +8,44 @@ import ( "fmt" "os" "protocol" + "strconv" "time" + "unicode" ) +func ParseTimeDuration(value string) (int64, error) { + parsedFloat, err := strconv.ParseFloat(value[:len(value)-1], 64) + if err != nil { + return 0, err + } + + switch value[len(value)-1] { + case 'u': + return int64(parsedFloat * float64(time.Microsecond)), nil + case 's': + return int64(parsedFloat * float64(time.Second)), nil + case 'm': + return int64(parsedFloat * float64(time.Minute)), nil + case 'h': + return int64(parsedFloat * float64(time.Hour)), nil + case 'd': + return int64(parsedFloat * 24 * float64(time.Hour)), nil + case 'w': + return int64(parsedFloat * 7 * 24 * float64(time.Hour)), nil + } + + lastChar := value[len(value)-1] + if !unicode.IsDigit(rune(lastChar)) && lastChar != '.' { + return 0, fmt.Errorf("Invalid character '%c'", lastChar) + } + + if value[len(value)-2] != '.' { + extraDigit := float64(lastChar - '0') + parsedFloat = parsedFloat*10 + extraDigit + } + return int64(parsedFloat), nil +} + func GetFileSize(path string) (int64, error) { info, err := os.Stat(path) if err != nil { diff --git a/src/parser/query_api.go b/src/parser/query_api.go index a45931090c8..d09249ea0c9 100644 --- a/src/parser/query_api.go +++ b/src/parser/query_api.go @@ -8,7 +8,6 @@ import ( "strconv" "strings" "time" - "unicode" ) // this file provides the high level api of the query object @@ -226,39 +225,6 @@ func parseTimeString(t string) (time.Time, error) { return time.Parse("2006-01-02", t) } -func parseTimeDuration(value string) (int64, error) { - parsedFloat, err := strconv.ParseFloat(value[:len(value)-1], 64) - if err != nil { - return 0, err - } - - switch value[len(value)-1] { - case 'u': - return int64(parsedFloat * float64(time.Microsecond)), nil - case 's': - return int64(parsedFloat * float64(time.Second)), nil - case 'm': - return int64(parsedFloat * float64(time.Minute)), nil - case 'h': - return int64(parsedFloat * float64(time.Hour)), nil - case 'd': - return int64(parsedFloat * 24 * float64(time.Hour)), nil - case 'w': - return int64(parsedFloat * 7 * 24 * float64(time.Hour)), nil - } - - lastChar := value[len(value)-1] - if !unicode.IsDigit(rune(lastChar)) && lastChar != '.' { - return 0, fmt.Errorf("Invalid character '%c'", lastChar) - } - - if value[len(value)-2] != '.' { - extraDigit := float64(lastChar - '0') - parsedFloat = parsedFloat*10 + extraDigit - } - return int64(parsedFloat), nil -} - // parse time expressions, e.g. now() - 1d func parseTime(value *Value) (int64, error) { if value.Type != ValueExpression { @@ -275,7 +241,7 @@ func parseTime(value *Value) (int64, error) { return t.UnixNano(), err } - return parseTimeDuration(value.Name) + return common.ParseTimeDuration(value.Name) } leftValue, err := parseTime(value.Elems[0]) From ca1d9bf16013dd2ab9e03873445a4a158669f1c5 Mon Sep 17 00:00:00 2001 From: John Shahid Date: Mon, 17 Feb 2014 14:20:13 -0500 Subject: [PATCH 15/15] fix #189. Use name instead of username when listing users --- CHANGELOG.md | 10 ++++++++-- src/api/http/api.go | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 01f4dd78244..aedc0971cce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -225,13 +225,19 @@ - Make the leveldb max open files configurable in the toml file -## v0.4.5 [unreleased] +## v0.5.0 [unreleased] ### Bugfixes - Ensure large deletes don't take too much memory - [Issue #240](https://github.com/influxdb/influxdb/pull/240). Unable to query against columns with `.` in the name. +- [Issue #189](https://github.com/influxdb/influxdb/issues/189). Deprecate more field names that were missed in 0.4.0 ### Features -- [Issue #243](https://github.com/influxdb/influxdb/issues/243). Should have endpoint to GET a user's attributes. \ No newline at end of file +- [Issue #243](https://github.com/influxdb/influxdb/ +issues/243). Should have endpoint to GET a user's attributes. + +### Deprecated + +- `/cluster_admins` and `/db/:db/users` return usernames in a `name` key instead of `username` key. diff --git a/src/api/http/api.go b/src/api/http/api.go index cfe7ca224f1..7c6ef79f850 100644 --- a/src/api/http/api.go +++ b/src/api/http/api.go @@ -691,7 +691,7 @@ type UpdateClusterAdminUser struct { } type User struct { - Name string `json:"username"` + Name string `json:"name"` } type UserDetail struct {