Skip to content

Commit 1973a62

Browse files
authored
Optimize API Calls (#235)
Any handler (regions, clusters) that needed access to either the identity service or the region service would unconditionally create a client, and that would involve TLS handshakes and token exchange. For things like clusters, they only need to update allocations on create/update/delete, not read, and only need access to flavors and images on create/update, thus there is an implicit penalty for APIs that don't need those clients. This makes API access to those client lazy, and they will only create a client if necessary. Secondly, much like the region service, we expect things like regions, flavors and images to be relatively static, so we can put a cache in front of these calls with an hour timeout. The result is that on the standard "List Kubernetes Clusters" UI view, the API response times are now less that 100ms, down from over 1000ms.
1 parent 4abf12b commit 1973a62

File tree

11 files changed

+305
-227
lines changed

11 files changed

+305
-227
lines changed

charts/kubernetes/templates/server/clusterrole.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,10 @@ rules:
3636
verbs:
3737
- list
3838
- watch
39+
- apiGroups:
40+
- ""
41+
resources:
42+
- secrets
43+
verbs:
44+
- list
45+
- watch

go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ require (
1313
github.com/prometheus/client_golang v1.22.0
1414
github.com/spf13/pflag v1.0.6
1515
github.com/stretchr/testify v1.10.0
16-
github.com/unikorn-cloud/core v0.1.95
16+
github.com/unikorn-cloud/core v0.1.96-rc1
1717
github.com/unikorn-cloud/identity v0.2.63
1818
github.com/unikorn-cloud/region v0.1.54
1919
go.opentelemetry.io/otel v1.35.0
@@ -33,6 +33,7 @@ require (
3333
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
3434
github.com/beorn7/perks v1.0.1 // indirect
3535
github.com/blang/semver/v4 v4.0.0 // indirect
36+
github.com/brunoga/deep v1.2.4 // indirect
3637
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
3738
github.com/cespare/xxhash/v2 v2.3.0 // indirect
3839
github.com/coreos/go-oidc/v3 v3.14.1 // indirect

go.sum

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r
1010
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
1111
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
1212
github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
13+
github.com/brunoga/deep v1.2.4 h1:Aj9E9oUbE+ccbyh35VC/NHlzzjfIVU69BXu2mt2LmL8=
14+
github.com/brunoga/deep v1.2.4/go.mod h1:GDV6dnXqn80ezsLSZ5Wlv1PdKAWAO4L5PnKYtv2dgaI=
1315
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
1416
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
1517
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
@@ -173,8 +175,8 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf
173175
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
174176
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
175177
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
176-
github.com/unikorn-cloud/core v0.1.95 h1:i5jyWnleYbFxvniKfneiEeeNL2oCtkJjdGtRJipYH9Y=
177-
github.com/unikorn-cloud/core v0.1.95/go.mod h1:OdZlqXlkO0EFaThARFYRctKAtsVKimhSZid72OaC43Y=
178+
github.com/unikorn-cloud/core v0.1.96-rc1 h1:TgSMyHhUWYmWgLXiTWNUsroO3F2LtTJ/k+nEDgDnOAo=
179+
github.com/unikorn-cloud/core v0.1.96-rc1/go.mod h1:stInT6j9sM9KzDHgNxBtmrdDIAxQuIZI1/TCGo0jNK8=
178180
github.com/unikorn-cloud/identity v0.2.63 h1:cG3Aa3LmweqOwNtOeq9W29/aoJ4wY0uiulLNzWwn7TY=
179181
github.com/unikorn-cloud/identity v0.2.63/go.mod h1:xuOIyB4wDAz4+kJfZk2q+8MqGj+9IhVbd0Q38iqBY24=
180182
github.com/unikorn-cloud/region v0.1.54 h1:orGCMLIMUmSWLOlBBha6q5SgXKAlzFqQJRVhneD4/so=

pkg/provisioners/managers/cluster/provisioner.go

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@ import (
5353
"github.com/unikorn-cloud/kubernetes/pkg/provisioners/helmapplications/openstackcloudprovider"
5454
"github.com/unikorn-cloud/kubernetes/pkg/provisioners/helmapplications/openstackplugincindercsi"
5555
"github.com/unikorn-cloud/kubernetes/pkg/provisioners/helmapplications/vcluster"
56-
"github.com/unikorn-cloud/kubernetes/pkg/server/handler/region"
5756
regionclient "github.com/unikorn-cloud/region/pkg/client"
5857
regionapi "github.com/unikorn-cloud/region/pkg/openapi"
5958

@@ -428,6 +427,21 @@ func (p *Provisioner) getRegionClient(ctx context.Context, traceName string) (co
428427
return ctx, client, nil
429428
}
430429

430+
func (p *Provisioner) getFlavors(ctx context.Context, client regionapi.ClientWithResponsesInterface, organizationID, regionID string) ([]regionapi.Flavor, error) {
431+
resp, err := client.GetApiV1OrganizationsOrganizationIDRegionsRegionIDFlavorsWithResponse(ctx, organizationID, regionID)
432+
if err != nil {
433+
return nil, err
434+
}
435+
436+
if resp.StatusCode() != http.StatusOK {
437+
return nil, coreapiutils.ExtractError(resp.StatusCode(), resp)
438+
}
439+
440+
flavors := *resp.JSON200
441+
442+
return flavors, nil
443+
}
444+
431445
func (p *Provisioner) getIdentity(ctx context.Context, client regionapi.ClientWithResponsesInterface) (*regionapi.IdentityRead, error) {
432446
log := log.FromContext(ctx)
433447

@@ -538,7 +552,7 @@ func (p *Provisioner) identityOptions(ctx context.Context, client regionapi.Clie
538552
return nil, err
539553
}
540554

541-
flavors, err := region.Flavors(ctx, client, p.cluster.Labels[coreconstants.OrganizationLabel], p.cluster.Spec.RegionID)
555+
flavors, err := p.getFlavors(ctx, client, p.cluster.Labels[coreconstants.OrganizationLabel], p.cluster.Spec.RegionID)
542556
if err != nil {
543557
return nil, err
544558
}

pkg/server/handler/cluster/client.go

Lines changed: 40 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import (
4040
"github.com/unikorn-cloud/kubernetes/pkg/provisioners/helmapplications/vcluster"
4141
"github.com/unikorn-cloud/kubernetes/pkg/server/handler/clustermanager"
4242
"github.com/unikorn-cloud/kubernetes/pkg/server/handler/common"
43+
"github.com/unikorn-cloud/kubernetes/pkg/server/handler/identity"
4344
"github.com/unikorn-cloud/kubernetes/pkg/server/handler/region"
4445
regionapi "github.com/unikorn-cloud/region/pkg/openapi"
4546

@@ -92,14 +93,14 @@ type Client struct {
9293
options *Options
9394

9495
// identity is a client to access the identity service.
95-
identity identityapi.ClientWithResponsesInterface
96+
identity *identity.Client
9697

9798
// region is a client to access regions.
98-
region regionapi.ClientWithResponsesInterface
99+
region *region.Client
99100
}
100101

101102
// NewClient returns a new client with required parameters.
102-
func NewClient(client client.Client, namespace string, options *Options, identity identityapi.ClientWithResponsesInterface, region regionapi.ClientWithResponsesInterface) *Client {
103+
func NewClient(client client.Client, namespace string, options *Options, identity *identity.Client, region *region.Client) *Client {
103104
return &Client{
104105
client: client,
105106
namespace: namespace,
@@ -200,7 +201,7 @@ func (c *Client) GetKubeconfig(ctx context.Context, organizationID, projectID, c
200201
}
201202

202203
func (c *Client) generateAllocations(ctx context.Context, organizationID string, resource *unikornv1.KubernetesCluster) (*identityapi.AllocationWrite, error) {
203-
flavors, err := region.Flavors(ctx, c.region, organizationID, resource.Spec.RegionID)
204+
flavors, err := c.region.Flavors(ctx, organizationID, resource.Spec.RegionID)
204205
if err != nil {
205206
return nil, err
206207
}
@@ -280,7 +281,12 @@ func (c *Client) createAllocation(ctx context.Context, organizationID, projectID
280281
return nil, err
281282
}
282283

283-
resp, err := c.identity.PostApiV1OrganizationsOrganizationIDProjectsProjectIDAllocationsWithResponse(ctx, organizationID, projectID, *allocations)
284+
client, err := c.identity.Client(ctx)
285+
if err != nil {
286+
return nil, err
287+
}
288+
289+
resp, err := client.PostApiV1OrganizationsOrganizationIDProjectsProjectIDAllocationsWithResponse(ctx, organizationID, projectID, *allocations)
284290
if err != nil {
285291
return nil, err
286292
}
@@ -298,7 +304,12 @@ func (c *Client) updateAllocation(ctx context.Context, organizationID, projectID
298304
return err
299305
}
300306

301-
resp, err := c.identity.PutApiV1OrganizationsOrganizationIDProjectsProjectIDAllocationsAllocationIDWithResponse(ctx, organizationID, projectID, resource.Annotations[constants.AllocationAnnotation], *allocations)
307+
client, err := c.identity.Client(ctx)
308+
if err != nil {
309+
return err
310+
}
311+
312+
resp, err := client.PutApiV1OrganizationsOrganizationIDProjectsProjectIDAllocationsAllocationIDWithResponse(ctx, organizationID, projectID, resource.Annotations[constants.AllocationAnnotation], *allocations)
302313
if err != nil {
303314
return err
304315
}
@@ -311,7 +322,12 @@ func (c *Client) updateAllocation(ctx context.Context, organizationID, projectID
311322
}
312323

313324
func (c *Client) deleteAllocation(ctx context.Context, organizationID, projectID, allocationID string) error {
314-
resp, err := c.identity.DeleteApiV1OrganizationsOrganizationIDProjectsProjectIDAllocationsAllocationIDWithResponse(ctx, organizationID, projectID, allocationID)
325+
client, err := c.identity.Client(ctx)
326+
if err != nil {
327+
return err
328+
}
329+
330+
resp, err := client.DeleteApiV1OrganizationsOrganizationIDProjectsProjectIDAllocationsAllocationIDWithResponse(ctx, organizationID, projectID, allocationID)
315331
if err != nil {
316332
return err
317333
}
@@ -342,7 +358,12 @@ func (c *Client) createIdentity(ctx context.Context, organizationID, projectID,
342358
},
343359
}
344360

345-
resp, err := c.region.PostApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesWithResponse(ctx, organizationID, projectID, request)
361+
client, err := c.region.Client(ctx)
362+
if err != nil {
363+
return nil, errors.OAuth2ServerError("unable to create region client").WithError(err)
364+
}
365+
366+
resp, err := client.PostApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesWithResponse(ctx, organizationID, projectID, request)
346367
if err != nil {
347368
return nil, errors.OAuth2ServerError("unable to create identity").WithError(err)
348369
}
@@ -380,7 +401,12 @@ func (c *Client) createPhysicalNetworkOpenstack(ctx context.Context, organizatio
380401
},
381402
}
382403

383-
resp, err := c.region.PostApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDNetworksWithResponse(ctx, organizationID, projectID, identity.Metadata.Id, request)
404+
client, err := c.region.Client(ctx)
405+
if err != nil {
406+
return nil, errors.OAuth2ServerError("unable to create region client").WithError(err)
407+
}
408+
409+
resp, err := client.PostApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDNetworksWithResponse(ctx, organizationID, projectID, identity.Metadata.Id, request)
384410
if err != nil {
385411
return nil, errors.OAuth2ServerError("unable to physical network").WithError(err)
386412
}
@@ -392,30 +418,6 @@ func (c *Client) createPhysicalNetworkOpenstack(ctx context.Context, organizatio
392418
return resp.JSON201, nil
393419
}
394420

395-
func (c *Client) getRegion(ctx context.Context, organizationID, regionID string) (*regionapi.RegionRead, error) {
396-
// TODO: Need a straight get interface rather than a list.
397-
resp, err := c.region.GetApiV1OrganizationsOrganizationIDRegionsWithResponse(ctx, organizationID)
398-
if err != nil {
399-
return nil, errors.OAuth2ServerError("unable to get region").WithError(err)
400-
}
401-
402-
if resp.StatusCode() != http.StatusOK {
403-
return nil, errors.OAuth2ServerError("unable to get region")
404-
}
405-
406-
results := *resp.JSON200
407-
408-
index := slices.IndexFunc(results, func(region regionapi.RegionRead) bool {
409-
return region.Metadata.Id == regionID
410-
})
411-
412-
if index < 0 {
413-
return nil, errors.OAuth2ServerError("unable to get region")
414-
}
415-
416-
return &results[index], nil
417-
}
418-
419421
func (c *Client) applyCloudSpecificConfiguration(ctx context.Context, organizationID, projectID, regionID string, allocation *identityapi.AllocationRead, identity *regionapi.IdentityRead, cluster *unikornv1.KubernetesCluster) error {
420422
// Save the identity ID for later cleanup.
421423
if cluster.Annotations == nil {
@@ -426,7 +428,7 @@ func (c *Client) applyCloudSpecificConfiguration(ctx context.Context, organizati
426428
cluster.Annotations[constants.IdentityAnnotation] = identity.Metadata.Id
427429

428430
// Apply any region specific configuration based on feature flags.
429-
region, err := c.getRegion(ctx, organizationID, regionID)
431+
region, err := c.region.Get(ctx, organizationID, regionID)
430432
if err != nil {
431433
return err
432434
}
@@ -576,10 +578,6 @@ func (c *Client) Update(ctx context.Context, organizationID, projectID, clusterI
576578
return errors.OAuth2ServerError("failed to merge annotations").WithError(err)
577579
}
578580

579-
if err := c.updateAllocation(ctx, organizationID, projectID, required); err != nil {
580-
return errors.OAuth2ServerError("failed to update quota allocation").WithError(err)
581-
}
582-
583581
// Preserve networking options as if they change it'll be fairly catastrophic.
584582
required.Spec.Network = current.Spec.Network
585583

@@ -590,6 +588,10 @@ func (c *Client) Update(ctx context.Context, organizationID, projectID, clusterI
590588
updated.Annotations = required.Annotations
591589
updated.Spec = required.Spec
592590

591+
if err := c.updateAllocation(ctx, organizationID, projectID, updated); err != nil {
592+
return errors.OAuth2ServerError("failed to update quota allocation").WithError(err)
593+
}
594+
593595
if err := c.client.Patch(ctx, updated, client.MergeFrom(current)); err != nil {
594596
return errors.OAuth2ServerError("failed to patch cluster").WithError(err)
595597
}

pkg/server/handler/cluster/conversion.go

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ type generator struct {
6060
// options allows access to resource defaults.
6161
options *Options
6262
// region is a client to access regions.
63-
region regionapi.ClientWithResponsesInterface
63+
region *region.Client
6464
// namespace the resource is provisioned in.
6565
namespace string
6666
// organizationID is the unique organization identifier.
@@ -74,7 +74,7 @@ type generator struct {
7474
existing *unikornv1.KubernetesCluster
7575
}
7676

77-
func newGenerator(client client.Client, options *Options, region regionapi.ClientWithResponsesInterface, namespace, organizationID, projectID string) *generator {
77+
func newGenerator(client client.Client, options *Options, region *region.Client, namespace, organizationID, projectID string) *generator {
7878
return &generator{
7979
client: client,
8080
options: options,
@@ -241,7 +241,7 @@ func (g *generator) defaultApplicationBundle(ctx context.Context) (*unikornv1.Ku
241241

242242
// defaultControlPlaneFlavor returns a default control plane flavor.
243243
func (g *generator) defaultControlPlaneFlavor(ctx context.Context, request *openapi.KubernetesClusterWrite) (*regionapi.Flavor, error) {
244-
flavors, err := region.Flavors(ctx, g.region, g.organizationID, request.Spec.RegionId)
244+
flavors, err := g.region.Flavors(ctx, g.organizationID, request.Spec.RegionId)
245245
if err != nil {
246246
return nil, errors.OAuth2ServerError("failed to list flavors").WithError(err)
247247
}
@@ -278,20 +278,11 @@ func (g *generator) defaultControlPlaneFlavor(ctx context.Context, request *open
278278
// defaultImage returns a default image for either control planes or workload pools
279279
// based on the specified Kubernetes version.
280280
func (g *generator) defaultImage(ctx context.Context, request *openapi.KubernetesClusterWrite) (*regionapi.Image, error) {
281-
images, err := region.Images(ctx, g.region, g.organizationID, request.Spec.RegionId)
281+
images, err := g.region.Images(ctx, g.organizationID, request.Spec.RegionId)
282282
if err != nil {
283283
return nil, errors.OAuth2ServerError("failed to list images").WithError(err)
284284
}
285285

286-
// Only get the version asked for.
287-
images = slices.DeleteFunc(images, func(x regionapi.Image) bool {
288-
return (*x.Spec.SoftwareVersions)["kubernetes"] != request.Spec.Version
289-
})
290-
291-
if len(images) == 0 {
292-
return nil, errors.OAuth2ServerError("unable to select an image")
293-
}
294-
295286
return &images[0], nil
296287
}
297288

0 commit comments

Comments
 (0)