Skip to content

Commit

Permalink
refactor list all model summaries
Browse files Browse the repository at this point in the history
  • Loading branch information
ale8k committed Jun 18, 2024
1 parent 7b68f63 commit 034e8be
Show file tree
Hide file tree
Showing 5 changed files with 171 additions and 18 deletions.
86 changes: 86 additions & 0 deletions internal/jimm/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -758,6 +758,92 @@ func (j *JIMM) ModelStatus(ctx context.Context, user *openfga.User, mt names.Mod
return &ms, nil
}

// dialAllController dials all controllers on JIMM and returns a closer
// to close all connections. Defer the closer.
//
// TODO(ale8k): Should this live here? We don't have like a utils.go in jimm pkg
func (j *JIMM) dialAllControllers() ([]API, func()) {
var controllers []dbmodel.Controller
apis := []API{}

_ = j.DB().DB.Find(&controllers).Error
for _, c := range controllers {
api, err := j.dial(context.Background(), &c, names.ModelTag{})
_ = err
apis = append(apis, api)
}

closer := func() {
for _, a := range apis {
a.Close()
}
}

return apis, closer
}

// GetAllModelSummariesForUser dials all controllers JIMM is aware of and calls ListAllModelSummaries
// on each. It filters out controller models and ensures the user has access to the summarised
// models and filters out the results.
func (j *JIMM) GetAllModelSummariesForUser(ctx context.Context, user *openfga.User) (jujuparams.ModelSummaryResults, error) {
const op = errors.Op("jimm.GetAllModelSummariesForUser")

apis, closer := j.dialAllControllers()
defer closer()

filteredSummaries := jujuparams.ModelSummaryResults{}
flattenedSummaries := jujuparams.ModelSummaryResults{}
summaries := []jujuparams.ModelSummaryResults{}

// Get all summaries from all controllers
for _, api := range apis {
var out jujuparams.ModelSummaryResults
in := jujuparams.ModelSummariesRequest{UserTag: names.NewUserTag("admin").String(), All: true}
if err := api.ListModelSummaries(context.Background(), &in, &out); err != nil {
return filteredSummaries, err
}

summaries = append(summaries, out)
}

// Flatten the summaries into a single results
for _, s := range summaries {
flattenedSummaries.Results = append(flattenedSummaries.Results, s.Results...)
}

// Now we filter the flattened summaries for two things:
// 1. If it's an IsController, remove it
// 2. Check the user has access
for _, r := range flattenedSummaries.Results {
// Skip controller models
if r.Result.IsController {
continue
}

access, err := j.GetUserModelAccess(context.Background(), user, names.NewModelTag(r.Result.UUID))
if err != nil {
return filteredSummaries, errors.E(op, err)
}

// Update the access to reflect our OpenFGA and not what the controller reported
// for the admin user that JIMM retrieved the model summary as.
r.Result.UserAccess = jujuparams.UserAccessPermission(access)

switch access {
case "read":
fallthrough
case "write":
fallthrough
case "admin":
filteredSummaries.Results = append(filteredSummaries.Results, r)
default:
continue
}
}

return filteredSummaries, nil
}

// ForEachUserModel calls the given function once for each model that the
// given user has been granted explicit access to. The UserModelAccess
// object passed to f will always include the Model_, Access, and
Expand Down
8 changes: 8 additions & 0 deletions internal/jimmtest/jimm_mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,14 @@ type JIMM struct {
UpdateServiceAccountCredentials_ func()
ValidateModelUpgrade_ func(ctx context.Context, u *openfga.User, mt names.ModelTag, force bool) error
WatchAllModelSummaries_ func(ctx context.Context, controller *dbmodel.Controller) (_ func() error, err error)
GetAllModelSummariesForUser_ func(ctx context.Context, user *openfga.User) (jujuparams.ModelSummaryResults, error)
}

func (j *JIMM) GetAllModelSummariesForUser(ctx context.Context, user *openfga.User) (jujuparams.ModelSummaryResults, error) {
if j.GetAllModelSummariesForUser_ == nil {
panic("not implemented")
}
return j.GetAllModelSummariesForUser(ctx, user)
}

func (j *JIMM) AddAuditLogEntry(ale *dbmodel.AuditLogEntry) {
Expand Down
1 change: 1 addition & 0 deletions internal/jujuapi/controllerroot.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ type JIMM interface {
ValidateModelUpgrade(ctx context.Context, u *openfga.User, mt names.ModelTag, force bool) error
WatchAllModelSummaries(ctx context.Context, controller *dbmodel.Controller) (_ func() error, err error)
GetOpenFGAUserAndAuthorise(ctx context.Context, email string) (*openfga.User, error)
GetAllModelSummariesForUser(ctx context.Context, user *openfga.User) (jujuparams.ModelSummaryResults, error)
}

// controllerRoot is the root for endpoints served on controller connections.
Expand Down
31 changes: 13 additions & 18 deletions internal/jujuapi/modelmanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
jujuparams "github.com/juju/juju/rpc/params"
"github.com/juju/names/v5"

"github.com/canonical/jimm/internal/dbmodel"
"github.com/canonical/jimm/internal/errors"
"github.com/canonical/jimm/internal/jimm"
"github.com/canonical/jimm/internal/jujuapi/rpc"
Expand Down Expand Up @@ -81,26 +80,22 @@ func (r *controllerRoot) DumpModels(ctx context.Context, args jujuparams.DumpMod
func (r *controllerRoot) ListModelSummaries(ctx context.Context, _ jujuparams.ModelSummariesRequest) (jujuparams.ModelSummaryResults, error) {
const op = errors.Op("jujuapi.ListModelSummaries")

var results []jujuparams.ModelSummaryResult
err := r.jimm.ForEachUserModel(ctx, r.user, func(m *dbmodel.Model, access jujuparams.UserAccessPermission) error {
// TODO(Kian) CSS-6040 Refactor the below to use a better abstraction for Postgres/OpenFGA to Juju types.
ms := m.ToJujuModelSummary()
ms.UserAccess = access
if r.controllerUUIDMasking {
summaries, err := r.jimm.GetAllModelSummariesForUser(ctx, r.user)
if err != nil {
return summaries, err
}

// If controller masking is set, don't reveal the underlying controllers UUID
// when performing a summary and instead set JIMM's controller ID for each.
if r.controllerUUIDMasking {
for _, results := range summaries.Results {
ms := results.Result
ms.ControllerUUID = r.params.ControllerUUID
}
result := jujuparams.ModelSummaryResult{
Result: &ms,
}
results = append(results, result)
return nil
})
if err != nil {
return jujuparams.ModelSummaryResults{}, errors.E(op, err)
}
return jujuparams.ModelSummaryResults{
Results: results,
}, nil

// Return the masked summaries from all underlying controllers.
return summaries, err
}

// ListModels returns the models that the authenticated user
Expand Down
63 changes: 63 additions & 0 deletions internal/jujuapi/modelmanager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,69 @@ type modelManagerSuite struct {

var _ = gc.Suite(&modelManagerSuite{})

func (s *modelManagerSuite) TestListModelSummariesV2(c *gc.C) {
conn := s.open(c, nil, "bob")
defer conn.Close()

client := modelmanager.NewClient(conn)

// List for bob
models, err := client.ListModelSummaries("[email protected]", false)
c.Assert(err, gc.Equals, nil)
c.Assert(models, jimmtest.CmpEquals(
cmpopts.IgnoreTypes(&time.Time{}),
cmpopts.SortSlices(func(a, b base.UserModelSummary) bool {
return a.Name < b.Name
}),
), []base.UserModelSummary{{
Name: "model-1",
UUID: s.Model.UUID.String,
ControllerUUID: "914487b5-60e7-42bb-bd63-1adc3fd3a388",
ProviderType: jimmtest.TestProviderType,
DefaultSeries: "jammy",
Cloud: jimmtest.TestCloudName,
CloudRegion: jimmtest.TestCloudRegionName,
CloudCredential: jimmtest.TestCloudName + "/[email protected]/cred",
Owner: "[email protected]",
Life: life.Value(state.Alive.String()),
Status: base.Status{
Status: status.Available,
Data: map[string]interface{}{},
},
ModelUserAccess: "admin",
Counts: []base.EntityCount{},
AgentVersion: &jujuversion.Current,
Type: "iaas",
SLA: &base.SLASummary{
Level: "",
Owner: "[email protected]",
},
}, {
Name: "model-3",
UUID: s.Model3.UUID.String,
ControllerUUID: "914487b5-60e7-42bb-bd63-1adc3fd3a388",
ProviderType: jimmtest.TestProviderType,
DefaultSeries: "jammy",
Cloud: jimmtest.TestCloudName,
CloudRegion: jimmtest.TestCloudRegionName,
CloudCredential: jimmtest.TestCloudName + "/[email protected]/cred",
Owner: "[email protected]",
Life: life.Value(state.Alive.String()),
Status: base.Status{
Status: status.Available,
Data: map[string]interface{}{},
},
ModelUserAccess: "read",
Counts: []base.EntityCount{},
AgentVersion: &jujuversion.Current,
Type: "iaas",
SLA: &base.SLASummary{
Level: "",
Owner: "[email protected]",
},
}})
}

func (s *modelManagerSuite) TestListModelSummaries(c *gc.C) {
conn := s.open(c, nil, "bob")
defer conn.Close()
Expand Down

0 comments on commit 034e8be

Please sign in to comment.