-
Notifications
You must be signed in to change notification settings - Fork 242
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
base: main
Are you sure you want to change the base?
Conversation
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: harche 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 |
/hold |
types/options.go
Outdated
} | ||
|
||
func mergeStoreOptions(base, dropIn StoreOptions) StoreOptions { | ||
if dropIn.RunRoot != "" { |
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.
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
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. Turns out they already have a function that does that,
Line 415 in 1fd0dc1
func ReloadConfigurationFile(configFile string, storeOptions *StoreOptions) 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.
Not really, they don't serialize and automatically 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.
@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?
cce7ffd
to
b13d446
Compare
types/options.go
Outdated
// 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" |
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.
these may need to remain in the platform specific paths. i'm not sure if there are windows paths that need to be supported.
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 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
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, 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 |
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.
Do you mean to return an err here, since the file 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.
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 { |
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.
return filepath.Walk(...)... The if statements are not necessary.
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. updated.
if info.IsDir() { | ||
return 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.
Nice update. Should check for file extension 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.
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.
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.
Hmm. We support a file without an extension?
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.
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
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.
Usually there is a filter, but this is fine.
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.
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...
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 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
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.
Yes require a .conf extension, that follows what we have done with other dropin files.
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 @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.
d56119b
to
697c412
Compare
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) |
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.
Very quickly skimming the implementation of ReloadConfigurationFile
, it is not designed to merge things at all; it starts with *storeOptions = StoreOptions{}
.
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 reminder: ^^^. I can’t see how this works at all. What am I missing?
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 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.
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.
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)
}
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 @mtrmac .
/hold until we fix it.
@rhatdan I opened containers/podman#22484. Most of the jobs were green, apart from the one for the |
|
||
if _, err := os.Stat(defaultDropInConfigDir); err == nil { | ||
// The directory exists, so merge the configuration from this directory | ||
err = mergeConfigFromDirectory(&baseOptions, defaultDropInConfigDir) |
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 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
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 @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
Outdated
// 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" |
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.
Let's make those const
if they're not variable.
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.
Because of this change #1885 (comment), we may not be able to make it a const
.
7ca8f82
to
9192784
Compare
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 { |
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.
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.
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.
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.
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.
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.
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 @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
}
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 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,
}
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 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?
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 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?
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.
https://issues.redhat.com/browse/OCPBUGS-14399 I remember we had to hack around it.
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.
systemd has definitely benefited from a single consistent config file format with consistent semantics and merge support.
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.
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
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. |
@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.
If you have the patch for the |
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. |
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. |
Helps in use cases where a drop-in config could help by allowing users to update a subsection of the config instead of touching an entire config file.
e.g.