-
Notifications
You must be signed in to change notification settings - Fork 102
[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
base: master
Are you sure you want to change the base?
[sql-41] firewalldb: add migration code for kvstores from kvdb to SQL #1079
Conversation
There was a problem hiding this 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
4151b93
to
7a517ad
Compare
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. |
There was a problem hiding this 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 👍
firewalldb/interface.go
Outdated
@@ -141,4 +141,7 @@ type FirewallDBs interface { | |||
RulesDB | |||
PrivacyMapper | |||
ActionDB | |||
|
|||
// Close closes the underlying store. | |||
Close() error |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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") | |||
) | |||
|
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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!
firewalldb/sql_migration.go
Outdated
// SQL database during the migration. | ||
var allParams []kvParams | ||
|
||
err := kvStore.Update(func(kvTx *bbolt.Tx) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why Update
?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
7a517ad
to
10550d4
Compare
Thanks for the review @ellemouton 🙏! Addressed the latest feedback and rebased this once again on the important commits from #1051. |
There was a problem hiding this 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} |
There was a problem hiding this comment.
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 👍
There was a problem hiding this comment.
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!! 🙏
// It inserts the records into the SQL database and asserts that | ||
// the migrated values match the original values in the KV store. |
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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)) |
There was a problem hiding this comment.
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
Global bool | ||
GroupID *session.ID |
There was a problem hiding this comment.
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} |
There was a problem hiding this comment.
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} |
There was a problem hiding this comment.
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>
There was a problem hiding this comment.
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} |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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
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, | ||
} |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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
// Create a session that we can reference. | ||
sess, err := sessionStore.NewSession( | ||
ctx, "test", session.TypeAutopilot, | ||
time.Unix(1000, 0), "something", | ||
) | ||
require.NoError(t, err) | ||
|
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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:
- collect pairs (kvdb only)
- migrate pairs (sql only)
- validate migrated pairs. (compare kvdb & sql)
(just delete .txt
from the name - GH wont let me upload the .go
file)
sql_migration.go.txt
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