diff --git a/cmd/guard/main_test.go b/cmd/guard/main_test.go index 2f47634..6346392 100644 --- a/cmd/guard/main_test.go +++ b/cmd/guard/main_test.go @@ -20,8 +20,6 @@ import ( var ( _ api.Service = guard.New(config.ServiceConfig{}, nil) _ guard.Storage = storage.Must() - _ guard.AccountStorage = storage.Must() - _ guard.LicenseStorage = storage.Must() _ grpc.ProtectedStorage = storage.Must() _ grpc.Maintenance = guard.New(config.ServiceConfig{}, nil) ) diff --git a/pkg/service/guard/contract.go b/pkg/service/guard/contract.go index 377fac1..d78082f 100644 --- a/pkg/service/guard/contract.go +++ b/pkg/service/guard/contract.go @@ -11,11 +11,18 @@ import ( // Storage TODO issue#docs type Storage interface { + accountStorage + licenseStorage +} + +type accountStorage interface { + // RegisterAccount TODO issue#docs + RegisterAccount(context.Context, *query.RegisterAccount) (*repository.Account, error) +} + +type licenseStorage interface { // LicenseByID TODO issue#docs LicenseByID(context.Context, domain.ID) (repository.License, error) // LicenseByEmployee TODO issue#docs LicenseByEmployee(context.Context, domain.ID) (repository.License, error) - - // RegisterAccount TODO issue#docs - RegisterAccount(context.Context, *query.RegisterAccount) (*repository.Account, error) } diff --git a/pkg/service/guard/internal/cache.go b/pkg/service/guard/internal/cache.go new file mode 100644 index 0000000..ee91289 --- /dev/null +++ b/pkg/service/guard/internal/cache.go @@ -0,0 +1,125 @@ +package internal + +import ( + "context" + "time" + + domain "github.com/kamilsk/guard/pkg/service/types" + repository "github.com/kamilsk/guard/pkg/storage/types" + + "github.com/kamilsk/guard/pkg/storage/types" + "github.com/pkg/errors" +) + +// TODO issue#draft { + +type licenseCache interface { + LicenseByID(context.Context, domain.ID) (repository.License, error) + LicenseByEmployee(context.Context, domain.ID) (repository.License, error) +} + +// NewLicenseCache returns a blob of memory +// to store licenses. +func NewLicenseCache(in licenseCache) (out licenseCache) { + c := &cache{ + origin: in, + idx: make(map[domain.ID]response, maxLicenses), + + byID: make(chan request, 1), + byEmployee: make(chan request, 1), + memorize: make(chan response, maxLicenses/100), + } + go c.listen() + return c +} + +type cache struct { + origin licenseCache + idx map[domain.ID]response + + memorize chan response + byID chan request + byEmployee chan request +} + +// LicenseByID TODO issue#docs +func (c *cache) LicenseByID(ctx context.Context, id domain.ID) (types.License, error) { + req := request{ctx, id, make(chan response, 1)} + c.byID <- req + resp := <-req.result + return resp.license, resp.err +} + +// LicenseByEmployee TODO issue#docs +func (c *cache) LicenseByEmployee(ctx context.Context, id domain.ID) (types.License, error) { + req := request{ctx, id, make(chan response, 1)} + c.byEmployee <- req + resp := <-req.result + return resp.license, resp.err +} + +func (c *cache) listen() { + defer func() { + if r := recover(); r != nil { + // TODO issue#critical + // TODO issue#6 + go c.listen() + } + }() + for { + select { + case res := <-c.memorize: + c.idx[res.license.ID] = response{res.license, res.err, time.Now().Add(licenseTTL)} + case req := <-c.byID: + res, found := c.idx[req.id] + if found && res.ttl.After(time.Now()) { + req.result <- res + continue + } + go func() { + defer func() { + if r := recover(); r != nil { + // TODO issue#critical + // TODO issue#6 + req.result <- response{err: errors.New("unexpected panic handled")} + } + }() + res.license, res.err = c.origin.LicenseByID(req.ctx, req.id) + req.result <- res + c.memorize <- res + }() + case req := <-c.byEmployee: + res, found := c.idx[req.id] + if found && res.ttl.After(time.Now()) { + req.result <- res + continue + } + go func() { + defer func() { + if r := recover(); r != nil { + // TODO issue#critical + // TODO issue#6 + req.result <- response{err: errors.New("unexpected panic handled")} + } + }() + res.license, res.err = c.origin.LicenseByEmployee(req.ctx, req.id) + req.result <- res + c.memorize <- res + }() + } + } +} + +type request struct { + ctx context.Context + id domain.ID + result chan response +} + +type response struct { + license repository.License + err error + ttl time.Time +} + +// issue#draft } diff --git a/pkg/service/guard/internal/counter.go b/pkg/service/guard/internal/counter.go index ac46b01..487406f 100644 --- a/pkg/service/guard/internal/counter.go +++ b/pkg/service/guard/internal/counter.go @@ -173,7 +173,7 @@ func (s *slot) acquire(workplace domain.ID, capacity int) bool { if i = len(s.pool); i < capacity { s.idx[workplace] = i - s.pool = append(s.pool, record{id: workplace, lastActive: time.Now()}) + s.pool = append(s.pool, record{workplace: workplace, lastActive: time.Now()}) return true } @@ -181,9 +181,9 @@ func (s *slot) acquire(workplace domain.ID, capacity int) bool { now := time.Now() for i = range s.pool { if now.Sub(s.pool[i].lastActive) > workplaceTTL { - delete(s.idx, s.pool[i].id) + delete(s.idx, s.pool[i].workplace) s.idx[workplace] = i - s.pool[i] = record{id: workplace, lastActive: now} + s.pool[i] = record{workplace: workplace, lastActive: now} return true } } @@ -195,12 +195,12 @@ func (s *slot) shrink(size int) { sort.Sort(sort.Reverse(recordsByActivity(s.pool))) s.pool = s.pool[:size] for i := range s.pool { - s.idx[s.pool[i].id] = i + s.idx[s.pool[i].workplace] = i } } type record struct { - id domain.ID + workplace domain.ID lastActive time.Time } diff --git a/pkg/service/guard/license.go b/pkg/service/guard/license.go index 5cce730..ad835f6 100644 --- a/pkg/service/guard/license.go +++ b/pkg/service/guard/license.go @@ -15,7 +15,7 @@ import ( type licenseService struct { disabled bool - storage Storage + storage licenseStorage } // Check TODO issue#docs @@ -30,10 +30,10 @@ func (service *licenseService) Check(ctx context.Context, req request.CheckLicen var license domain.License switch { - case !(req.ID.IsValid() && req.Employee.IsValid()) || !req.Workplace.IsValid(): + case !(req.License.IsValid() || req.Employee.IsValid()) || !req.Workplace.IsValid(): return resp.With(errors.New(http.StatusText(http.StatusBadRequest))) - case req.ID.IsValid(): - entity, err := service.storage.LicenseByID(ctx, req.ID) + case req.License.IsValid(): + entity, err := service.storage.LicenseByID(ctx, req.License) if err != nil { return resp.With(err) } @@ -47,23 +47,23 @@ func (service *licenseService) Check(ctx context.Context, req request.CheckLicen } // TODO issue#composite - if err := service.checkLifetimeLimits(&license, req.Workplace); err != nil { + if err := service.checkLifetimeLimits(license, req.Workplace); err != nil { return resp.With(err) } - if err := service.checkRateLimits(&license, req.Workplace); err != nil { + if err := service.checkRateLimits(license, req.Workplace); err != nil { return resp.With(err) } - if err := service.checkRequestLimits(&license, req.Workplace); err != nil { + if err := service.checkRequestLimits(license, req.Workplace); err != nil { return resp.With(err) } - if err := service.checkWorkplaceLimits(&license, req.Workplace); err != nil { + if err := service.checkWorkplaceLimits(license, req.Workplace); err != nil { return resp.With(err) } return } -func (service *licenseService) checkLifetimeLimits(license *domain.License, _ domain.ID) error { +func (service *licenseService) checkLifetimeLimits(license domain.License, _ domain.ID) error { now := time.Now() if license.Since != nil { if license.Since.After(now) { @@ -78,7 +78,7 @@ func (service *licenseService) checkLifetimeLimits(license *domain.License, _ do return nil } -func (service *licenseService) checkRateLimits(license *domain.License, _ domain.ID) error { +func (service *licenseService) checkRateLimits(license domain.License, _ domain.ID) error { if license.Rate.IsValid() { // TODO issue#future // errors.New(http.StatusText(http.StatusTooManyRequests)) @@ -87,7 +87,7 @@ func (service *licenseService) checkRateLimits(license *domain.License, _ domain return nil } -func (service *licenseService) checkRequestLimits(license *domain.License, _ domain.ID) error { +func (service *licenseService) checkRequestLimits(license domain.License, _ domain.ID) error { counter := internal.LicenseRequests if license.Requests > 0 && license.Requests < counter.Increment(license.ID) { go counter.Rollback(license.ID) @@ -96,7 +96,7 @@ func (service *licenseService) checkRequestLimits(license *domain.License, _ dom return nil } -func (service *licenseService) checkWorkplaceLimits(license *domain.License, workplace domain.ID) error { +func (service *licenseService) checkWorkplaceLimits(license domain.License, workplace domain.ID) error { if !internal.LicenseWorkplaces.Acquire(license.ID, workplace, int(license.Workplaces)) { return errors.New(http.StatusText(http.StatusPaymentRequired)) } diff --git a/pkg/service/guard/maintenance.go b/pkg/service/guard/maintenance.go index 5d0bfa5..9140ef3 100644 --- a/pkg/service/guard/maintenance.go +++ b/pkg/service/guard/maintenance.go @@ -9,7 +9,7 @@ import ( ) type maintenanceService struct { - storage Storage + storage accountStorage } // Install TODO issue#docs diff --git a/pkg/service/guard/service.go b/pkg/service/guard/service.go index 40b4eb6..f1e0982 100644 --- a/pkg/service/guard/service.go +++ b/pkg/service/guard/service.go @@ -4,13 +4,17 @@ import ( "context" "github.com/kamilsk/guard/pkg/config" + "github.com/kamilsk/guard/pkg/service/guard/internal" "github.com/kamilsk/guard/pkg/service/types/request" "github.com/kamilsk/guard/pkg/service/types/response" ) // New TODO issue#docs func New(cnf config.ServiceConfig, storage Storage) *Guard { - return &Guard{&licenseService{cnf.Disabled, storage}, &maintenanceService{storage}} + return &Guard{ + &licenseService{cnf.Disabled, internal.NewLicenseCache(storage)}, + &maintenanceService{storage}, + } } // Guard TODO issue#docs diff --git a/pkg/service/types/request/license.go b/pkg/service/types/request/license.go index 4a7c3ab..c3fde0c 100644 --- a/pkg/service/types/request/license.go +++ b/pkg/service/types/request/license.go @@ -4,7 +4,7 @@ import domain "github.com/kamilsk/guard/pkg/service/types" // CheckLicense TODO issue#docs type CheckLicense struct { - ID domain.ID + License domain.ID Employee domain.ID Workplace domain.ID }