chore: use the juju client for model related methods#1633
chore: use the juju client for model related methods#1633kian99 merged 13 commits intocanonical:v3from
Conversation
643ceb3 to
fca0e39
Compare
There was a problem hiding this comment.
This file was renamed and modified, see types.go for what existed previously.
b3b7330 to
f7aa97d
Compare
alesstimec
left a comment
There was a problem hiding this comment.
1st pass review with a few comments
internal/errors/errors.go
Outdated
| // API Error types. | ||
| if ec, ok := e.Err.(interface{ ErrorCode() string }); ok { | ||
| e.Code = Code(ec.ErrorCode()) | ||
| var errCode interface{ ErrorCode() string } |
There was a problem hiding this comment.
The error that comes out of the Juju client is wrapped internally due to a errors.Trace(err) call where that original err has an error code. Then, when do errors.E(err) and try to extract an error code, we get nothing because the error with the code is 2 levels deep. So we can try to unwrap the error until we find an error that implement ErrorCode() or we reach the end of the list of wrapped errors.
internal/jimm/juju/interface.go
Outdated
|
|
||
| // ChangeModelCredential replaces cloud credential for a given model with the provided one. | ||
| ChangeModelCredential(context.Context, names.ModelTag, names.CloudCredentialTag) error | ||
| ChangeModelCredential(model names.ModelTag, credential names.CloudCredentialTag) error |
There was a problem hiding this comment.
i'm not fond of named arguments in interface definitions.. feels like the godoc above does not fully explain what the method is expected to do
There was a problem hiding this comment.
Will remove the named arguments and update the godoc.
There was a problem hiding this comment.
Removed the named arguments where unneccessary (some were useful and I've left them).
Left the docstring since I didn't touch that in this PR.
| "gopkg.in/yaml.v3" | ||
| ) | ||
|
|
||
| func cloudFromParams(cloudName string, p jujuparams.Cloud) cloud.Cloud { |
There was a problem hiding this comment.
it would be nice to have some consistency in naming.. XYToZW where X is a package, Y a type, Z a package and W a type.. e.g. paramsCloudToCloudCloud wdyt?
There was a problem hiding this comment.
or have a type that embeds cloud.Cloud and has a FromParamsCloud method
There was a problem hiding this comment.
I hear you, I'll take a look at this
There was a problem hiding this comment.
Here are some thoughts, we either need to convert from an RPC struct to some type that we use internally or we need to convert to whatever type we use internally to an RPC type - speaking specifically about the jujuapi package.
For converting InternalType -> RPC, we can look at internal/jujuapi/cloud.go where we've got the API handler CloudInfo() which receives a dbmodel.Cloud and runs cloud.ToJujuCloudInfo() on the dbmodel object converting from the dbmodel type to the RPC type. I don't think that is great because it imports the RPC types into the dbmodel package for the purposes of putting the conversion logic in there.
Then for RPC -> InternalType we can look at AddCloud in the same file, which calls cloudFromParams to convert a jujuparams.Cloud to github.com/juju/juju/cloud.Cloud.
So ultimately, my idea is that it would be nice if the naming convention for the mapping functions easily indicated we are pushing data down vs surfacing it.
I.e. if we are converting to an internal type we could name the function toX where X is some type. And if we are converting to an RPC type we could name the function fromX.
The actual words don't matter to me but at least names that reflect which way the data is going. Another options might be rpcToX and xFromRPC.
There was a problem hiding this comment.
exactly.. some naming convention where i can tell which way we're converting just by looking at the method name
| return d.createLoginRequest1(ctx, ctl.ResourceTag(), user.ResourceTag(), p) | ||
| } | ||
|
|
||
| // toModelAccessString maps relation to a model access string. |
There was a problem hiding this comment.
hhmmm.. i vaguely remember we have a place for these conversion methods between openfga relations to juju access strings, don't we?
There was a problem hiding this comment.
Yeah inside internal/jimm/permissions , and you'll see that's where it was used from initially, but I don't think the jujuclient should be reaching in there for things so I've copied it here. Especially because internal/jimm/juju imports this package, so you can get into a dependency cycle very easily (can't remember if this import did cause a cycle but better to avoid importing anything internal/jimm from internal/jujuclient).
The issue makes me think the conversion of OpenFGA types to Juju strings should happen in some kind of adapter package that like internal/openfgaToJuju or at least something along those lines is worth thinking about.
There was a problem hiding this comment.
what do you think about a test to make sure these checks can't drift.
There was a problem hiding this comment.
I've tried to add a test now but the package does blackbox testing so I can't without more changes,
There was a problem hiding this comment.
perhaps the openfga.Relation type could implement ModelAccessString or ControllerAccessString as needed to convert them to juju access strings..
There was a problem hiding this comment.
Yeah that could work. I'll keep it in mind but won't add it to this PR.
internal/jujuclient/types.go
Outdated
| // types preferably of the form base.ModelInfo. Until either that inconsitency | ||
| // is fixed or we export the convertParamsModelInfo function, we copy it here | ||
| // here have better consistency in our domain layer. | ||
| func convertParamsModelInfo(modelInfo params.ModelInfo) (base.ModelInfo, error) { |
There was a problem hiding this comment.
again a comment about naming consistency, or having a type that wraps base.ModelInfo and has a method for conversion from params.ModelInfo and a method that returns the wrapped base.ModelInfo
There was a problem hiding this comment.
This function and name was copied from Juju so maybe this one should stay the same?
ec13770 to
82493fa
Compare
|
So this is a change to use the existing machinery that juju clients rely on for resolving RPC methods to specific ones in case of multiple versions or similar? Looks reasonable overall, but I don't know if I understand it enough to approve. |
477808e to
dbc97a1
Compare
| // a params.ModelInfo type is returned. We want consistent types preferably of | ||
| // the form base.ModelInfo. Until the client returns a consistent set of types | ||
| // we copy it here here to have better consistency in our domain layer. | ||
| func convertParamsModelInfo(modelInfo params.ModelInfo) (ModelInfo, error) { |
There was a problem hiding this comment.
An important note here. The params.ModelInfo holds a few more fields than base.ModelInfo. The difference is below.
The following fields appear in params.ModelInfo but don't appear in base.ModelInfo (conversely there is nothing missing):
DefaultBase string `json:"default-base,omitempty"`
// CloudCredentialValidity contains if model credential is valid, if known.
CloudCredentialValidity *bool `json:"cloud-credential-validity,omitempty"`
// SecretBackends contains information about the secret backends
// currently in use by the model.
SecretBackends []SecretBackendResult `json:"secret-backends"`
// Migration contains information about the latest failed or
// currently-running migration. It'll be nil if there isn't one.
Migration *ModelMigrationStatus `json:"migration,omitempty"`
// SLA contains the information about the SLA for the model, if set.
SLA *ModelSLAInfo `json:"sla"`
// SupportedFeatures provides information about the set of features
// supported by this model. The feature set contains both Juju-specific
// entries (e.g. juju version) and other features that depend on the
// substrate the model is deployed to.
SupportedFeatures []SupportedFeature `json:"supported-features,omitempty"`
Out of these fields I believe SLA is long deprecated and unused, SupportedFeatures I'm unsure on its usage, Migration is necessary and we've added it to our wrapper, SecretBackends seems useful but not currently parsed and DefaultBase is also not parsed. So I've gone and checked the output of juju show-model after this change via JIMM and via the backing Juju controller. Below is the output from JIMM with the fields from Juju added and marked. Note that the output doesn't include ommitted fields like migration status which is only present in certain circumstances.
foo:
name: jimm-test@canonical.com/foo
short-name: foo
model-uuid: fe9cecbf-676e-4592-8c0c-effcaf981400
model-type: iaas
controller-uuid: 3217dbc9-8ea9-4381-9e97-01eab0b3f6bb
controller-name: jimm-dev
is-controller: false
owner: jimm-test@canonical.com
cloud: localhost
region: localhost
type: lxd
life: alive
status:
current: available
since: 7 seconds ago
users:
jimm-test@canonical.com:\
display-name: jimm-test ---- missing
access: admin
last-connection: never connected
sla: unsupported ---- missing
agent-version: 3.6.12
credential:
name: localhost
owner: jimm-test@canonical.com
cloud: localhost
validity-check: invalid ---- backing Juju controller says valid
supported-features: ---- this entire block is missing
- name: juju
description: the version of Juju used by the model
version: 3.6.12
What I'll do is expand our ModelInfo struct to include the missing fields so that we can always return them and add a test that verifies the difference between juju show-model against JIMM vs Juju.
There was a problem hiding this comment.
I'm thinking about doing the above in a follow-up PR.
There was a problem hiding this comment.
Alternatively, maybe we should just pass around the rpc type when we call this method instead of the base type.
There was a problem hiding this comment.
It doesn't seem like it's a problem to pass the RPC type.
There was a problem hiding this comment.
mmm, that's not good. Why would this type be less than the rpc type? Should we fix it in juju?
There was a problem hiding this comment.
To summarise the work done here,
- I tried changing to use the RPC type and that proved challenging because there are functions used on code-paths that are common to both CreateModel and ModelInfo calls. If the one returns a base.ModelInfo (returned by CreateModel) and the other returns a
rpc.ModelInfo, we create a lot more friction in the code for nothing. - I discussed making the change in Juju and there is an appetite for it, but I've decided to forego making the change in 3.6 as we'll move to 4.0 soon and it would be a breaking change in 3.6.
- I've added the missing types to our jujuclient package and update our mapping logic in
jujuapi. Now the response from JIMM and Juju are nearly identical.
573ef33 to
8e462f2
Compare
| }}, nil | ||
| } | ||
| }, | ||
| GrantJIMMModelAdmin_: func(_ context.Context, _ names.ModelTag) error { |
There was a problem hiding this comment.
leave the ctx even if it's useless rn because once we migrate to juju 4 client, all of the methods will require ctx
There was a problem hiding this comment.
I was thinking about that, I don't mind adding it back afterwards, wdyt?
There was a problem hiding this comment.
We'll definitely need it, I'd imagine it's simpler to not remove it now.
There was a problem hiding this comment.
Yeah let me add them back
b9c5607 to
648d8c3
Compare
2704a9d to
c40fa4c
Compare
luci1900
left a comment
There was a problem hiding this comment.
Mostly mechanical changes, should be fine.
I do think we need to QA this somewhat more thoroughly than other changes, simply because it touches so many areas.
SimoneDutto
left a comment
There was a problem hiding this comment.
Some questions to be sure everything we're doing here is correct
| @@ -131,13 +132,13 @@ type API interface { | |||
| ListApplicationOffers(context.Context, []jujuparams.OfferFilter) ([]jujuparams.ApplicationOfferAdminDetailsV5, error) | |||
|
|
|||
| // ListModelSummaries lists models summaries | |||
| ListModelSummaries(context.Context, jujuparams.ModelSummariesRequest) (jujuparams.ModelSummaryResults, error) | |||
| ListModelSummaries(context.Context, jujuparams.ModelSummariesRequest) ([]base.UserModelSummary, error) | |||
|
|
|||
| // ModelInfo fetches a model's ModelInfo. | |||
| ModelInfo(context.Context, *jujuparams.ModelInfo) error | |||
| ModelInfo(context.Context, names.ModelTag) (jujuclient.ModelInfo, error) | |||
|
|
|||
| // ModelStatus fetches a model's ModelStatus. | |||
| ModelStatus(context.Context, *jujuparams.ModelStatus) error | |||
| ModelStatus(context.Context, names.ModelTag) (base.ModelStatus, error) | |||
|
|
|||
| // ModelSummaryWatcherNext returns the next set of model summaries from | |||
| // the watcher. | |||
| @@ -179,7 +180,7 @@ type API interface { | |||
| UpdateCredential(context.Context, jujuparams.TaggedCredential) ([]jujuparams.UpdateCredentialModelResult, error) | |||
|
|
|||
| // ValidateModelUpgrade validates that a model can be upgraded. | |||
| ValidateModelUpgrade(context.Context, names.ModelTag, bool) error | |||
| ValidateModelUpgrade(ctx context.Context, model names.ModelTag, force bool) error | |||
There was a problem hiding this comment.
why do we need the named args?
There was a problem hiding this comment.
I added them where it was unclear what the bool/string were. A names.ModelTag is clear, but bool, string, bool was very unclear.
|
|
||
| // AddModel adds the specified model to JIMM. | ||
| func (j *JujuManager) AddModel(ctx context.Context, user *openfga.User, args *ModelCreateArgs) (_ *jujuparams.ModelInfo, err error) { | ||
| func (j *JujuManager) AddModel(ctx context.Context, user *openfga.User, args *ModelCreateArgs) (base.ModelInfo, error) { |
There was a problem hiding this comment.
why did we have the named return here? I can't see any defer something, but double check please
There was a problem hiding this comment.
Double checked the current code and looks fine without a named return.
| // access to the model then the returned error will have the code | ||
| // CodeUnauthorized. | ||
| func (j *JujuManager) ModelInfo(ctx context.Context, user *openfga.User, mt names.ModelTag) (*jujuparams.ModelInfo, error) { | ||
| func (j *JujuManager) ModelInfo(ctx context.Context, user *openfga.User, mt names.ModelTag) (jujuclient.ModelInfo, error) { |
There was a problem hiding this comment.
what's the reason of using the struct instead of the pointer?
There was a problem hiding this comment.
I believe a pointer was used to make the return values "simpler", i.e. return null, but there was no reason for a pointer.
| models = append(models, struct { | ||
| model *dbmodel.Model | ||
| userAccess jujuparams.UserAccessPermission | ||
| userAccess string |
There was a problem hiding this comment.
this was an "enum", converting it to a string isn't dangerous for when we do the permission checks?
There was a problem hiding this comment.
So the struct value being a string is okay because that's just used for returning to the user later, the signature for the ForEachUserModel callback could be a better type but it's also only being used here so I believe it's okay.
| func (j *JujuManager) mergeModelInfo(ctx context.Context, user *openfga.User, modelInfo *jujuparams.ModelInfo, jimmModel dbmodel.Model) (*jujuparams.ModelInfo, error) { | ||
| modelInfo.CloudCredentialTag = jimmModel.CloudCredential.Tag().String() | ||
| func (j *JujuManager) mergeModelInfo(ctx context.Context, user *openfga.User, modelInfo jujuclient.ModelInfo, jimmModel dbmodel.Model) (jujuclient.ModelInfo, error) { | ||
| modelInfo.CloudCredential = jimmModel.CloudCredential.ResourceTag().Id() |
There was a problem hiding this comment.
so now this is just the cloud credential name instead of the full tag?
There was a problem hiding this comment.
Yip, similarly for ownerTag -> Owner and CloudTag -> Cloud
| } | ||
|
|
||
| // CloudSpec retrieves the cloud spec of the model connected to. | ||
| func (c Connection) CloudSpec(ctx context.Context) (cloudspec.CloudSpec, error) { |
There was a problem hiding this comment.
why do we need this now?
There was a problem hiding this comment.
It replaces ControllerModelSummary which was being used to get the controller model's cloud and cloud-credential then we'd fetch the cloud-credential. If/when we remove the cloning of controllers we can remove this and simplify.
There was a problem hiding this comment.
I left out a bit of info, there was no Juju API equivalent of ControllerModelSummary, but CloudSpec() exists and did what we needed.
| return d.createLoginRequest1(ctx, ctl.ResourceTag(), user.ResourceTag(), p) | ||
| } | ||
|
|
||
| // toModelAccessString maps relation to a model access string. |
There was a problem hiding this comment.
what do you think about a test to make sure these checks can't drift.
| err := c.Call(ctx, "ModelManager", 10, "", "ModelStatus", &args, &resp) | ||
| // ModelStatus retrieves the status of a model from the controller. | ||
| func (c Connection) ModelStatus(ctx context.Context, modelTag names.ModelTag) (base.ModelStatus, error) { | ||
| statuses, err := modelmanager.NewClient(&c).ModelStatus(modelTag) |
There was a problem hiding this comment.
here err is the first error in the Result or does it work in the client?
I'm kinda worried because they don't "type" errors when the result is of Result and it can't potentially mess with our NotFound checks.
I think our errors.E was typing the error, so this change you've made could be breaking
There was a problem hiding this comment.
It should all work out once we get to the API layer and call mapError which will inspect the error with ErrorCode until it finds a code.
| // a params.ModelInfo type is returned. We want consistent types preferably of | ||
| // the form base.ModelInfo. Until the client returns a consistent set of types | ||
| // we copy it here here to have better consistency in our domain layer. | ||
| func convertParamsModelInfo(modelInfo params.ModelInfo) (ModelInfo, error) { |
There was a problem hiding this comment.
mmm, that's not good. Why would this type be less than the rpc type? Should we fix it in juju?
| return d.createLoginRequest1(ctx, ctl.ResourceTag(), user.ResourceTag(), p) | ||
| } | ||
|
|
||
| // toModelAccessString maps relation to a model access string. |
There was a problem hiding this comment.
perhaps the openfga.Relation type could implement ModelAccessString or ControllerAccessString as needed to convert them to juju access strings..
Description
This change started off as a simple one, a continuation of #1611 where we slowly move our
jujuclientpackage to use Juju's Go client so that we don't need to call facades directly and we get nicer types out of the client.The downside of this change is that I had to replace all usages of
jujuparams.*withbase.*where were were using the RPC object in many places (i.e. an object with JSON tags used for marshalling/unmarshalling over the wire). We are now using a different but similar type that simplifies a few rough edges.There was also a somewhat unrelated but necessary change in the
internal/rpcpackage where I had previously introduced a dependency oninternal/jimm/that was incorrect and caused cyclic dependencies during this PR. That's now been addressed.Finally, you'll see new functions in
internal/jujuapi/types.gothat convert from thebase.*types to the API type. I haven't written any tests for these as our integration tests cover this behavior, but as discussed before, if we have any objections to this approach we can always revisit it. Also, confusingly, the types in Juju'sbasepackage are not the same types used by Juju's apiserver, this might be worth looking at further.Fixes JUJU-9132
Engineering checklist