Unified storage: Add quotas app to apiserver (#114425)

* initial generation

* went through doc to add new resource

* added dummy kind so grafana will run

* added dummy handler and custom route

* fix app name

* gets custom route working - still a dummy route

* adds groupOverride to manifest

* adds quotas to grpc client and server

* WIP - trying to get api recognized - not working

* Gets route working

* fixes group and resource vars

* expects group and resource as separate params

* set content-type header on response

* removes Quotas kind and regens

* Update grafana-app-sdk to v0.48.5

* Update codegen

* updates manifest

* formatting

* updates grafana-app-sdk version to 0.48.5

* regen ResourceClient mocks

* adds tests

* remove commented code

* uncomment go mod tidy

* fix tests and make update workspace

* adds quotas app to codeowners

* formatting

* make gen-apps

* deletes temp file

* fix generated folder code

* make gofmt

* make gen-go

* make update-workspace

* add COPY apps/quotas to Dockerfile

* fix test mock

* fixes undefined NewFolderStatus()

* make gen-apps, and add func for NewFolderStatus

* make gen-apps again

* make update-workspace

* regen folder_object_gen.go

* gofmt

* fix linting

* apps/folder make update-workspace

* make gen-apps

* make gen-apps

* fixes enterprise_imports.go

* go get testcontainers

* adds feature toggle

* make update-workspace

* fix go mod

* fix another client mock

---------

Co-authored-by: Steve Simpson <steve@grafana.com>
This commit is contained in:
owensmallwood
2025-12-09 09:40:34 -06:00
committed by GitHub
co-authored by Steve Simpson
parent 297e886e1b
commit a3daf0e39d
47 changed files with 1758 additions and 149 deletions
@@ -5,6 +5,8 @@ import (
"encoding/json"
"errors"
"net/http"
"os"
"path/filepath"
"strings"
"sync"
"testing"
@@ -22,6 +24,7 @@ import (
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
"github.com/grafana/grafana/pkg/util/scheduler"
)
@@ -614,3 +617,70 @@ func TestArtificialDelayAfterSuccessfulOperation(t *testing.T) {
check(t, false, &resourcepb.UpdateResponse{Error: AsErrorResult(errors.New("some error"))}, nil)
check(t, false, &resourcepb.DeleteResponse{Error: AsErrorResult(errors.New("some error"))}, nil)
}
func TestGetQuotaUsage(t *testing.T) {
ctx := context.Background()
t.Run("returns error when overrides service is not configured", func(t *testing.T) {
s := &server{
overridesService: nil,
log: log.NewNopLogger(),
}
resp, err := s.GetQuotaUsage(ctx, &resourcepb.QuotaUsageRequest{
Key: &resourcepb.ResourceKey{
Namespace: "stacks-123",
Group: "dashboard.grafana.app",
Resource: "dashboards",
},
})
require.NoError(t, err)
require.NotNil(t, resp.Error)
assert.Equal(t, int32(http.StatusNotImplemented), resp.Error.Code)
assert.Equal(t, "overrides service not configured on resource server", resp.Error.Message)
})
t.Run("returns usage and limit successfully", func(t *testing.T) {
// Create a temporary overrides config file
tmpFile := filepath.Join(t.TempDir(), "overrides.yaml")
content := `"123":
quotas:
dashboard.grafana.app/dashboards:
limit: 500
`
require.NoError(t, os.WriteFile(tmpFile, []byte(content), 0644))
// Create a real OverridesService with the temp file
overridesService, err := NewOverridesService(ctx, log.NewNopLogger(), prometheus.NewRegistry(), tracing.NewNoopTracerService(), ReloadOptions{
FilePath: tmpFile,
})
require.NoError(t, err)
require.NoError(t, overridesService.init(ctx))
defer func() {
_ = overridesService.stop(ctx)
}()
// Create a mock backend that returns resource stats (reusing mockStorageBackend from search_test.go)
mockBackend := &mockStorageBackend{
resourceStats: []ResourceStats{{Count: 42}},
}
s := &server{
backend: mockBackend,
overridesService: overridesService,
log: log.NewNopLogger(),
}
resp, err := s.GetQuotaUsage(ctx, &resourcepb.QuotaUsageRequest{
Key: &resourcepb.ResourceKey{
Namespace: "stacks-123",
Group: "dashboard.grafana.app",
Resource: "dashboards",
},
})
require.NoError(t, err)
require.Nil(t, resp.Error)
assert.Equal(t, int64(42), resp.Usage)
assert.Equal(t, int64(500), resp.Limit)
})
}