-
Notifications
You must be signed in to change notification settings - Fork 497
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 model config handlers for save and load for secret backends #17404
Conversation
dee1b3a
to
240ec3d
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.
Nice change. Just one question.
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 sure I like this pattern but I see the need.
We discussed at the sprint and it was agreed it was what we needed to do. |
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.
juju add-secret-backend myvault vault endpoint=http://10.0.0.1:8200 --debug
hangs for me. There is no corresponding debug-log output.
I'm concerned about the cross-DB transaction issues. Even though we would lose some RI, wouldn't it be better to record the model's secret back-end in the model database, allowing you to get transactional safety?
err := h.BackendState.SetModelSecretBackend(ctx, h.ModelUUID, backendName) | ||
if err != nil && !errors.Is(err, backenderrors.NotFound) { | ||
return fmt.Errorf("cannot set secret backend to %q: %w", backendName, 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.
Are we always going to be able to set the value in model config before setting the model config, are we ever going to have the requirement to do it post setting the model config?
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 I understand you correctly, I'd rather do things in this order - do the relational model update(s) and then save the model config.
Thank you for the work @wallyworld. My review will follow after this but just wanted to dump a thought here that will lead into the review. I think we need to make the decision now on what model config actually is as we are distorting the concepts at every layer and we are ending up with an identity problem. To that end my current thinking is we need to think about ModelConfig as a user interface component and nothing more. Hopefully this will make more sense in my review comments. |
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 @wallyworld for playing around with this and providing the prototype. Please know my comments come from a good place.
I want to discuss this more as I think there is a lot of concerns being mixed here.
// - There is no changes to authorized keys. | ||
func (s *Service) updateModelConfigValidator( | ||
modelType model.ModelType, |
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.
modelType should be fetched within this func. It makes no sense to make the call site do the extra work when we can do it in here do what we say on the tin.
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 avoids doing 2 queries when 1 will do the job. The caller makes a call to st to get the model uuid and the model type in one query on the model table. The model type is used here and the uuid used by the caller in the next step. I really didn't want to make 2 queries to the model table.
Do you have a vault running? Part of the validation is to Ping() the backend to ensure it is configured correctly. Perhaps the ping is taking a while to time out. This isn't new to this PR per se. Assuming this does need to be a synchronous check, we really need a better way to provide feedback to the caller. If we want to make it async, that would allow a bad backend config to get into the model and the workflow to deal with that I think would possibly be more inconvenient; we don't have a way currently to surface bad config and inform the user how to fix it. |
RE: the cross txn issues. This was discussed and it was felt this approach was better. It's a case of pick your poison. Adding a best effort rollback - which should have been done originally - mitigates somewhat the transactional safety issue whilst maintaining RI. Happy to discuss f2f. The purpose of this PR was to give a concrete look at the abstract idea thrown around at the sprint so as to allow more informed feedback. So the discussion is good. We can talk next dqlite sync. |
fb47984
to
0643d64
Compare
0e93196
to
6448e85
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.
Thanks @wallyworld for riffing on this. Have left some more comments. Happy to discuss.
// if there's any error. | ||
currentBackendName, err := h.BackendState.GetModelSecretBackendName(ctx, h.ModelUUID) | ||
if err != nil { | ||
return noopRollback, errors.Trace(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.
Can we add some context here. Like what we are trying to do and with information.
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.
We need to be careful not to go overboard with annotating errors. The caller which calls the OnSave will print the name of the handler and the message from GetModelSecretBackendName when combined with that will tell you exactly what went wrong without needing to write an essay. It was a deliberate choice not to annotate here.
return noopRollback, errors.Trace(err) | ||
} | ||
rollbackFunc := func(ctx context.Context) error { | ||
return h.BackendState.SetModelSecretBackend(ctx, h.ModelUUID, currentBackendName) |
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 would be great if we can add some context to the error here, What we are trying to do and with what information.
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.
ditto
}, nil | ||
} | ||
|
||
func (s *Service) runOnLoadHandlers(ctx context.Context, modelUUID model.UUID, stConfig map[string]string) 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.
I have some thoughts on this func.
- modelUUID should not be passed into the func. It is a requirement internally to the func to do its job and construct the config Handlers.
runOnLoadHandlers
can do this work for it self as model config is scoped to single model. - stConfig should not be passed into the function as on load handlers is performing merging that should be left up to the caller. Simply this func should just return the result of calling on load for all the handlers as a merged collection and let the call site worry about the merge. Also the func doesn't modify it's input arguments then.
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 want to assume the model config map has modelUUID in it as we are trying to get away from that ASAP. So we need to pass in modelUUID as a separate parameter.
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.
OnLoad handler runner has been tweaked.
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.
modelUUID can be fetched from the model info table. You don't need to read model config for the id. My comments above still hold.
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, it is read from the model info table. But only once. It is then passed to all the handlers. There's no need to read the same thing multiple times.
4611f4f
to
347aa02
Compare
1f5ac1a
to
aa7ca1c
Compare
Model config handlers are used to extract model config values to populate the data model on save, and the reverse on load
Refactor test model creation
Improve doc comments Fix come contexts in tests Onload handlers return merged config
aa7ca1c
to
2f512c2
Compare
Introduce the concept of partial saves: save what we can and return a PartialSaveError Handlers take a specific set of attributes to work on
2f512c2
to
89f21f2
Compare
Not required anymore. |
Model config handlers are used to extract model config values to populate the data model on save, and the reverse on load. Many model config values are scalars and do belong in the key value data bag. However, things like secret backend and default space form part of the relational data model; as such, we need to provide a mechanism to handle this scenario.
As part of the implementation, the previously added logic to update the model secret backend was moved from secretbackend package to model config.
A config handler is called as part of the "update" and "get" service calls. Handlers act on a defined subset of the config keys. They specify what keys they are interested in - the service will extra those keys from the databag being saved and pass their values to the handler. After the handler is done, the keys are removed from the databag so the final write to the db only persists key values not already processed by a handler. For gets, the data model is queried and values are inserted into the config. This allows the current UX and api semantics to be retained.
There were also some missing validations for secret backend, based on model type. The validation logic is updated accordingly. Handlers have a Validate() method - if a handler is used, it's responsible for all validation for the attributes it cares about.
Sadly, updates from txn boundaries (writes to different dbs) and so the updates are not atomic. Iff handlers are run but the final db updates result in an error, a PartialSaveError is returned containing (if known) the attributes that could not be saved and the cause (some attributes will have been saved). It is expected the caller might try again with the same values. This strategy was done instead of attempting to rollback on error.
Also, test helpers are updated to take a model type.
Note - future work will be needed to improve model defaults. These are still just treated as a data bag of values and there's no validation done at all, and there's no corresponding modelling of non scalar values.
This PR implements support for secret backend - the model default space will also need to be done separately.
QA steps
Links
Jira card: JUJU-6051