Skip to content

Commit 924d83f

Browse files
Copilotrakechill
andauthored
Add nil checks for vm.Properties.TimeCreated with fallback to preserve GC grace period (#1390)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: rakechill <30397998+rakechill@users.noreply.github.com> Co-authored-by: Rachel Gregory <rgregory@microsoft.com>
1 parent e94709f commit 924d83f

File tree

2 files changed

+82
-1
lines changed

2 files changed

+82
-1
lines changed

pkg/cloudprovider/cloudprovider.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -532,7 +532,18 @@ func (c *CloudProvider) vmInstanceToNodeClaim(ctx context.Context, vm *armcomput
532532
nodeClaim.Name = GetNodeClaimNameFromVMName(*vm.Name)
533533
nodeClaim.Labels = labels
534534
nodeClaim.Annotations = annotations
535-
nodeClaim.CreationTimestamp = metav1.Time{Time: *vm.Properties.TimeCreated}
535+
if vm.Properties != nil && vm.Properties.TimeCreated != nil {
536+
nodeClaim.CreationTimestamp = metav1.Time{Time: *vm.Properties.TimeCreated}
537+
} else {
538+
// Fallback to current time to ensure garbage collection grace period is enforced
539+
// when TimeCreated is unavailable. Without this, CreationTimestamp would be epoch (zero value)
540+
// and the instance could be immediately garbage collected, bypassing the 5-minute grace period.
541+
// TODO: Investigate a more fail-safe approach. If vm.Properties.TimeCreated is NEVER populated,
542+
// this fallback means the VM will never be garbage collected since we call this helper every time
543+
// we create an in-memory NodeClaim. We currently assume this shouldn't happen because VMs that fail
544+
// to come up should eventually stop appearing in Azure API responses.
545+
nodeClaim.CreationTimestamp = metav1.Time{Time: time.Now()}
546+
}
536547
// Set the deletionTimestamp to be the current time if the instance is currently terminating
537548
if utils.IsVMDeleting(*vm) {
538549
nodeClaim.DeletionTimestamp = &metav1.Time{Time: time.Now()}

pkg/cloudprovider/cloudprovider_test.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,14 @@ limitations under the License.
1717
package cloudprovider
1818

1919
import (
20+
"context"
2021
"testing"
22+
"time"
2123

24+
"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
25+
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v7"
2226
. "github.com/onsi/gomega"
27+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2328
)
2429

2530
func TestGenerateNodeClaimName(t *testing.T) {
@@ -54,3 +59,68 @@ func TestGenerateNodeClaimName(t *testing.T) {
5459
})
5560
}
5661
}
62+
63+
func TestVmInstanceToNodeClaim_NilProperties(t *testing.T) {
64+
tests := []struct {
65+
name string
66+
vm *armcompute.VirtualMachine
67+
expectFallbackToNow bool
68+
expectExactTime *time.Time
69+
}{
70+
{
71+
name: "nil Properties - fallback to time.Now()",
72+
vm: &armcompute.VirtualMachine{
73+
Name: to.Ptr("aks-test-vm"),
74+
ID: to.Ptr("/subscriptions/sub/resourceGroups/rg/providers/Microsoft.Compute/virtualMachines/aks-test-vm"),
75+
},
76+
expectFallbackToNow: true,
77+
},
78+
{
79+
name: "nil TimeCreated - fallback to time.Now()",
80+
vm: &armcompute.VirtualMachine{
81+
Name: to.Ptr("aks-test-vm"),
82+
ID: to.Ptr("/subscriptions/sub/resourceGroups/rg/providers/Microsoft.Compute/virtualMachines/aks-test-vm"),
83+
Properties: &armcompute.VirtualMachineProperties{},
84+
},
85+
expectFallbackToNow: true,
86+
},
87+
{
88+
name: "valid TimeCreated - use exact time",
89+
vm: &armcompute.VirtualMachine{
90+
Name: to.Ptr("aks-test-vm"),
91+
ID: to.Ptr("/subscriptions/sub/resourceGroups/rg/providers/Microsoft.Compute/virtualMachines/aks-test-vm"),
92+
Properties: &armcompute.VirtualMachineProperties{
93+
TimeCreated: to.Ptr(time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)),
94+
},
95+
},
96+
expectExactTime: to.Ptr(time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)),
97+
},
98+
}
99+
100+
for _, tt := range tests {
101+
t.Run(tt.name, func(t *testing.T) {
102+
g := NewWithT(t)
103+
ctx := context.Background()
104+
105+
cp := &CloudProvider{}
106+
before := time.Now()
107+
nodeClaim, err := cp.vmInstanceToNodeClaim(ctx, tt.vm, nil)
108+
after := time.Now()
109+
110+
g.Expect(err).ToNot(HaveOccurred())
111+
g.Expect(nodeClaim).ToNot(BeNil())
112+
g.Expect(nodeClaim.CreationTimestamp).ToNot(Equal(metav1.Time{}))
113+
114+
if tt.expectFallbackToNow {
115+
// When TimeCreated is unavailable, should fallback to time.Now() for GC safety
116+
g.Expect(nodeClaim.CreationTimestamp.Time).To(BeTemporally(">=", before))
117+
g.Expect(nodeClaim.CreationTimestamp.Time).To(BeTemporally("<=", after))
118+
}
119+
120+
if tt.expectExactTime != nil {
121+
// When TimeCreated is available, should use the exact time from VM
122+
g.Expect(nodeClaim.CreationTimestamp.Time).To(Equal(*tt.expectExactTime))
123+
}
124+
})
125+
}
126+
}

0 commit comments

Comments
 (0)