storage: add a method to tidy/optimize the database

This commit is contained in:
nemunaire 2020-06-24 17:20:01 +02:00
parent 729a4e3fa0
commit 20d4299239
7 changed files with 237 additions and 4 deletions

View File

@ -63,6 +63,8 @@ func init() {
router.POST("/api/users/:userid/send_validation_email", api.ApiHandler(userHandler(sendValidateUserEmail)))
router.POST("/api/users/:userid/validation_link", api.ApiHandler(userHandler(emailValidationLink)))
router.POST("/api/users/:userid/validate_email", api.ApiHandler(userHandler(validateEmail)))
router.POST("/api/tidy", api.ApiHandler(tidyDB))
}
func getUsers(_ *config.Options, _ httprouter.Params, _ io.Reader) api.Response {
@ -171,3 +173,7 @@ func validateEmail(_ *config.Options, user *happydns.User, _ httprouter.Params,
user.EmailValidated = &now
return api.NewAPIResponse(user, storage.MainStore.UpdateUser(user))
}
func tidyDB(_ *config.Options, _ httprouter.Params, _ io.Reader) api.Response {
return api.NewAPIResponse(true, storage.MainStore.Tidy())
}

View File

@ -37,6 +37,7 @@ import (
type Storage interface {
DoMigration() error
Tidy() error
Close() error
GetDomains(u *happydns.User) (happydns.Domains, error)

View File

@ -59,6 +59,15 @@ func (s *LevelDBStorage) DoMigration() error {
return nil
}
func (s *LevelDBStorage) Tidy() error {
for _, tidy := range []func() error{s.TidySessions, s.TidySources, s.TidyDomains, s.TidyZones} {
if err := tidy(); err != nil {
return err
}
}
return nil
}
func (s *LevelDBStorage) Close() error {
return s.db.Close()
}

View File

@ -33,6 +33,7 @@ package database
import (
"fmt"
"log"
"git.happydns.org/happydns/model"
@ -60,9 +61,18 @@ func (s *LevelDBStorage) GetDomains(u *happydns.User) (domains happydns.Domains,
return
}
func (s *LevelDBStorage) GetDomain(u *happydns.User, id int64) (z *happydns.Domain, err error) {
func (s *LevelDBStorage) getDomain(id string) (z *happydns.Domain, err error) {
z = &happydns.Domain{}
err = s.get(fmt.Sprintf("domain-%d", id), &z)
err = s.get(id, z)
return
}
func (s *LevelDBStorage) GetDomain(u *happydns.User, id int64) (z *happydns.Domain, err error) {
z, err = s.getDomain(fmt.Sprintf("domain-%d", id))
if err != nil {
return
}
if z.IdUser != u.Id {
z = nil
@ -161,3 +171,43 @@ func (s *LevelDBStorage) ClearDomains() error {
return nil
}
func (s *LevelDBStorage) TidyDomains() error {
tx, err := s.db.OpenTransaction()
if err != nil {
return err
}
iter := tx.NewIterator(util.BytesPrefix([]byte("domain-")), nil)
defer iter.Release()
for iter.Next() {
domain, err := s.getDomain(string(iter.Key()))
if err == nil {
_, err = s.GetUser(domain.IdUser)
if err == leveldb.ErrNotFound {
// Drop domain of unexistant users
err = tx.Delete(iter.Key(), nil)
log.Printf("Deleting orphan domain (user %d not found): %v\n", domain.IdUser, domain)
}
} else {
// Drop domain of unexistant users
log.Printf("Deleting unreadable domain (%w): %v\n", err, domain)
err = tx.Delete(iter.Key(), nil)
}
if err != nil {
tx.Discard()
return err
}
}
err = tx.Commit()
if err != nil {
tx.Discard()
return err
}
return nil
}

View File

@ -33,18 +33,24 @@ package database
import (
"fmt"
"log"
"github.com/syndtr/goleveldb/leveldb"
"github.com/syndtr/goleveldb/leveldb/util"
"git.happydns.org/happydns/model"
)
func (s *LevelDBStorage) GetSession(id []byte) (session *happydns.Session, err error) {
func (s *LevelDBStorage) getSession(id string) (session *happydns.Session, err error) {
session = &happydns.Session{}
err = s.get(fmt.Sprintf("user.session-%x", id), &session)
err = s.get(id, &session)
return
}
func (s *LevelDBStorage) GetSession(id []byte) (session *happydns.Session, err error) {
return s.getSession(fmt.Sprintf("user.session-%x", id))
}
func (s *LevelDBStorage) CreateSession(session *happydns.Session) error {
key, id, err := s.findBytesKey("user.session-", 255)
if err != nil {
@ -88,3 +94,43 @@ func (s *LevelDBStorage) ClearSessions() error {
return nil
}
func (s *LevelDBStorage) TidySessions() error {
tx, err := s.db.OpenTransaction()
if err != nil {
return err
}
iter := tx.NewIterator(util.BytesPrefix([]byte("user.session-")), nil)
defer iter.Release()
for iter.Next() {
session, err := s.getSession(string(iter.Key()))
if err != nil {
// Drop unreadable sessions
log.Printf("Deleting unreadable session (%w): %v\n", err, session)
err = tx.Delete(iter.Key(), nil)
} else {
_, err = s.GetUser(session.IdUser)
if err == leveldb.ErrNotFound {
// Drop session from unexistant users
log.Printf("Deleting orphan session (user %d not found): %v\n", session.IdUser, session)
err = tx.Delete(iter.Key(), nil)
}
}
if err != nil {
tx.Discard()
return err
}
}
err = tx.Commit()
if err != nil {
tx.Discard()
return err
}
return nil
}

View File

@ -33,6 +33,7 @@ package database
import (
"fmt"
"log"
"reflect"
"github.com/syndtr/goleveldb/leveldb"
@ -42,6 +43,12 @@ import (
"git.happydns.org/happydns/sources"
)
func (s *LevelDBStorage) getSourceType(id []byte) (srcType *happydns.SourceType, err error) {
srcType = &happydns.SourceType{}
err = decodeData(id, srcType)
return
}
func (s *LevelDBStorage) GetSourceTypes(u *happydns.User) (srcs []happydns.SourceType, err error) {
iter := s.search("source-")
defer iter.Release()
@ -152,3 +159,43 @@ func (s *LevelDBStorage) ClearSources() error {
return nil
}
func (s *LevelDBStorage) TidySources() error {
tx, err := s.db.OpenTransaction()
if err != nil {
return err
}
iter := tx.NewIterator(util.BytesPrefix([]byte("source-")), nil)
defer iter.Release()
for iter.Next() {
srcType, err := s.getSourceType(iter.Key())
if err != nil {
// Drop unreadable sources
log.Printf("Deleting unreadable source (%w): %v\n", err, srcType)
err = tx.Delete(iter.Key(), nil)
} else {
_, err = s.GetUser(srcType.OwnerId)
if err == leveldb.ErrNotFound {
// Drop sources of unexistant users
log.Printf("Deleting orphan source (user %d not found): %v\n", srcType.OwnerId, srcType)
err = tx.Delete(iter.Key(), nil)
}
}
if err != nil {
tx.Discard()
return err
}
}
err = tx.Commit()
if err != nil {
tx.Discard()
return err
}
return nil
}

View File

@ -33,6 +33,9 @@ package database
import (
"fmt"
"log"
"strconv"
"strings"
"git.happydns.org/happydns/model"
@ -45,6 +48,17 @@ func (s *LevelDBStorage) GetZone(id int64) (z *happydns.Zone, err error) {
return
}
func (s *LevelDBStorage) getZoneMeta(id string) (z *happydns.ZoneMeta, err error) {
z = &happydns.ZoneMeta{}
err = s.get(id, z)
return
}
func (s *LevelDBStorage) GetZoneMeta(id int64) (z *happydns.ZoneMeta, err error) {
z, err = s.getZoneMeta(fmt.Sprintf("domain.zone-%d", id))
return
}
func (s *LevelDBStorage) CreateZone(z *happydns.Zone) error {
key, id, err := s.findInt63Key("domain.zone-")
if err != nil {
@ -88,3 +102,63 @@ func (s *LevelDBStorage) ClearZones() error {
return nil
}
func (s *LevelDBStorage) TidyZones() error {
tx, err := s.db.OpenTransaction()
if err != nil {
return err
}
iter := tx.NewIterator(util.BytesPrefix([]byte("domain-")), nil)
defer iter.Release()
var referencedZones []int64
for iter.Next() {
domain, _ := s.getDomain(string(iter.Key()))
if domain != nil {
for _, zh := range domain.ZoneHistory {
referencedZones = append(referencedZones, zh)
}
}
}
iter = tx.NewIterator(util.BytesPrefix([]byte("domain.zone-")), nil)
defer iter.Release()
for iter.Next() {
if zoneId, err := strconv.ParseInt(strings.TrimPrefix(string(iter.Key()), "domain.zone-"), 10, 64); err != nil {
// Drop zones with invalid ID
log.Printf("Deleting unindentified zone: key=%s\n", iter.Key())
err = tx.Delete(iter.Key(), nil)
} else {
foundZone := false
for _, zid := range referencedZones {
if zid == zoneId {
foundZone = true
break
}
}
if !foundZone {
// Drop orphan zones
log.Printf("Deleting orphan zone: %d\n", zoneId)
err = tx.Delete(iter.Key(), nil)
}
}
if err != nil {
tx.Discard()
return err
}
}
err = tx.Commit()
if err != nil {
tx.Discard()
return err
}
return nil
}