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

refactor: drives model config into mongo from dqlite. #18042

Open
wants to merge 20 commits into
base: main
Choose a base branch
from

Conversation

tlm
Copy link
Member

@tlm tlm commented Sep 6, 2024

This PR introduces provider based model config defaults into the model default service. Previously when calculating a model's default values for config we only considered config attributes owned by the provider and not defaults the provider wished to have opinions on for keys that are owned by the entire controller.

This has now been migrated into the ModelDefaults service through a new environs interface. The new interface was introduced so we did not have to keep using prepare config that would also require a cloud spec for validation.

Because of the above changes it is now possible to remove the Mongo code that has opinions about setting up model config on a newly created model and allow the domain services to drive the process end to end.

Checklist

  • Code style: imports ordered, good names, simple structure, etc
  • Comments saying why design decisions were made
  • Go unit tests, with comments saying what you're testing
  • [] Integration tests, with comments saying what you're testing
  • [ ] doc.go added or updated in changed packages

QA steps

  1. Bootstrap a controller to any provider. In this case I used LXD but the same principal will work for any provider.
  2. Add a new model to the controller
  3. Ask the controller for the model-config of the new model.
  4. Confirm in the model config that the storage defaults for the provider have been set and are the correct value as per the code in the provider. In the case of LXD we are confirming that storage-default-filesystem-source is set to lxd.

Documentation changes

N/A

Links

Jira card: JUJU-6422

@tlm tlm added do not merge Even if a PR has been approved, do not merge the PR! 4.0 labels Sep 6, 2024
@tlm tlm force-pushed the model-config-model-manager branch 2 times, most recently from cd487aa to d5174c7 Compare September 24, 2024 23:33
@tlm tlm removed the do not merge Even if a PR has been approved, do not merge the PR! label Sep 24, 2024
@tlm tlm requested a review from hmlanigan September 25, 2024 00:27
Copy link
Member

@wallyworld wallyworld 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 questions to start with.

environs/interface.go Outdated Show resolved Hide resolved
environs/interface.go Outdated Show resolved Hide resolved
@@ -60,6 +61,24 @@ type EnvironProvider interface {
PrepareConfig(context.Context, PrepareConfigParams) (*config.Config, error)
}

// ModelConfigProvider represents an interface that a [EnvironProvider] can
// implement to provide opinions and defaults into a models config.
type ModelConfigProvider interface {
Copy link
Member

Choose a reason for hiding this comment

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

We have ConfigSchemaSource which the providers all implement (nothing else implements it, just the providers).
I suspect the intent was to replace this interface? Why keep both; we should look to consolidate.

// ConfigSchemaSource instances provide information on config attributes
// and the default attribute values.
type ConfigSchemaSource interface {
	// ConfigSchema returns extra config attributes specific
	// to this provider only.
	ConfigSchema() schema.Fields

	// ConfigDefaults returns the default values for the
	// provider specific config attributes.
	ConfigDefaults() schema.Defaults
}

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 is the intent to replace this long term. It is used in lots of places and I don't think we are ready to pull it. I could try and do it in this PR but it will be a lot of noise.

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 at least add a TODO or 3 to the affected places and ensure we follow up very soon (not long term) with a PR to do the fixing. This type of tech debt is not something we'd want to carry. The changes should essentially be boiler plate given were just adding a method to a new duplicate interface and all the implementors of the original interface also implement the new method.

@@ -82,6 +82,14 @@ func (p azureEnvironProvider) ConfigDefaults() schema.Defaults {
return configDefaults
}

// ModelConfigDefaults provides a set of default model config attributes that
// should be set on a models config if they have not been specified by the user.
func (prov *azureEnvironProvider) ModelConfigDefaults(_ context.Context) (map[string]any, error) {
Copy link
Member

Choose a reason for hiding this comment

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

With this here, is there a reason why the corresponding code in PrepareConfig isn't removed?
There's probably an argument that PrepareConfig could be designed out now. It existed primarily to add in provider specific config to the model, which is essentially what this PR does. It also does validation of the cloud spec but I would argue that's a conflation of responsibilities.

	// PrepareConfig prepares the configuration for a new model, based on
	// the provided arguments. PrepareConfig is expected to produce a
	// deterministic output. Any unique values should be based on the
	// "uuid" attribute of the base configuration. This is called for the
	// controller model during bootstrap, and also for new hosted models.
	PrepareConfig(context.Context, PrepareConfigParams) (*config.Config, 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.

You are correct in every respect above. I want to remove it but I am trying to avoid busy work as this change is a little hard to pull out because of the client.

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 add a TODO on the method declaration in the interface. At the least, we should follow up immediately with a PR to remove the unnecessary config manipulation, even if the cloud spec validation remains for now.

@hmlanigan
Copy link
Member

not defaults the provider wished to have opinions on for keys that are owned by the entire controller.

How does this work in multi-cloud controllers? Could the different providers be in conflict?

Copy link
Member

@hmlanigan hmlanigan left a comment

Choose a reason for hiding this comment

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

Model config has the storage value for lxd, however I expected the model defaults to have it from the start and it did not. What is the expectation here?

storage-default-filesystem-source is not available from model-defaults unless set first by the user.

$ juju model-defaults storage-default-filesystem-source
ERROR there are no default model values for "storage-default-filesystem-source"
$ juju add-model six
Added 'six' model on localhost/localhost with credential 'localhost' for user 'admin'
To use "juju ssh", "juju scp" and "juju debug-hooks" ssh public keys need to be added to the model with "juju add-ssh-key"
$ juju model-config storage-default-filesystem-source
lxd
$ juju model-defaults storage-default-filesystem-source=lxd
$ juju model-defaults storage-default-filesystem-source
Attribute                          Default  Controller
storage-default-filesystem-source  -        lxd

domain/modeldefaults/service/service.go Show resolved Hide resolved
// model config provider exists for the supplied cloud type then a
// [coreerrors.NotFound] error is returned. If the cloud type provider does not
// support model config then a [coreerrors.NotSupported] error is returned.
type ModelConfigProviderFunc func(string) (environs.ModelConfigProvider, error)
Copy link
Member

Choose a reason for hiding this comment

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

question: Why use a string here rather than an actual type for a cloud? Same for the ModelCloudType return value.

Copy link
Member Author

Choose a reason for hiding this comment

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

We don't have a dedicated type to represent this. We use strings everywhere. I agree it would be nice but I am avoiding the side quests at the moment.

}

// ProviderModelConfigGetter returns a [ModelConfigProviderFunc] for
// retrieving provider based model config values.
Copy link
Member

Choose a reason for hiding this comment

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

Is this for ModelConfig or DefaultModelConfig?

Copy link
Member Author

Choose a reason for hiding this comment

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

Both.

// that
// should be set on a models config if they have not been specified by the
// user.
ModelConfigDefaults(context.Context) (map[string]any, error)
Copy link
Member

Choose a reason for hiding this comment

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

question: why provide a context which is never used?

Copy link
Member Author

Choose a reason for hiding this comment

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

Trying to future proof. Defaults could be calculated in a number of ways include http calls to public api's (possible for ec2) to gather information. Trying to get ahead of the curve now and wire it up.

@tlm
Copy link
Member Author

tlm commented Sep 25, 2024

not defaults the provider wished to have opinions on for keys that are owned by the entire controller.

How does this work in multi-cloud controllers? Could the different providers be in conflict?

No we only ever consider the provider that is set for the model. We can only have one cloud per model. Conflicts can't happen.

@tlm
Copy link
Member Author

tlm commented Sep 25, 2024

Model config has the storage value for lxd, however I expected the model defaults to have it from the start and it did not. What is the expectation here?

storage-default-filesystem-source is not available from model-defaults unless set first by the user.

$ juju model-defaults storage-default-filesystem-source
ERROR there are no default model values for "storage-default-filesystem-source"
$ juju add-model six
Added 'six' model on localhost/localhost with credential 'localhost' for user 'admin'
To use "juju ssh", "juju scp" and "juju debug-hooks" ssh public keys need to be added to the model with "juju add-ssh-key"
$ juju model-config storage-default-filesystem-source
lxd
$ juju model-defaults storage-default-filesystem-source=lxd
$ juju model-defaults storage-default-filesystem-source
Attribute                          Default  Controller
storage-default-filesystem-source  -        lxd

I would consider this a bug. We have never considered the defaults of the provider in the model defaults command. If you run the same test on 3.6 it has the same result. Possible we could fix this in the model defaults work on the model manager soon.

This commit adds a new interface type to the environ package to describe
providers that want to have opinions about a models configuration. I am
introducing this change to place together all of the disparate funcs we
currently have that mix concerns.

This will make it easier to inject into the model defaults domain.
Add missing model config defaults func to the azure provider.
Adds missing model config defaults func to the dummy provider.
This commit adds support get getting provider based model config
defaults in the DQlite domain services. Previously this was only
happening for config attributes that the provider is extending model
config with and not defaults the provider wanted to apply for controller
wide attributes. The most common case of a provider wanting to do this
is for storage.

As well in this PR we are know longer letting the mongo based model
config creation process take place. Instead we are using the DQlite
domain services to perform the full generation and copying this result
down into Mongo while we dual write.

Because of this I have been able to remove the controller/modelmanager
package that is no longer needed.
Have removed some agent version test by commenting them out. This will
need to be address in future updates when moving models fully over to
dqlite.

Removed validation checks for model config in state layer as they are
not needed anymore.
@wallyworld
Copy link
Member

Model config has the storage value for lxd, however I expected the model defaults to have it from the start and it did not. What is the expectation here?
storage-default-filesystem-source is not available from model-defaults unless set first by the user.

$ juju model-defaults storage-default-filesystem-source
ERROR there are no default model values for "storage-default-filesystem-source"
$ juju add-model six
Added 'six' model on localhost/localhost with credential 'localhost' for user 'admin'
To use "juju ssh", "juju scp" and "juju debug-hooks" ssh public keys need to be added to the model with "juju add-ssh-key"
$ juju model-config storage-default-filesystem-source
lxd
$ juju model-defaults storage-default-filesystem-source=lxd
$ juju model-defaults storage-default-filesystem-source
Attribute                          Default  Controller
storage-default-filesystem-source  -        lxd

I would consider this a bug. We have never considered the defaults of the provider in the model defaults command. If you run the same test on 3.6 it has the same result. Possible we could fix this in the model defaults work on the model manager soon.

Yes, this is current behaviour - model defaults does not have the concept of default values for provider specific config. As seen, when a model is added, and the user doesn't specify anything for a provider config value, the provider has the option to insert a value at that point. We'll need to think carefully about how this could be implemented if we decide to do it, especially with regard to things like how the Source is reported, since now we're introducing potentially what's considered as a new source option.

Copy link
Member

@wallyworld wallyworld left a comment

Choose a reason for hiding this comment

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

We can land this since it's great progress, on the proviso that the 2 items of tech debt are addressed short term. Some tech debt we can live with, but here it's a fairly significant dilution of the architecture which we'd not want to carry.

}
}

return rval, nil
}

// ModelDefaults will return the default config values to be used for a model
// and it's config. If no model for uuid is found then a error satisfying
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 removed the unnecessary apostrophe as a drive by

Comment on lines +195 to +196
// change are layered in the form of a funnel where we apply the most granular
// specific last. The current order is:
Copy link
Member

Choose a reason for hiding this comment

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

"the most granular specific last" doesn't really parse to me.
Perhaps tweak the wording a bit.

"github.com/juju/juju/environs/config"
interrors "github.com/juju/juju/internal/errors"
Copy link
Member

Choose a reason for hiding this comment

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

I've been trying to avoid importing both juju errors and the new errors package - my personal preference is to keep things consistent in a given file. Make the changes completely or not at all. Or land new work keeping juju errors and separately in a refactoring PR make the change. As a side effect, would avoid the "interrors" horror.

err := tx.Query(ctx, stmt, modelUUIDVal).Get(&result)
if errors.Is(err, sqlair.ErrNoRows) {
return interrors.Errorf(
"cannot get cloud type for model %q because model 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.

My strong preference is to not double up on the top level "what is this method doing" messaging in every single error. It's sufficient here to just surface the issue with this specific operation, ie "model doesn't exist". And below, the error would be "cannot get cloud type". Then, instead of just "return err" below when calling db.Txn(), you'd include the extra context at that point. The end result is the same but with much less verbosity and message duplication.

return nil, fmt.Errorf("getting cloud type of model %q cloud: %w", uuid, err)
}

provider, err := environs.Provider(cloudType)
Copy link
Member

Choose a reason for hiding this comment

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

Ugh, how the fark this this ever get landed in state. Glad to see it gone. State should never import environs.

@@ -60,6 +61,24 @@ type EnvironProvider interface {
PrepareConfig(context.Context, PrepareConfigParams) (*config.Config, error)
}

// ModelConfigProvider represents an interface that a [EnvironProvider] can
// implement to provide opinions and defaults into a models config.
type ModelConfigProvider interface {
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 at least add a TODO or 3 to the affected places and ensure we follow up very soon (not long term) with a PR to do the fixing. This type of tech debt is not something we'd want to carry. The changes should essentially be boiler plate given were just adding a method to a new duplicate interface and all the implementors of the original interface also implement the new method.

@@ -82,6 +82,14 @@ func (p azureEnvironProvider) ConfigDefaults() schema.Defaults {
return configDefaults
}

// ModelConfigDefaults provides a set of default model config attributes that
// should be set on a models config if they have not been specified by the user.
func (prov *azureEnvironProvider) ModelConfigDefaults(_ context.Context) (map[string]any, error) {
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 add a TODO on the method declaration in the interface. At the least, we should follow up immediately with a PR to remove the unnecessary config manipulation, even if the cloud spec validation remains for now.

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

Successfully merging this pull request may close these issues.

3 participants