Skip to content
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

Merged
merged 13 commits into from
Feb 23, 2023

Conversation

mheon
Copy link
Member

@mheon mheon commented Feb 20, 2023

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.

Podman can now use a SQLite database as a backend for increased stability.

@openshift-ci openshift-ci bot added do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. release-note labels Feb 20, 2023
@openshift-ci
Copy link
Contributor

openshift-ci bot commented Feb 20, 2023

[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 /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@openshift-ci openshift-ci bot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Feb 20, 2023
@baude baude added the bloat_approved Approve a PR in which binary file size grows by over 50k label Feb 20, 2023
@mheon mheon force-pushed the add_sql_state branch 2 times, most recently from 48d1d85 to 533aafc Compare February 20, 2023 21:16
Copy link
Member

@vrothberg vrothberg left a 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.

@@ -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.
Copy link
Member

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 != "" {
Copy link
Member

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:

  1. Update the error message and point to containers.conf
  2. Update containers.conf (i.e., c/common/pkg/config) and turn the namespace into a NOP along with removing it from the docs etc.

libpod/runtime_ctr.go Outdated Show resolved Hide resolved
return nil, fmt.Errorf("cannot connect to database: %w", err)
}

if _, err := state.conn.Exec("PRAGMA foreign_keys = ON;"); err != nil {
Copy link
Member

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.

Copy link
Member

Choose a reason for hiding this comment

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

Can the Execs be bundle into one?

Copy link
Member Author

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.

Copy link
Member

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.

Copy link
Member Author

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.

CREATE TABLE IF NOT EXISTS DBConfig(
Id INTEGER PRIMARY KEY NOT NULL,
SchemaVersion INTEGER NOT NULL,
Os TEXT NOT NULL,
Copy link
Member

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 Show resolved Hide resolved
"PodConfig": podConfig,
"PodState": podState,
"VolumeConfig": volumeConfig,
"volumeState": volumeState,
Copy link
Member

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?

libpod/sqlite_state_internal.go Outdated Show resolved Hide resolved
libpod/sqlite_state_internal.go Outdated Show resolved Hide resolved
libpod/state.go Show resolved Hide resolved
@edsantiago
Copy link
Member

CI seems stuck?

mheon added 11 commits February 22, 2023 11:00
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]>
@mheon mheon changed the title [WIP] Add initial SQLite-backed state implementation Add initial SQLite-backed state implementation Feb 22, 2023
@openshift-ci openshift-ci bot removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Feb 22, 2023
@mheon
Copy link
Member Author

mheon commented Feb 22, 2023

Removing WIP. This is good to review now.

@Luap99 @baude @vrothberg PTAL

runtime *Runtime
}

// NewBoltState creates a new bolt-backed state database
Copy link
Member

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)
Copy link
Member

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
Copy link
Member

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?

Copy link
Member Author

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
Copy link
Member

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)
Copy link
Member

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 ?

Copy link
Member Author

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
}

Copy link
Member

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?

Copy link
Member Author

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 {
Copy link
Member

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?

Copy link
Member Author

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 {
Copy link
Member

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

Copy link
Member Author

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 == "" {
Copy link
Member

@baude baude Feb 22, 2023

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}

@baude
Copy link
Member

baude commented Feb 22, 2023

LGTM, made suggestions for consideration. Not required for merge.

@vrothberg
Copy link
Member

/lgtm

Let's merge so I can do the Pod plumbing. Once that's in, we can tackle refactoring and code clones.

@openshift-ci openshift-ci bot added the lgtm Indicates that a PR is ready to be merged. label Feb 23, 2023
@openshift-merge-robot openshift-merge-robot merged commit 3796e22 into containers:main Feb 23, 2023
Copy link
Collaborator

@mtrmac mtrmac left a 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.)

Comment on lines +286 to +290
INSERT INTO DBconfig VALUES (
?, ?, ?,
?, ?, ?,
?, ?, ?
);`
Copy link
Collaborator

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 INSERTs, 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)
Copy link
Collaborator

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.)

Comment on lines +846 to +856
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)
}
}
}()
Copy link
Collaborator

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.

Comment on lines +993 to +1001
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)
}
Copy link
Collaborator

@mtrmac mtrmac Feb 23, 2023

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.

@github-actions github-actions bot added the locked - please file new issue/PR Assist humans wanting to comment on an old issue or PR with locked comments. label Sep 8, 2023
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Sep 8, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
approved Indicates a PR has been approved by an approver from all required OWNERS files. bloat_approved Approve a PR in which binary file size grows by over 50k lgtm Indicates that a PR is ready to be merged. locked - please file new issue/PR Assist humans wanting to comment on an old issue or PR with locked comments. release-note
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants