Skip to content

Commit 009b6a3

Browse files
lionellonullfuncjordanstephens
authored
Move scaling quota check to base BYOC provider (#1083)
* Move scaling quota check to base BYOC provider * Update src/pkg/cli/client/byoc/baseclient.go Co-authored-by: Jordan Stephens <[email protected]> * rename quota to policy to be more encompassing of quotas and access * update naming of storage of user CanIUse values --------- Co-authored-by: Eric Liu <[email protected]> Co-authored-by: Jordan Stephens <[email protected]> Co-authored-by: Eric Liu <[email protected]>
1 parent e76bea1 commit 009b6a3

File tree

11 files changed

+58
-78
lines changed

11 files changed

+58
-78
lines changed

src/cmd/cli/command/commands.go

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -60,14 +60,6 @@ func getCluster() string {
6060
return org + "@" + cluster
6161
}
6262

63-
const TIER_ERROR_MESSAGE = "current subscription tier does not allow this action: "
64-
65-
type ErrNoPermission string
66-
67-
func (e ErrNoPermission) Error() string {
68-
return TIER_ERROR_MESSAGE + string(e)
69-
}
70-
7163
func prettyError(err error) error {
7264
// To avoid printing the internal gRPC error code
7365
var cerr *connect.Error
@@ -1223,10 +1215,7 @@ func canIUseProvider(ctx context.Context, provider cliClient.Provider, projectNa
12231215
if err != nil {
12241216
return err
12251217
}
1226-
// Allow local override of the CD image
1227-
cdImage := pkg.Getenv("DEFANG_CD_IMAGE", resp.CdImage)
1228-
provider.SetCDImage(cdImage)
1229-
1218+
provider.SetCanIUseConfig(resp)
12301219
return nil
12311220
}
12321221

src/cmd/cli/command/compose.go

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -95,34 +95,17 @@ func makeComposeUpCmd() *cobra.Command {
9595
return err
9696
}
9797

98+
// Check if the user has permission to use the provider
9899
err = canIUseProvider(ctx, provider, project.Name)
99100
if err != nil {
100101
return err
101102
}
102103

103104
managedServices, _ := cli.SplitManagedAndUnmanagedServices(project.Services)
104-
105105
if len(managedServices) > 0 {
106106
term.Warnf("Defang cannot monitor status of the following managed service(s): %v.\n To check if the managed service is up, check the status of the service which depends on it.", managedServices)
107107
}
108108

109-
numGPUS := compose.GetNumOfGPUs(ctx, project)
110-
if numGPUS > 0 {
111-
req := &defangv1.CanIUseRequest{
112-
Project: project.Name,
113-
Provider: providerID.EnumValue(),
114-
}
115-
116-
resp, err := client.CanIUse(ctx, req)
117-
if err != nil {
118-
return err
119-
}
120-
121-
if !resp.Gpu {
122-
return ErrNoPermission("usage of GPUs. Please upgrade on https://s.defang.io/subscription")
123-
}
124-
}
125-
126109
deploy, project, err := cli.ComposeUp(ctx, project, client, provider, upload, mode.Value())
127110

128111
if err != nil {

src/pkg/cli/client/byoc/aws/byoc.go

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -210,28 +210,17 @@ func (b *ByocAws) deploy(ctx context.Context, req *defangv1.DeployRequest, cmd s
210210
return nil, err
211211
}
212212

213-
etag := pkg.RandomID()
214-
quotaClient = NewServiceQuotasClient(ctx, cfg)
215-
if err = ValidateGPUResources(ctx, project); err != nil {
213+
quotaClient = NewServiceQuotasClient(cfg)
214+
if err = validateGPUResources(ctx, project); err != nil {
216215
return nil, err
217216
}
218217

218+
etag := pkg.RandomID()
219219
serviceInfos, err := b.GetServiceInfos(ctx, project.Name, req.DelegateDomain, etag, project.Services)
220220
if err != nil {
221221
return nil, err
222222
}
223223

224-
// Ensure all service endpoints are unique
225-
endpoints := make(map[string]bool)
226-
for _, serviceInfo := range serviceInfos {
227-
for _, endpoint := range serviceInfo.Endpoints {
228-
if endpoints[endpoint] {
229-
return nil, fmt.Errorf("duplicate endpoint: %s", endpoint) // CodeInvalidArgument
230-
}
231-
endpoints[endpoint] = true
232-
}
233-
}
234-
235224
data, err := proto.Marshal(&defangv1.ProjectUpdate{
236225
CdVersion: b.CDImage,
237226
Compose: req.Compose,

src/pkg/cli/client/byoc/aws/validation.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ var ErrAWSNoConnection = errors.New("no connect to AWS service quotas")
2525
var ErrGPUQuotaZero = errors.New("no GPUs enabled. To resolve see https://docs.defang.io/docs/tutorials/deploy-with-gpu")
2626
var ErrNoQuotasReceived = errors.New("no service quotas received")
2727

28-
func NewServiceQuotasClient(ctx context.Context, cfg aws.Config) *servicequotas.Client {
28+
func NewServiceQuotasClient(cfg aws.Config) *servicequotas.Client {
2929
return servicequotas.NewFromConfig(cfg)
3030
}
3131

@@ -69,12 +69,11 @@ func hasGPUQuota(ctx context.Context) (bool, error) {
6969
return false, nil
7070
}
7171

72-
func ValidateGPUResources(ctx context.Context, project *composeTypes.Project) error {
72+
func validateGPUResources(ctx context.Context, project *composeTypes.Project) error {
7373
// return after checking if there are actually non-zero GPUs requested
7474
gpusAvailable, quotaErr := hasGPUQuota(ctx)
7575

76-
gpuCount := compose.GetNumOfGPUs(ctx, project)
77-
76+
gpuCount := compose.GetNumOfGPUs(project.Services)
7877
if gpuCount == 0 {
7978
return nil
8079
}

src/pkg/cli/client/byoc/aws/validation_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ func TestValidateGPUResources(t *testing.T) {
5757
quotaClient = mockQuotaClient
5858
mockQuotaClient.output = nil
5959
mockQuotaClient.err = ErrNoQuotasReceived
60-
err := ValidateGPUResources(ctx, &project)
60+
err := validateGPUResources(ctx, &project)
6161
if err != nil && errors.Is(err, ErrNoQuotasReceived) {
6262
t.Fatalf("ValidateGPUResources() failed: Unexpected errors %v", err)
6363
}
@@ -73,7 +73,7 @@ func TestValidateGPUResources(t *testing.T) {
7373

7474
quotaClient = nil
7575
mockQuotaClient.err = nil
76-
err := ValidateGPUResources(ctx, &project)
76+
err := validateGPUResources(ctx, &project)
7777
if err != nil {
7878
t.Fatalf("ValidateGPUResources() failed: expected no errors but got %v", err)
7979
}
@@ -97,7 +97,7 @@ func TestValidateGPUResources(t *testing.T) {
9797
},
9898
},
9999
}
100-
err := ValidateGPUResources(ctx, &project)
100+
err := validateGPUResources(ctx, &project)
101101
if err != nil && !errors.Is(err, ErrGPUQuotaZero) {
102102
t.Fatalf("ValidateGPUResources() failed: Unexpected err %v", err)
103103
}
@@ -113,7 +113,7 @@ func TestValidateGPUResources(t *testing.T) {
113113
}
114114

115115
quotaClient = nil
116-
err := ValidateGPUResources(ctx, &project)
116+
err := validateGPUResources(ctx, &project)
117117
if err != nil && !errors.Is(err, ErrAWSNoConnection) {
118118
t.Fatalf("ValidateGPUResources() failed: Unexpected err %v", err)
119119
}

src/pkg/cli/client/byoc/baseclient.go

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,20 @@ type HasStackSupport interface {
3636
GetStackName() string
3737
}
3838

39+
type CanIUseConfig struct {
40+
CDImage string
41+
AllowScaling bool
42+
AllowGPU bool
43+
}
44+
3945
type ByocBaseClient struct {
4046
client.RetryDelayer
4147

4248
PulumiStack string
4349
SetupDone bool
4450
ShouldDelegateSubdomain bool
4551
TenantName string
46-
CDImage string
52+
CanIUseConfig
4753

4854
projectBackend ProjectBackend
4955
}
@@ -78,8 +84,11 @@ func (b *ByocBaseClient) Debug(context.Context, *defangv1.DebugRequest) (*defang
7884
return nil, client.ErrNotImplemented("AI debugging is not yet supported for BYOC")
7985
}
8086

81-
func (b *ByocBaseClient) SetCDImage(image string) {
82-
b.CDImage = image
87+
func (b *ByocBaseClient) SetCanIUseConfig(quotas *defangv1.CanIUseResponse) {
88+
// Allow local override of the CD image
89+
b.CDImage = pkg.Getenv("DEFANG_CD_IMAGE", quotas.CdImage)
90+
b.AllowScaling = quotas.AllowScaling
91+
b.AllowGPU = quotas.Gpu
8392
}
8493

8594
func (b *ByocBaseClient) ServiceDNS(name string) string {
@@ -122,7 +131,18 @@ func (b *ByocBaseClient) GetProjectDomain(projectName, zone string) string {
122131
return domain
123132
}
124133

134+
type ErrNoPermission string
135+
136+
func (e ErrNoPermission) Error() string {
137+
return "current subscription tier does not allow this action: " + string(e)
138+
}
139+
125140
func (b *ByocBaseClient) GetServiceInfos(ctx context.Context, projectName, delegateDomain, etag string, services map[string]composeTypes.ServiceConfig) ([]*defangv1.ServiceInfo, error) {
141+
numGPUS := compose.GetNumOfGPUs(services)
142+
if numGPUS > 0 && !b.AllowGPU {
143+
return nil, ErrNoPermission("usage of GPUs. Please upgrade on https://s.defang.io/subscription")
144+
}
145+
126146
serviceInfoMap := make(map[string]*Node)
127147
for _, service := range services {
128148
serviceInfo, err := b.update(ctx, projectName, delegateDomain, service)
@@ -138,7 +158,20 @@ func (b *ByocBaseClient) GetServiceInfos(ctx context.Context, projectName, deleg
138158
}
139159

140160
// Reorder the serviceInfos to make sure the dependencies are created first
141-
return topologicalSort(serviceInfoMap), nil
161+
serviceInfos := topologicalSort(serviceInfoMap)
162+
163+
// Ensure all service endpoints are unique
164+
endpoints := make(map[string]bool)
165+
for _, serviceInfo := range serviceInfos {
166+
for _, endpoint := range serviceInfo.Endpoints {
167+
if endpoints[endpoint] {
168+
return nil, fmt.Errorf("duplicate endpoint: %s", endpoint) // CodeInvalidArgument
169+
}
170+
endpoints[endpoint] = true
171+
}
172+
}
173+
174+
return serviceInfos, nil
142175
}
143176

144177
// Simple DFS topological sort to make sure the dependencies are created first
@@ -180,10 +213,11 @@ func (b *ByocBaseClient) update(ctx context.Context, projectName, delegateDomain
180213

181214
pkg.Ensure(projectName != "", "ProjectName not set")
182215
si := &defangv1.ServiceInfo{
183-
Etag: pkg.RandomID(), // TODO: could be hash for dedup/idempotency
184-
Project: projectName, // was: tenant
185-
Service: &defangv1.Service{Name: service.Name},
186-
Domainname: service.DomainName,
216+
AllowScaling: b.AllowScaling,
217+
Domainname: service.DomainName,
218+
Etag: pkg.RandomID(), // TODO: could be hash for dedup/idempotency
219+
Project: projectName, // was: tenant
220+
Service: &defangv1.Service{Name: service.Name},
187221
}
188222

189223
hasHost := false

src/pkg/cli/client/byoc/do/byoc.go

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -146,23 +146,11 @@ func (b *ByocDo) deploy(ctx context.Context, req *defangv1.DeployRequest, cmd st
146146
}
147147

148148
etag := pkg.RandomID()
149-
150149
serviceInfos, err := b.GetServiceInfos(ctx, project.Name, req.DelegateDomain, etag, project.Services)
151150
if err != nil {
152151
return nil, err
153152
}
154153

155-
// Ensure all service endpoints are unique
156-
endpoints := make(map[string]bool)
157-
for _, serviceInfo := range serviceInfos {
158-
for _, endpoint := range serviceInfo.Endpoints {
159-
if endpoints[endpoint] {
160-
return nil, fmt.Errorf("duplicate endpoint: %s", endpoint) // CodeInvalidArgument
161-
}
162-
endpoints[endpoint] = true
163-
}
164-
}
165-
166154
data, err := proto.Marshal(&defangv1.ProjectUpdate{
167155
CdVersion: b.CDImage,
168156
Compose: req.Compose,

src/pkg/cli/client/byoc/gcp/byoc.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -410,7 +410,6 @@ func (b *ByocGcp) deploy(ctx context.Context, req *defangv1.DeployRequest, comma
410410
}
411411

412412
etag := pkg.RandomID()
413-
414413
serviceInfos, err := b.GetServiceInfos(ctx, project.Name, req.DelegateDomain, etag, project.Services)
415414
if err != nil {
416415
return nil, err

src/pkg/cli/client/playground.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ func (g *PlaygroundProvider) QueryForDebug(ctx context.Context, req *defangv1.De
128128
func (g *PlaygroundProvider) PrepareDomainDelegation(ctx context.Context, req PrepareDomainDelegationRequest) (*PrepareDomainDelegationResponse, error) {
129129
return nil, nil // Playground does not support delegate domains
130130
}
131-
func (g *PlaygroundProvider) SetCDImage(string) {}
131+
func (g *PlaygroundProvider) SetCanIUseConfig(*defangv1.CanIUseResponse) {}
132132

133133
type PlaygroundAccountInfo struct{}
134134

src/pkg/cli/client/provider.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ type Provider interface {
147147
PutConfig(context.Context, *defangv1.PutConfigRequest) error
148148
RemoteProjectName(context.Context) (string, error)
149149
ServiceDNS(string) string
150-
SetCDImage(string)
150+
SetCanIUseConfig(*defangv1.CanIUseResponse)
151151
Subscribe(context.Context, *defangv1.SubscribeRequest) (ServerStream[defangv1.SubscribeResponse], error)
152152
TearDown(context.Context) error
153153
}

0 commit comments

Comments
 (0)