-
Notifications
You must be signed in to change notification settings - Fork 2.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add initial SQLite-backed state implementation #17586
Conversation
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: mheon The full list of commands accepted by this bot can be found here. The pull request process is described here
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
48d1d85
to
533aafc
Compare
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.
A couple of minor nits and questions.
libpod/boltdb_state.go
Outdated
@@ -570,6 +565,61 @@ func (s *BoltState) GetName(id string) (string, error) { | |||
return name, nil | |||
} | |||
|
|||
// GetPodName returns the name associated with a given ID. | |||
// Returns ErrNoSuchPor if the ID does not exist. |
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.
s/ErrNoSuchPor/ErrNoSuchPoD/
@@ -415,10 +427,9 @@ func makeRuntime(runtime *Runtime) (retErr error) { | |||
logrus.Errorf("Runtime paths differ from those stored in database, storage reset may not remove all files") | |||
} | |||
|
|||
if err := runtime.state.SetNamespace(runtime.config.Engine.Namespace); err != nil { | |||
return fmt.Errorf("setting libpod namespace in state: %w", err) | |||
if runtime.config.Engine.Namespace != "" { |
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.
The error message isn't that helpful for users who have a namespace configured in containers.conf. I suggest two changes:
- Update the error message and point to containers.conf
- Update containers.conf (i.e., c/common/pkg/config) and turn the namespace into a NOP along with removing it from the docs etc.
return nil, fmt.Errorf("cannot connect to database: %w", err) | ||
} | ||
|
||
if _, err := state.conn.Exec("PRAGMA foreign_keys = ON;"); err != nil { |
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.
Please add comments to what these settings do. Ideally with links to docs.
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.
Can the Exec
s be bundle into 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.
I don't think so, one statement per exec seems to be safest.
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.
"seems" or "is"? I don't want to challenge but am mostly looking for cases that could be further tweaked for performance.
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.
Seems. I'm not 100% on this - my reading on the subject is not conclusive.
libpod/sqlite_state_internal.go
Outdated
CREATE TABLE IF NOT EXISTS DBConfig( | ||
Id INTEGER PRIMARY KEY NOT NULL, | ||
SchemaVersion INTEGER NOT NULL, | ||
Os TEXT NOT NULL, |
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.
Can we call it ID
and OS
? Maybe just my personal preference but Id
and Os
always look wrong to me.
libpod/sqlite_state_internal.go
Outdated
"PodConfig": podConfig, | ||
"PodState": podState, | ||
"VolumeConfig": volumeConfig, | ||
"volumeState": volumeState, |
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.
The key is lower caps. Is that intended?
CI seems stuck? |
This contains the implementation of (most) container functions, with stubs for all pod and volume functions. Presently accessed via environment variable only for testing purposes. Signed-off-by: Matt Heon <[email protected]>
This has been broken since we added Volumes - so, Podman v0.12.1 (so, around 5 years). I have no evidence anyone is using it in the wild. It doesn't really function as expected. And it's a lot of extraneous code and tests for the database. Rip it out entirely, we can re-add once BoltDB is gone if there is a requirement to do so. Signed-off-by: Matt Heon <[email protected]>
- Added a mechanism to check schema version and migrate (no migrations yet since schema hasn't changed yet). - Added pod support to AddContainer, and unified AddContainer and RemoveContainer between containers and pods. - Fixed newly-added GetPodName and GetCtrName in BoltDB so they only return pod/container names. Signed-off-by: Matt Heon <[email protected]>
Signed-off-by: Matt Heon <[email protected]>
Signed-off-by: Matt Heon <[email protected]>
Signed-off-by: Matt Heon <[email protected]>
Signed-off-by: Matt Heon <[email protected]>
Signed-off-by: Matt Heon <[email protected]>
Signed-off-by: Matt Heon <[email protected]>
Signed-off-by: Matt Heon <[email protected]>
Signed-off-by: Matt Heon <[email protected]>
Signed-off-by: Matt Heon <[email protected]>
Removing WIP. This is good to review now. @Luap99 @baude @vrothberg PTAL |
libpod/sqlite_state.go
Outdated
runtime *Runtime | ||
} | ||
|
||
// NewBoltState creates a new bolt-backed state database |
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.
bolt references
runtimeOS = goruntime.GOOS | ||
runtimeStaticDir = filepath.Clean(s.runtime.config.Engine.StaticDir) | ||
runtimeTmpDir = filepath.Clean(s.runtime.config.Engine.TmpDir) | ||
runtimeGraphRoot = filepath.Clean(s.runtime.StorageConfig().GraphRoot) |
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.
long-term these could be ripe for a struct, esp bc you do a bunch of validation afterwards, take it or leave it
return "", fmt.Errorf("looking up container %s name: %w", id, err) | ||
} | ||
|
||
return name, nil |
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.
would it be faster to put the return name after the query and deal with the errors afterwards?
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.
Can you describe this better? I can't really visualize what you're looking for.
return "", fmt.Errorf("looking up pod %s name: %w", id, err) | ||
} | ||
|
||
return name, nil |
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 as above, about early return
foundResult := false | ||
for rows.Next() { | ||
if foundResult { | ||
return "", fmt.Errorf("more than one result for container %q: %w", idOrName, define.ErrCtrExists) |
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.
should this be more about unable to find unique container from %s ? Also, ErrCtrExists ... is that the correct 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.
This matches what Bolt did.
if !s.valid { | ||
return nil, define.ErrDBClosed | ||
} | ||
|
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.
can this use Lookupcontainerorid? Or perhaps an unexported func that prevents the duplicate query between funcs?
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.
Duplication saves us one query, so it's a bit faster this was, but we can probably share parts of the logic.
// WARNING: This function is DANGEROUS. Do not use without reading the full | ||
// comment on this function in state.go. | ||
// TODO: Once BoltDB is removed, this can be combined with SafeRewriteContainerConfig. | ||
func (s *SQLiteState) RewriteContainerConfig(ctr *Container, newCfg *ContainerConfig) 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.
does it need to be exported?
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.
To meet the interface, yes.
// DO NOT USE TO: Change container dependencies, change pod membership, change | ||
// locks, change container ID. | ||
// TODO: Once BoltDB is removed, this can be combined with RewriteContainerConfig. | ||
func (s *SQLiteState) SafeRewriteContainerConfig(ctr *Container, oldName, newName string, newCfg *ContainerConfig) 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.
same question wrt "dangerous" methods
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's not accessible outside of Libpod because the DB is never passed out of libpod.
|
||
// LookupPod retrieves a pod from a full or unique partial ID, or a name. | ||
func (s *SQLiteState) LookupPod(idOrName string) (*Pod, error) { | ||
if idOrName == "" { |
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.
this same boilerplate is used each time. i could some beauty in something like:
if err := s.validate(idOrName); err != nil { return nil, err}
LGTM, made suggestions for consideration. Not required for merge. |
Signed-off-by: Matt Heon <[email protected]>
/lgtm Let's merge so I can do the Pod plumbing. Once that's in, we can tackle refactoring and code clones. |
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 was just curious, and I have no expertise in this area of Podman nor specifics of SQLite. Consider these as just mild suggestions.)
INSERT INTO DBconfig VALUES ( | ||
?, ?, ?, | ||
?, ?, ?, | ||
?, ?, ? | ||
);` |
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.
(Maybe this should specify the destination columns explicitly, to account for possible future writers that add more columns to the table. I suppose that it should never happen that DBconfig
schema is defined while there are no rows in that table… still, it seems to me that naming the destination columns is a good habit in general.
E.g. naming columns explicitly means that the Go code doesn’t need to worry about the column ordering in the schema at all.
To an extent, applies to all INSERT
s, although the others should be even less affected than this one.)
return "", define.ErrDBClosed | ||
} | ||
|
||
rows, err := s.conn.Query("SELECT ID FROM ContainerConfig WHERE ContainerConfig.Name=? OR (ContainerConfig.ID LIKE ?);", idOrName, idOrName) |
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.
Doesn’t this allow users to inject SQL metacharacters into the LIKE
pattern?
(Sure, Podman itself probably doesn’t care. I’m worried that someone builds a system with half a dozen abstraction layers on top, and this ends up enabling some kind of sandbox / namespaced account escape. So I’d be more comfortable with either some code that escapes pattern characters in idOrName
, or ideally restricting idOrName
to an explicitly-allowed set of characters.)
(Applies to LIKE ?
throughout.)
tx, err := s.conn.Begin() | ||
if err != nil { | ||
return fmt.Errorf("beginning transaction to add exit code: %w", err) | ||
} | ||
defer func() { | ||
if defErr != nil { | ||
if err := tx.Rollback(); err != nil { | ||
logrus.Errorf("Rolling back transaction to add exit code: %v", 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.
The begin/rollback/exec/commit boilerplate really strongly suggests to me the need for an execInTransaction(command string, params any...) error
helper that implements that once instead of copy&pasting.
A lower-level execFnInTransaction(func () error) error
that just encapsulates the begin/rollback/commit parts, allowing the caller to perform multiple operations, might also pay off.
row := s.conn.QueryRow("SELECT ContainerID FROM ContainerExecSession WHERE ID=?;", id) | ||
|
||
var ctrID string | ||
if err := row.Scan(&ctrID); err != nil { | ||
if errors.Is(err, sql.ErrNoRows) { | ||
return "", fmt.Errorf("no exec session with ID %s found: %w", id, define.ErrNoSuchExecSession) | ||
} | ||
return "", fmt.Errorf("retrieving exec session %s from database: %w", id, 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.
A getSingleSelectResult(dest *any, query string, params any...)
might also pay off.
This contains the implementation of (most) container functions, with stubs for all pod and volume functions. Presently accessed via environment variable only for testing purposes.