User: Add tracing (#87028)

* Inject tracer in tests

* Annotate with traces

Co-authored-by: Gabriel MABILLE <gamab@users.noreply.github.com>
This commit is contained in:
Karl Persson
2024-04-30 13:15:56 +02:00
committed by GitHub
co-authored by Gabriel MABILLE
parent 7f1b2ef205
commit a2cba3d0b5
31 changed files with 368 additions and 202 deletions
+13 -3
View File
@@ -10,6 +10,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/org/orgimpl"
@@ -37,7 +38,10 @@ func TestIntegrationUserDataAccess(t *testing.T) {
orgService, err := orgimpl.ProvideService(ss, cfg, quotaService)
require.NoError(t, err)
userStore := ProvideStore(ss, setting.NewCfg())
usrSvc, err := ProvideService(ss, orgService, cfg, nil, nil, quotaService, supportbundlestest.NewFakeBundleService())
usrSvc, err := ProvideService(
ss, orgService, cfg, nil, nil, tracing.InitializeTracerForTest(),
quotaService, supportbundlestest.NewFakeBundleService(),
)
require.NoError(t, err)
usr := &user.SignedInUser{
OrgID: 1,
@@ -554,7 +558,10 @@ func TestIntegrationUserDataAccess(t *testing.T) {
ss := db.InitTestDB(t)
orgService, err := orgimpl.ProvideService(ss, cfg, quotaService)
require.NoError(t, err)
usrSvc, err := ProvideService(ss, orgService, cfg, nil, nil, quotaService, supportbundlestest.NewFakeBundleService())
usrSvc, err := ProvideService(
ss, orgService, cfg, nil, nil, tracing.InitializeTracerForTest(),
quotaService, supportbundlestest.NewFakeBundleService(),
)
require.NoError(t, err)
createFiveTestUsers(t, usrSvc, func(i int) *user.CreateUserCommand {
@@ -958,7 +965,10 @@ func createOrgAndUserSvc(t *testing.T, store db.DB, cfg *setting.Cfg) (org.Servi
quotaService := quotaimpl.ProvideService(store, cfg)
orgService, err := orgimpl.ProvideService(store, cfg, quotaService)
require.NoError(t, err)
usrSvc, err := ProvideService(store, orgService, cfg, nil, nil, quotaService, supportbundlestest.NewFakeBundleService())
usrSvc, err := ProvideService(
store, orgService, cfg, nil, nil, tracing.InitializeTracerForTest(),
quotaService, supportbundlestest.NewFakeBundleService(),
)
require.NoError(t, err)
return orgService, usrSvc
+108 -45
View File
@@ -7,8 +7,12 @@ import (
"strings"
"time"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/localcache"
"github.com/grafana/grafana/pkg/infra/tracing"
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/quota"
@@ -27,6 +31,7 @@ type Service struct {
teamService team.Service
cacheService *localcache.CacheService
cfg *setting.Cfg
tracer tracing.Tracer
}
func ProvideService(
@@ -34,9 +39,8 @@ func ProvideService(
orgService org.Service,
cfg *setting.Cfg,
teamService team.Service,
cacheService *localcache.CacheService,
quotaService quota.Service,
bundleRegistry supportbundles.Service,
cacheService *localcache.CacheService, tracer tracing.Tracer,
quotaService quota.Service, bundleRegistry supportbundles.Service,
) (user.Service, error) {
store := ProvideStore(db, cfg)
s := &Service{
@@ -45,6 +49,7 @@ func ProvideService(
cfg: cfg,
teamService: teamService,
cacheService: cacheService,
tracer: tracer,
}
defaultLimits, err := readQuotaConfig(cfg)
@@ -55,7 +60,7 @@ func ProvideService(
if err := quotaService.RegisterQuotaReporter(&quota.NewUsageReporter{
TargetSrv: quota.TargetSrv(user.QuotaTargetSrv),
DefaultLimits: defaultLimits,
Reporter: s.Usage,
Reporter: s.usage,
}); err != nil {
return s, err
}
@@ -87,21 +92,10 @@ func (s *Service) GetUsageStats(ctx context.Context) map[string]any {
return stats
}
func (s *Service) Usage(ctx context.Context, _ *quota.ScopeParameters) (*quota.Map, error) {
u := &quota.Map{}
if used, err := s.store.Count(ctx); err != nil {
return u, err
} else {
tag, err := quota.NewTag(quota.TargetSrv(user.QuotaTargetSrv), quota.Target(user.QuotaTarget), quota.GlobalScope)
if err != nil {
return u, err
}
u.Set(tag, used)
}
return u, nil
}
func (s *Service) Create(ctx context.Context, cmd *user.CreateUserCommand) (*user.User, error) {
ctx, span := s.tracer.Start(ctx, "user.Create")
defer span.End()
if len(cmd.Login) == 0 {
cmd.Login = cmd.Email
}
@@ -126,8 +120,7 @@ func (s *Service) Create(ctx context.Context, cmd *user.CreateUserCommand) (*use
cmd.Email = cmd.Login
}
err = s.store.LoginConflict(ctx, cmd.Login, cmd.Email)
if err != nil {
if err := s.store.LoginConflict(ctx, cmd.Login, cmd.Email); err != nil {
return nil, user.ErrUserAlreadyExists
}
@@ -202,27 +195,48 @@ func (s *Service) Create(ctx context.Context, cmd *user.CreateUserCommand) (*use
}
func (s *Service) Delete(ctx context.Context, cmd *user.DeleteUserCommand) error {
ctx, span := s.tracer.Start(ctx, "user.Delete", trace.WithAttributes(
attribute.Int64("userID", cmd.UserID),
))
defer span.End()
_, err := s.store.GetByID(ctx, cmd.UserID)
if err != nil {
return err
}
// delete from all the stores
return s.store.Delete(ctx, cmd.UserID)
}
func (s *Service) GetByID(ctx context.Context, query *user.GetUserByIDQuery) (*user.User, error) {
ctx, span := s.tracer.Start(ctx, "user.GetByID", trace.WithAttributes(
attribute.Int64("userID", query.ID),
))
defer span.End()
return s.store.GetByID(ctx, query.ID)
}
func (s *Service) GetByLogin(ctx context.Context, query *user.GetUserByLoginQuery) (*user.User, error) {
ctx, span := s.tracer.Start(ctx, "user.GetByLogin")
defer span.End()
return s.store.GetByLogin(ctx, query)
}
func (s *Service) GetByEmail(ctx context.Context, query *user.GetUserByEmailQuery) (*user.User, error) {
ctx, span := s.tracer.Start(ctx, "user.GetByEmail")
defer span.End()
return s.store.GetByEmail(ctx, query)
}
func (s *Service) Update(ctx context.Context, cmd *user.UpdateUserCommand) error {
ctx, span := s.tracer.Start(ctx, "user.Update", trace.WithAttributes(
attribute.Int64("userID", cmd.UserID),
))
defer span.End()
usr, err := s.store.GetByID(ctx, cmd.UserID)
if err != nil {
return err
@@ -273,6 +287,11 @@ func (s *Service) Update(ctx context.Context, cmd *user.UpdateUserCommand) error
}
func (s *Service) UpdateLastSeenAt(ctx context.Context, cmd *user.UpdateUserLastSeenAtCommand) error {
ctx, span := s.tracer.Start(ctx, "user.UpdateLastSeen", trace.WithAttributes(
attribute.Int64("userID", cmd.UserID),
))
defer span.End()
u, err := s.GetSignedInUserWithCacheCtx(ctx, &user.GetSignedInUserQuery{
UserID: cmd.UserID,
OrgID: cmd.OrgID,
@@ -294,6 +313,12 @@ func shouldUpdateLastSeen(t time.Time) bool {
}
func (s *Service) GetSignedInUserWithCacheCtx(ctx context.Context, query *user.GetSignedInUserQuery) (*user.SignedInUser, error) {
ctx, span := s.tracer.Start(ctx, "user.GetSignedInUserWithCacheCtx", trace.WithAttributes(
attribute.Int64("userID", query.UserID),
attribute.Int64("orgID", query.OrgID),
))
defer span.End()
var signedInUser *user.SignedInUser
// only check cache if we have a user ID and an org ID in query
@@ -321,54 +346,62 @@ func newSignedInUserCacheKey(orgID, userID int64) string {
}
func (s *Service) GetSignedInUser(ctx context.Context, query *user.GetSignedInUserQuery) (*user.SignedInUser, error) {
signedInUser, err := s.store.GetSignedInUser(ctx, query)
ctx, span := s.tracer.Start(ctx, "user.GetSignedInUser", trace.WithAttributes(
attribute.Int64("userID", query.UserID),
attribute.Int64("orgID", query.OrgID),
))
defer span.End()
usr, err := s.store.GetSignedInUser(ctx, query)
if err != nil {
return nil, err
}
getTeamsByUserQuery := &team.GetTeamIDsByUserQuery{
OrgID: signedInUser.OrgID,
UserID: signedInUser.UserID,
}
signedInUser.Teams, err = s.teamService.GetTeamIDsByUser(ctx, getTeamsByUserQuery)
usr.Teams, err = s.teamService.GetTeamIDsByUser(ctx, &team.GetTeamIDsByUserQuery{
OrgID: usr.OrgID,
UserID: usr.UserID,
})
if err != nil {
return nil, err
}
return signedInUser, err
return usr, err
}
func (s *Service) Search(ctx context.Context, query *user.SearchUsersQuery) (*user.SearchUserQueryResult, error) {
ctx, span := s.tracer.Start(ctx, "user.Search", trace.WithAttributes(
attribute.Int64("orgID", query.OrgID),
))
defer span.End()
return s.store.Search(ctx, query)
}
func (s *Service) BatchDisableUsers(ctx context.Context, cmd *user.BatchDisableUsersCommand) error {
ctx, span := s.tracer.Start(ctx, "user.BatchDisableUsers", trace.WithAttributes(
attribute.Int64Slice("userIDs", cmd.UserIDs),
))
defer span.End()
return s.store.BatchDisableUsers(ctx, cmd)
}
func (s *Service) GetProfile(ctx context.Context, query *user.GetUserProfileQuery) (*user.UserProfileDTO, error) {
result, err := s.store.GetProfile(ctx, query)
return result, err
}
ctx, span := s.tracer.Start(ctx, "user.GetProfile", trace.WithAttributes(
attribute.Int64("userID", query.UserID),
))
defer span.End()
func readQuotaConfig(cfg *setting.Cfg) (*quota.Map, error) {
limits := &quota.Map{}
if cfg == nil {
return limits, nil
}
globalQuotaTag, err := quota.NewTag(quota.TargetSrv(user.QuotaTargetSrv), quota.Target(user.QuotaTarget), quota.GlobalScope)
if err != nil {
return limits, err
}
limits.Set(globalQuotaTag, cfg.Quota.Global.User)
return limits, nil
return s.store.GetProfile(ctx, query)
}
// CreateServiceAccount creates a service account in the user table and adds service account to an organisation in the org_user table
func (s *Service) CreateServiceAccount(ctx context.Context, cmd *user.CreateUserCommand) (*user.User, error) {
ctx, span := s.tracer.Start(ctx, "user.CreateServiceAccount", trace.WithAttributes(
attribute.Int64("orgID", cmd.OrgID),
))
defer span.End()
cmd.Email = cmd.Login
err := s.store.LoginConflict(ctx, cmd.Login, cmd.Email)
if err != nil {
@@ -462,6 +495,36 @@ func (s *Service) supportBundleCollector() supportbundles.Collector {
}
}
func (s *Service) usage(ctx context.Context, _ *quota.ScopeParameters) (*quota.Map, error) {
u := &quota.Map{}
if used, err := s.store.Count(ctx); err != nil {
return u, err
} else {
tag, err := quota.NewTag(quota.TargetSrv(user.QuotaTargetSrv), quota.Target(user.QuotaTarget), quota.GlobalScope)
if err != nil {
return u, err
}
u.Set(tag, used)
}
return u, nil
}
func readQuotaConfig(cfg *setting.Cfg) (*quota.Map, error) {
limits := &quota.Map{}
if cfg == nil {
return limits, nil
}
globalQuotaTag, err := quota.NewTag(quota.TargetSrv(user.QuotaTargetSrv), quota.Target(user.QuotaTarget), quota.GlobalScope)
if err != nil {
return limits, err
}
limits.Set(globalQuotaTag, cfg.Quota.Global.User)
return limits, nil
}
// This is just to ensure that all users have a valid uid.
// To protect against upgrade / downgrade we need to run this for a couple of releases.
// FIXME: Remove this migration and make uid field required https://github.com/grafana/identity-access-team/issues/552
+35 -27
View File
@@ -10,6 +10,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/infra/localcache"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/org/orgtest"
"github.com/grafana/grafana/pkg/services/team/teamtest"
@@ -25,6 +26,7 @@ func TestUserService(t *testing.T) {
orgService: orgService,
cacheService: localcache.ProvideService(),
teamService: &teamtest.FakeService{},
tracer: tracing.InitializeTracerForTest(),
}
userService.cfg = setting.NewCfg()
@@ -100,6 +102,7 @@ func TestUserService(t *testing.T) {
orgService: orgService,
cacheService: localcache.ProvideService(),
teamService: teamtest.NewFakeService(),
tracer: tracing.InitializeTracerForTest(),
}
usr := &user.SignedInUser{
OrgID: 1,
@@ -149,7 +152,10 @@ func TestUserService(t *testing.T) {
func TestService_Update(t *testing.T) {
setup := func(opts ...func(svc *Service)) *Service {
service := &Service{store: &FakeUserStore{}}
service := &Service{
store: &FakeUserStore{},
tracer: tracing.InitializeTracerForTest(),
}
for _, o := range opts {
o(service)
}
@@ -204,6 +210,33 @@ func TestService_Update(t *testing.T) {
})
}
func TestUpdateLastSeenAt(t *testing.T) {
userStore := newUserStoreFake()
orgService := orgtest.NewOrgServiceFake()
userService := Service{
store: userStore,
orgService: orgService,
cacheService: localcache.ProvideService(),
teamService: &teamtest.FakeService{},
tracer: tracing.InitializeTracerForTest(),
}
userService.cfg = setting.NewCfg()
t.Run("update last seen at", func(t *testing.T) {
userStore.ExpectedSignedInUser = &user.SignedInUser{UserID: 1, OrgID: 1, Email: "email", Login: "login", Name: "name", LastSeenAt: time.Now().Add(-10 * time.Minute)}
err := userService.UpdateLastSeenAt(context.Background(), &user.UpdateUserLastSeenAtCommand{UserID: 1, OrgID: 1})
require.NoError(t, err)
})
userService.cacheService.Flush()
t.Run("do not update last seen at", func(t *testing.T) {
userStore.ExpectedSignedInUser = &user.SignedInUser{UserID: 1, OrgID: 1, Email: "email", Login: "login", Name: "name", LastSeenAt: time.Now().Add(-1 * time.Minute)}
err := userService.UpdateLastSeenAt(context.Background(), &user.UpdateUserLastSeenAtCommand{UserID: 1, OrgID: 1})
require.ErrorIs(t, err, user.ErrLastSeenUpToDate, err)
})
}
func TestMetrics(t *testing.T) {
userStore := newUserStoreFake()
orgService := orgtest.NewOrgServiceFake()
@@ -213,6 +246,7 @@ func TestMetrics(t *testing.T) {
orgService: orgService,
cacheService: localcache.ProvideService(),
teamService: &teamtest.FakeService{},
tracer: tracing.InitializeTracerForTest(),
}
t.Run("update user with role None", func(t *testing.T) {
@@ -303,29 +337,3 @@ func (f *FakeUserStore) Count(ctx context.Context) (int64, error) {
func (f *FakeUserStore) CountUserAccountsWithEmptyRole(ctx context.Context) (int64, error) {
return f.ExpectedCountUserAccountsWithEmptyRoles, nil
}
func TestUpdateLastSeenAt(t *testing.T) {
userStore := newUserStoreFake()
orgService := orgtest.NewOrgServiceFake()
userService := Service{
store: userStore,
orgService: orgService,
cacheService: localcache.ProvideService(),
teamService: &teamtest.FakeService{},
}
userService.cfg = setting.NewCfg()
t.Run("update last seen at", func(t *testing.T) {
userStore.ExpectedSignedInUser = &user.SignedInUser{UserID: 1, OrgID: 1, Email: "email", Login: "login", Name: "name", LastSeenAt: time.Now().Add(-10 * time.Minute)}
err := userService.UpdateLastSeenAt(context.Background(), &user.UpdateUserLastSeenAtCommand{UserID: 1, OrgID: 1})
require.NoError(t, err)
})
userService.cacheService.Flush()
t.Run("do not update last seen at", func(t *testing.T) {
userStore.ExpectedSignedInUser = &user.SignedInUser{UserID: 1, OrgID: 1, Email: "email", Login: "login", Name: "name", LastSeenAt: time.Now().Add(-1 * time.Minute)}
err := userService.UpdateLastSeenAt(context.Background(), &user.UpdateUserLastSeenAtCommand{UserID: 1, OrgID: 1})
require.ErrorIs(t, err, user.ErrLastSeenUpToDate, err)
})
}