Skip to content

Commit c69e37a

Browse files
committed
refactor multiplex logic for quota_limits
commit_hash:c8e3bb5105249287739cc8ae63d342d41796ead6
1 parent 85590f0 commit c69e37a

File tree

3 files changed

+53
-38
lines changed

3 files changed

+53
-38
lines changed

plugin/tables.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ func PluginAutoGeneratedTables() schema.Tables {
107107
organizationmanager.OsloginSettings(),
108108
organizationmanager.Users(),
109109
privatelink.PrivateEndpoints(),
110+
quotamanager.QuotaLimits(),
110111
quotamanager.QuotaServices(),
111112
resourcemanager.Clouds(),
112113
resourcemanager.Folders(),

resources/quotamanager/limits.go

Lines changed: 51 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,38 @@ import (
44
"context"
55
"errors"
66
"fmt"
7-
"slices"
7+
"sync"
88

99
"github.com/cloudquery/plugin-sdk/v4/schema"
1010
"github.com/cloudquery/plugin-sdk/v4/transformers"
1111
"github.com/yandex-cloud/cq-source-yc/client"
1212
"github.com/yandex-cloud/go-genproto/yandex/cloud/quotamanager/v1"
1313
)
1414

15+
type servicesCacher struct {
16+
responses sync.Map
17+
}
18+
19+
func (sc *servicesCacher) Get(ctx context.Context, client *client.Client, resourceType client.ResourceType) ([]*quotamanager.Service, error) {
20+
if val, ok := sc.responses.Load(resourceType); ok {
21+
return val.([]*quotamanager.Service), nil
22+
}
23+
24+
result, err := client.SDK.QuotaManager().QuotaLimit().ListServices(ctx, &quotamanager.ListServicesRequest{
25+
ResourceType: string(resourceType),
26+
// HACK: if services > 1000 this would work incorrectly
27+
PageSize: 1000,
28+
})
29+
if err != nil {
30+
return nil, err
31+
}
32+
33+
sc.responses.Store(resourceType, result.Services)
34+
return result.Services, nil
35+
}
36+
37+
var cacher = servicesCacher{}
38+
1539
type QuotaLimit struct {
1640
Resource *quotamanager.Resource
1741
*quotamanager.QuotaLimit
@@ -26,59 +50,51 @@ func QuotaLimits() *schema.Table {
2650
transformers.WithUnwrapStructFields("Resource", "QuotaLimit"),
2751
transformers.WithPrimaryKeys("Resource.Id", "QuotaId"),
2852
),
53+
Multiplex: client.CombineMultiplex(client.OrganizationMultiplex, client.CloudMultiplex),
2954
}
3055
}
3156

3257
func fetchQuotaLimits(ctx context.Context, meta schema.ClientMeta, parent *schema.Resource, res chan<- interface{}) error {
33-
service, ok := parent.Item.(*Service)
34-
if !ok {
35-
return fmt.Errorf("failed to extract service id from parent %T (%s)", parent.Item, parent.Item)
58+
c := meta.(*client.Client)
59+
60+
if c.MultiplexedResourceId == "" {
61+
return fmt.Errorf("client must be multiplexed by resource hierarchy entity (multiplexed resource id is empty)")
62+
}
63+
if c.MultiplexedResourceType == "" {
64+
return fmt.Errorf("client must be multiplexed by resource hierarchy entity (multiplexed resource type is empty) ")
3665
}
3766

38-
var joinedErr error
39-
// HACK: artificial child + multiplex: service (from parent) -> resource (from multiplex).
40-
// When using `schema.Tables/Relates`, the child table doesn't use multiplexing (feature), so we are doing it manually.
41-
// e.g. we want to resolve all `Service{Id: "vpc"}` quotas for `quotamanager.Resource{Id: "bg...", Type: "resource-manager.cloud"}`
42-
for _, cmeta := range client.CombineMultiplex(client.OrganizationMultiplex, client.CloudMultiplex)(meta) {
43-
c := cmeta.(*client.Client)
44-
45-
resourceId := c.MultiplexedResourceId
46-
if resourceId == "" {
47-
return fmt.Errorf("client must be multiplexed by resource hierarchy entity (multiplexed resource id is empty)")
48-
}
67+
// HACK: artificial (resource, service) multiplex: (Organizations + Clouds) ✕ service (from API call).
68+
// Bad concurrency as CQ SDK limits apply per multiplexer entity – resource in our case,
69+
// so concurrency key would be (resource), hence if services count is big, it would be slow
4970

50-
resourceType := c.MultiplexedResourceType
51-
if resourceType == "" {
52-
return fmt.Errorf("client must be multiplexed by resource hierarchy entity (multiplexed resource type is empty) ")
53-
}
71+
// Don't want to call rarely-changing response API per each org or cloud
72+
services, err := cacher.Get(ctx, c, c.MultiplexedResourceType)
73+
if err != nil {
74+
return fmt.Errorf("failed to fetch quota-enabled services for %s", c.MultiplexedResourceType)
75+
}
5476

55-
if slices.Index(quotaEnabledResourceTypes, resourceType) == -1 {
56-
return fmt.Errorf("%T (%s) is not a quota-enabled service", resourceType, resourceType)
57-
}
77+
resource := &quotamanager.Resource{
78+
Id: c.MultiplexedResourceId,
79+
Type: string(c.MultiplexedResourceType),
80+
}
5881

82+
var joinedErr error
83+
for _, service := range services {
5984
it := c.SDK.QuotaManager().QuotaLimit().QuotaLimitIterator(ctx, &quotamanager.ListQuotaLimitsRequest{
60-
Resource: &quotamanager.Resource{
61-
Id: resourceId,
62-
Type: string(resourceType),
63-
},
64-
Service: service.Id,
85+
Resource: resource,
86+
Service: service.Id,
6587
})
6688

6789
for it.Next() {
68-
ql := it.Value()
69-
7090
res <- &QuotaLimit{
71-
Resource: &quotamanager.Resource{
72-
Id: resourceId,
73-
Type: string(resourceType),
74-
},
75-
QuotaLimit: ql,
91+
Resource: resource,
92+
QuotaLimit: it.Value(),
7693
}
7794
}
7895

7996
if err := it.Error(); err != nil {
8097
// continue iterating because of artificial multiplex
81-
c.Logger.Err(err)
8298
joinedErr = errors.Join(joinedErr, err)
8399
}
84100
}

resources/quotamanager/services.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,10 @@ func QuotaServices() *schema.Table {
2121
Description: `https://yandex.cloud/ru/docs/quota-manager/api-ref/grpc/QuotaLimit/listServices#yandex.cloud.quotamanager.v1.Service`,
2222
Resolver: fetchQuotaServices,
2323
Transform: client.TransformWithStruct(&Service{}, transformers.WithUnwrapAllEmbeddedStructs(), transformers.WithPrimaryKeys("Id", "ResourceType")),
24-
Relations: schema.Tables{
25-
QuotaLimits(),
26-
},
2724
}
2825
}
2926

27+
// NOTE: no API to get this list, so hardcode
3028
var quotaEnabledResourceTypes = []client.ResourceType{client.ResourceTypeOrganization, client.ResourceTypeCloud}
3129

3230
func fetchQuotaServices(ctx context.Context, meta schema.ClientMeta, _ *schema.Resource, res chan<- interface{}) error {

0 commit comments

Comments
 (0)