Skip to content

Commit 12d986b

Browse files
committed
address comments and fix test
Signed-off-by: Britania Rodriguez Reyes <britaniar@microsoft.com>
1 parent a062cad commit 12d986b

File tree

9 files changed

+141
-71
lines changed

9 files changed

+141
-71
lines changed

charts/hub-agent/templates/deployment.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ spec:
5454
- --cluster-unhealthy-threshold={{ .Values.clusterUnhealthyThreshold }}
5555
- --resource-snapshot-creation-minimum-interval={{ .Values.resourceSnapshotCreationMinimumInterval }}
5656
- --resource-changes-collection-duration={{ .Values.resourceChangesCollectionDuration }}
57-
- --compute-endpoint={{ .Values.computeEndpoint }}
57+
- --compute-service-endpoint={{ .Values.computeServiceEndpoint }}
5858
ports:
5959
- name: metrics
6060
containerPort: 8080

charts/hub-agent/values.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ enableEvictionAPIs: true
5454
enablePprof: true
5555
pprofPort: 6065
5656

57-
computeEndpoint: "http://localhost:9090"
57+
computeServiceEndpoint: "http://localhost:8421"
5858

5959
hubAPIQPS: 250
6060
hubAPIBurst: 1000

cmd/hubagent/options/options.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,8 @@ type Options struct {
104104
EnablePprof bool
105105
// PprofPort is the port for pprof profiling.
106106
PprofPort int
107-
// ComputeEndpoint is the endpoint for the compute service.
108-
ComputeEndpoint string
107+
// ComputeServiceEndpoint is the endpoint for the compute service.
108+
ComputeServiceEndpoint string
109109
// DenyModifyMemberClusterLabels indicates if the member cluster labels cannot be modified by groups (excluding system:masters)
110110
DenyModifyMemberClusterLabels bool
111111
// ResourceSnapshotCreationMinimumInterval is the minimum interval at which resource snapshots could be created.
@@ -133,7 +133,7 @@ func NewOptions() *Options {
133133
EnableResourcePlacement: true,
134134
EnablePprof: false,
135135
PprofPort: 6065,
136-
ComputeEndpoint: "http://localhost:9090",
136+
ComputeServiceEndpoint: "http://localhost:8421",
137137
ResourceSnapshotCreationMinimumInterval: 30 * time.Second,
138138
ResourceChangesCollectionDuration: 15 * time.Second,
139139
}
@@ -182,7 +182,7 @@ func (o *Options) AddFlags(flags *flag.FlagSet) {
182182
flags.BoolVar(&o.EnableResourcePlacement, "enable-resource-placement", true, "If set, the agents will watch for the ResourcePlacement APIs.")
183183
flags.BoolVar(&o.EnablePprof, "enable-pprof", false, "If set, the pprof profiling is enabled.")
184184
flags.IntVar(&o.PprofPort, "pprof-port", 6065, "The port for pprof profiling.")
185-
flags.StringVar(&o.ComputeEndpoint, "compute-endpoint", "http://localhost:9090", "The endpoint for the compute service.")
185+
flags.StringVar(&o.ComputeServiceEndpoint, "compute-service-endpoint", "http://localhost:8421", "The endpoint for the compute service.")
186186
flags.BoolVar(&o.DenyModifyMemberClusterLabels, "deny-modify-member-cluster-labels", false, "If set, users not in the system:masters cannot modify member cluster labels.")
187187
flags.DurationVar(&o.ResourceSnapshotCreationMinimumInterval, "resource-snapshot-creation-minimum-interval", 30*time.Second, "The minimum interval at which resource snapshots could be created.")
188188
flags.DurationVar(&o.ResourceChangesCollectionDuration, "resource-changes-collection-duration", 15*time.Second,

cmd/hubagent/workload/setup.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import (
2222
"strings"
2323
"sync"
2424

25+
"go.goms.io/fleet/pkg/scheduler/framework/plugins/clusteraffinity/azure"
26+
2527
"k8s.io/apimachinery/pkg/runtime/schema"
2628
"k8s.io/client-go/discovery"
2729
"k8s.io/client-go/dynamic"
@@ -385,7 +387,7 @@ func SetupControllers(ctx context.Context, wg *sync.WaitGroup, mgr ctrl.Manager,
385387
// Set up the scheduler
386388
klog.Info("Setting up scheduler")
387389
defaultProfile := profile.NewDefaultProfileWithOptions(profile.ProfileOptions{
388-
Endpoint: opts.ComputeEndpoint,
390+
ComputeService: azure.NewAzureCapacityService(opts.ComputeServiceEndpoint),
389391
})
390392
defaultFramework := framework.NewFramework(defaultProfile, mgr)
391393
defaultSchedulingQueue := queue.NewSimplePlacementSchedulingQueue(

pkg/scheduler/framework/plugins/clusteraffinity/azure/capcacity.go

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package azure
22

33
import (
4-
"context"
4+
"bytes"
55
"encoding/json"
66
"fmt"
77
"io"
@@ -15,29 +15,34 @@ import (
1515
placementv1beta1 "go.goms.io/fleet/apis/placement/v1beta1"
1616
)
1717

18-
// DefaultAzureCapacityService is the default implementation of AzureCapacityService.
19-
type DefaultAzureCapacityService struct {
20-
endpoint string
21-
client *http.Client
18+
// defaultAzureCapacityService is the default implementation of AzureCapacityService.
19+
type defaultAzureCapacityService struct {
20+
// ServerAddress is the Azure capacity service endpoint.
21+
ServerAddress string
22+
23+
// Client is the HTTP client used to make requests to the Azure capacity service.
24+
Client *http.Client
2225
}
2326

24-
// Compile-time check to ensure DefaultAzureCapacityService implements AzureCapacityService
25-
var _ AzureCapacityService = &DefaultAzureCapacityService{}
27+
// Compile-time check to ensure defaultAzureCapacityService implements AzureCapacityService
28+
var _ AzureCapacityService = &defaultAzureCapacityService{}
2629

2730
// NewAzureCapacityService creates a new default Azure capacity service with the given endpoint.
28-
func NewAzureCapacityService(endpoint string) *DefaultAzureCapacityService {
29-
return &DefaultAzureCapacityService{
30-
endpoint: endpoint,
31-
client: &http.Client{},
31+
func NewAzureCapacityService(serverAddress string, client *http.Client) AzureCapacityService {
32+
if client == nil {
33+
client = http.DefaultClient
34+
}
35+
return &defaultAzureCapacityService{
36+
ServerAddress: serverAddress,
37+
Client: client,
3238
}
3339
}
3440

3541
// ValidateCapacityRequirement validates a capacity requirement against Azure APIs.
36-
func (s *DefaultAzureCapacityService) ValidateCapacityRequirement(
42+
func (s *defaultAzureCapacityService) ValidateCapacityRequirement(
3743
cluster *clusterv1beta1.MemberCluster,
3844
req placementv1beta1.PropertySelectorRequirement,
3945
) (bool, error) {
40-
ctx := context.Background()
4146
subID, location := extractAzureInfoFromLabels(cluster)
4247
if subID == "" || location == "" {
4348
return false, fmt.Errorf("cluster %s does not have required Azure labels", cluster.Name)
@@ -48,7 +53,7 @@ func (s *DefaultAzureCapacityService) ValidateCapacityRequirement(
4853
}
4954

5055
// TODO: Replace with actual Azure capacity client call
51-
endpoint := s.endpoint
56+
endpoint := s.ServerAddress
5257
if !strings.HasPrefix(endpoint, "http://") && !strings.HasPrefix(endpoint, "https://") {
5358
endpoint = "http://" + endpoint
5459
}
@@ -58,38 +63,41 @@ func (s *DefaultAzureCapacityService) ValidateCapacityRequirement(
5863
"subscription_id": subID,
5964
"location": location,
6065
"regular_priority_profile": map[string]interface{}{
61-
"capacity_unit_type": 1, // CAPACITY_UNIT_TYPE_VM_INSTANCE_COUNT
62-
"target_capacity": uint32(capacity.Value()),
66+
"capacity_unit_type": 1, // CAPACITY_UNIT_TYPE_VM_INSTANCE_COUNT
67+
"target_capacity": int32(capacity.Value()), // Safe conversion as VM counts won't exceed int32
6368
},
6469
"recommendation_properties": map[string]interface{}{
6570
"restrictions_filter": 4, // RESTRICTIONS_FILTER_QUOTA_AND_OFFER_RESTRICTIONS
6671
},
6772
"resource_properties": map[string]interface{}{},
6873
}
69-
body, err := json.Marshal(payload)
74+
// Convert the payload to JSON
75+
jsonData, err := json.Marshal(payload)
7076
if err != nil {
7177
return false, fmt.Errorf("failed to marshal request payload: %w", err)
7278
}
7379

74-
httpReq, err := http.NewRequestWithContext(ctx, "POST", url, strings.NewReader(string(body)))
80+
// Create a new POST request
81+
httpReq, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
7582
if err != nil {
7683
return false, fmt.Errorf("failed to create HTTP request: %w", err)
7784
}
85+
7886
httpReq.Header.Set("Content-Type", "application/json")
7987
httpReq.Header.Set("Accept", "application/json")
8088
// Add service mesh headers
8189
httpReq.Header.Set("x-service-name", "fleet")
8290
httpReq.Header.Set("x-service-version", "v1")
8391

84-
resp, err := s.client.Do(httpReq)
92+
resp, err := s.Client.Do(httpReq)
8593
if err != nil {
8694
return false, fmt.Errorf("failed to make HTTP request to Azure service: %w", err)
8795
}
8896
defer resp.Body.Close()
8997

9098
bodyBytes, _ := io.ReadAll(resp.Body)
9199
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
92-
return false, fmt.Errorf("Azure service returned status %d: %s", resp.StatusCode, string(bodyBytes))
100+
return false, fmt.Errorf("azure service returned status %d: %s", resp.StatusCode, string(bodyBytes))
93101
}
94102

95103
var respObj struct {
@@ -143,14 +151,14 @@ func extractCapacityRequirements(req placementv1beta1.PropertySelectorRequiremen
143151
}
144152

145153
// Remove prefix and suffix to get the SKU
146-
sku := strings.TrimSuffix(strings.TrimPrefix(req.Name, SkuCapacityPropertyPrefix+"."), "/capacity")
154+
sku := strings.TrimSuffix(strings.TrimPrefix(req.Name, SkuCapacityPropertyPrefix+"/"), "/capacity")
147155
if sku == "" {
148156
return nil, "", fmt.Errorf("cannot extract SKU from property name: %q", req.Name)
149157
}
150158

151159
// Validate that we have exactly one value
152160
if len(req.Values) != 1 {
153-
return nil, "", fmt.Errorf("Azure SKU capacity property must have exactly one value, got %d", len(req.Values))
161+
return nil, "", fmt.Errorf("azure SKU capacity property must have exactly one value, got %d", len(req.Values))
154162
}
155163

156164
// Parse the capacity value

pkg/scheduler/framework/plugins/clusteraffinity/azure/capcacity_test.go

Lines changed: 93 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
package azure
22

33
import (
4-
"encoding/json"
54
"net/http"
65
"net/http/httptest"
76
"strings"
87
"testing"
9-
10-
"k8s.io/apimachinery/pkg/api/resource"
11-
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
8+
"time"
129

1310
clusterv1beta1 "go.goms.io/fleet/apis/cluster/v1beta1"
1411
placementv1beta1 "go.goms.io/fleet/apis/placement/v1beta1"
12+
13+
"k8s.io/apimachinery/pkg/api/resource"
14+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1515
)
1616

1717
func TestExtractAzureInfoFromLabels(t *testing.T) {
@@ -239,31 +239,85 @@ func TestExtractCapacityRequirements(t *testing.T) {
239239
}
240240
}
241241

242-
func TestValidateCapacityRequirement(t *testing.T) {
243-
// Mock response JSON for recommendedVmSizes
244-
mockResponse := map[string]interface{}{
245-
"recommendedVmSizes": map[string]interface{}{
246-
"regularVmSizes": []map[string]interface{}{
247-
{"name": "Standard_D2s_v3"},
248-
{"name": "Standard_A1_v2"},
249-
},
242+
func TestNewAzureCapacityService(t *testing.T) {
243+
tests := []struct {
244+
name string
245+
serverAddress string
246+
client *http.Client
247+
}{
248+
{
249+
name: "with valid client",
250+
serverAddress: "test.example.com",
251+
client: &http.Client{},
252+
},
253+
{
254+
name: "with nil client",
255+
serverAddress: "test.example.com",
256+
client: nil,
250257
},
251258
}
252-
respBytes, _ := json.Marshal(mockResponse)
253259

254-
// Start a test HTTP server
255-
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
256-
w.Header().Set("Content-Type", "application/json")
260+
for _, tt := range tests {
261+
t.Run(tt.name, func(t *testing.T) {
262+
svc := NewAzureCapacityService(tt.serverAddress, tt.client).(*defaultAzureCapacityService)
263+
if svc == nil {
264+
t.Fatal("NewAzureCapacityService returned nil")
265+
}
266+
if svc.Client == nil {
267+
t.Error("client is nil")
268+
}
269+
if svc.ServerAddress != tt.serverAddress {
270+
t.Errorf("serverAddress = %v, want %v", svc.ServerAddress, tt.serverAddress)
271+
}
272+
})
273+
}
274+
}
275+
276+
// Mock the expected response from the Azure API.
277+
var mockAzureResponse = `{
278+
"recommendedVmSizes": {
279+
"regularVmSizes": [
280+
{
281+
"family": "Dsv3",
282+
"name": "Standard_D2s_v3",
283+
"size": "D2"
284+
},
285+
{
286+
"family": "Standard",
287+
"name": "Standard_B1s",
288+
"size": "Standard_B1s"
289+
}
290+
]
291+
}
292+
}`
293+
294+
func TestValidateCapacityRequirement(t *testing.T) {
295+
// Create mock server
296+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
297+
// Verify request method
298+
if r.Method != http.MethodPost {
299+
t.Errorf("got %s, want POST request", r.Method)
300+
}
301+
302+
// Verify headers
303+
if r.Header.Get("Content-Type") != "application/json" {
304+
t.Errorf("got %s, want Content-Type: application/json", r.Header.Get("Content-Type"))
305+
}
306+
if r.Header.Get("Accept") != "application/json" {
307+
t.Errorf("got %s, want Accept: application/json", r.Header.Get("Accept"))
308+
}
309+
310+
// Write mock response
257311
w.WriteHeader(http.StatusOK)
258-
w.Write(respBytes)
312+
if _, err := w.Write([]byte(mockAzureResponse)); err != nil {
313+
t.Fatalf("failed to write response: %v", err)
314+
}
259315
}))
260-
defer ts.Close()
261316

262-
// Create the service pointing to the test server
263-
svc := &DefaultAzureCapacityService{
264-
endpoint: ts.URL,
265-
client: ts.Client(),
266-
}
317+
client := &http.Client{Timeout: 60 * time.Second}
318+
319+
// Step 2: Create the AzureCapacityService with the mock client and server URL
320+
svc := NewAzureCapacityService(server.URL, client)
267321

268322
// Prepare test data
269323
cluster := &clusterv1beta1.MemberCluster{
@@ -279,6 +333,7 @@ func TestValidateCapacityRequirement(t *testing.T) {
279333
name string
280334
cluster *clusterv1beta1.MemberCluster
281335
req placementv1beta1.PropertySelectorRequirement
336+
mockStatusCode int
282337
wantAvailable bool
283338
expectError bool
284339
errorSubstring string
@@ -287,18 +342,21 @@ func TestValidateCapacityRequirement(t *testing.T) {
287342
name: "valid request",
288343
cluster: cluster,
289344
req: placementv1beta1.PropertySelectorRequirement{
290-
Name: "kubernetes.azure.com/vm-size/Standard_D2s_v3/capacity",
291-
Values: []string{"10"},
345+
Name: "kubernetes.azure.com/vm-size/Standard_D2s_v3/capacity",
346+
Operator: placementv1beta1.PropertySelectorGreaterThan,
347+
Values: []string{"10"},
292348
},
293-
wantAvailable: true,
294-
expectError: false,
349+
mockStatusCode: http.StatusOK,
350+
wantAvailable: true,
351+
expectError: false,
295352
},
296353
{
297354
name: "invalid capacity request",
298355
cluster: cluster,
299356
req: placementv1beta1.PropertySelectorRequirement{
300-
Name: "kubernetes.azure.com/vm-size/Standard_D3s_v3/capacity",
301-
Values: []string{"2"},
357+
Name: "kubernetes.azure.com/vm-size/Standard_D3s_v3/capacity",
358+
Operator: placementv1beta1.PropertySelectorGreaterThan,
359+
Values: []string{"2"},
302360
},
303361
wantAvailable: false,
304362
expectError: false,
@@ -313,8 +371,9 @@ func TestValidateCapacityRequirement(t *testing.T) {
313371
},
314372
},
315373
req: placementv1beta1.PropertySelectorRequirement{
316-
Name: "kubernetes.azure.com/vm-size/Standard_D2s_v3/capacity",
317-
Values: []string{"10"},
374+
Name: "kubernetes.azure.com/vm-size/Standard_D2s_v3/capacity",
375+
Operator: placementv1beta1.PropertySelectorGreaterThan,
376+
Values: []string{"10"},
318377
},
319378
expectError: true,
320379
errorSubstring: "does not have required Azure labels",
@@ -323,14 +382,14 @@ func TestValidateCapacityRequirement(t *testing.T) {
323382
name: "invalid capacity property",
324383
cluster: cluster,
325384
req: placementv1beta1.PropertySelectorRequirement{
326-
Name: "kubernetes.azure.com/vm-size/Standard_D2s_v3/usage",
327-
Values: []string{"10"},
385+
Name: "kubernetes.azure.com/vm-size/Standard_D2s_v3/usage",
386+
Operator: placementv1beta1.PropertySelectorGreaterThan,
387+
Values: []string{"10"},
328388
},
329389
expectError: true,
330390
errorSubstring: "invalid Azure SKU capacity property format",
331391
},
332392
}
333-
334393
for _, tt := range tests {
335394
t.Run(tt.name, func(t *testing.T) {
336395
result, err := svc.ValidateCapacityRequirement(tt.cluster, tt.req)

0 commit comments

Comments
 (0)