Skip to content

Commit e326f3a

Browse files
authored
Merge pull request #1278 from tigrisdata/main
2 parents 4ae41ad + 875ad91 commit e326f3a

File tree

11 files changed

+161
-6
lines changed

11 files changed

+161
-6
lines changed

Makefile

+3
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ local_rt_run: server
6767
fdbcli -C ./test/config/fdb.cluster --exec "configure new single memory" || true
6868
TIGRIS_SERVER_SERVER_TYPE=realtime ./server/service -c config/server.dev.yaml
6969

70+
local_debug:
71+
$(DOCKER_COMPOSE) up --no-build --detach tigris_search tigris_db2
72+
7073
# Runs tigris server and foundationdb, plus additional tools for it like:
7174
# - prometheus and grafana for monitoring
7275
run_full: coverdir

api/proto

Submodule proto updated from 2f9df79 to 7e29de0

api/server/v1/marshaler.go

+15
Original file line numberDiff line numberDiff line change
@@ -1370,6 +1370,21 @@ func (x *ChargeTier) MarshalJSON() ([]byte, error) {
13701370
return jsoniter.Marshal(resp)
13711371
}
13721372

1373+
func (x *ProjectInfo) MarshalJSON() ([]byte, error) {
1374+
resp := struct {
1375+
Project string `json:"project"`
1376+
Limits *ProjectLimits `json:"limits"`
1377+
}{
1378+
Project: x.GetProject(),
1379+
Limits: x.GetLimits(),
1380+
}
1381+
if resp.Limits == nil {
1382+
resp.Limits = &ProjectLimits{}
1383+
}
1384+
1385+
return jsoniter.Marshal(resp)
1386+
}
1387+
13731388
func unmarshalAdditionalFunction(data []byte) (*AdditionalFunction, error) {
13741389
var mp map[string]jsoniter.RawMessage
13751390

api/server/v1/validator.go

+4
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,10 @@ func (x *CreateProjectRequest) Validate() error {
166166
return isValidDatabase(x.Project)
167167
}
168168

169+
func (x *UpdateProjectRequest) Validate() error {
170+
return isValidDatabase(x.Project)
171+
}
172+
169173
func (x *DeleteProjectRequest) Validate() error {
170174
return isValidDatabase(x.Project)
171175
}

server/config/options.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ type AuthConfig struct {
107107
}
108108

109109
type Invitation struct {
110-
Enabled bool `json:"enabled" mapstructure:"enabled" yaml:"enabled"`
110+
Enabled bool `json:"enabled" mapstructure:"enabled" yaml:"enabled"`
111111
ExpireAfterSec int64 `json:"expire_after_sec" mapstructure:"expire_after_sec" yaml:"expire_after_sec"`
112112
}
113113

@@ -388,6 +388,7 @@ var DefaultConfig = Config{
388388
Compression: false,
389389
IgnoreExtraFields: false,
390390
LogFilter: false,
391+
AuthKey: "ts_test_key",
391392
},
392393
KV: KVConfig{
393394
Chunking: false,

server/metadata/namespace.go

+6
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,12 @@ type ProjectMetadata struct {
6060
CreatedAt int64
6161
CachesMetadata []CacheMetadata
6262
SearchMetadata []SearchMetadata
63+
Limits *ProjectLimits
64+
}
65+
66+
type ProjectLimits struct {
67+
MaxCollections *int32
68+
MaxSearchIndexes *int32
6369
}
6470

6571
type CacheMetadata struct {

server/metadata/tenant.go

+23
Original file line numberDiff line numberDiff line change
@@ -1222,6 +1222,29 @@ func (tenant *Tenant) ListProjects(_ context.Context) []string {
12221222
return projects
12231223
}
12241224

1225+
// GetProjectMetadata retrieves the metadata associated with the project.
1226+
func (tenant *Tenant) GetProjectMetadata(ctx context.Context, tx transaction.Tx, projName string) (*ProjectMetadata, error) {
1227+
tenant.RLock()
1228+
defer tenant.RUnlock()
1229+
1230+
_, ok := tenant.projects[projName]
1231+
if !ok {
1232+
return nil, NewProjectNotFoundErr(projName)
1233+
}
1234+
return tenant.namespaceStore.GetProjectMetadata(ctx, tx, tenant.namespace.Id(), projName)
1235+
}
1236+
1237+
func (tenant *Tenant) UpdateProjectMetadata(ctx context.Context, tx transaction.Tx, projName string, update *ProjectMetadata) error {
1238+
tenant.Lock()
1239+
defer tenant.Unlock()
1240+
1241+
_, ok := tenant.projects[projName]
1242+
if !ok {
1243+
return NewProjectNotFoundErr(projName)
1244+
}
1245+
return tenant.namespaceStore.UpdateProjectMetadata(ctx, tx, tenant.namespace.Id(), projName, update)
1246+
}
1247+
12251248
// DeleteTenant is used to delete tenant and all the content within it.
12261249
// This needs to be followed up with a "restart" of server to clear memory state.
12271250
// Be careful calling this.

server/services/v1/api.go

+17-1
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ func (s *apiService) RegisterHTTP(router chi.Router, inproc *inprocgrpc.Channel)
126126
router.HandleFunc(apiPathPrefix+projectsPath, func(w http.ResponseWriter, r *http.Request) {
127127
mux.ServeHTTP(w, r)
128128
})
129-
for _, projectPath := range []string{"/create", "/delete"} {
129+
for _, projectPath := range []string{"/create", "/delete", "/update"} {
130130
// explicit add project related path
131131
router.HandleFunc(apiPathPrefix+fullProjectPath+projectPath, func(w http.ResponseWriter, r *http.Request) {
132132
mux.ServeHTTP(w, r)
@@ -522,6 +522,22 @@ func (s *apiService) CreateProject(ctx context.Context, r *api.CreateProjectRequ
522522
}, nil
523523
}
524524

525+
func (s *apiService) UpdateProject(ctx context.Context, r *api.UpdateProjectRequest) (*api.UpdateProjectResponse, error) {
526+
accessToken, _ := request.GetAccessToken(ctx)
527+
runner := s.runnerFactory.GetProjectQueryRunner(accessToken)
528+
runner.SetUpdateProjectReq(r)
529+
530+
resp, err := s.sessions.Execute(ctx, runner, database.ReqOptions{
531+
MetadataChange: true,
532+
InstantVerTracking: true,
533+
})
534+
if err != nil {
535+
return nil, err
536+
}
537+
538+
return resp.Response.(*api.UpdateProjectResponse), nil
539+
}
540+
525541
func (s *apiService) DeleteProject(ctx context.Context, r *api.DeleteProjectRequest) (*api.DeleteProjectResponse, error) {
526542
accessToken, _ := request.GetAccessToken(ctx)
527543
runner := s.runnerFactory.GetProjectQueryRunner(accessToken)

server/services/v1/database/collection_runner.go

+14
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,20 @@ func (runner *CollectionQueryRunner) createOrUpdate(ctx context.Context, tx tran
115115
return Response{}, ctx, errors.AlreadyExists("collection already exist")
116116
}
117117

118+
// collectionExists == true && check project limits
119+
if collectionExists {
120+
projMeta, err := tenant.GetProjectMetadata(ctx, tx, req.GetProject())
121+
if err != nil {
122+
return Response{}, ctx, err
123+
}
124+
if projMeta != nil && projMeta.Limits != nil && projMeta.Limits.MaxCollections != nil {
125+
maxCollections := int(*(projMeta.Limits.MaxCollections))
126+
if len(db.ListCollection()) >= maxCollections {
127+
return Response{}, ctx, errors.InvalidArgument("collections limit reached for project: %d", maxCollections)
128+
}
129+
}
130+
}
131+
118132
schFactory, err := schema.NewFactoryBuilder(true).Build(req.GetCollection(), req.GetSchema())
119133
if err != nil {
120134
return Response{}, ctx, err

server/services/v1/database/database_runner.go

+64-3
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ type ProjectQueryRunner struct {
3737
createReq *api.CreateProjectRequest
3838
listReq *api.ListProjectsRequest
3939
describeReq *api.DescribeDatabaseRequest
40+
updateReq *api.UpdateProjectRequest
4041
}
4142

4243
func (runner *ProjectQueryRunner) SetCreateProjectReq(create *api.CreateProjectRequest) {
@@ -55,8 +56,12 @@ func (runner *ProjectQueryRunner) SetDescribeDatabaseReq(describe *api.DescribeD
5556
runner.describeReq = describe
5657
}
5758

59+
func (runner *ProjectQueryRunner) SetUpdateProjectReq(update *api.UpdateProjectRequest) {
60+
runner.updateReq = update
61+
}
62+
5863
func (runner *ProjectQueryRunner) create(ctx context.Context, tx transaction.Tx, tenant *metadata.Tenant) (Response, context.Context, error) {
59-
projMetadata, err := createProjectMetadata(ctx)
64+
projMetadata, err := createProjectMetadata(ctx, runner.createReq.Options)
6065
if err != nil {
6166
return Response{}, ctx, err
6267
}
@@ -86,7 +91,7 @@ func (runner *ProjectQueryRunner) delete(ctx context.Context, tx transaction.Tx,
8691
return Response{Status: DroppedStatus}, ctx, nil
8792
}
8893

89-
func (*ProjectQueryRunner) list(ctx context.Context, _ transaction.Tx, tenant *metadata.Tenant) (Response, context.Context, error) {
94+
func (*ProjectQueryRunner) list(ctx context.Context, tx transaction.Tx, tenant *metadata.Tenant) (Response, context.Context, error) {
9095
// listReq projects need not include any branches
9196

9297
projectList := tenant.ListProjects(ctx)
@@ -95,6 +100,17 @@ func (*ProjectQueryRunner) list(ctx context.Context, _ transaction.Tx, tenant *m
95100
projects[i] = &api.ProjectInfo{
96101
Project: l,
97102
}
103+
104+
meta, err := tenant.GetProjectMetadata(ctx, tx, l)
105+
if err != nil {
106+
return Response{}, ctx, err
107+
}
108+
if meta != nil && meta.Limits != nil {
109+
projects[i].Limits = &api.ProjectLimits{
110+
MaxCollections: meta.Limits.MaxCollections,
111+
MaxSearchIndexes: meta.Limits.MaxSearchIndexes,
112+
}
113+
}
98114
}
99115

100116
return Response{
@@ -104,6 +120,40 @@ func (*ProjectQueryRunner) list(ctx context.Context, _ transaction.Tx, tenant *m
104120
}, ctx, nil
105121
}
106122

123+
func (runner *ProjectQueryRunner) update(ctx context.Context, tx transaction.Tx, tenant *metadata.Tenant) (Response, context.Context, error) {
124+
meta, err := tenant.GetProjectMetadata(ctx, tx, runner.updateReq.GetProject())
125+
if err != nil {
126+
return Response{}, ctx, err
127+
}
128+
129+
if meta == nil {
130+
meta, err = createProjectMetadata(ctx, runner.updateReq.GetOptions())
131+
if err != nil {
132+
return Response{}, ctx, err
133+
}
134+
} else if runner.updateReq.GetOptions() != nil {
135+
limits := runner.updateReq.GetOptions().GetLimits()
136+
if limits == nil {
137+
meta.Limits = nil
138+
} else {
139+
meta.Limits = &metadata.ProjectLimits{
140+
MaxCollections: limits.MaxCollections,
141+
MaxSearchIndexes: limits.MaxSearchIndexes,
142+
}
143+
}
144+
}
145+
err = tenant.UpdateProjectMetadata(ctx, tx, runner.updateReq.GetProject(), meta)
146+
if err != nil {
147+
return Response{}, ctx, err
148+
}
149+
150+
return Response{
151+
Response: &api.UpdateProjectResponse{
152+
Status: UpdatedStatus,
153+
},
154+
}, ctx, nil
155+
}
156+
107157
func (runner *ProjectQueryRunner) describe(ctx context.Context, tx transaction.Tx, tenant *metadata.Tenant) (Response, context.Context, error) {
108158
db, err := runner.getDatabase(ctx, tx, tenant, runner.describeReq.GetProject(), runner.describeReq.GetBranch())
109159
if err != nil {
@@ -171,6 +221,8 @@ func (runner *ProjectQueryRunner) Run(ctx context.Context, tx transaction.Tx, te
171221
return runner.list(ctx, tx, tenant)
172222
case runner.describeReq != nil:
173223
return runner.describe(ctx, tx, tenant)
224+
case runner.updateReq != nil:
225+
return runner.update(ctx, tx, tenant)
174226
}
175227

176228
return Response{}, ctx, errors.Unknown("unknown request path")
@@ -244,14 +296,23 @@ func (runner *BranchQueryRunner) Run(ctx context.Context, tx transaction.Tx, ten
244296
return Response{}, ctx, errors.Unknown("unknown request path")
245297
}
246298

247-
func createProjectMetadata(ctx context.Context) (*metadata.ProjectMetadata, error) {
299+
func createProjectMetadata(ctx context.Context, r *api.ProjectOptions) (*metadata.ProjectMetadata, error) {
248300
currentSub, err := auth.GetCurrentSub(ctx)
249301
if err != nil && config.DefaultConfig.Auth.Enabled {
250302
return nil, errors.Internal("Failed to createReq database metadata")
251303
}
304+
var limits *metadata.ProjectLimits
305+
if r != nil && r.Limits != nil {
306+
limits = &metadata.ProjectLimits{
307+
MaxCollections: r.Limits.MaxCollections,
308+
MaxSearchIndexes: r.Limits.MaxSearchIndexes,
309+
}
310+
}
311+
252312
return &metadata.ProjectMetadata{
253313
ID: 0, // it will be set to right value later on
254314
Creator: currentSub,
255315
CreatedAt: time.Now().Unix(),
316+
Limits: limits,
256317
}, nil
257318
}

server/services/v1/search/runner.go

+12
Original file line numberDiff line numberDiff line change
@@ -950,6 +950,18 @@ func (runner *IndexRunner) Run(ctx context.Context, tx transaction.Tx, tenant *m
950950
if err != nil {
951951
return Response{}, createApiError(err)
952952
}
953+
projMeta, err := tenant.GetProjectMetadata(ctx, tx, runner.create.GetProject())
954+
if err != nil {
955+
return Response{}, createApiError(err)
956+
}
957+
if projMeta != nil && projMeta.Limits != nil && projMeta.Limits.MaxSearchIndexes != nil {
958+
maxIndexes := int(*projMeta.Limits.MaxSearchIndexes)
959+
indexes := project.GetSearch().GetIndexes()
960+
if len(indexes) >= maxIndexes {
961+
return Response{}, errors.InvalidArgument("search indexes limit reached for project: %d", maxIndexes)
962+
}
963+
}
964+
953965
factory.Sub = currentSub
954966
if err = tenant.CreateSearchIndex(ctx, tx, project, factory); err != nil {
955967
return Response{}, createApiError(err)

0 commit comments

Comments
 (0)