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 support for drop-in config #1885

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open

Conversation

harche
Copy link

@harche harche commented Apr 12, 2024

Copy link
Contributor

openshift-ci bot commented Apr 12, 2024

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: harche
Once this PR has been reviewed and has the lgtm label, please assign nalind for approval. For more information see the Kubernetes Code Review Process.

The full list of commands accepted by this bot can be found 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

@harche
Copy link
Author

harche commented Apr 12, 2024

/hold

types/options.go Outdated
}

func mergeStoreOptions(base, dropIn StoreOptions) StoreOptions {
if dropIn.RunRoot != "" {
Copy link
Contributor

Choose a reason for hiding this comment

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

instead of individually adding fields (which won't scale well, can we just serialize into the struct: https://github.com/cri-o/cri-o/blob/256fda5ac98de1d67e26133d0b25df2a74e2ebd2/pkg/config/config.go#L705

Copy link
Author

Choose a reason for hiding this comment

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

Thanks. Turns out they already have a function that does that,

func ReloadConfigurationFile(configFile string, storeOptions *StoreOptions) error {

Copy link
Author

Choose a reason for hiding this comment

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

Not really, they don't serialize and automatically update.

Copy link
Author

Choose a reason for hiding this comment

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

@haircommander I updated the code to reuse existing ReloadConfigurationFile (instead of writing a new merge config logic) which seem to be doing more than just updating the options. ReloadConfigurationFile is in use already, so I think we can safely rely on it. Although I admit it is not as scalable as the cri-o approach is, but using ReloadConfigurationFile allows us to reuse their existing way of reading and parsing a config file.

WDYT?

@harche harche force-pushed the dropin branch 2 times, most recently from cce7ffd to b13d446 Compare April 12, 2024 18:49
types/options.go Outdated
Comment on lines 53 to 57
// defaultOverrideConfigFile path to override the default system wide storage.conf file
defaultOverrideConfigFile = "/etc/containers/storage.conf"

// defaultDropInConfigDir path to the folder containing drop in config files
defaultDropInConfigDir = "/etc/containers/storage.conf.d"
Copy link
Contributor

Choose a reason for hiding this comment

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

these may need to remain in the platform specific paths. i'm not sure if there are windows paths that need to be supported.

Copy link
Author

Choose a reason for hiding this comment

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

I moved it here because all platform specific files had same value for that variable, including windows one. Although it does look incorrect for the windows, but I just moved it from options_windows.go

Copy link
Author

@harche harche Apr 16, 2024

Choose a reason for hiding this comment

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

Also, if I don't add it here, then just like existing defaultOverrideConfigFile, I will have to add new defaultDropInConfigDir to all platform specific files, otherwise the compilation would fail on those platforms.

types/options.go Outdated
}

if _, err := os.Stat(defaultDropInConfigDir); err != nil && os.IsNotExist(err) {
return defaultStoreOptions, err
Copy link
Contributor

Choose a reason for hiding this comment

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

Do you mean to return an err here, since the file does not exist?

Copy link
Author

Choose a reason for hiding this comment

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

yes, you are right. even if the defaultDropInConfigDir does not exist then there is no need to return the error. Thanks for point it out. I will update the changes.

types/options.go Outdated

return nil
})
if err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

return filepath.Walk(...)... The if statements are not necessary.

Copy link
Author

Choose a reason for hiding this comment

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

Thanks. updated.

if info.IsDir() {
return nil
}

Copy link
Contributor

Choose a reason for hiding this comment

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

Nice update. Should check for file extension here.

Copy link
Author

Choose a reason for hiding this comment

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

But a text file without a .conf or .toml can also contain a valid drop-in config. For any reason the content is not valid (irrespective of the extension) it will fail in ReloadConfigurationFile below while parsing the toml.

Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm. We support a file without an extension?

Copy link
Author

Choose a reason for hiding this comment

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

as long as the content of the file is valid, it shouldn't matter, right?

crio doesn't enforce the extension either - https://github.com/cri-o/cri-o/blob/256fda5ac98de1d67e26133d0b25df2a74e2ebd2/pkg/config/config.go#L776

Copy link
Contributor

Choose a reason for hiding this comment

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

Usually there is a filter, but this is fine.

Copy link
Contributor

Choose a reason for hiding this comment

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

one risk is temporary files written by editor and the like. It mostly only is an issue if we're reloading automatically. If it's manually triggered (like here) then the user usually has written and edited the editor before the reload happens...

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 requiring a .conf or .toml extension could be a nice change both here and cri-o. @sohankunkerkar added a filter for the kubelet's drop-in work

Copy link
Member

Choose a reason for hiding this comment

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

Yes require a .conf extension, that follows what we have done with other dropin files.

Copy link
Author

Choose a reason for hiding this comment

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

Thanks @rphillips @haircommander @rhatdan for this feedback. I have updated the code to select files with .conf extension only and also updated corresponding unit test to verify that.

@harche harche force-pushed the dropin branch 5 times, most recently from d56119b to 697c412 Compare April 19, 2024 15:05
@harche harche marked this pull request as ready for review April 19, 2024 15:05
@rhatdan
Copy link
Member

rhatdan commented Apr 20, 2024

Could you open a [WIP] PR against Podman to test this feature, and make sure it does not break anything in it's ci/cd system.

types/options.go Outdated
}

// Load drop-in options from the current file
err = ReloadConfigurationFile(path, baseOptions)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Very quickly skimming the implementation of ReloadConfigurationFile, it is not designed to merge things at all; it starts with *storeOptions = StoreOptions{}.

Copy link
Collaborator

Choose a reason for hiding this comment

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

A reminder: ^^^. I can’t see how this works at all. What am I missing?

Copy link
Author

Choose a reason for hiding this comment

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

The included unit test TestMergeConfigFromDirectory gives me the impression that the change is working as expected, but maybe I am missing something. Can you suggest a change to that test which can bring out the failure you are anticipating?

The way I am looking at it is, for every drop-in config file we are initializing *storeOptions = StoreOptions{} and then merging it with the earlier values. But again, there is a good chance I might have overlooked something. A test case would definitely help me understand it better.

Copy link
Collaborator

Choose a reason for hiding this comment

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

That test is not testing merging at all.

As just a starting point (I don’t understand all of the c/storage option loading code, too many functions and too many code paths and too many hooks; I’m scared of that):

diff --git a/types/options_test.go b/types/options_test.go
index bd5a70c8b..92d35dc6e 100644
--- a/types/options_test.go
+++ b/types/options_test.go
@@ -227,8 +227,7 @@ graphroot = 'temp/graph2'`,
 runroot = 'should/ignore'
 graphroot = 'should/ignore'`,
                `[storage]
-runroot = 'temp/run3'
-graphroot = 'temp/graph3'`,
+runroot = 'temp/run3'`,
                `[storage]
 runroot = 'temp/run4'
 graphroot = 'temp/graph4'`,
@@ -242,14 +241,16 @@ graphroot = 'temp/graph4'`,
 
        // Set base options
        baseOptions := StoreOptions{
-               RunRoot:   "initial/run",
-               GraphRoot: "initial/graph",
+               RunRoot:        "initial/run",
+               GraphRoot:      "initial/graph",
+               TransientStore: true,
        }
 
        // Expected results after merging configurations from only .conf files
        expectedOptions := StoreOptions{
-               RunRoot:   "temp/run3", // Last .conf file (config3.conf) read overrides earlier values
-               GraphRoot: "temp/graph3",
+               RunRoot:        "temp/run3", // Last .conf file (config3.conf) read overrides earlier values
+               GraphRoot:      "temp/graph2",
+               TransientStore: true,
        }
 
        // Run the merging function
@@ -258,7 +259,5 @@ graphroot = 'temp/graph4'`,
                t.Fatalf("Error merging config from directory: %v", err)
        }
 
-       if baseOptions.RunRoot != expectedOptions.RunRoot || baseOptions.GraphRoot != expectedOptions.GraphRoot {
-               t.Errorf("Expected RunRoot to be %q and GraphRoot to be %q, got RunRoot %q and GraphRoot %q", expectedOptions.RunRoot, expectedOptions.GraphRoot, baseOptions.RunRoot, baseOptions.GraphRoot)
-       }
+       assert.DeepEqual(t, expectedOptions, baseOptions)
 }

Copy link
Author

Choose a reason for hiding this comment

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

Thanks @mtrmac .

/hold until we fix it.

@harche
Copy link
Author

harche commented Apr 24, 2024

Could you open a [WIP] PR against Podman to test this feature, and make sure it does not break anything in it's ci/cd system.

@rhatdan I opened containers/podman#22484. Most of the jobs were green, apart from the one for the mac. I am not able to trigger the retest of that job, it needs /ok-to-test label.

@rhatdan
Copy link
Member

rhatdan commented Apr 24, 2024


if _, err := os.Stat(defaultDropInConfigDir); err == nil {
// The directory exists, so merge the configuration from this directory
err = mergeConfigFromDirectory(&baseOptions, defaultDropInConfigDir)
Copy link
Member

@giuseppe giuseppe Apr 24, 2024

Choose a reason for hiding this comment

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

I am not sure we should hardcode a specific directory, but instead try to load each file from the $STORAGE_CONF + ".d" directory, where $STORAGE_CONF is whatever file we decided to use

Copy link
Author

Choose a reason for hiding this comment

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

Thanks @giuseppe, that's a good idea. I modified the code to set the drop-in config dir to storage conf path + .d

types/options.go Show resolved Hide resolved
types/options.go Outdated
Comment on lines 53 to 57
// defaultOverrideConfigFile path to override the default system wide storage.conf file
defaultOverrideConfigFile = "/etc/containers/storage.conf"

// defaultDropInConfigDir path to the folder containing drop in config files
defaultDropInConfigDir = "/etc/containers/storage.conf.d"
Copy link
Member

Choose a reason for hiding this comment

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

Let's make those const if they're not variable.

Copy link
Author

Choose a reason for hiding this comment

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

Because of this change #1885 (comment), we may not be able to make it a const.

@harche harche force-pushed the dropin branch 2 times, most recently from 7ca8f82 to 9192784 Compare April 26, 2024 17:54
Signed-off-by: Harshal Patil <harpatil@redhat.com>
@@ -519,8 +570,13 @@ func ReloadConfigurationFile(configFile string, storeOptions *StoreOptions) erro
storeOptions.PullOptions = config.Storage.Options.PullOptions
}

storeOptions.DisableVolatile = config.Storage.Options.DisableVolatile
storeOptions.TransientStore = config.Storage.TransientStore
if config.Storage.Options.DisableVolatile {
Copy link
Collaborator

Choose a reason for hiding this comment

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

How do I set disableFolatile = false in the drop-in?

It seems to me that all of this is going to require some other approach, and probably very detailed testing.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Or, alternatively, be very strict and very restrictive about which options are supported in drop-ins, support/test only those, and hard-fail if any others are present. Then we can add support for more options over time … assuming it’s sufficiently certain adding the others will actually be possible in the future.

Copy link
Contributor

Choose a reason for hiding this comment

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

Agreed, we need to call meta.Keys() to pull out the keys that are being set, then selectively set those values on a storeOptions instance.

Copy link
Author

@harche harche Apr 30, 2024

Choose a reason for hiding this comment

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

Thanks @rphillips. If I get the toml.DecodeFile(configFile, &config).Keys() and update the values only if the key is defined in a drop-in config then it seem to fix the issue.

keySet := make(map[string]bool)
for _, key := range meta.Keys() {
	keySet[key.String()] = true
}

if _, ok := keySet["storage.transient_store"]; ok {
	storeOptions.TransientStore = config.Storage.TransientStore
}

cc @mtrmac @haircommander @sohankunkerkar

Copy link
Author

Choose a reason for hiding this comment

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

The updated snippet of the included test looks like this,

contents := []string{
		`[storage]
runroot = 'temp/run1'
graphroot = 'temp/graph1'
transient_store = false`,
		`[storage]
runroot = 'temp/run2'
graphroot = 'temp/graph2'`,
		`[storage]
runroot = 'should/ignore'
graphroot = 'should/ignore'`,
		`[storage]
runroot = 'temp/run3'`,
		`[storage]
runroot = 'temp/run4'
graphroot = 'temp/graph4'`,
	}
	for i, fileName := range fileNames {
		filePath := filepath.Join(tempDir, fileName)
		if err := os.WriteFile(filePath, []byte(contents[i]), 0o666); err != nil {
			t.Fatalf("Failed to write to temp file: %v", err)
		}
	}

	// Set base options
	baseOptions := StoreOptions{
		RunRoot:        "initial/run",
		GraphRoot:      "initial/graph",
		TransientStore: true,
	}

	// Expected results after merging configurations from only .conf files
	expectedOptions := StoreOptions{
		RunRoot:        "temp/run3", // Last .conf file (config3.conf) read overrides earlier values
		GraphRoot:      "temp/graph2",
		TransientStore: false,
	}

Copy link
Contributor

Choose a reason for hiding this comment

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

I sorta worry about mergo setting options to false. Did we get past this issue in MCO where values being set to false wouldn’t get set?

Copy link
Author

Choose a reason for hiding this comment

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

Yeah the mergo option didn't work. It does set the option to false as you predicated.

code is at, harche@f0b92df#diff-5c0b7c9ae084e6c4613c438d00cede106862b604f184a331df1c3806cfbd11d2R164

So it seems like our only option is to use meta.Keys()?

BTW, which MCO issue are you talking about?

Copy link
Contributor

Choose a reason for hiding this comment

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

https://issues.redhat.com/browse/OCPBUGS-14399 I remember we had to hack around it.

Copy link
Contributor

Choose a reason for hiding this comment

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

systemd has definitely benefited from a single consistent config file format with consistent semantics and merge support.

Copy link
Contributor

Choose a reason for hiding this comment

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

Explicitly iterating over the keys seems like it'd work, but another thing to do is to use nullable values for all entries. This is much more elegant in Rust of course...here's how I recently did a basic "mergeable toml" in bootc:

https://github.com/containers/bootc/blob/f152bfe8da47c3bfbafca33c2142d10611375fa3/lib/src/install/config.rs#L47 (Note each entry is Option<T>) and there's a Mergeable trait which can be called recursively e.g. https://github.com/containers/bootc/blob/f152bfe8da47c3bfbafca33c2142d10611375fa3/lib/src/install/config.rs#L106

@cgwalters
Copy link
Contributor

This would be really quite useful to have, it's just completely annoying right now to e.g. enable composefs because one needs to copy all of the storage config.

@ktock
Copy link
Contributor

ktock commented Sep 9, 2024

@harche Hi, thanks for working on this PR. This PR looks beneficial for several use cases so it would be great if we can move this PR forward.

So it seems like our only option is to use meta.Keys()?

If you have the patch for the meta.Keys() approach, could you update this PR with that patch? Or, if you don't have enough time for this PR, I'm willing to take over this PR.

@harche
Copy link
Author

harche commented Sep 10, 2024

@harche Hi, thanks for working on this PR. This PR looks beneficial for several use cases so it would be great if we can move this PR forward.

So it seems like our only option is to use meta.Keys()?

If you have the patch for the meta.Keys() approach, could you update this PR with that patch? Or, if you don't have enough time for this PR, I'm willing to take over this PR.

Thanks @ktock but this is an optional (good to have) thing for the lazy image pull. In case of Openshift, there is an operator called MCO which drops the storage configuration on the node. It is totally possible to implement the logic in MCO to drop a storage config file on the node to support lazy image pulls. MCO has been doing that with KubeletConfig for awhile now.

Since this is an optional feature from lazy image point of view, this change is not on high priority. But you can still look into it if you feel this library should support drop-in config nevertheless.

@mtrmac
Copy link
Collaborator

mtrmac commented Sep 10, 2024

My personal opinion is that this is fairly likely to be brittle and hard-to-maintain code (though I could be well wrong about that, there might be a neat trick that makes it easy); and that the option parsing in c/storage is already convoluted enough that I’d prefer not to add any extra complexity.

I’m not a c/storage maintainer, though, so it’s not up to me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants