Skip to content

Commit 7900220

Browse files
committed
resolve comments
1 parent 719293c commit 7900220

9 files changed

Lines changed: 151 additions & 140 deletions

File tree

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -323,8 +323,8 @@ manifests: $(CONTROLLER_GEN)
323323
protos: $(PROTOC_GEN_GO) $(PROTOC_GEN_GO_GRPC) $(PROTOC_GEN_GRPC_GATEWAY) $(PROTOC)
324324
PATH=$$PATH:$(TOOLS_BIN_DIR) $(PROTOC) --go_out=. --go_opt=paths=source_relative \
325325
--go-grpc_out=. --go-grpc_opt=paths=source_relative \
326-
--grpc-gateway_out=grpc_api_configuration=pkg/protos/azure/compute/v1/vmsizerecommender_http.yaml,logtostderr=true:. --grpc-gateway_opt=paths=source_relative,generate_unbound_methods=true \
327-
pkg/protos/azure/compute/v1/vmsizerecommender.proto
326+
--grpc-gateway_out=grpc_api_configuration=apis/protos/azure/compute/v1/vmsizerecommender_http.yaml,logtostderr=true:. --grpc-gateway_opt=paths=source_relative,generate_unbound_methods=true \
327+
apis/protos/azure/compute/v1/vmsizerecommender.proto
328328

329329
# Generate code
330330
generate: $(CONTROLLER_GEN) protos

pkg/protos/azure/compute/v1/vmsizerecommender.pb.go renamed to apis/protos/azure/compute/v1/vmsizerecommender.pb.go

Lines changed: 59 additions & 59 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/protos/azure/compute/v1/vmsizerecommender.pb.gw.go renamed to apis/protos/azure/compute/v1/vmsizerecommender.pb.gw.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
File renamed without changes.

pkg/protos/azure/compute/v1/vmsizerecommender_grpc.pb.go renamed to apis/protos/azure/compute/v1/vmsizerecommender_grpc.pb.go

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/protos/azure/compute/v1/vmsizerecommender_http.yaml renamed to apis/protos/azure/compute/v1/vmsizerecommender_http.yaml

File renamed without changes.

pkg/utils/capacityclient/client.go renamed to pkg/clients/azure/compute/vmsizerecommenderclient.go

Lines changed: 32 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
package capacityclient
17+
package compute
1818

1919
import (
2020
"bytes"
@@ -24,56 +24,63 @@ import (
2424
"net/http"
2525
"strings"
2626

27+
"github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime"
2728
"google.golang.org/protobuf/encoding/protojson"
2829

29-
computev1 "go.goms.io/fleet/pkg/protos/azure/compute/v1"
30+
computev1 "go.goms.io/fleet/apis/protos/azure/compute/v1"
31+
"go.goms.io/fleet/pkg/clients/consts"
3032
)
3133

3234
const (
3335
// recommendationsPathTemplate is the URL path template for VM size recommendations API.
3436
recommendationsPathTemplate = "/subscriptions/%s/providers/Microsoft.Compute/locations/%s/vmSizeRecommendations/vmAttributeBased/generate"
3537
)
3638

37-
// CapacityClientFactory is a function type for creating CapacityClient instances.
38-
type CapacityClientFactory func(endpoint string, httpClient *http.Client) CapacityClient
39+
// AttributeBasedVMSizeRecommenderClientFactory is a function type for creating AttributeBasedVMSizeRecommenderClient instances.
40+
type AttributeBasedVMSizeRecommenderClientFactory func(endpoint string, httpClient *http.Client) AttributeBasedVMSizeRecommenderClient
3941

40-
// CapacityClient is an interface for interacting with the Azure Capacity API.
41-
type CapacityClient interface {
42+
// AttributeBasedVMSizeRecommenderClient is an interface for interacting with the Azure Attribute-Based VM Size Recommender API.
43+
type AttributeBasedVMSizeRecommenderClient interface {
4244
// GenerateAttributeBasedRecommendations generates VM size recommendations based on attributes.
4345
GenerateAttributeBasedRecommendations(ctx context.Context, req *computev1.GenerateAttributeBasedRecommendationsRequest) (*computev1.GenerateAttributeBasedRecommendationsResponse, error)
4446
}
4547

46-
var _ CapacityClient = &client{}
48+
var _ AttributeBasedVMSizeRecommenderClient = &attributeBasedVMSizeRecommenderClient{}
4749

48-
// client implements the CapacityClient interface for interacting with Azure Capacity API.
49-
type client struct {
50-
// baseURL is the base URL of the capacity service endpoint.
50+
// attributeBasedVMSizeRecommenderClient implements the AttributeBasedVMSizeRecommenderClient interface
51+
// for interacting with Azure Attribute-Based VM Size Recommender API.
52+
type attributeBasedVMSizeRecommenderClient struct {
53+
// baseURL is the base URL of the http(s) requests to the attribute-based VM size recommender service endpoint.
5154
baseURL string
5255
// httpClient is the HTTP client used for making requests.
5356
httpClient *http.Client
5457
}
5558

56-
// NewClient creates a new capacity client with the given endpoint and HTTP client.
57-
// If httpClient is nil, http.DefaultClient will be used.
58-
// If the endpoint does not have a scheme (http:// or https://), like localhost:8080, http:// will be added.
59-
func NewClient(endpoint string, httpClient *http.Client) CapacityClient {
59+
// NewAttributeBasedVMSizeRecommenderClient creates a new attribute-based VM size recommender client.
60+
// The serverAddress is the remote Azure Attribute-Based VM Size Recommender service endpoint.
61+
// If httpClient is nil, a default client with 60s timeout will be used.
62+
// If the serverAddress does not have a scheme (http:// or https://), like localhost:8080, http:// will be added.
63+
func NewAttributeBasedVMSizeRecommenderClient(serverAddress string, httpClient *http.Client) AttributeBasedVMSizeRecommenderClient {
6064
if httpClient == nil {
61-
httpClient = http.DefaultClient
65+
httpClient = &http.Client{Timeout: consts.HTTPTimeoutAzure} // Client with default transport and 60s timeout.
6266
}
6367
// Add http:// scheme if no scheme is present
64-
if !strings.HasPrefix(endpoint, "http://") && !strings.HasPrefix(endpoint, "https://") {
65-
endpoint = "http://" + endpoint
68+
if !strings.HasPrefix(serverAddress, "http://") && !strings.HasPrefix(serverAddress, "https://") {
69+
serverAddress = "http://" + serverAddress
6670
}
67-
// Ensure endpoint doesn't have trailing slash
68-
endpoint = strings.TrimSuffix(endpoint, "/")
69-
return &client{
70-
baseURL: endpoint,
71+
// Ensure serverAddress doesn't have trailing slash
72+
serverAddress = strings.TrimSuffix(serverAddress, "/")
73+
return &attributeBasedVMSizeRecommenderClient{
74+
baseURL: serverAddress,
7175
httpClient: httpClient,
7276
}
7377
}
7478

7579
// GenerateAttributeBasedRecommendations generates VM size recommendations based on attributes.
76-
func (c *client) GenerateAttributeBasedRecommendations(ctx context.Context, req *computev1.GenerateAttributeBasedRecommendationsRequest) (*computev1.GenerateAttributeBasedRecommendationsResponse, error) {
80+
func (c *attributeBasedVMSizeRecommenderClient) GenerateAttributeBasedRecommendations(
81+
ctx context.Context,
82+
req *computev1.GenerateAttributeBasedRecommendationsRequest,
83+
) (*computev1.GenerateAttributeBasedRecommendationsResponse, error) {
7784
if req == nil {
7885
return nil, fmt.Errorf("request cannot be nil")
7986
}
@@ -108,8 +115,8 @@ func (c *client) GenerateAttributeBasedRecommendations(ctx context.Context, req
108115
}
109116

110117
// Set headers
111-
httpReq.Header.Set("Content-Type", "application/json")
112-
httpReq.Header.Set("Accept", "application/json")
118+
httpReq.Header.Set(consts.HeaderContentTypeKey, consts.HeaderContentTypeJSON)
119+
httpReq.Header.Set(consts.HeaderAcceptKey, consts.HeaderContentTypeJSON)
113120

114121
// Execute the request
115122
resp, err := c.httpClient.Do(httpReq)
@@ -126,7 +133,7 @@ func (c *client) GenerateAttributeBasedRecommendations(ctx context.Context, req
126133

127134
// Check status code
128135
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
129-
return nil, fmt.Errorf("request failed with status %d: %s", resp.StatusCode, string(respBody))
136+
return nil, fmt.Errorf("request failed with status %d: %w", resp.StatusCode, runtime.NewResponseError(resp))
130137
}
131138

132139
// Unmarshal response using protojson for proper proto3 support

pkg/utils/capacityclient/client_test.go renamed to pkg/clients/azure/compute/vmsizerecommenderclient_test.go

Lines changed: 21 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
package capacityclient
17+
package compute
1818

1919
import (
2020
"context"
@@ -25,13 +25,16 @@ import (
2525
"strings"
2626
"testing"
2727

28+
"github.com/google/go-cmp/cmp"
2829
"google.golang.org/protobuf/encoding/protojson"
2930
"google.golang.org/protobuf/proto"
3031

31-
computev1 "go.goms.io/fleet/pkg/protos/azure/compute/v1"
32+
computev1 "go.goms.io/fleet/apis/protos/azure/compute/v1"
33+
"go.goms.io/fleet/pkg/clients/consts"
3234
)
3335

34-
func TestNewClient(t *testing.T) {
36+
func TestNewAttributeBasedVMSizeRecommenderClient(t *testing.T) {
37+
defaultClient := &http.Client{Timeout: consts.HTTPTimeoutAzure}
3538
tests := []struct {
3639
name string
3740
endpoint string
@@ -51,51 +54,46 @@ func TestNewClient(t *testing.T) {
5154
endpoint: "https://example.com",
5255
httpClient: nil,
5356
wantBaseURL: "https://example.com",
54-
wantHTTPClient: http.DefaultClient,
57+
wantHTTPClient: defaultClient,
5558
},
5659
{
5760
name: "removes trailing slash from endpoint",
5861
endpoint: "https://example.com/",
5962
httpClient: nil,
6063
wantBaseURL: "https://example.com",
61-
wantHTTPClient: http.DefaultClient,
64+
wantHTTPClient: defaultClient,
6265
},
6366
{
6467
name: "adds http scheme to endpoint without scheme",
6568
endpoint: "localhost:8080",
6669
httpClient: nil,
6770
wantBaseURL: "http://localhost:8080",
68-
wantHTTPClient: http.DefaultClient,
71+
wantHTTPClient: defaultClient,
6972
},
7073
{
7174
name: "adds http scheme and removes trailing slash",
7275
endpoint: "example.com:8080/",
7376
httpClient: nil,
7477
wantBaseURL: "http://example.com:8080",
75-
wantHTTPClient: http.DefaultClient,
78+
wantHTTPClient: defaultClient,
7679
},
7780
{
7881
name: "preserves existing http scheme",
7982
endpoint: "http://localhost:8080",
8083
httpClient: nil,
8184
wantBaseURL: "http://localhost:8080",
82-
wantHTTPClient: http.DefaultClient,
85+
wantHTTPClient: defaultClient,
8386
},
8487
}
8588

8689
for _, tt := range tests {
8790
t.Run(tt.name, func(t *testing.T) {
88-
got := NewClient(tt.endpoint, tt.httpClient).(*client)
91+
got := NewAttributeBasedVMSizeRecommenderClient(tt.endpoint, tt.httpClient).(*attributeBasedVMSizeRecommenderClient)
8992
if got.baseURL != tt.wantBaseURL {
9093
t.Errorf("NewClient() baseURL = %v, want %v", got.baseURL, tt.wantBaseURL)
9194
}
92-
// For custom HTTP client, just verify it's not nil
93-
if tt.httpClient != nil && got.httpClient == nil {
94-
t.Errorf("NewClient() httpClient is nil, want non-nil")
95-
}
96-
// For nil HTTP client, verify it uses default client
97-
if tt.httpClient == nil && got.httpClient != http.DefaultClient {
98-
t.Errorf("NewClient() httpClient = %v, want http.DefaultClient", got.httpClient)
95+
if !cmp.Equal(got.httpClient, tt.wantHTTPClient) {
96+
t.Errorf("NewClient() httpClient = %v, want %v", got.httpClient, tt.wantHTTPClient)
9997
}
10098
})
10199
}
@@ -240,22 +238,22 @@ func TestClient_GenerateAttributeBasedRecommendations(t *testing.T) {
240238
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
241239
// Verify request method
242240
if r.Method != http.MethodPost {
243-
t.Errorf("expected POST request, got %s", r.Method)
241+
t.Errorf("got %s, want POST request", r.Method)
244242
}
245243

246244
// Verify headers
247245
if r.Header.Get("Content-Type") != "application/json" {
248-
t.Errorf("expected Content-Type: application/json, got %s", r.Header.Get("Content-Type"))
246+
t.Errorf("got %s, want Content-Type: application/json", r.Header.Get("Content-Type"))
249247
}
250248
if r.Header.Get("Accept") != "application/json" {
251-
t.Errorf("expected Accept: application/json, got %s", r.Header.Get("Accept"))
249+
t.Errorf("got %s, want Accept: application/json", r.Header.Get("Accept"))
252250
}
253251

254252
// Verify URL path if request is not nil
255253
if tt.request != nil && tt.request.SubscriptionId != "" && tt.request.Location != "" {
256-
expectedPath := fmt.Sprintf(recommendationsPathTemplate, tt.request.SubscriptionId, tt.request.Location)
257-
if r.URL.Path != expectedPath {
258-
t.Errorf("expected path %s, got %s", expectedPath, r.URL.Path)
254+
wantPath := fmt.Sprintf(recommendationsPathTemplate, tt.request.SubscriptionId, tt.request.Location)
255+
if r.URL.Path != wantPath {
256+
t.Errorf("got %s, want path %s", r.URL.Path, wantPath)
259257
}
260258

261259
// Verify request body using protojson for proper proto3 oneof support
@@ -284,7 +282,7 @@ func TestClient_GenerateAttributeBasedRecommendations(t *testing.T) {
284282
defer server.Close()
285283

286284
// Create client
287-
client := NewClient(server.URL, nil)
285+
client := NewAttributeBasedVMSizeRecommenderClient(server.URL, nil)
288286

289287
// Execute request
290288
got, err := client.GenerateAttributeBasedRecommendations(context.Background(), tt.request)
@@ -307,31 +305,3 @@ func TestClient_GenerateAttributeBasedRecommendations(t *testing.T) {
307305
})
308306
}
309307
}
310-
311-
func TestClient_GenerateAttributeBasedRecommendations_ContextCancellation(t *testing.T) {
312-
// Create a server that delays response
313-
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
314-
// This handler will never respond in time
315-
<-r.Context().Done()
316-
}))
317-
defer server.Close()
318-
319-
client := NewClient(server.URL, nil)
320-
321-
// Create a context that is already cancelled
322-
ctx, cancel := context.WithCancel(context.Background())
323-
cancel()
324-
325-
req := &computev1.GenerateAttributeBasedRecommendationsRequest{
326-
SubscriptionId: "sub-123",
327-
Location: "eastus",
328-
PriorityProfile: &computev1.GenerateAttributeBasedRecommendationsRequest_RegularPriorityProfile{
329-
RegularPriorityProfile: &computev1.RegularPriorityProfile{},
330-
},
331-
}
332-
333-
_, err := client.GenerateAttributeBasedRecommendations(ctx, req)
334-
if err == nil {
335-
t.Error("GenerateAttributeBasedRecommendations() expected error for cancelled context, got nil")
336-
}
337-
}

pkg/clients/consts/consts.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
Copyright 2025 The KubeFleet Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package consts
18+
19+
import "time"
20+
21+
// Common HTTP constants.
22+
const (
23+
// HTTPTimeoutAzure is the timeout for HTTP requests to Azure services.
24+
// Setting to 60 seconds, following ARM client request timeout conventions:
25+
// https://github.com/Azure/azure-resource-manager-rpc/blob/master/v1.0/common-api-details.md#client-request-timeout.
26+
HTTPTimeoutAzure = 60 * time.Second
27+
28+
// HeaderContentTypeKey is the HTTP header key for Content-Type.
29+
HeaderContentTypeKey = "Content-Type"
30+
// HeaderAcceptKey is the HTTP header key for Accept.
31+
HeaderAcceptKey = "Accept"
32+
// HeaderContentTypeJSON is the Content-Type header value for JSON payloads.
33+
HeaderContentTypeJSON = "application/json"
34+
)

0 commit comments

Comments
 (0)