Skip to content

Commit 9715313

Browse files
firewalldb: add privacy mapper SQL migration
This commit introduces the migration logic for transitioning the privacy mapper store from kvdb to SQL. Note that as of this commit, the migration is not yet triggered by any production code, i.e. only tests execute the migration logic.
1 parent be1c99a commit 9715313

File tree

2 files changed

+492
-4
lines changed

2 files changed

+492
-4
lines changed

firewalldb/sql_migration.go

Lines changed: 307 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,11 @@ func (e *kvEntry) namespacedKey() string {
6767
return ns
6868
}
6969

70+
// privacyPairs is a type alias for a map that holds the privacy pairs, where
71+
// the outer key is the group ID, and the value is a map of real to pseudo
72+
// values.
73+
type privacyPairs = map[int64]map[string]string
74+
7075
// MigrateFirewallDBToSQL runs the migration of the firwalldb stores from the
7176
// bbolt database to a SQL database. The migration is done in a single
7277
// transaction to ensure that all rows in the stores are migrated or none at
@@ -87,10 +92,14 @@ func MigrateFirewallDBToSQL(ctx context.Context, kvStore *bbolt.DB,
8792
return err
8893
}
8994

95+
err = migratePrivacyMapperDBToSQL(ctx, kvStore, sqlTx)
96+
if err != nil {
97+
return err
98+
}
99+
90100
log.Infof("The rules DB has been migrated from KV to SQL.")
91101

92-
// TODO(viktor): Add migration for the privacy mapper and the action
93-
// stores.
102+
// TODO(viktor): Add migration for the action stores.
94103

95104
return nil
96105
}
@@ -490,3 +499,299 @@ func verifyBktKeys(bkt *bbolt.Bucket, errorOnKeyValues bool,
490499
return fmt.Errorf("unexpected key found: %s", key)
491500
})
492501
}
502+
503+
func migratePrivacyMapperDBToSQL(ctx context.Context, kvStore *bbolt.DB,
504+
sqlTx SQLQueries) error {
505+
506+
log.Infof("Starting migration of the privacy mapper store to SQL")
507+
508+
// 1) Collect all privacy pairs from the KV store.
509+
privPairs, err := collectPrivacyPairs(ctx, kvStore, sqlTx)
510+
if err != nil {
511+
return fmt.Errorf("error migrating privacy mapper store: %w",
512+
err)
513+
}
514+
515+
// 2) Insert all collected privacy pairs into the SQL database.
516+
err = insertPrivacyPairs(ctx, sqlTx, privPairs)
517+
if err != nil {
518+
return fmt.Errorf("insertion of privacy pairs failed: %w", err)
519+
}
520+
521+
// 3) Validate that all inserted privacy pairs match the original values
522+
// in the KV store. Note that this is done after all values have been
523+
// inserted, to ensure that the migration doesn't overwrite any values
524+
// after they were inserted.
525+
err = validatePrivacyPairsMigration(ctx, sqlTx, privPairs)
526+
if err != nil {
527+
return fmt.Errorf("migration validation of privacy pairs "+
528+
"failed: %w", err)
529+
}
530+
531+
log.Infof("Migration of the privacy mapper stores to SQL completed. "+
532+
"Total number of rows migrated: %d", len(privPairs))
533+
return nil
534+
}
535+
536+
// collectPrivacyPairs collects all privacy pairs from the KV store and
537+
// returns them as the privacyPairs type alias.
538+
func collectPrivacyPairs(ctx context.Context, kvStore *bbolt.DB,
539+
sqlTx SQLQueries) (privacyPairs, error) {
540+
541+
groupPairs := make(privacyPairs)
542+
543+
return groupPairs, kvStore.View(func(kvTx *bbolt.Tx) error {
544+
bkt := kvTx.Bucket(privacyBucketKey)
545+
if bkt == nil {
546+
// If we haven't generated any privacy bucket yet,
547+
// we can skip the migration, as there are no privacy
548+
// pairs to migrate.
549+
return nil
550+
}
551+
552+
return bkt.ForEach(func(groupId, v []byte) error {
553+
if v != nil {
554+
return fmt.Errorf("expected only buckets "+
555+
"under %s bkt, but found value %s",
556+
privacyBucketKey, v)
557+
}
558+
559+
gBkt := bkt.Bucket(groupId)
560+
if gBkt == nil {
561+
return fmt.Errorf("group bkt for group id "+
562+
"%s not found", groupId)
563+
}
564+
565+
groupSqlId, err := sqlTx.GetSessionIDByAlias(
566+
ctx, groupId,
567+
)
568+
if errors.Is(err, sql.ErrNoRows) {
569+
return fmt.Errorf("session with group id %x "+
570+
"not found in sql db", groupId)
571+
} else if err != nil {
572+
return err
573+
}
574+
575+
groupRealToPseudoPairs, err := collectGroupPairs(gBkt)
576+
if err != nil {
577+
return fmt.Errorf("processing group bkt "+
578+
"for group id %s (sqlID %d) failed: %w",
579+
groupId, groupSqlId, err)
580+
}
581+
582+
groupPairs[groupSqlId] = groupRealToPseudoPairs
583+
584+
return nil
585+
})
586+
})
587+
}
588+
589+
// collectGroupPairs collects all privacy pairs for a specific session group,
590+
// i.e. the group buckets under the privacy mapper bucket in the KV store.
591+
// The function returns them as a map, where the key is the real value, and
592+
// the value for the key is the pseudo values.
593+
// It also checks that the pairs are consistent, i.e. that for each real value
594+
// there is a corresponding pseudo value, and vice versa. If the pairs are
595+
// inconsistent, it returns an error indicating the mismatch.
596+
func collectGroupPairs(bkt *bbolt.Bucket) (map[string]string, error) {
597+
var (
598+
realToPseudoRes map[string]string
599+
pseudoToRealRes map[string]string
600+
err error
601+
missMatchErr = errors.New("privacy mapper pairs mismatch")
602+
)
603+
604+
if realBkt := bkt.Bucket(realToPseudoKey); realBkt != nil {
605+
realToPseudoRes, err = collectPairs(realBkt)
606+
if err != nil {
607+
return nil, fmt.Errorf("fetching real to pseudo pairs "+
608+
"failed: %w", err)
609+
}
610+
} else {
611+
return nil, fmt.Errorf("%s bucket not found", realToPseudoKey)
612+
}
613+
614+
if pseudoBkt := bkt.Bucket(pseudoToRealKey); pseudoBkt != nil {
615+
pseudoToRealRes, err = collectPairs(pseudoBkt)
616+
if err != nil {
617+
return nil, fmt.Errorf("fetching pseudo to real pairs "+
618+
"failed: %w", err)
619+
}
620+
} else {
621+
return nil, fmt.Errorf("%s bucket not found", pseudoToRealKey)
622+
}
623+
624+
if len(realToPseudoRes) != len(pseudoToRealRes) {
625+
return nil, missMatchErr
626+
}
627+
628+
for realVal, pseudoVal := range realToPseudoRes {
629+
if rv, ok := pseudoToRealRes[pseudoVal]; !ok || rv != realVal {
630+
return nil, missMatchErr
631+
}
632+
}
633+
634+
return realToPseudoRes, nil
635+
}
636+
637+
// collectPairs collects all privacy pairs from a specific realToPseudoKey or
638+
// pseudoToRealKey bucket in the KV store. It returns a map where the key is
639+
// the real value or pseudo value, and the value is the corresponding pseudo
640+
// value or real value, respectively (depending on if the realToPseudo or
641+
// pseudoToReal bucket is passed to the function).
642+
func collectPairs(pairsBucket *bbolt.Bucket) (map[string]string, error) {
643+
pairsRes := make(map[string]string)
644+
645+
return pairsRes, pairsBucket.ForEach(func(k, v []byte) error {
646+
if v == nil {
647+
return fmt.Errorf("expected only key-values under "+
648+
"pairs bucket, but found bucket %s", k)
649+
}
650+
651+
if len(v) == 0 {
652+
return fmt.Errorf("empty value stored for privacy "+
653+
"pairs key %s", k)
654+
}
655+
656+
pairsRes[string(k)] = string(v)
657+
658+
return nil
659+
})
660+
}
661+
662+
// insertPrivacyPairs inserts the collected privacy pairs into the SQL database.
663+
func insertPrivacyPairs(ctx context.Context, sqlTx SQLQueries,
664+
pairs privacyPairs) error {
665+
666+
for groupId, groupPairs := range pairs {
667+
err := insertGroupPairs(ctx, sqlTx, groupPairs, groupId)
668+
if err != nil {
669+
return fmt.Errorf("inserting group pairs for group "+
670+
"id %d failed: %w", groupId, err)
671+
}
672+
}
673+
674+
return nil
675+
}
676+
677+
// insertGroupPairs inserts the privacy pairs for a specific group into
678+
// the SQL database. It checks for duplicates before inserting, and returns
679+
// an error if a duplicate pair is found. The function takes a map of real
680+
// to pseudo values, where the key is the real value and the value is the
681+
// corresponding pseudo value.
682+
func insertGroupPairs(ctx context.Context, sqlTx SQLQueries,
683+
pairs map[string]string, groupID int64) error {
684+
685+
for realVal, pseudoVal := range pairs {
686+
_, err := sqlTx.GetPseudoForReal(
687+
ctx, sqlc.GetPseudoForRealParams{
688+
GroupID: groupID,
689+
RealVal: realVal,
690+
},
691+
)
692+
if err == nil {
693+
return fmt.Errorf("duplicate privacy pair %s:%s: %w",
694+
realVal, pseudoVal, ErrDuplicatePseudoValue)
695+
} else if !errors.Is(err, sql.ErrNoRows) {
696+
return err
697+
}
698+
699+
_, err = sqlTx.GetRealForPseudo(
700+
ctx, sqlc.GetRealForPseudoParams{
701+
GroupID: groupID,
702+
PseudoVal: pseudoVal,
703+
},
704+
)
705+
if err == nil {
706+
return fmt.Errorf("duplicate privacy pair %s:%s: %w",
707+
realVal, pseudoVal, ErrDuplicatePseudoValue)
708+
} else if !errors.Is(err, sql.ErrNoRows) {
709+
return err
710+
}
711+
712+
err = sqlTx.InsertPrivacyPair(
713+
ctx, sqlc.InsertPrivacyPairParams{
714+
GroupID: groupID,
715+
RealVal: realVal,
716+
PseudoVal: pseudoVal,
717+
},
718+
)
719+
if err != nil {
720+
return fmt.Errorf("inserting privacy pair %s:%s "+
721+
"failed: %w", realVal, pseudoVal, err)
722+
}
723+
}
724+
725+
return nil
726+
}
727+
728+
// validatePrivacyPairsMigration validates that the migrated privacy pairs
729+
// match the original values in the KV store.
730+
func validatePrivacyPairsMigration(ctx context.Context, sqlTx SQLQueries,
731+
pairs privacyPairs) error {
732+
733+
for groupId, groupPairs := range pairs {
734+
err := validateGroupPairsMigration(
735+
ctx, sqlTx, groupPairs, groupId,
736+
)
737+
if err != nil {
738+
return fmt.Errorf("migration validation of privacy "+
739+
"pairs for group %d failed: %w", groupId, err)
740+
}
741+
}
742+
743+
return nil
744+
}
745+
746+
// validateGroupPairsMigration validates that the migrated privacy pairs for
747+
// a specific group match the original values in the KV store. It checks that
748+
// for each real value, the pseudo value in the SQL database matches the
749+
// original pseudo value, and vice versa. If any mismatch is found, it returns
750+
// an error indicating the mismatch.
751+
func validateGroupPairsMigration(ctx context.Context, sqlTx SQLQueries,
752+
pairs map[string]string, groupID int64) error {
753+
754+
for realVal, pseudoVal := range pairs {
755+
resPseudoVal, err := sqlTx.GetPseudoForReal(
756+
ctx, sqlc.GetPseudoForRealParams{
757+
GroupID: groupID,
758+
RealVal: realVal,
759+
},
760+
)
761+
if errors.Is(err, sql.ErrNoRows) {
762+
return fmt.Errorf("migrated privacy pair %s:%s not "+
763+
"found for real value", realVal, pseudoVal)
764+
}
765+
if err != nil {
766+
return err
767+
}
768+
769+
if resPseudoVal != pseudoVal {
770+
return fmt.Errorf("pseudo value in db %s, does not "+
771+
"match original value %s, for real value %s",
772+
resPseudoVal, pseudoVal, realVal)
773+
}
774+
775+
resRealVal, err := sqlTx.GetRealForPseudo(
776+
ctx, sqlc.GetRealForPseudoParams{
777+
GroupID: groupID,
778+
PseudoVal: pseudoVal,
779+
},
780+
)
781+
if errors.Is(err, sql.ErrNoRows) {
782+
return fmt.Errorf("migrated privacy pair %s:%s not "+
783+
"found for pseudo value", realVal, pseudoVal)
784+
}
785+
if err != nil {
786+
return err
787+
}
788+
789+
if resRealVal != realVal {
790+
return fmt.Errorf("real value in db %s, does not "+
791+
"match original value %s, for pseudo value %s",
792+
resRealVal, realVal, pseudoVal)
793+
}
794+
}
795+
796+
return nil
797+
}

0 commit comments

Comments
 (0)