Skip to content

[sql-41] firewalldb: add migration code for kvstores from kvdb to SQL #1079

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Open
wants to merge 9 commits into
base: master
Choose a base branch
from

Conversation

ViktorTigerstrom
Copy link
Contributor

Based on #1051

This PR introduces the migration logic for transitioning the kv stores from kvdb to SQL.

Note that as of this PR, the migration is not yet triggered by any production code, i.e. only tests execute the migration logic.

Once #1051 is merged, I will rebase this PR and request reviews.

Part of #917

@ViktorTigerstrom ViktorTigerstrom changed the title 2025 05 migrate kvstores [sql-41] firewalldb: add migration code for kvstores from kvdb to SQL May 27, 2025
@ViktorTigerstrom ViktorTigerstrom added the no-changelog This PR is does not require a release notes entry label May 27, 2025
@ViktorTigerstrom ViktorTigerstrom self-assigned this May 27, 2025
Copy link
Member

@ellemouton ellemouton left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think this doesnt need to be based on the sessions pr right? yes order of merge matters, but i think we dont need to base this one off of that one or at least can change the base branch here so that we can review both migrations in parallel

@ViktorTigerstrom ViktorTigerstrom force-pushed the 2025-05-migrate-kvstores branch 2 times, most recently from 4151b93 to 7a517ad Compare June 3, 2025 18:22
@ViktorTigerstrom
Copy link
Contributor Author

Thanks for the swift review @ellemouton 🙏! I've rebased this to be based on master and not the sessions migration PR. Note that I've kept 2 commits from the sessions migration PR in this PR (the 2 first), as they are required. So feel free to ignore reviewing the first to commits of this PR.
Also note that since this is not based on the sessions migrations PR, we can't test this migration locally as we need the sessions to be migrated prior to the kvstores (so that we can fetch the sessions SQL IDs). Let me know if you feel a need to test this prior to merging the sessions PR though, and I'll send you a branch which adds local testing that's based on both PRs.

Copy link
Member

@ellemouton ellemouton left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looking great! a couple of comments. I'll do a final round once the session PR is in 👍

@@ -141,4 +141,7 @@ type FirewallDBs interface {
RulesDB
PrivacyMapper
ActionDB

// Close closes the underlying store.
Close() error
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dont think we need this

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just call t.Cleanup sooner

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(again commit message could explain more as it took me a sec to figure out why you're adding this)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks 🙏! Addressed with a new commit in the latest push :)

@@ -14,29 +14,21 @@ var (
ErrNoSuchKeyFound = fmt.Errorf("no such key found")
)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

your commit messages could be fleshed out a bit more pls. it isnt clear why this needs to be exported (same for other commits in this PR)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, completely forgot updating the commit messages in the latest push. I've updated them in the latest push though, so I hope the commits should be understandable now!

// SQL database during the migration.
var allParams []kvParams

err := kvStore.Update(func(kvTx *bbolt.Tx) error {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why Update?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was due to the migration utilising the getMainBucket helper function, which with the previous version created the main bucket if it didn't exist. However I realised that if the bucket doesn't exist, it just means that we can skip migrating the records, so I updated the PR to do that instead with the latest push.

return err
}

// After the migration is done, we validate that all inserted kvParams
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cool yeah thanks for separating this 👍 i think it is especially important for the kv stores since there is a higher chance of overwriting 👍

},
{
name: "random records",
populateDB: randomKVRecords,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think it's worth having a case that does some explicit entries that write to all the store types. currently we only do that for the random one.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea! Added an extra test with the latest push.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it still doesnt have a deterministic test that stores in 2 feature tables/ 2 rule tables etc right?

like, currently globalRecords only inserts under a single rule name, for example

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also, looks like for this new test, all the key-value pairs inserted at all the levels are the same... so again it doesnt properly test that the correct things mapping is taking place

We add a helper function to the functions that creates the test SQL
stores, in order to ensure that the store is properly closed when the
test is cleaned up.
In preparation for upcoming migration tests from a kvdb to an SQL store,
this commit updates the NewTestDB function to return the Store interface
rather than a concrete store implementation.

This change ensures that migration tests can call NewTestDB under any
build tag while receiving a consistent return type.
We add a helper function to the functions that creates the test SQL
stores, in order to ensure that the store is properly closed when the
test is cleaned up.
We add a helper function to the functions that creates the test SQL
stores, in order to ensure that the store is properly closed when the
test is cleaned up.
In the upcoming migration of the firewall database to SQL, the helper
functions that creates the test databases of different types, need to
return a unified interface in order to not have to control the
migration tests file by build tags. Therefore, we export the unified
interface FirewallDBs, so that it can be returned public test DB
creation functions
In the upcoming migration of the firewall database to SQL, the helper
functions that creates the test databases of different types, need to
return a unified interface in order to not have to control the
migration tests file by build tags. Therefore, we update the
`NewTestDB` functions to return the `FirewallDBs` interface instead of
the specific store implementation type.
During the upcoming upcoming migration of the firewall database to SQL,
we need to be able to check all kvstores records in the SQL database,
to validate that the migration is successful in tests.

This commits adds a query to list all kvstores records, which enables
that functionality.
During the migration of the kvstores to SQL, we'll iterate over the
buckets in the bbolt database, which holds all kvstores records.

In order to understand why the migration iterates over the buckets in
the specific order, we need to clarify the bbolt kvstores illustration
docs, so that it correctly reflects how the records are actually stored
in the bbolt database.
This commit introduces the migration logic for transitioning the
kvstores 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.
@ViktorTigerstrom ViktorTigerstrom force-pushed the 2025-05-migrate-kvstores branch from 7a517ad to 10550d4 Compare June 9, 2025 12:20
@ViktorTigerstrom
Copy link
Contributor Author

ViktorTigerstrom commented Jun 9, 2025

Thanks for the review @ellemouton 🙏! Addressed the latest feedback and rebased this once again on the important commits from #1051.

Copy link
Contributor

@bitromortac bitromortac left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work 🚀, took an initial look, the structure looks great!

rules -> perm -> rule-name -> global -> {k:v}
-> sessions -> group ID -> session-kv-store -> {k:v}
-> feature-kv-stores -> feature-name -> {k:v}
"rules" -> "perm" -> rule-name -> "global" -> {k:v}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for the update here 👍

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah thanks for the fix!! 🙏

Comment on lines +268 to +269
// It inserts the records into the SQL database and asserts that
// the migrated values match the original values in the KV store.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: there are no assertions in here, same in processGroupBucket

ruleSqlId int64, groupAlias []byte,
groupBucket *bbolt.Bucket) ([]kvParams, error) {

groupSqlId, err := sqlTx.GetSessionIDByAlias(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: contract to line

ruleNameBucket := mainBucket.Bucket(k)
if ruleNameBucket == nil {
return fmt.Errorf("rule bucket %s "+
"not found", string(k))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you don't need those casts as the %s directive already does it

Comment on lines +26 to +27
Global bool
GroupID *session.ID
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you don't use the nilness of GroupID, right? could you use fn.Option[GroupID] to encode that this is a global entry?

-> sessions -> group ID -> session-kv-store -> {k:v}
-> feature-kv-stores -> feature-name -> {k:v}
"rules" -> "perm" -> rule-name -> "global" -> {k:v}
"session-kv-store" -> group ID -> {k:v}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing the -> here

rules -> perm -> rule-name -> global -> {k:v}
-> sessions -> group ID -> session-kv-store -> {k:v}
-> feature-kv-stores -> feature-name -> {k:v}
"rules" -> "perm" -> rule-name -> "global" -> {k:v}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

while we are here, can we add < & > around keys that vary? like <rule-name>

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and <group ID>& <feature name>

rules -> perm -> rule-name -> global -> {k:v}
-> sessions -> group ID -> session-kv-store -> {k:v}
-> feature-kv-stores -> feature-name -> {k:v}
"rules" -> "perm" -> rule-name -> "global" -> {k:v}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah thanks for the fix!! 🙏

continue
}

err = mainBucket.ForEach(func(k, v []byte) error {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggest adding a short comment: "Loop over each rule-name in the bucket"

}

ruleName := k
ruleNameBucket := mainBucket.Bucket(k)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ruleNameBucket := mainBucket.Bucket(k) -> ruleNameBucket := mainBucket.Bucket(ruleName)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same below, let's not continue to use k

Comment on lines +359 to +379
tempKvRecord := kvStoreRecord{
RuleName: ruleName,
GroupID: &sess.GroupID,
FeatureName: featureNameOpt,
EntryKey: entryKey,
Value: entryVal,
Perm: false,
Global: global,
}

insertKvRecord(t, ctx, boltDB, tempKvRecord)

permKvRecord := kvStoreRecord{
RuleName: ruleName,
GroupID: &sess.GroupID,
FeatureName: featureNameOpt,
EntryKey: entryKey,
Value: entryVal,
Perm: true,
Global: global,
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if you are inserting the same key-value pair in both, then things are not properly being tested since you are not testing that the one namespace doesnt overwrite the other

},
{
name: "random records",
populateDB: randomKVRecords,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also, looks like for this new test, all the key-value pairs inserted at all the levels are the same... so again it doesnt properly test that the correct things mapping is taking place

Comment on lines +352 to +358
// Create a session that we can reference.
sess, err := sessionStore.NewSession(
ctx, "test", session.TypeAutopilot,
time.Unix(1000, 0), "something",
)
require.NoError(t, err)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

only need to create session if !global

record.FeatureName.UnwrapOr(""),
)

err := kvStores.Update(ctx, func(ctx context.Context,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggest:

	err := kvStores.Update(ctx, func(ctx context.Context,
		tx KVStoreTx) error {

		store := tx.Global()
		switch {
		case record.Global && !record.Perm:
			store = tx.GlobalTemp()
		case !record.Global && !record.Perm:
			store = tx.LocalTemp()
		case !record.Global && record.Perm:
			store = tx.Local()
		}

		return store.Set(ctx, record.EntryKey, record.Value)
	})
	require.NoError(t, err)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(ie, reduce repetition where possible)

@@ -0,0 +1,486 @@
package firewalldb
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is a lot of nesting happening here with various operations happening a couple layers deep. There is also quite a lot of repetition.

Let me know what you think of this diff which follows a more step-by-step approach with fewer layers & more code re-use. Notice how migrateKVStoresDBToSQL follows an easy-to-ready step-by-step flow:

  1. collect pairs (kvdb only)
  2. migrate pairs (sql only)
  3. validate migrated pairs. (compare kvdb & sql)

(just delete .txt from the name - GH wont let me upload the .go file)
sql_migration.go.txt

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
no-changelog This PR is does not require a release notes entry
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants