Skip to content

Commit 84ed9ec

Browse files
committed
feat: Update sandbox status with phase
Adds a 'Phase' field to the 'SandboxStatus' to provide a more explicit status representation of the sandbox. The phase can be one of 'Pending', 'Running', 'Paused', 'Terminating', or 'Failed'. This addresses issue #119.
1 parent e568f6c commit 84ed9ec

File tree

7 files changed

+101
-3
lines changed

7 files changed

+101
-3
lines changed

api/v1alpha1/sandbox_types.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,22 @@ import (
1919
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2020
)
2121

22+
// SandboxPhase is a simple, high-level summary of where the Sandbox is in its lifecycle.
23+
type SandboxPhase string
24+
25+
const (
26+
// SandboxPhasePending means the sandbox is being created
27+
SandboxPhasePending SandboxPhase = "Pending"
28+
// SandboxPhaseRunning means the sandbox is running
29+
SandboxPhaseRunning SandboxPhase = "Running"
30+
// SandboxPhasePaused means the sandbox is paused
31+
SandboxPhasePaused SandboxPhase = "Paused"
32+
// SandboxPhaseTerminating means the sandbox is terminating
33+
SandboxPhaseTerminating SandboxPhase = "Terminating"
34+
// SandboxPhaseFailed means the sandbox has failed
35+
SandboxPhaseFailed SandboxPhase = "Failed"
36+
)
37+
2238
// ConditionType is a type of condition for a resource.
2339
type ConditionType string
2440

@@ -121,6 +137,23 @@ type SandboxSpec struct {
121137

122138
// SandboxStatus defines the observed state of Sandbox.
123139
type SandboxStatus struct {
140+
// The phase of a Sandbox is a simple, high-level summary of where the Sandbox is in its lifecycle.
141+
// The conditions array, the reason and message fields, and the individual container status arrays are
142+
// more detail about the pod's status.
143+
// There are five possible phase values:
144+
//
145+
// Pending: The Sandbox has been accepted by the Kubernetes system, but one or more of the Pod
146+
// startup steps is not yet complete.
147+
// Running: The Sandbox has been bound to a node, and all of the Pods have been created. At least
148+
// one Pod is still running, or is in the process of starting or restarting.
149+
// Paused: The Sandbox has been paused.
150+
// Failed: All Pods in the Sandbox have terminated, and at least one Pod has terminated in a failure
151+
// (exited with a non-zero exit code or was terminated by the system).
152+
// Terminating: The Sandbox is terminating.
153+
//
154+
// +optional
155+
Phase SandboxPhase `json:"phase,omitempty"`
156+
124157
// FQDN that is valid for default cluster settings
125158
// Limitation: Hardcoded to the domain .cluster.local
126159
// e.g. sandbox-example.default.svc.cluster.local

controllers/sandbox_controller.go

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,9 +95,15 @@ func (r *SandboxReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
9595

9696
if !sandbox.ObjectMeta.DeletionTimestamp.IsZero() {
9797
log.Info("Sandbox is being deleted")
98+
//sandbox.Status.Phase = sandboxv1alpha1.SandboxPhaseTerminating
9899
return ctrl.Result{}, nil
99100
}
100101

102+
// Set a default phase
103+
if sandbox.Status.Phase == "" {
104+
sandbox.Status.Phase = sandboxv1alpha1.SandboxPhasePending
105+
}
106+
101107
oldStatus := sandbox.Status.DeepCopy()
102108
var err error
103109

@@ -137,9 +143,20 @@ func (r *SandboxReconciler) reconcileChildResources(ctx context.Context, sandbox
137143
if pod == nil {
138144
sandbox.Status.Replicas = 0
139145
sandbox.Status.LabelSelector = ""
146+
if sandbox.Spec.Replicas != nil && *sandbox.Spec.Replicas == 0 {
147+
sandbox.Status.Phase = sandboxv1alpha1.SandboxPhasePaused
148+
}
140149
} else {
141150
sandbox.Status.Replicas = 1
142151
sandbox.Status.LabelSelector = fmt.Sprintf("%s=%s", sandboxLabel, NameHash(sandbox.Name))
152+
switch pod.Status.Phase {
153+
case corev1.PodRunning:
154+
sandbox.Status.Phase = sandboxv1alpha1.SandboxPhaseRunning
155+
case corev1.PodPending:
156+
sandbox.Status.Phase = sandboxv1alpha1.SandboxPhasePending
157+
case corev1.PodFailed:
158+
sandbox.Status.Phase = sandboxv1alpha1.SandboxPhaseFailed
159+
}
143160
}
144161

145162
// Reconcile Service
@@ -186,9 +203,9 @@ func (r *SandboxReconciler) computeReadyCondition(sandbox *sandboxv1alpha1.Sandb
186203
}
187204
}
188205
} else {
189-
if sandbox.Spec.Replicas != nil && *sandbox.Spec.Replicas == 0 {
190-
message = "Pod does not exist, replicas is 0"
206+
if sandbox.Status.Phase == sandboxv1alpha1.SandboxPhasePaused {
191207
// This is intended behaviour. So marking it ready.
208+
message = "Sandbox is paused"
192209
podReady = true
193210
} else {
194211
message = "Pod does not exist"
@@ -431,6 +448,7 @@ func (r *SandboxReconciler) handleSandboxExpiry(ctx context.Context, sandbox *sa
431448
Message: "Sandbox has expired",
432449
})
433450

451+
sandbox.Status.Phase = sandboxv1alpha1.SandboxPhaseTerminating
434452
sandbox.Status.Replicas = 0
435453
sandbox.Status.LabelSelector = ""
436454

controllers/sandbox_controller_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,22 @@ func TestComputeReadyCondition(t *testing.T) {
175175
expectedStatus: metav1.ConditionFalse,
176176
expectedReason: "DependenciesNotReady",
177177
},
178+
{
179+
name: "sandbox paused",
180+
sandbox: &sandboxv1alpha1.Sandbox{
181+
ObjectMeta: metav1.ObjectMeta{
182+
Generation: 1,
183+
},
184+
Status: sandboxv1alpha1.SandboxStatus{
185+
Phase: sandboxv1alpha1.SandboxPhasePaused,
186+
},
187+
},
188+
err: nil,
189+
svc: &corev1.Service{},
190+
pod: nil,
191+
expectedStatus: metav1.ConditionTrue,
192+
expectedReason: "DependenciesReady",
193+
},
178194
{
179195
name: "all not ready",
180196
sandbox: &sandboxv1alpha1.Sandbox{
@@ -227,6 +243,7 @@ func TestReconcile(t *testing.T) {
227243
},
228244
// Verify Sandbox status
229245
wantStatus: sandboxv1alpha1.SandboxStatus{
246+
Phase: sandboxv1alpha1.SandboxPhasePending,
230247
Service: sandboxName,
231248
ServiceFQDN: "sandbox-name.sandbox-ns.svc.cluster.local",
232249
Replicas: 1,
@@ -320,6 +337,7 @@ func TestReconcile(t *testing.T) {
320337
},
321338
// Verify Sandbox status
322339
wantStatus: sandboxv1alpha1.SandboxStatus{
340+
Phase: sandboxv1alpha1.SandboxPhasePending,
323341
Service: sandboxName,
324342
ServiceFQDN: "sandbox-name.sandbox-ns.svc.cluster.local",
325343
Replicas: 1,
@@ -406,6 +424,28 @@ func TestReconcile(t *testing.T) {
406424
},
407425
},
408426
},
427+
{
428+
name: "paused sandbox",
429+
sandboxSpec: sandboxv1alpha1.SandboxSpec{
430+
Replicas: ptr.To(int32(0)),
431+
},
432+
// Verify Sandbox status
433+
wantStatus: sandboxv1alpha1.SandboxStatus{
434+
Phase: sandboxv1alpha1.SandboxPhasePaused,
435+
Replicas: 0,
436+
ServiceFQDN: "sandbox-name.sandbox-ns.svc.cluster.local",
437+
Service: "sandbox-name",
438+
Conditions: []metav1.Condition{
439+
{
440+
Type: "Ready",
441+
Status: "True",
442+
ObservedGeneration: 1,
443+
Reason: "DependenciesReady",
444+
Message: "Sandbox is paused; Service Exists",
445+
},
446+
},
447+
},
448+
},
409449
}
410450

411451
for _, tc := range testCases {

k8s/crds/agents.x-k8s.io_sandboxes.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3971,6 +3971,8 @@ spec:
39713971
- type
39723972
type: object
39733973
type: array
3974+
phase:
3975+
type: string
39743976
replicas:
39753977
format: int32
39763978
type: integer

test/e2e/basic_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ func TestSimpleSandbox(t *testing.T) {
7676
// Assert Sandbox object status reconciles as expected
7777
p := []predicates.ObjectPredicate{
7878
predicates.SandboxHasStatus(sandboxv1alpha1.SandboxStatus{
79+
Phase: "Running",
7980
Service: "my-sandbox",
8081
ServiceFQDN: "my-sandbox.my-sandbox-ns.svc.cluster.local",
8182
Replicas: 1,

test/e2e/replicas_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ func TestSandboxReplicas(t *testing.T) {
4242
// Assert Sandbox object status reconciles as expected
4343
p := []predicates.ObjectPredicate{
4444
predicates.SandboxHasStatus(sandboxv1alpha1.SandboxStatus{
45+
Phase: "Running",
4546
Service: "my-sandbox",
4647
ServiceFQDN: "my-sandbox.my-sandbox-ns.svc.cluster.local",
4748
Replicas: 1,
@@ -74,13 +75,14 @@ func TestSandboxReplicas(t *testing.T) {
7475
// Wait for sandbox status to reflect new state
7576
p = []predicates.ObjectPredicate{
7677
predicates.SandboxHasStatus(sandboxv1alpha1.SandboxStatus{
78+
Phase: "Paused",
7779
Service: "my-sandbox",
7880
ServiceFQDN: "my-sandbox.my-sandbox-ns.svc.cluster.local",
7981
Replicas: 0,
8082
LabelSelector: "",
8183
Conditions: []metav1.Condition{
8284
{
83-
Message: "Pod does not exist, replicas is 0; Service Exists",
85+
Message: "Sandbox is paused; Service Exists",
8486
ObservedGeneration: 2,
8587
Reason: "DependenciesReady",
8688
Status: "True",

test/e2e/shutdown_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ func TestSandboxShutdownTime(t *testing.T) {
4141
// Assert Sandbox object status reconciles as expected
4242
p := []predicates.ObjectPredicate{
4343
predicates.SandboxHasStatus(sandboxv1alpha1.SandboxStatus{
44+
Phase: "Running",
4445
Service: "my-sandbox",
4546
ServiceFQDN: "my-sandbox.my-sandbox-ns.svc.cluster.local",
4647
Replicas: 1,
@@ -75,6 +76,7 @@ func TestSandboxShutdownTime(t *testing.T) {
7576
p = []predicates.ObjectPredicate{
7677
predicates.SandboxHasStatus(sandboxv1alpha1.SandboxStatus{
7778
// TODO: should Service/ServiceFQDN be cleared from status when the Service is deleted?
79+
Phase: "Terminating",
7880
Service: "my-sandbox",
7981
ServiceFQDN: "my-sandbox.my-sandbox-ns.svc.cluster.local",
8082
Replicas: 0,

0 commit comments

Comments
 (0)