Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 74 additions & 5 deletions test/e2e/framework/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,18 @@ import (
corev1 "k8s.io/api/core/v1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/agent-sandbox/test/e2e/framework/predicates"
"sigs.k8s.io/controller-runtime/pkg/client"
)

const (
// DefaultTimeout is the default timeout for WaitForObject.
DefaultTimeout = 60 * time.Second
)

// ClusterClient is an abstraction layer for test cases to interact with the cluster.
type ClusterClient struct {
*testing.T
Expand Down Expand Up @@ -112,9 +119,11 @@ func (cl *ClusterClient) ValidateObjectNotFound(ctx context.Context, obj client.
// predicates.
func (cl *ClusterClient) WaitForObject(ctx context.Context, obj client.Object, p ...predicates.ObjectPredicate) error {
cl.Helper()
// Static 30 second timeout, this can be adjusted if needed
timeoutCtx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
var cancel context.CancelFunc
if _, ok := ctx.Deadline(); !ok {
ctx, cancel = context.WithTimeout(ctx, DefaultTimeout)
defer cancel()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: print a message if timeout occurs.

}
start := time.Now()
nn := types.NamespacedName{Name: obj.GetName(), Namespace: obj.GetNamespace()}
defer func() {
Expand All @@ -124,10 +133,11 @@ func (cl *ClusterClient) WaitForObject(ctx context.Context, obj client.Object, p
var validationErr error
for {
select {
case <-timeoutCtx.Done():
case <-ctx.Done():
cl.Logf("Timed out waiting for object %s/%s", obj.GetNamespace(), obj.GetName())
return fmt.Errorf("timed out waiting for object: %w", validationErr)
default:
if validationErr = cl.ValidateObject(timeoutCtx, obj, p...); validationErr == nil {
if validationErr = cl.ValidateObject(ctx, obj, p...); validationErr == nil {
return nil
}
// Simple sleep for fixed duration (basic MVP)
Expand Down Expand Up @@ -254,3 +264,62 @@ func (cl *ClusterClient) PortForward(ctx context.Context, pod types.NamespacedNa
}
return nil
}

var sandboxGVK = schema.GroupVersionKind{
Group: "agents.x-k8s.io",
Version: "v1alpha1",
Kind: "Sandbox",
}

var sandboxWarmpoolGVK = schema.GroupVersionKind{
Group: "extensions.agents.x-k8s.io",
Version: "v1alpha1",
Kind: "SandboxWarmPool",
}

func (cl *ClusterClient) WaitForSandboxReady(ctx context.Context, sandboxID types.NamespacedName) error {
sandbox := &unstructured.Unstructured{}
sandbox.SetGroupVersionKind(sandboxGVK)
sandbox.SetName(sandboxID.Name)
sandbox.SetNamespace(sandboxID.Namespace)
timeoutCtx, cancel := context.WithTimeout(ctx, 2*time.Minute)
defer cancel()
if err := cl.WaitForObject(timeoutCtx, sandbox, predicates.ReadyConditionIsTrue); err != nil {
cl.Logf("waiting for sandbox to be ready: %v", err)
return err
}
return nil
}

func (cl *ClusterClient) WaitForWarmPoolReady(ctx context.Context, sandboxWarmpoolID types.NamespacedName) error {
cl.Helper()
cl.Logf("Waiting for SandboxWarmPool Pods to be ready: warmpoolID - %s", sandboxWarmpoolID)

warmpool := &unstructured.Unstructured{}
warmpool.SetGroupVersionKind(sandboxWarmpoolGVK)
if err := cl.client.Get(ctx, sandboxWarmpoolID, warmpool); err != nil {
cl.T.Fatalf("Failed to get SandboxWarmPool %s: %v", sandboxWarmpoolID, err)
return err
}

if err := cl.WaitForObject(ctx, warmpool, predicates.ReadyReplicasConditionIsTrue); err != nil {
cl.T.Fatalf("waiting for warmpool to be ready: %v", err)
return err
}
return nil

}

// GetSandbox returns the Sandbox object from the cluster.
func (cl *ClusterClient) GetSandbox(ctx context.Context, sandboxID types.NamespacedName) (*unstructured.Unstructured, error) {
sandbox := &unstructured.Unstructured{}
sandbox.SetGroupVersionKind(sandboxGVK)
sandbox.SetName(sandboxID.Name)
sandbox.SetNamespace(sandboxID.Namespace)

if err := cl.client.Get(ctx, sandboxID, sandbox); err != nil {
cl.Logf("failed to get Sandbox %s: %v", sandboxID, err)
return nil, err
}
return sandbox, nil
}
2 changes: 2 additions & 0 deletions test/e2e/framework/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/client-go/tools/clientcmd"
"sigs.k8s.io/agent-sandbox/controllers"
extensionsv1alpha1 "sigs.k8s.io/agent-sandbox/extensions/api/v1alpha1"
"sigs.k8s.io/controller-runtime/pkg/client"
)

Expand All @@ -37,6 +38,7 @@ var (

func init() {
utilruntime.Must(apiextensionsv1.AddToScheme(controllers.Scheme))
utilruntime.Must(extensionsv1alpha1.AddToScheme(controllers.Scheme))
}

// TestContext is a helper for managing e2e test scaffolding.
Expand Down
23 changes: 22 additions & 1 deletion test/e2e/framework/predicates/conditions.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,12 @@ import (
// objectWithStatus is a simplified struct to parse the status of a resource.
type objectWithStatus struct {
Status struct {
Conditions []metav1.Condition `json:"conditions,omitempty"`
Conditions []metav1.Condition `json:"conditions,omitempty"`
ReadyReplicas int `json:"readyReplicas,omitempty"`
} `json:"status"`
Spec struct {
Replicas int `json:"replicas,omitempty"`
} `json:"spec"`
}

// ReadyConditionIsTrue checks if the given object has a Ready condition set to True.
Expand Down Expand Up @@ -62,3 +66,20 @@ func asUnstructured(obj client.Object) (*unstructured.Unstructured, error) {
}
return &unstructured.Unstructured{Object: m}, nil
}

// ReadyReplicasConditionIsTrue checks if the given object has more than 0 replicas.
func ReadyReplicasConditionIsTrue(obj client.Object) error {
u, err := asUnstructured(obj)
if err != nil {
return fmt.Errorf("failed to convert to unstructured: %w", err)
}

var status objectWithStatus
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &status); err != nil {
return fmt.Errorf("failed to convert to objectWithStatus: %v", err)
}
if status.Status.ReadyReplicas == status.Spec.Replicas {
return nil
}
return fmt.Errorf("Object has %d ready replicas and the required replicas are %d", status.Status.ReadyReplicas, status.Spec.Replicas)
}
Loading