mirror of
https://github.com/actions-runner-controller/actions-runner-controller.git
synced 2026-10-17 19:09:11 +02:00
wip
This commit is contained in:
@@ -0,0 +1,957 @@
|
||||
/*
|
||||
Copyright 2020 The actions-runner-controller authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package actionsgithubcom
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
actionsv1alpha1 "github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1"
|
||||
"github.com/actions/actions-runner-controller/controllers/actions.github.com/secretresolver"
|
||||
"github.com/go-logr/logr"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
logf "sigs.k8s.io/controller-runtime/pkg/log"
|
||||
)
|
||||
|
||||
// BenchmarkActionsGithub_Reconcile_AutoscalingListener_FinalizerAdd benchmarks adding finalizer to AutoscalingListener
|
||||
func BenchmarkActionsGithub_Reconcile_AutoscalingListener_FinalizerAdd(b *testing.B) {
|
||||
scheme := runtime.NewScheme()
|
||||
_ = clientgoscheme.AddToScheme(scheme)
|
||||
_ = actionsv1alpha1.AddToScheme(scheme)
|
||||
|
||||
namespace := "test-ns"
|
||||
listenerName := "test-listener"
|
||||
arsName := "test-ars"
|
||||
|
||||
// Create AutoscalingRunnerSet (required dependency)
|
||||
ars := NewMinimalAutoscalingRunnerSet(namespace, arsName)
|
||||
|
||||
// Create listener without finalizer
|
||||
listener := NewMinimalAutoscalingListener(namespace, listenerName)
|
||||
listener.Spec.AutoscalingRunnerSetNamespace = namespace
|
||||
listener.Spec.AutoscalingRunnerSetName = arsName
|
||||
listener.Finalizers = nil
|
||||
|
||||
// Create secret for listener configuration
|
||||
secret := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-secret",
|
||||
Namespace: namespace,
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"github_token": []byte("fake-token"),
|
||||
},
|
||||
}
|
||||
listener.Spec.GitHubConfigSecret = secret.Name
|
||||
|
||||
fakeClient := fake.NewClientBuilder().
|
||||
WithScheme(scheme).
|
||||
WithObjects(ars, listener, secret).
|
||||
WithStatusSubresource(&actionsv1alpha1.AutoscalingListener{}).
|
||||
Build()
|
||||
|
||||
secretResolver := secretresolver.New(fakeClient, NewFakeMultiClient())
|
||||
rb := ResourceBuilder{SecretResolver: secretResolver}
|
||||
|
||||
reconciler := &AutoscalingListenerReconciler{
|
||||
Client: fakeClient,
|
||||
Scheme: scheme,
|
||||
Log: logf.Log.WithName("benchmark"),
|
||||
ResourceBuilder: rb,
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
req := ctrl.Request{
|
||||
NamespacedName: client.ObjectKey{
|
||||
Namespace: namespace,
|
||||
Name: listenerName,
|
||||
},
|
||||
}
|
||||
|
||||
// Warmup
|
||||
WarmupIteration(b, func() {
|
||||
_, _ = reconciler.Reconcile(ctx, req)
|
||||
})
|
||||
|
||||
for b.Loop() {
|
||||
// Reset listener state (remove finalizer for each iteration)
|
||||
updatedListener := &actionsv1alpha1.AutoscalingListener{}
|
||||
_ = fakeClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: listenerName}, updatedListener)
|
||||
updatedListener.Finalizers = nil
|
||||
_ = fakeClient.Update(ctx, updatedListener)
|
||||
|
||||
_, _ = reconciler.Reconcile(ctx, req)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkActionsGithub_Reconcile_AutoscalingListener_FinalizerRemove benchmarks removing finalizer during deletion
|
||||
func BenchmarkActionsGithub_Reconcile_AutoscalingListener_FinalizerRemove(b *testing.B) {
|
||||
scheme := runtime.NewScheme()
|
||||
_ = clientgoscheme.AddToScheme(scheme)
|
||||
_ = actionsv1alpha1.AddToScheme(scheme)
|
||||
|
||||
namespace := "test-ns"
|
||||
listenerName := "test-listener"
|
||||
arsName := "test-ars"
|
||||
|
||||
// Create AutoscalingRunnerSet
|
||||
ars := NewMinimalAutoscalingRunnerSet(namespace, arsName)
|
||||
|
||||
// Create listener with finalizer and deletion timestamp
|
||||
listener := NewMinimalAutoscalingListener(namespace, listenerName)
|
||||
listener.Spec.AutoscalingRunnerSetNamespace = namespace
|
||||
listener.Spec.AutoscalingRunnerSetName = arsName
|
||||
listener.Finalizers = []string{autoscalingListenerFinalizerName}
|
||||
now := metav1.Now()
|
||||
listener.DeletionTimestamp = &now
|
||||
|
||||
// Create secret
|
||||
secret := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-secret",
|
||||
Namespace: namespace,
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"github_token": []byte("fake-token"),
|
||||
},
|
||||
}
|
||||
listener.Spec.GitHubConfigSecret = secret.Name
|
||||
|
||||
fakeClient := fake.NewClientBuilder().
|
||||
WithScheme(scheme).
|
||||
WithObjects(ars, listener, secret).
|
||||
WithStatusSubresource(&actionsv1alpha1.AutoscalingListener{}).
|
||||
Build()
|
||||
|
||||
secretResolver := secretresolver.New(fakeClient, NewFakeMultiClient())
|
||||
rb := ResourceBuilder{SecretResolver: secretResolver}
|
||||
|
||||
reconciler := &AutoscalingListenerReconciler{
|
||||
Client: fakeClient,
|
||||
Scheme: scheme,
|
||||
Log: logf.Log.WithName("benchmark"),
|
||||
ResourceBuilder: rb,
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
req := ctrl.Request{
|
||||
NamespacedName: client.ObjectKey{
|
||||
Namespace: namespace,
|
||||
Name: listenerName,
|
||||
},
|
||||
}
|
||||
|
||||
// Warmup
|
||||
WarmupIteration(b, func() {
|
||||
_, _ = reconciler.Reconcile(ctx, req)
|
||||
})
|
||||
|
||||
for b.Loop() {
|
||||
// Reset listener with finalizer
|
||||
updatedListener := &actionsv1alpha1.AutoscalingListener{}
|
||||
if err := fakeClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: listenerName}, updatedListener); err == nil {
|
||||
updatedListener.Finalizers = []string{autoscalingListenerFinalizerName}
|
||||
_ = fakeClient.Update(ctx, updatedListener)
|
||||
}
|
||||
|
||||
_, _ = reconciler.Reconcile(ctx, req)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkActionsGithub_Reconcile_AutoscalingListener_MissingARSDeletion benchmarks deleting listener when AutoscalingRunnerSet is missing
|
||||
func BenchmarkActionsGithub_Reconcile_AutoscalingListener_MissingARSDeletion(b *testing.B) {
|
||||
scheme := runtime.NewScheme()
|
||||
_ = clientgoscheme.AddToScheme(scheme)
|
||||
_ = actionsv1alpha1.AddToScheme(scheme)
|
||||
|
||||
namespace := "test-ns"
|
||||
listenerName := "test-listener"
|
||||
arsName := "test-ars"
|
||||
|
||||
// Create listener with finalizer but NO AutoscalingRunnerSet
|
||||
listener := NewMinimalAutoscalingListener(namespace, listenerName)
|
||||
listener.Spec.AutoscalingRunnerSetNamespace = namespace
|
||||
listener.Spec.AutoscalingRunnerSetName = arsName
|
||||
listener.Finalizers = []string{autoscalingListenerFinalizerName}
|
||||
|
||||
// Create secret
|
||||
secret := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-secret",
|
||||
Namespace: namespace,
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"github_token": []byte("fake-token"),
|
||||
},
|
||||
}
|
||||
listener.Spec.GitHubConfigSecret = secret.Name
|
||||
|
||||
fakeClient := fake.NewClientBuilder().
|
||||
WithScheme(scheme).
|
||||
WithObjects(listener, secret).
|
||||
WithStatusSubresource(&actionsv1alpha1.AutoscalingListener{}).
|
||||
Build()
|
||||
|
||||
secretResolver := secretresolver.New(fakeClient, NewFakeMultiClient())
|
||||
rb := ResourceBuilder{SecretResolver: secretResolver}
|
||||
|
||||
reconciler := &AutoscalingListenerReconciler{
|
||||
Client: fakeClient,
|
||||
Scheme: scheme,
|
||||
Log: logf.Log.WithName("benchmark"),
|
||||
ResourceBuilder: rb,
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
req := ctrl.Request{
|
||||
NamespacedName: client.ObjectKey{
|
||||
Namespace: namespace,
|
||||
Name: listenerName,
|
||||
},
|
||||
}
|
||||
|
||||
// Warmup
|
||||
WarmupIteration(b, func() {
|
||||
_, _ = reconciler.Reconcile(ctx, req)
|
||||
})
|
||||
|
||||
for b.Loop() {
|
||||
// Recreate listener for each iteration
|
||||
_ = fakeClient.Delete(ctx, listener)
|
||||
newListener := NewMinimalAutoscalingListener(namespace, listenerName)
|
||||
newListener.Spec.AutoscalingRunnerSetNamespace = namespace
|
||||
newListener.Spec.AutoscalingRunnerSetName = arsName
|
||||
newListener.Spec.GitHubConfigSecret = secret.Name
|
||||
newListener.Finalizers = []string{autoscalingListenerFinalizerName}
|
||||
_ = fakeClient.Create(ctx, newListener)
|
||||
|
||||
_, _ = reconciler.Reconcile(ctx, req)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkActionsGithub_Reconcile_AutoscalingListener_CreateServiceAccount benchmarks creating ServiceAccount
|
||||
func BenchmarkActionsGithub_Reconcile_AutoscalingListener_CreateServiceAccount(b *testing.B) {
|
||||
scheme := runtime.NewScheme()
|
||||
_ = clientgoscheme.AddToScheme(scheme)
|
||||
_ = actionsv1alpha1.AddToScheme(scheme)
|
||||
|
||||
namespace := "test-ns"
|
||||
listenerName := "test-listener"
|
||||
arsName := "test-ars"
|
||||
|
||||
// Create AutoscalingRunnerSet
|
||||
ars := NewMinimalAutoscalingRunnerSet(namespace, arsName)
|
||||
|
||||
// Create listener with finalizer (already added)
|
||||
listener := NewMinimalAutoscalingListener(namespace, listenerName)
|
||||
listener.Spec.AutoscalingRunnerSetNamespace = namespace
|
||||
listener.Spec.AutoscalingRunnerSetName = arsName
|
||||
listener.Finalizers = []string{autoscalingListenerFinalizerName}
|
||||
|
||||
// Create secret
|
||||
secret := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-secret",
|
||||
Namespace: namespace,
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"github_token": []byte("fake-token"),
|
||||
},
|
||||
}
|
||||
listener.Spec.GitHubConfigSecret = secret.Name
|
||||
|
||||
fakeClient := fake.NewClientBuilder().
|
||||
WithScheme(scheme).
|
||||
WithObjects(ars, listener, secret).
|
||||
WithStatusSubresource(&actionsv1alpha1.AutoscalingListener{}).
|
||||
Build()
|
||||
|
||||
secretResolver := secretresolver.New(fakeClient, NewFakeMultiClient())
|
||||
rb := ResourceBuilder{SecretResolver: secretResolver}
|
||||
|
||||
reconciler := &AutoscalingListenerReconciler{
|
||||
Client: fakeClient,
|
||||
Scheme: scheme,
|
||||
Log: logf.Log.WithName("benchmark"),
|
||||
ResourceBuilder: rb,
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
req := ctrl.Request{
|
||||
NamespacedName: client.ObjectKey{
|
||||
Namespace: namespace,
|
||||
Name: listenerName,
|
||||
},
|
||||
}
|
||||
|
||||
// Warmup
|
||||
WarmupIteration(b, func() {
|
||||
_, _ = reconciler.Reconcile(ctx, req)
|
||||
})
|
||||
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
// Delete ServiceAccount to trigger creation path
|
||||
sa := &corev1.ServiceAccount{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: listenerName,
|
||||
Namespace: namespace,
|
||||
},
|
||||
}
|
||||
_ = fakeClient.Delete(ctx, sa)
|
||||
|
||||
_, _ = reconciler.Reconcile(ctx, req)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkActionsGithub_Reconcile_AutoscalingListener_UpdateServiceAccount benchmarks updating ServiceAccount
|
||||
func BenchmarkActionsGithub_Reconcile_AutoscalingListener_UpdateServiceAccount(b *testing.B) {
|
||||
scheme := runtime.NewScheme()
|
||||
_ = clientgoscheme.AddToScheme(scheme)
|
||||
_ = actionsv1alpha1.AddToScheme(scheme)
|
||||
|
||||
namespace := "test-ns"
|
||||
listenerName := "test-listener"
|
||||
arsName := "test-ars"
|
||||
|
||||
// Create AutoscalingRunnerSet
|
||||
ars := NewMinimalAutoscalingRunnerSet(namespace, arsName)
|
||||
|
||||
// Create listener with finalizer
|
||||
listener := NewMinimalAutoscalingListener(namespace, listenerName)
|
||||
listener.Spec.AutoscalingRunnerSetNamespace = namespace
|
||||
listener.Spec.AutoscalingRunnerSetName = arsName
|
||||
listener.Finalizers = []string{autoscalingListenerFinalizerName}
|
||||
|
||||
// Create ServiceAccount with outdated labels
|
||||
sa := &corev1.ServiceAccount{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: listenerName,
|
||||
Namespace: namespace,
|
||||
Labels: map[string]string{
|
||||
"outdated-label": "old-value",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Create secret
|
||||
secret := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-secret",
|
||||
Namespace: namespace,
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"github_token": []byte("fake-token"),
|
||||
},
|
||||
}
|
||||
listener.Spec.GitHubConfigSecret = secret.Name
|
||||
|
||||
fakeClient := fake.NewClientBuilder().
|
||||
WithScheme(scheme).
|
||||
WithObjects(ars, listener, sa, secret).
|
||||
WithStatusSubresource(&actionsv1alpha1.AutoscalingListener{}).
|
||||
Build()
|
||||
|
||||
secretResolver := secretresolver.New(fakeClient, NewFakeMultiClient())
|
||||
rb := ResourceBuilder{SecretResolver: secretResolver}
|
||||
|
||||
reconciler := &AutoscalingListenerReconciler{
|
||||
Client: fakeClient,
|
||||
Scheme: scheme,
|
||||
Log: logr.Discard(),
|
||||
ResourceBuilder: rb,
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
req := ctrl.Request{
|
||||
NamespacedName: client.ObjectKey{
|
||||
Namespace: namespace,
|
||||
Name: listenerName,
|
||||
},
|
||||
}
|
||||
|
||||
// Warmup
|
||||
WarmupIteration(b, func() {
|
||||
_, _ = reconciler.Reconcile(ctx, req)
|
||||
})
|
||||
|
||||
for b.Loop() {
|
||||
// Reset ServiceAccount labels to trigger update
|
||||
updatedSA := &corev1.ServiceAccount{}
|
||||
_ = fakeClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: listenerName}, updatedSA)
|
||||
updatedSA.Labels = map[string]string{"outdated-label": "old-value"}
|
||||
_ = fakeClient.Update(ctx, updatedSA)
|
||||
|
||||
_, _ = reconciler.Reconcile(ctx, req)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkActionsGithub_Reconcile_AutoscalingListener_CreateRole benchmarks creating Role
|
||||
func BenchmarkActionsGithub_Reconcile_AutoscalingListener_CreateRole(b *testing.B) {
|
||||
scheme := runtime.NewScheme()
|
||||
_ = clientgoscheme.AddToScheme(scheme)
|
||||
_ = actionsv1alpha1.AddToScheme(scheme)
|
||||
|
||||
namespace := "test-ns"
|
||||
listenerName := "test-listener"
|
||||
arsName := "test-ars"
|
||||
|
||||
// Create AutoscalingRunnerSet
|
||||
ars := NewMinimalAutoscalingRunnerSet(namespace, arsName)
|
||||
|
||||
// Create listener with finalizer
|
||||
listener := NewMinimalAutoscalingListener(namespace, listenerName)
|
||||
listener.Spec.AutoscalingRunnerSetNamespace = namespace
|
||||
listener.Spec.AutoscalingRunnerSetName = arsName
|
||||
listener.Finalizers = []string{autoscalingListenerFinalizerName}
|
||||
|
||||
// Create ServiceAccount (prerequisite)
|
||||
sa := &corev1.ServiceAccount{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: listenerName,
|
||||
Namespace: namespace,
|
||||
},
|
||||
}
|
||||
|
||||
// Create secret
|
||||
secret := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-secret",
|
||||
Namespace: namespace,
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"github_token": []byte("fake-token"),
|
||||
},
|
||||
}
|
||||
listener.Spec.GitHubConfigSecret = secret.Name
|
||||
|
||||
fakeClient := fake.NewClientBuilder().
|
||||
WithScheme(scheme).
|
||||
WithObjects(ars, listener, sa, secret).
|
||||
WithStatusSubresource(&actionsv1alpha1.AutoscalingListener{}).
|
||||
Build()
|
||||
|
||||
secretResolver := secretresolver.New(fakeClient, NewFakeMultiClient())
|
||||
rb := ResourceBuilder{SecretResolver: secretResolver}
|
||||
|
||||
reconciler := &AutoscalingListenerReconciler{
|
||||
Client: fakeClient,
|
||||
Scheme: scheme,
|
||||
Log: logr.Discard(),
|
||||
ResourceBuilder: rb,
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
req := ctrl.Request{
|
||||
NamespacedName: client.ObjectKey{
|
||||
Namespace: namespace,
|
||||
Name: listenerName,
|
||||
},
|
||||
}
|
||||
|
||||
// Warmup
|
||||
WarmupIteration(b, func() {
|
||||
_, _ = reconciler.Reconcile(ctx, req)
|
||||
})
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
// Delete Role to trigger creation path
|
||||
role := &rbacv1.Role{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: listenerName,
|
||||
Namespace: namespace,
|
||||
},
|
||||
}
|
||||
_ = fakeClient.Delete(ctx, role)
|
||||
|
||||
_, _ = reconciler.Reconcile(ctx, req)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkActionsGithub_Reconcile_AutoscalingListener_UpdateRole benchmarks updating Role
|
||||
func BenchmarkActionsGithub_Reconcile_AutoscalingListener_UpdateRole(b *testing.B) {
|
||||
scheme := runtime.NewScheme()
|
||||
_ = clientgoscheme.AddToScheme(scheme)
|
||||
_ = actionsv1alpha1.AddToScheme(scheme)
|
||||
|
||||
namespace := "test-ns"
|
||||
listenerName := "test-listener"
|
||||
arsName := "test-ars"
|
||||
|
||||
// Create AutoscalingRunnerSet
|
||||
ars := NewMinimalAutoscalingRunnerSet(namespace, arsName)
|
||||
|
||||
// Create listener with finalizer
|
||||
listener := NewMinimalAutoscalingListener(namespace, listenerName)
|
||||
listener.Spec.AutoscalingRunnerSetNamespace = namespace
|
||||
listener.Spec.AutoscalingRunnerSetName = arsName
|
||||
listener.Finalizers = []string{autoscalingListenerFinalizerName}
|
||||
|
||||
// Create ServiceAccount
|
||||
sa := &corev1.ServiceAccount{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: listenerName,
|
||||
Namespace: namespace,
|
||||
},
|
||||
}
|
||||
|
||||
// Create Role with outdated labels
|
||||
role := &rbacv1.Role{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: listenerName,
|
||||
Namespace: namespace,
|
||||
Labels: map[string]string{
|
||||
"outdated-label": "old-value",
|
||||
},
|
||||
},
|
||||
Rules: []rbacv1.PolicyRule{
|
||||
{
|
||||
APIGroups: []string{""},
|
||||
Resources: []string{"pods"},
|
||||
Verbs: []string{"get"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Create secret
|
||||
secret := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-secret",
|
||||
Namespace: namespace,
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"github_token": []byte("fake-token"),
|
||||
},
|
||||
}
|
||||
listener.Spec.GitHubConfigSecret = secret.Name
|
||||
|
||||
fakeClient := fake.NewClientBuilder().
|
||||
WithScheme(scheme).
|
||||
WithObjects(ars, listener, sa, role, secret).
|
||||
WithStatusSubresource(&actionsv1alpha1.AutoscalingListener{}).
|
||||
Build()
|
||||
|
||||
secretResolver := secretresolver.New(fakeClient, NewFakeMultiClient())
|
||||
rb := ResourceBuilder{SecretResolver: secretResolver}
|
||||
|
||||
reconciler := &AutoscalingListenerReconciler{
|
||||
Client: fakeClient,
|
||||
Scheme: scheme,
|
||||
Log: logr.Discard(),
|
||||
ResourceBuilder: rb,
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
req := ctrl.Request{
|
||||
NamespacedName: client.ObjectKey{
|
||||
Namespace: namespace,
|
||||
Name: listenerName,
|
||||
},
|
||||
}
|
||||
|
||||
// Warmup
|
||||
WarmupIteration(b, func() {
|
||||
_, _ = reconciler.Reconcile(ctx, req)
|
||||
})
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
// Reset Role labels to trigger update
|
||||
updatedRole := &rbacv1.Role{}
|
||||
_ = fakeClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: listenerName}, updatedRole)
|
||||
updatedRole.Labels = map[string]string{"outdated-label": "old-value"}
|
||||
_ = fakeClient.Update(ctx, updatedRole)
|
||||
|
||||
_, _ = reconciler.Reconcile(ctx, req)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkActionsGithub_Reconcile_AutoscalingListener_CreateRoleBinding benchmarks creating RoleBinding
|
||||
func BenchmarkActionsGithub_Reconcile_AutoscalingListener_CreateRoleBinding(b *testing.B) {
|
||||
scheme := runtime.NewScheme()
|
||||
_ = clientgoscheme.AddToScheme(scheme)
|
||||
_ = actionsv1alpha1.AddToScheme(scheme)
|
||||
|
||||
namespace := "test-ns"
|
||||
listenerName := "test-listener"
|
||||
arsName := "test-ars"
|
||||
|
||||
// Create AutoscalingRunnerSet
|
||||
ars := NewMinimalAutoscalingRunnerSet(namespace, arsName)
|
||||
|
||||
// Create listener with finalizer
|
||||
listener := NewMinimalAutoscalingListener(namespace, listenerName)
|
||||
listener.Spec.AutoscalingRunnerSetNamespace = namespace
|
||||
listener.Spec.AutoscalingRunnerSetName = arsName
|
||||
listener.Finalizers = []string{autoscalingListenerFinalizerName}
|
||||
|
||||
// Create ServiceAccount
|
||||
sa := &corev1.ServiceAccount{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: listenerName,
|
||||
Namespace: namespace,
|
||||
},
|
||||
}
|
||||
|
||||
// Create Role
|
||||
role := &rbacv1.Role{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: listenerName,
|
||||
Namespace: namespace,
|
||||
},
|
||||
Rules: []rbacv1.PolicyRule{},
|
||||
}
|
||||
|
||||
// Create secret
|
||||
secret := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-secret",
|
||||
Namespace: namespace,
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"github_token": []byte("fake-token"),
|
||||
},
|
||||
}
|
||||
listener.Spec.GitHubConfigSecret = secret.Name
|
||||
|
||||
fakeClient := fake.NewClientBuilder().
|
||||
WithScheme(scheme).
|
||||
WithObjects(ars, listener, sa, role, secret).
|
||||
WithStatusSubresource(&actionsv1alpha1.AutoscalingListener{}).
|
||||
Build()
|
||||
|
||||
secretResolver := secretresolver.New(fakeClient, NewFakeMultiClient())
|
||||
rb := ResourceBuilder{SecretResolver: secretResolver}
|
||||
|
||||
reconciler := &AutoscalingListenerReconciler{
|
||||
Client: fakeClient,
|
||||
Scheme: scheme,
|
||||
Log: logr.Discard(),
|
||||
ResourceBuilder: rb,
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
req := ctrl.Request{
|
||||
NamespacedName: client.ObjectKey{
|
||||
Namespace: namespace,
|
||||
Name: listenerName,
|
||||
},
|
||||
}
|
||||
|
||||
// Warmup
|
||||
WarmupIteration(b, func() {
|
||||
_, _ = reconciler.Reconcile(ctx, req)
|
||||
})
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
// Delete RoleBinding to trigger creation path
|
||||
rb := &rbacv1.RoleBinding{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: listenerName,
|
||||
Namespace: namespace,
|
||||
},
|
||||
}
|
||||
_ = fakeClient.Delete(ctx, rb)
|
||||
|
||||
_, _ = reconciler.Reconcile(ctx, req)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkActionsGithub_Reconcile_AutoscalingListener_UpdateRoleBinding benchmarks updating RoleBinding
|
||||
func BenchmarkActionsGithub_Reconcile_AutoscalingListener_UpdateRoleBinding(b *testing.B) {
|
||||
scheme := runtime.NewScheme()
|
||||
_ = clientgoscheme.AddToScheme(scheme)
|
||||
_ = actionsv1alpha1.AddToScheme(scheme)
|
||||
|
||||
namespace := "test-ns"
|
||||
listenerName := "test-listener"
|
||||
arsName := "test-ars"
|
||||
|
||||
// Create AutoscalingRunnerSet
|
||||
ars := NewMinimalAutoscalingRunnerSet(namespace, arsName)
|
||||
|
||||
// Create listener with finalizer
|
||||
listener := NewMinimalAutoscalingListener(namespace, listenerName)
|
||||
listener.Spec.AutoscalingRunnerSetNamespace = namespace
|
||||
listener.Spec.AutoscalingRunnerSetName = arsName
|
||||
listener.Finalizers = []string{autoscalingListenerFinalizerName}
|
||||
|
||||
// Create ServiceAccount
|
||||
sa := &corev1.ServiceAccount{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: listenerName,
|
||||
Namespace: namespace,
|
||||
},
|
||||
}
|
||||
|
||||
// Create Role
|
||||
role := &rbacv1.Role{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: listenerName,
|
||||
Namespace: namespace,
|
||||
},
|
||||
Rules: []rbacv1.PolicyRule{},
|
||||
}
|
||||
|
||||
// Create RoleBinding with outdated labels
|
||||
roleBinding := &rbacv1.RoleBinding{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: listenerName,
|
||||
Namespace: namespace,
|
||||
Labels: map[string]string{
|
||||
"outdated-label": "old-value",
|
||||
},
|
||||
},
|
||||
RoleRef: rbacv1.RoleRef{
|
||||
APIGroup: "rbac.authorization.k8s.io",
|
||||
Kind: "Role",
|
||||
Name: listenerName,
|
||||
},
|
||||
Subjects: []rbacv1.Subject{
|
||||
{
|
||||
Kind: "ServiceAccount",
|
||||
Name: listenerName,
|
||||
Namespace: namespace,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Create secret
|
||||
secret := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-secret",
|
||||
Namespace: namespace,
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"github_token": []byte("fake-token"),
|
||||
},
|
||||
}
|
||||
listener.Spec.GitHubConfigSecret = secret.Name
|
||||
|
||||
fakeClient := fake.NewClientBuilder().
|
||||
WithScheme(scheme).
|
||||
WithObjects(ars, listener, sa, role, roleBinding, secret).
|
||||
WithStatusSubresource(&actionsv1alpha1.AutoscalingListener{}).
|
||||
Build()
|
||||
|
||||
secretResolver := secretresolver.New(fakeClient, NewFakeMultiClient())
|
||||
rb := ResourceBuilder{SecretResolver: secretResolver}
|
||||
|
||||
reconciler := &AutoscalingListenerReconciler{
|
||||
Client: fakeClient,
|
||||
Scheme: scheme,
|
||||
Log: logr.Discard(),
|
||||
ResourceBuilder: rb,
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
req := ctrl.Request{
|
||||
NamespacedName: client.ObjectKey{
|
||||
Namespace: namespace,
|
||||
Name: listenerName,
|
||||
},
|
||||
}
|
||||
|
||||
// Warmup
|
||||
WarmupIteration(b, func() {
|
||||
_, _ = reconciler.Reconcile(ctx, req)
|
||||
})
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
// Reset RoleBinding labels to trigger update
|
||||
updatedRB := &rbacv1.RoleBinding{}
|
||||
_ = fakeClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: listenerName}, updatedRB)
|
||||
updatedRB.Labels = map[string]string{"outdated-label": "old-value"}
|
||||
_ = fakeClient.Update(ctx, updatedRB)
|
||||
|
||||
_, _ = reconciler.Reconcile(ctx, req)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkActionsGithub_Reconcile_AutoscalingListener_SteadyState benchmarks no-op steady-state reconciliation
|
||||
func BenchmarkActionsGithub_Reconcile_AutoscalingListener_SteadyState(b *testing.B) {
|
||||
scheme := runtime.NewScheme()
|
||||
_ = clientgoscheme.AddToScheme(scheme)
|
||||
_ = actionsv1alpha1.AddToScheme(scheme)
|
||||
|
||||
namespace := "test-ns"
|
||||
listenerName := "test-listener"
|
||||
arsName := "test-ars"
|
||||
|
||||
// Create AutoscalingRunnerSet
|
||||
ars := NewMinimalAutoscalingRunnerSet(namespace, arsName)
|
||||
|
||||
// Create fully configured listener
|
||||
listener := NewMinimalAutoscalingListener(namespace, listenerName)
|
||||
listener.Spec.AutoscalingRunnerSetNamespace = namespace
|
||||
listener.Spec.AutoscalingRunnerSetName = arsName
|
||||
listener.Finalizers = []string{autoscalingListenerFinalizerName}
|
||||
|
||||
// Create all required resources (steady state)
|
||||
sa := &corev1.ServiceAccount{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: listenerName,
|
||||
Namespace: namespace,
|
||||
Labels: map[string]string{
|
||||
"actions.github.com/autoscaling-listener-name": listenerName,
|
||||
"actions.github.com/autoscaling-listener-namespace": namespace,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
role := &rbacv1.Role{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: listenerName,
|
||||
Namespace: namespace,
|
||||
Labels: map[string]string{
|
||||
"auto-scaling-listener-name": listenerName,
|
||||
"auto-scaling-listener-namespace": namespace,
|
||||
},
|
||||
},
|
||||
Rules: rulesForListenerRole([]string{listener.Spec.EphemeralRunnerSetName}),
|
||||
}
|
||||
|
||||
roleBinding := &rbacv1.RoleBinding{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: listenerName,
|
||||
Namespace: namespace,
|
||||
Labels: map[string]string{
|
||||
"auto-scaling-listener-name": listenerName,
|
||||
"auto-scaling-listener-namespace": namespace,
|
||||
},
|
||||
},
|
||||
RoleRef: rbacv1.RoleRef{
|
||||
APIGroup: "rbac.authorization.k8s.io",
|
||||
Kind: "Role",
|
||||
Name: listenerName,
|
||||
},
|
||||
Subjects: []rbacv1.Subject{
|
||||
{
|
||||
Kind: "ServiceAccount",
|
||||
Name: listenerName,
|
||||
Namespace: namespace,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Create config secret
|
||||
configSecret := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: scaleSetListenerConfigName(listener),
|
||||
Namespace: namespace,
|
||||
Labels: map[string]string{
|
||||
"auto-scaling-listener-name": listenerName,
|
||||
},
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"listener": []byte(`{"url":"https://github.com/example/repo","token":"fake-token"}`),
|
||||
},
|
||||
}
|
||||
|
||||
// Create github config secret
|
||||
secret := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-secret",
|
||||
Namespace: namespace,
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"github_token": []byte("fake-token"),
|
||||
},
|
||||
}
|
||||
listener.Spec.GitHubConfigSecret = secret.Name
|
||||
|
||||
// Create listener pod (running state)
|
||||
listenerPod := &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: listenerName,
|
||||
Namespace: namespace,
|
||||
Labels: map[string]string{
|
||||
"auto-scaling-listener-name": listenerName,
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
AnnotationKeyIntegrityHash: "deterministic-hash",
|
||||
},
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
ServiceAccountName: listenerName,
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: autoscalingListenerContainerName,
|
||||
Image: listener.Spec.Image,
|
||||
},
|
||||
},
|
||||
},
|
||||
Status: corev1.PodStatus{
|
||||
Phase: corev1.PodRunning,
|
||||
ContainerStatuses: []corev1.ContainerStatus{
|
||||
{
|
||||
Name: autoscalingListenerContainerName,
|
||||
State: corev1.ContainerState{
|
||||
Running: &corev1.ContainerStateRunning{
|
||||
StartedAt: metav1.Now(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
fakeClient := fake.NewClientBuilder().
|
||||
WithScheme(scheme).
|
||||
WithObjects(ars, listener, sa, role, roleBinding, configSecret, secret, listenerPod).
|
||||
WithStatusSubresource(&actionsv1alpha1.AutoscalingListener{}).
|
||||
Build()
|
||||
|
||||
secretResolver := secretresolver.New(fakeClient, NewFakeMultiClient())
|
||||
rb := ResourceBuilder{SecretResolver: secretResolver}
|
||||
|
||||
reconciler := &AutoscalingListenerReconciler{
|
||||
Client: fakeClient,
|
||||
Scheme: scheme,
|
||||
Log: logr.Discard(),
|
||||
ResourceBuilder: rb,
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
req := ctrl.Request{
|
||||
NamespacedName: client.ObjectKey{
|
||||
Namespace: namespace,
|
||||
Name: listenerName,
|
||||
},
|
||||
}
|
||||
|
||||
// Warmup
|
||||
WarmupIteration(b, func() {
|
||||
_, _ = reconciler.Reconcile(ctx, req)
|
||||
})
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
// No-op steady state - everything is in sync
|
||||
_, _ = reconciler.Reconcile(ctx, req)
|
||||
}
|
||||
}
|
||||
@@ -701,6 +701,9 @@ func (r *AutoscalingRunnerSetReconciler) updateRunnerScaleSetName(ctx context.Co
|
||||
|
||||
logger.Info("Updating runner scale set name as an annotation")
|
||||
original := autoscalingRunnerSet.DeepCopy()
|
||||
if autoscalingRunnerSet.Annotations == nil {
|
||||
autoscalingRunnerSet.Annotations = make(map[string]string, 1)
|
||||
}
|
||||
autoscalingRunnerSet.Annotations[AnnotationKeyGitHubRunnerScaleSetName] = updatedRunnerScaleSet.Name
|
||||
if err := r.Patch(ctx, autoscalingRunnerSet, client.MergeFrom(original)); err != nil {
|
||||
logger.Error(err, "Failed to update runner scale set name annotation")
|
||||
|
||||
@@ -0,0 +1,509 @@
|
||||
/*
|
||||
Copyright 2020 The actions-runner-controller authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package actionsgithubcom
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
actionsv1alpha1 "github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1"
|
||||
"github.com/actions/actions-runner-controller/build"
|
||||
scalefake "github.com/actions/actions-runner-controller/controllers/actions.github.com/multiclient/fake"
|
||||
"github.com/actions/actions-runner-controller/controllers/actions.github.com/secretresolver"
|
||||
"github.com/actions/scaleset"
|
||||
"github.com/go-logr/logr"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
)
|
||||
|
||||
// BenchmarkActionsGithub_Reconcile_AutoscalingRunnerSet_AddFinalizer benchmarks the reconcile loop
|
||||
// when adding a finalizer to a new AutoscalingRunnerSet
|
||||
func BenchmarkActionsGithub_Reconcile_AutoscalingRunnerSet_AddFinalizer(b *testing.B) {
|
||||
ctx := context.Background()
|
||||
scheme := runtime.NewScheme()
|
||||
_ = actionsv1alpha1.AddToScheme(scheme)
|
||||
_ = corev1.AddToScheme(scheme)
|
||||
|
||||
// Create fake secret
|
||||
secret := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-secret",
|
||||
Namespace: "test-ns",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"github_token": []byte("test-token"),
|
||||
},
|
||||
}
|
||||
|
||||
// Create minimal AutoscalingRunnerSet without finalizer
|
||||
ars := NewMinimalAutoscalingRunnerSet("test-ns", "test-ars")
|
||||
ars.Spec.GitHubConfigSecret = "test-secret"
|
||||
ars.Labels = map[string]string{
|
||||
LabelKeyKubernetesVersion: build.Version,
|
||||
}
|
||||
|
||||
fakeClient := fake.NewClientBuilder().
|
||||
WithScheme(scheme).
|
||||
WithObjects(secret, ars).
|
||||
WithStatusSubresource(&actionsv1alpha1.AutoscalingRunnerSet{}).
|
||||
Build()
|
||||
|
||||
reconciler := &AutoscalingRunnerSetReconciler{
|
||||
Client: fakeClient,
|
||||
Scheme: scheme,
|
||||
Log: logr.Discard(),
|
||||
ControllerNamespace: "test-ns",
|
||||
ResourceBuilder: ResourceBuilder{
|
||||
SecretResolver: secretresolver.New(fakeClient, NewFakeMultiClient()),
|
||||
},
|
||||
}
|
||||
|
||||
req := ctrl.Request{
|
||||
NamespacedName: types.NamespacedName{
|
||||
Namespace: "test-ns",
|
||||
Name: "test-ars",
|
||||
},
|
||||
}
|
||||
|
||||
for b.Loop() {
|
||||
b.StopTimer()
|
||||
// Reset the AutoscalingRunnerSet to initial state (no finalizer)
|
||||
ars := NewMinimalAutoscalingRunnerSet("test-ns", "test-ars")
|
||||
ars.Spec.GitHubConfigSecret = "test-secret"
|
||||
ars.Labels = map[string]string{
|
||||
LabelKeyKubernetesVersion: build.Version,
|
||||
}
|
||||
ars.Annotations = map[string]string{
|
||||
AnnotationKeyIntegrityHash: autoscalingRunnerSetIntegrityHash(ars),
|
||||
}
|
||||
ResetBenchmarkAutoscalingRunnerSet(b, ctx, fakeClient, ars)
|
||||
b.StartTimer()
|
||||
|
||||
_, _ = reconciler.Reconcile(ctx, req)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkActionsGithub_Reconcile_AutoscalingRunnerSet_RemoveFinalizer benchmarks the reconcile loop
|
||||
// when removing a finalizer during deletion
|
||||
func BenchmarkActionsGithub_Reconcile_AutoscalingRunnerSet_RemoveFinalizer(b *testing.B) {
|
||||
ctx := context.Background()
|
||||
scheme := runtime.NewScheme()
|
||||
_ = actionsv1alpha1.AddToScheme(scheme)
|
||||
_ = corev1.AddToScheme(scheme)
|
||||
|
||||
// Create fake secret
|
||||
secret := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-secret",
|
||||
Namespace: "test-ns",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"github_token": []byte("test-token"),
|
||||
},
|
||||
}
|
||||
|
||||
// Create AutoscalingRunnerSet with finalizer and deletion timestamp
|
||||
ars := NewMinimalAutoscalingRunnerSet("test-ns", "test-ars")
|
||||
ars.Spec.GitHubConfigSecret = "test-secret"
|
||||
ars.Labels = map[string]string{
|
||||
LabelKeyKubernetesVersion: build.Version,
|
||||
}
|
||||
ars.Finalizers = []string{autoscalingRunnerSetFinalizerName}
|
||||
now := metav1.Now()
|
||||
ars.DeletionTimestamp = &now
|
||||
|
||||
fakeClient := fake.NewClientBuilder().
|
||||
WithScheme(scheme).
|
||||
WithObjects(secret, ars).
|
||||
WithStatusSubresource(&actionsv1alpha1.AutoscalingRunnerSet{}).
|
||||
Build()
|
||||
|
||||
reconciler := &AutoscalingRunnerSetReconciler{
|
||||
Client: fakeClient,
|
||||
Scheme: scheme,
|
||||
Log: logr.Discard(),
|
||||
ControllerNamespace: "test-ns",
|
||||
ResourceBuilder: ResourceBuilder{
|
||||
SecretResolver: secretresolver.New(fakeClient, NewFakeMultiClient()),
|
||||
},
|
||||
}
|
||||
|
||||
req := ctrl.Request{
|
||||
NamespacedName: types.NamespacedName{
|
||||
Namespace: "test-ns",
|
||||
Name: "test-ars",
|
||||
},
|
||||
}
|
||||
|
||||
for b.Loop() {
|
||||
b.StopTimer()
|
||||
// Reset the AutoscalingRunnerSet with finalizer
|
||||
ars := NewMinimalAutoscalingRunnerSet("test-ns", "test-ars")
|
||||
ars.Spec.GitHubConfigSecret = "test-secret"
|
||||
ars.Labels = map[string]string{
|
||||
LabelKeyKubernetesVersion: build.Version,
|
||||
}
|
||||
ars.Finalizers = []string{autoscalingRunnerSetFinalizerName}
|
||||
now := metav1.Now()
|
||||
ars.DeletionTimestamp = &now
|
||||
_ = fakeClient.Update(ctx, ars)
|
||||
b.StartTimer()
|
||||
|
||||
_, _ = reconciler.Reconcile(ctx, req)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkActionsGithub_Reconcile_AutoscalingRunnerSet_CreateScaleSet benchmarks the reconcile loop
|
||||
// when creating a runner scale set
|
||||
func BenchmarkActionsGithub_Reconcile_AutoscalingRunnerSet_CreateScaleSet(b *testing.B) {
|
||||
ctx := context.Background()
|
||||
scheme := runtime.NewScheme()
|
||||
_ = actionsv1alpha1.AddToScheme(scheme)
|
||||
_ = corev1.AddToScheme(scheme)
|
||||
|
||||
// Create fake secret
|
||||
secret := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-secret",
|
||||
Namespace: "test-ns",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"github_token": []byte("test-token"),
|
||||
},
|
||||
}
|
||||
|
||||
// Create AutoscalingRunnerSet with finalizer but no scale set annotations
|
||||
ars := NewMinimalAutoscalingRunnerSet("test-ns", "test-ars")
|
||||
ars.Spec.GitHubConfigSecret = "test-secret"
|
||||
ars.Labels = map[string]string{
|
||||
LabelKeyKubernetesVersion: build.Version,
|
||||
}
|
||||
ars.Finalizers = []string{autoscalingRunnerSetFinalizerName}
|
||||
ars.Spec.RunnerGroup = "default"
|
||||
|
||||
fakeClient := fake.NewClientBuilder().
|
||||
WithScheme(scheme).
|
||||
WithObjects(secret, ars).
|
||||
WithStatusSubresource(&actionsv1alpha1.AutoscalingRunnerSet{}).
|
||||
Build()
|
||||
|
||||
// Create fake scale client with runner group and scale set creation support
|
||||
fakeScaleClient := scalefake.NewMultiClient(
|
||||
scalefake.WithClient(
|
||||
scalefake.NewClient(
|
||||
scalefake.WithGetRunnerGroupByNameFunc(func(ctx context.Context, groupName string) (*scaleset.RunnerGroup, error) {
|
||||
return &scaleset.RunnerGroup{ID: 1, Name: groupName}, nil
|
||||
}),
|
||||
scalefake.WithCreateRunnerScaleSetFunc(func(ctx context.Context, rs *scaleset.RunnerScaleSet) (*scaleset.RunnerScaleSet, error) {
|
||||
return &scaleset.RunnerScaleSet{
|
||||
ID: 1,
|
||||
Name: rs.Name,
|
||||
RunnerGroupID: rs.RunnerGroupID,
|
||||
RunnerGroupName: "default",
|
||||
}, nil
|
||||
}),
|
||||
scalefake.WithGetRunnerScaleSet(nil, nil),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
reconciler := &AutoscalingRunnerSetReconciler{
|
||||
Client: fakeClient,
|
||||
Scheme: scheme,
|
||||
Log: logr.Discard(),
|
||||
ControllerNamespace: "test-ns",
|
||||
ResourceBuilder: ResourceBuilder{
|
||||
SecretResolver: secretresolver.New(fakeClient, fakeScaleClient),
|
||||
},
|
||||
}
|
||||
|
||||
req := ctrl.Request{
|
||||
NamespacedName: types.NamespacedName{
|
||||
Namespace: "test-ns",
|
||||
Name: "test-ars",
|
||||
},
|
||||
}
|
||||
|
||||
for b.Loop() {
|
||||
b.StopTimer()
|
||||
// Reset the AutoscalingRunnerSet to state before scale set creation
|
||||
ars := NewMinimalAutoscalingRunnerSet("test-ns", "test-ars")
|
||||
ars.Spec.GitHubConfigSecret = "test-secret"
|
||||
ars.Labels = map[string]string{
|
||||
LabelKeyKubernetesVersion: build.Version,
|
||||
}
|
||||
ars.Finalizers = []string{autoscalingRunnerSetFinalizerName}
|
||||
ars.Spec.RunnerGroup = "default"
|
||||
// Remove scale set annotations to trigger creation
|
||||
ars.Annotations = nil
|
||||
ResetBenchmarkAutoscalingRunnerSet(b, ctx, fakeClient, ars)
|
||||
b.StartTimer()
|
||||
|
||||
_, _ = reconciler.Reconcile(ctx, req)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkActionsGithub_Reconcile_AutoscalingRunnerSet_OutdatedCleanup benchmarks the reconcile loop
|
||||
// when cleaning up outdated resources
|
||||
func BenchmarkActionsGithub_Reconcile_AutoscalingRunnerSet_OutdatedCleanup(b *testing.B) {
|
||||
ctx := context.Background()
|
||||
scheme := runtime.NewScheme()
|
||||
_ = actionsv1alpha1.AddToScheme(scheme)
|
||||
_ = corev1.AddToScheme(scheme)
|
||||
|
||||
// Create fake secret
|
||||
secret := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-secret",
|
||||
Namespace: "test-ns",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"github_token": []byte("test-token"),
|
||||
},
|
||||
}
|
||||
|
||||
// Create AutoscalingRunnerSet in outdated phase
|
||||
ars := NewMinimalAutoscalingRunnerSet("test-ns", "test-ars")
|
||||
ars.Spec.GitHubConfigSecret = "test-secret"
|
||||
ars.Labels = map[string]string{
|
||||
LabelKeyKubernetesVersion: build.Version,
|
||||
}
|
||||
ars.Finalizers = []string{autoscalingRunnerSetFinalizerName}
|
||||
ars.Status.Phase = actionsv1alpha1.AutoscalingRunnerSetPhaseOutdated
|
||||
|
||||
// Create AutoscalingListener to be cleaned up
|
||||
listener := NewMinimalAutoscalingListener("test-ns", "test-ars-listener")
|
||||
listener.Spec.AutoscalingRunnerSetNamespace = "test-ns"
|
||||
listener.Spec.AutoscalingRunnerSetName = "test-ars"
|
||||
|
||||
fakeClient := fake.NewClientBuilder().
|
||||
WithScheme(scheme).
|
||||
WithObjects(secret, ars, listener).
|
||||
WithStatusSubresource(&actionsv1alpha1.AutoscalingRunnerSet{}, &actionsv1alpha1.AutoscalingListener{}).
|
||||
Build()
|
||||
|
||||
reconciler := &AutoscalingRunnerSetReconciler{
|
||||
Client: fakeClient,
|
||||
Scheme: scheme,
|
||||
Log: logr.Discard(),
|
||||
ControllerNamespace: "test-ns",
|
||||
ResourceBuilder: ResourceBuilder{
|
||||
SecretResolver: secretresolver.New(fakeClient, NewFakeMultiClient()),
|
||||
},
|
||||
}
|
||||
|
||||
req := ctrl.Request{
|
||||
NamespacedName: types.NamespacedName{
|
||||
Namespace: "test-ns",
|
||||
Name: "test-ars",
|
||||
},
|
||||
}
|
||||
|
||||
for b.Loop() {
|
||||
b.StopTimer()
|
||||
// Reset the AutoscalingRunnerSet to outdated state
|
||||
ars := NewMinimalAutoscalingRunnerSet("test-ns", "test-ars")
|
||||
ars.Spec.GitHubConfigSecret = "test-secret"
|
||||
ars.Labels = map[string]string{
|
||||
LabelKeyKubernetesVersion: build.Version,
|
||||
}
|
||||
ars.Finalizers = []string{autoscalingRunnerSetFinalizerName}
|
||||
ars.Status.Phase = actionsv1alpha1.AutoscalingRunnerSetPhaseOutdated
|
||||
ResetBenchmarkAutoscalingRunnerSet(b, ctx, fakeClient, ars)
|
||||
|
||||
// Recreate listener
|
||||
listener := NewMinimalAutoscalingListener("test-ns", "test-ars-listener")
|
||||
listener.Spec.AutoscalingRunnerSetNamespace = "test-ns"
|
||||
listener.Spec.AutoscalingRunnerSetName = "test-ars"
|
||||
_ = fakeClient.Create(ctx, listener)
|
||||
b.StartTimer()
|
||||
|
||||
_, _ = reconciler.Reconcile(ctx, req)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkActionsGithub_Reconcile_AutoscalingRunnerSet_CreateEphemeralRunnerSet benchmarks the reconcile loop
|
||||
// when creating an EphemeralRunnerSet
|
||||
func BenchmarkActionsGithub_Reconcile_AutoscalingRunnerSet_CreateEphemeralRunnerSet(b *testing.B) {
|
||||
ctx := context.Background()
|
||||
scheme := runtime.NewScheme()
|
||||
_ = actionsv1alpha1.AddToScheme(scheme)
|
||||
_ = corev1.AddToScheme(scheme)
|
||||
|
||||
// Create fake secret
|
||||
secret := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-secret",
|
||||
Namespace: "test-ns",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"github_token": []byte("test-token"),
|
||||
},
|
||||
}
|
||||
|
||||
// Create AutoscalingRunnerSet with finalizer and scale set ID but no EphemeralRunnerSet
|
||||
ars := NewMinimalAutoscalingRunnerSet("test-ns", "test-ars")
|
||||
ars.Spec.GitHubConfigSecret = "test-secret"
|
||||
ars.Labels = map[string]string{
|
||||
LabelKeyKubernetesVersion: build.Version,
|
||||
}
|
||||
ars.Finalizers = []string{autoscalingRunnerSetFinalizerName}
|
||||
ars.Annotations = map[string]string{
|
||||
runnerScaleSetIDAnnotationKey: "1",
|
||||
AnnotationKeyGitHubRunnerScaleSetName: "test-scale-set",
|
||||
AnnotationKeyGitHubRunnerGroupName: "default",
|
||||
AnnotationKeyIntegrityHash: "test-hash",
|
||||
}
|
||||
|
||||
fakeClient := fake.NewClientBuilder().
|
||||
WithScheme(scheme).
|
||||
WithObjects(secret, ars).
|
||||
WithStatusSubresource(&actionsv1alpha1.AutoscalingRunnerSet{}, &actionsv1alpha1.EphemeralRunnerSet{}).
|
||||
Build()
|
||||
|
||||
reconciler := &AutoscalingRunnerSetReconciler{
|
||||
Client: fakeClient,
|
||||
Scheme: scheme,
|
||||
Log: logr.Discard(),
|
||||
ControllerNamespace: "test-ns",
|
||||
ResourceBuilder: ResourceBuilder{
|
||||
SecretResolver: secretresolver.New(fakeClient, NewFakeMultiClient()),
|
||||
},
|
||||
}
|
||||
|
||||
req := ctrl.Request{
|
||||
NamespacedName: types.NamespacedName{
|
||||
Namespace: "test-ns",
|
||||
Name: "test-ars",
|
||||
},
|
||||
}
|
||||
|
||||
for b.Loop() {
|
||||
b.StopTimer()
|
||||
// Delete any existing EphemeralRunnerSet
|
||||
ersList := &actionsv1alpha1.EphemeralRunnerSetList{}
|
||||
_ = fakeClient.List(ctx, ersList, client.InNamespace("test-ns"))
|
||||
for _, ers := range ersList.Items {
|
||||
_ = fakeClient.Delete(ctx, &ers)
|
||||
}
|
||||
|
||||
// Reset AutoscalingRunnerSet
|
||||
ars := NewMinimalAutoscalingRunnerSet("test-ns", "test-ars")
|
||||
ars.Spec.GitHubConfigSecret = "test-secret"
|
||||
ars.Labels = map[string]string{
|
||||
LabelKeyKubernetesVersion: build.Version,
|
||||
}
|
||||
ars.Finalizers = []string{autoscalingRunnerSetFinalizerName}
|
||||
ars.Annotations = map[string]string{
|
||||
runnerScaleSetIDAnnotationKey: "1",
|
||||
AnnotationKeyGitHubRunnerScaleSetName: "test-scale-set",
|
||||
AnnotationKeyGitHubRunnerGroupName: "default",
|
||||
AnnotationKeyIntegrityHash: "test-hash",
|
||||
}
|
||||
ResetBenchmarkAutoscalingRunnerSet(b, ctx, fakeClient, ars)
|
||||
b.StartTimer()
|
||||
|
||||
_, _ = reconciler.Reconcile(ctx, req)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkActionsGithub_Reconcile_AutoscalingRunnerSet_CreateListener benchmarks the reconcile loop
|
||||
// when creating an AutoscalingListener
|
||||
func BenchmarkActionsGithub_Reconcile_AutoscalingRunnerSet_CreateListener(b *testing.B) {
|
||||
ctx := context.Background()
|
||||
scheme := runtime.NewScheme()
|
||||
_ = actionsv1alpha1.AddToScheme(scheme)
|
||||
_ = corev1.AddToScheme(scheme)
|
||||
|
||||
// Create fake secret
|
||||
secret := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-secret",
|
||||
Namespace: "test-ns",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"github_token": []byte("test-token"),
|
||||
},
|
||||
}
|
||||
|
||||
// Create AutoscalingRunnerSet with finalizer and scale set ID
|
||||
ars := NewMinimalAutoscalingRunnerSet("test-ns", "test-ars")
|
||||
ars.Spec.GitHubConfigSecret = "test-secret"
|
||||
ars.Labels = map[string]string{
|
||||
LabelKeyKubernetesVersion: build.Version,
|
||||
}
|
||||
ars.Finalizers = []string{autoscalingRunnerSetFinalizerName}
|
||||
ars.Annotations = map[string]string{
|
||||
runnerScaleSetIDAnnotationKey: "1",
|
||||
AnnotationKeyGitHubRunnerScaleSetName: "test-scale-set",
|
||||
AnnotationKeyGitHubRunnerGroupName: "default",
|
||||
AnnotationKeyIntegrityHash: "test-hash",
|
||||
}
|
||||
|
||||
// Create EphemeralRunnerSet
|
||||
ers := NewMinimalEphemeralRunnerSet("test-ns", "test-ars")
|
||||
ers.Annotations = map[string]string{
|
||||
AnnotationKeyIntegrityHash: "test-hash",
|
||||
}
|
||||
ers.Spec.Replicas = 1
|
||||
|
||||
fakeClient := fake.NewClientBuilder().
|
||||
WithScheme(scheme).
|
||||
WithObjects(secret, ars, ers).
|
||||
WithStatusSubresource(
|
||||
&actionsv1alpha1.AutoscalingRunnerSet{},
|
||||
&actionsv1alpha1.EphemeralRunnerSet{},
|
||||
&actionsv1alpha1.AutoscalingListener{},
|
||||
).
|
||||
Build()
|
||||
|
||||
reconciler := &AutoscalingRunnerSetReconciler{
|
||||
Client: fakeClient,
|
||||
Scheme: scheme,
|
||||
Log: logr.Discard(),
|
||||
ControllerNamespace: "test-ns",
|
||||
DefaultRunnerScaleSetListenerImage: "ghcr.io/actions/arc:latest",
|
||||
ResourceBuilder: ResourceBuilder{
|
||||
SecretResolver: secretresolver.New(fakeClient, NewFakeMultiClient()),
|
||||
},
|
||||
}
|
||||
|
||||
req := ctrl.Request{
|
||||
NamespacedName: types.NamespacedName{
|
||||
Namespace: "test-ns",
|
||||
Name: "test-ars",
|
||||
},
|
||||
}
|
||||
|
||||
for b.Loop() {
|
||||
b.StopTimer()
|
||||
// Delete any existing AutoscalingListener
|
||||
listenerList := &actionsv1alpha1.AutoscalingListenerList{}
|
||||
_ = fakeClient.List(ctx, listenerList, client.InNamespace("test-ns"))
|
||||
for _, listener := range listenerList.Items {
|
||||
_ = fakeClient.Delete(ctx, &listener)
|
||||
}
|
||||
b.StartTimer()
|
||||
|
||||
_, _ = reconciler.Reconcile(ctx, req)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
/*
|
||||
Copyright 2020 The actions-runner-controller authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package actionsgithubcom
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
actionsv1alpha1 "github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1"
|
||||
scalefake "github.com/actions/actions-runner-controller/controllers/actions.github.com/multiclient/fake"
|
||||
"github.com/actions/scaleset"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
const (
|
||||
benchmarkGitHubConfigURL = "https://github.com/actions/actions-runner-controller"
|
||||
benchmarkGitHubConfigSecret = "test-secret"
|
||||
benchmarkRunnerImage = "ghcr.io/actions/runner:latest"
|
||||
)
|
||||
|
||||
func NewBenchmarkScheme(b *testing.B) *runtime.Scheme {
|
||||
b.Helper()
|
||||
|
||||
scheme := runtime.NewScheme()
|
||||
if err := clientgoscheme.AddToScheme(scheme); err != nil {
|
||||
b.Fatalf("add client-go scheme: %v", err)
|
||||
}
|
||||
if err := actionsv1alpha1.AddToScheme(scheme); err != nil {
|
||||
b.Fatalf("add actions.github.com scheme: %v", err)
|
||||
}
|
||||
|
||||
return scheme
|
||||
}
|
||||
|
||||
func NewBenchmarkRequest(namespace, name string) ctrl.Request {
|
||||
return ctrl.Request{
|
||||
NamespacedName: types.NamespacedName{
|
||||
Namespace: namespace,
|
||||
Name: name,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func ResetBenchmarkAutoscalingRunnerSet(b *testing.B, ctx context.Context, k8sClient client.Client, ars *actionsv1alpha1.AutoscalingRunnerSet) {
|
||||
b.Helper()
|
||||
|
||||
current := &actionsv1alpha1.AutoscalingRunnerSet{}
|
||||
key := client.ObjectKeyFromObject(ars)
|
||||
if err := k8sClient.Get(ctx, key, current); err != nil {
|
||||
b.Fatalf("get autoscaling runner set %s: %v", key, err)
|
||||
}
|
||||
|
||||
ars.ResourceVersion = current.ResourceVersion
|
||||
if err := k8sClient.Update(ctx, ars); err != nil {
|
||||
b.Fatalf("reset autoscaling runner set %s: %v", key, err)
|
||||
}
|
||||
}
|
||||
|
||||
func WarmupIteration(b *testing.B, fn func()) {
|
||||
b.Helper()
|
||||
b.StopTimer()
|
||||
fn()
|
||||
b.StartTimer()
|
||||
}
|
||||
|
||||
func RunBenchmarkIterations(b *testing.B, fn func()) {
|
||||
b.Helper()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
fn()
|
||||
}
|
||||
}
|
||||
|
||||
func NewFakeMultiClient() *scalefake.MultiClient {
|
||||
runnerGroup := &scaleset.RunnerGroup{ID: 1, Name: "default"}
|
||||
runnerScaleSet := &scaleset.RunnerScaleSet{
|
||||
ID: 1,
|
||||
Name: "test-scale-set",
|
||||
RunnerGroupID: runnerGroup.ID,
|
||||
RunnerGroupName: runnerGroup.Name,
|
||||
}
|
||||
runner := &scaleset.RunnerReference{ID: 1, Name: "test-runner"}
|
||||
|
||||
return scalefake.NewMultiClient(
|
||||
scalefake.WithClient(
|
||||
scalefake.NewClient(
|
||||
scalefake.WithGetRunnerGroupByName(runnerGroup, nil),
|
||||
scalefake.WithGetRunnerScaleSet(runnerScaleSet, nil),
|
||||
scalefake.WithGetRunnerScaleSetByID(runnerScaleSet, nil),
|
||||
scalefake.WithGetRunner(runner, nil),
|
||||
scalefake.WithGetRunnerByName(runner, nil),
|
||||
scalefake.WithGenerateJitRunnerConfig(
|
||||
&scaleset.RunnerScaleSetJitRunnerConfig{
|
||||
Runner: runner,
|
||||
EncodedJITConfig: "fake-jit-config",
|
||||
},
|
||||
nil,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
func NewMinimalAutoscalingRunnerSet(namespace, name string) *actionsv1alpha1.AutoscalingRunnerSet {
|
||||
minRunners := 0
|
||||
maxRunners := 1
|
||||
|
||||
return &actionsv1alpha1.AutoscalingRunnerSet{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: actionsv1alpha1.GroupVersion.String(),
|
||||
Kind: "AutoscalingRunnerSet",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: actionsv1alpha1.AutoscalingRunnerSetSpec{
|
||||
GitHubConfigUrl: benchmarkGitHubConfigURL,
|
||||
GitHubConfigSecret: benchmarkGitHubConfigSecret,
|
||||
RunnerGroup: "default",
|
||||
MinRunners: &minRunners,
|
||||
MaxRunners: &maxRunners,
|
||||
Template: benchmarkRunnerTemplate(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func NewMinimalAutoscalingListener(namespace, name string) *actionsv1alpha1.AutoscalingListener {
|
||||
return &actionsv1alpha1.AutoscalingListener{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: actionsv1alpha1.GroupVersion.String(),
|
||||
Kind: "AutoscalingListener",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: actionsv1alpha1.AutoscalingListenerSpec{
|
||||
GitHubConfigURL: benchmarkGitHubConfigURL,
|
||||
GitHubConfigSecret: benchmarkGitHubConfigSecret,
|
||||
RunnerScaleSetID: 1,
|
||||
MaxRunners: 1,
|
||||
Image: benchmarkRunnerImage,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func NewMinimalEphemeralRunnerSet(namespace, name string) *actionsv1alpha1.EphemeralRunnerSet {
|
||||
return &actionsv1alpha1.EphemeralRunnerSet{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: actionsv1alpha1.GroupVersion.String(),
|
||||
Kind: "EphemeralRunnerSet",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: actionsv1alpha1.EphemeralRunnerSetSpec{
|
||||
Replicas: 1,
|
||||
PatchID: 1,
|
||||
EphemeralRunnerSpec: actionsv1alpha1.EphemeralRunnerSpec{
|
||||
GitHubConfigURL: benchmarkGitHubConfigURL,
|
||||
GitHubConfigSecret: benchmarkGitHubConfigSecret,
|
||||
RunnerScaleSetID: 1,
|
||||
PodTemplateSpec: benchmarkRunnerTemplate(),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func NewMinimalEphemeralRunner(namespace, name string) *actionsv1alpha1.EphemeralRunner {
|
||||
return &actionsv1alpha1.EphemeralRunner{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: actionsv1alpha1.GroupVersion.String(),
|
||||
Kind: "EphemeralRunner",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: actionsv1alpha1.EphemeralRunnerSpec{
|
||||
GitHubConfigURL: benchmarkGitHubConfigURL,
|
||||
GitHubConfigSecret: benchmarkGitHubConfigSecret,
|
||||
RunnerScaleSetID: 1,
|
||||
PodTemplateSpec: benchmarkRunnerTemplate(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkRunnerTemplate() corev1.PodTemplateSpec {
|
||||
return corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: actionsv1alpha1.EphemeralRunnerContainerName,
|
||||
Image: benchmarkRunnerImage,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,896 @@
|
||||
/*
|
||||
Copyright 2020 The actions-runner-controller authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package actionsgithubcom
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
actionsv1alpha1 "github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1"
|
||||
"github.com/actions/actions-runner-controller/controllers/actions.github.com/multiclient/fake"
|
||||
"github.com/actions/actions-runner-controller/controllers/actions.github.com/secretresolver"
|
||||
"github.com/actions/scaleset"
|
||||
"github.com/go-logr/logr"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
clientfake "sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
)
|
||||
|
||||
// BenchmarkActionsGithub_Reconcile_EphemeralRunner_NoOp benchmarks a no-op reconcile
|
||||
// scenario where an existing EphemeralRunner with finalizers requires no changes
|
||||
func BenchmarkActionsGithub_Reconcile_EphemeralRunner_NoOp(b *testing.B) {
|
||||
ctx := context.Background()
|
||||
|
||||
// Setup: Create scheme and fake client
|
||||
scheme := runtime.NewScheme()
|
||||
_ = actionsv1alpha1.AddToScheme(scheme)
|
||||
_ = corev1.AddToScheme(scheme)
|
||||
|
||||
// Create minimal EphemeralRunner with finalizers already added
|
||||
runner := NewMinimalEphemeralRunner("default", "test-runner")
|
||||
runner.Finalizers = []string{
|
||||
ephemeralRunnerFinalizerName,
|
||||
ephemeralRunnerActionsFinalizerName,
|
||||
}
|
||||
runner.Spec.GitHubConfigSecret = "config-secret"
|
||||
runner.Status.RunnerID = 12345
|
||||
runner.Status.RunnerName = "test-runner"
|
||||
|
||||
// Create config secret
|
||||
configSecret := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "config-secret",
|
||||
Namespace: "default",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"github_token": []byte("test-token"),
|
||||
},
|
||||
}
|
||||
|
||||
// Create JIT secret for the runner (already exists, so no JIT creation needed)
|
||||
jitSecret := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-runner",
|
||||
Namespace: "default",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"jitToken": []byte("test-jit-token"),
|
||||
"runnerId": []byte("12345"),
|
||||
"runnerName": []byte("test-runner"),
|
||||
},
|
||||
}
|
||||
|
||||
// Create pod (already exists, so no pod creation needed)
|
||||
pod := &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-runner",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "runner",
|
||||
Image: "ghcr.io/actions/runner:latest",
|
||||
},
|
||||
},
|
||||
},
|
||||
Status: corev1.PodStatus{
|
||||
Phase: corev1.PodRunning,
|
||||
},
|
||||
}
|
||||
|
||||
k8sClient := clientfake.NewClientBuilder().
|
||||
WithScheme(scheme).
|
||||
WithObjects(runner, configSecret, jitSecret, pod).
|
||||
WithStatusSubresource(&actionsv1alpha1.EphemeralRunner{}, &corev1.Pod{}).
|
||||
Build()
|
||||
|
||||
// Setup reconciler
|
||||
multiClient := NewFakeMultiClient()
|
||||
reconciler := &EphemeralRunnerReconciler{
|
||||
Client: k8sClient,
|
||||
Scheme: scheme,
|
||||
Log: logr.Discard(),
|
||||
ResourceBuilder: ResourceBuilder{
|
||||
SecretResolver: secretresolver.New(k8sClient, multiClient),
|
||||
},
|
||||
}
|
||||
|
||||
req := ctrl.Request{
|
||||
NamespacedName: types.NamespacedName{
|
||||
Namespace: "default",
|
||||
Name: "test-runner",
|
||||
},
|
||||
}
|
||||
|
||||
// Warmup iteration
|
||||
WarmupIteration(b, func() {
|
||||
_, _ = reconciler.Reconcile(ctx, req)
|
||||
})
|
||||
|
||||
// Benchmark loop
|
||||
|
||||
for b.Loop() {
|
||||
_, err := reconciler.Reconcile(ctx, req)
|
||||
if err != nil {
|
||||
b.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkActionsGithub_Reconcile_EphemeralRunner_FinalizerAdd benchmarks the
|
||||
// path where finalizers are added to a new EphemeralRunner
|
||||
func BenchmarkActionsGithub_Reconcile_EphemeralRunner_FinalizerAdd(b *testing.B) {
|
||||
ctx := context.Background()
|
||||
|
||||
// Setup: Create scheme
|
||||
scheme := runtime.NewScheme()
|
||||
_ = actionsv1alpha1.AddToScheme(scheme)
|
||||
_ = corev1.AddToScheme(scheme)
|
||||
|
||||
multiClient := fake.NewMultiClient(
|
||||
fake.WithClient(
|
||||
fake.NewClient(
|
||||
fake.WithGetRunner(nil, scaleset.RunnerNotFoundError),
|
||||
fake.WithGenerateJitRunnerConfig(
|
||||
&scaleset.RunnerScaleSetJitRunnerConfig{
|
||||
Runner: &scaleset.RunnerReference{ID: 1, Name: "test-runner"},
|
||||
EncodedJITConfig: "fake-jit-config",
|
||||
},
|
||||
nil,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
// Warmup iteration
|
||||
{
|
||||
runner := NewMinimalEphemeralRunner("default", "warmup-runner")
|
||||
runner.Spec.GitHubConfigSecret = "config-secret"
|
||||
|
||||
configSecret := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "config-secret",
|
||||
Namespace: "default",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"github_token": []byte("test-token"),
|
||||
},
|
||||
}
|
||||
|
||||
k8sClient := clientfake.NewClientBuilder().
|
||||
WithScheme(scheme).
|
||||
WithObjects(runner, configSecret).
|
||||
WithStatusSubresource(&actionsv1alpha1.EphemeralRunner{}).
|
||||
Build()
|
||||
|
||||
reconciler := &EphemeralRunnerReconciler{
|
||||
Client: k8sClient,
|
||||
Scheme: scheme,
|
||||
Log: logr.Discard(),
|
||||
ResourceBuilder: ResourceBuilder{
|
||||
SecretResolver: secretresolver.New(k8sClient, multiClient),
|
||||
},
|
||||
}
|
||||
|
||||
req := ctrl.Request{
|
||||
NamespacedName: types.NamespacedName{
|
||||
Namespace: "default",
|
||||
Name: "warmup-runner",
|
||||
},
|
||||
}
|
||||
|
||||
_, _ = reconciler.Reconcile(ctx, req)
|
||||
}
|
||||
|
||||
// Benchmark loop: Each iteration needs fresh objects since finalizers are added
|
||||
|
||||
for b.Loop() {
|
||||
b.StopTimer()
|
||||
|
||||
runner := NewMinimalEphemeralRunner("default", "test-runner")
|
||||
runner.Spec.GitHubConfigSecret = "config-secret"
|
||||
|
||||
configSecret := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "config-secret",
|
||||
Namespace: "default",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"github_token": []byte("test-token"),
|
||||
},
|
||||
}
|
||||
|
||||
k8sClient := clientfake.NewClientBuilder().
|
||||
WithScheme(scheme).
|
||||
WithObjects(runner, configSecret).
|
||||
WithStatusSubresource(&actionsv1alpha1.EphemeralRunner{}).
|
||||
Build()
|
||||
|
||||
reconciler := &EphemeralRunnerReconciler{
|
||||
Client: k8sClient,
|
||||
Scheme: scheme,
|
||||
Log: logr.Discard(),
|
||||
ResourceBuilder: ResourceBuilder{
|
||||
SecretResolver: secretresolver.New(k8sClient, multiClient),
|
||||
},
|
||||
}
|
||||
|
||||
req := ctrl.Request{
|
||||
NamespacedName: types.NamespacedName{
|
||||
Namespace: "default",
|
||||
Name: "test-runner",
|
||||
},
|
||||
}
|
||||
|
||||
b.StartTimer()
|
||||
|
||||
_, err := reconciler.Reconcile(ctx, req)
|
||||
if err != nil {
|
||||
b.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkActionsGithub_Reconcile_EphemeralRunner_Deletion benchmarks the deletion
|
||||
// path where finalizers are removed and resources are cleaned up
|
||||
func BenchmarkActionsGithub_Reconcile_EphemeralRunner_Deletion(b *testing.B) {
|
||||
ctx := context.Background()
|
||||
|
||||
// Setup: Create scheme
|
||||
scheme := runtime.NewScheme()
|
||||
_ = actionsv1alpha1.AddToScheme(scheme)
|
||||
_ = corev1.AddToScheme(scheme)
|
||||
|
||||
multiClient := NewFakeMultiClient()
|
||||
|
||||
// Warmup iteration
|
||||
{
|
||||
now := metav1.Now()
|
||||
runner := NewMinimalEphemeralRunner("default", "warmup-runner")
|
||||
runner.Finalizers = []string{ephemeralRunnerFinalizerName}
|
||||
runner.DeletionTimestamp = &now
|
||||
runner.Spec.GitHubConfigSecret = "config-secret"
|
||||
|
||||
configSecret := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "config-secret",
|
||||
Namespace: "default",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"github_token": []byte("test-token"),
|
||||
},
|
||||
}
|
||||
|
||||
k8sClient := clientfake.NewClientBuilder().
|
||||
WithScheme(scheme).
|
||||
WithObjects(runner, configSecret).
|
||||
WithStatusSubresource(&actionsv1alpha1.EphemeralRunner{}).
|
||||
Build()
|
||||
|
||||
reconciler := &EphemeralRunnerReconciler{
|
||||
Client: k8sClient,
|
||||
Scheme: scheme,
|
||||
Log: logr.Discard(),
|
||||
ResourceBuilder: ResourceBuilder{
|
||||
SecretResolver: secretresolver.New(k8sClient, multiClient),
|
||||
},
|
||||
}
|
||||
|
||||
req := ctrl.Request{
|
||||
NamespacedName: types.NamespacedName{
|
||||
Namespace: "default",
|
||||
Name: "warmup-runner",
|
||||
},
|
||||
}
|
||||
|
||||
_, _ = reconciler.Reconcile(ctx, req)
|
||||
}
|
||||
|
||||
// Benchmark loop: Each iteration needs fresh objects
|
||||
|
||||
for b.Loop() {
|
||||
b.StopTimer()
|
||||
|
||||
now := metav1.Now()
|
||||
runner := NewMinimalEphemeralRunner("default", "test-runner")
|
||||
runner.Finalizers = []string{ephemeralRunnerFinalizerName}
|
||||
runner.DeletionTimestamp = &now
|
||||
runner.Spec.GitHubConfigSecret = "config-secret"
|
||||
|
||||
configSecret := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "config-secret",
|
||||
Namespace: "default",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"github_token": []byte("test-token"),
|
||||
},
|
||||
}
|
||||
|
||||
k8sClient := clientfake.NewClientBuilder().
|
||||
WithScheme(scheme).
|
||||
WithObjects(runner, configSecret).
|
||||
WithStatusSubresource(&actionsv1alpha1.EphemeralRunner{}).
|
||||
Build()
|
||||
|
||||
reconciler := &EphemeralRunnerReconciler{
|
||||
Client: k8sClient,
|
||||
Scheme: scheme,
|
||||
Log: logr.Discard(),
|
||||
ResourceBuilder: ResourceBuilder{
|
||||
SecretResolver: secretresolver.New(k8sClient, multiClient),
|
||||
},
|
||||
}
|
||||
|
||||
req := ctrl.Request{
|
||||
NamespacedName: types.NamespacedName{
|
||||
Namespace: "default",
|
||||
Name: "test-runner",
|
||||
},
|
||||
}
|
||||
|
||||
b.StartTimer()
|
||||
|
||||
_, err := reconciler.Reconcile(ctx, req)
|
||||
if err != nil {
|
||||
b.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkActionsGithub_Reconcile_EphemeralRunner_DeletionWithActionsCleanup benchmarks
|
||||
// the deletion path with actions finalizer requiring service cleanup
|
||||
func BenchmarkActionsGithub_Reconcile_EphemeralRunner_DeletionWithActionsCleanup(b *testing.B) {
|
||||
ctx := context.Background()
|
||||
|
||||
// Setup: Create scheme
|
||||
scheme := runtime.NewScheme()
|
||||
_ = actionsv1alpha1.AddToScheme(scheme)
|
||||
_ = corev1.AddToScheme(scheme)
|
||||
|
||||
// Configure fake client to simulate successful cleanup
|
||||
multiClient := fake.NewMultiClient(
|
||||
fake.WithClient(
|
||||
fake.NewClient(
|
||||
fake.WithRemoveRunner(nil), // Successful cleanup
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
// Warmup iteration
|
||||
{
|
||||
now := metav1.Now()
|
||||
runner := NewMinimalEphemeralRunner("default", "warmup-runner")
|
||||
runner.Finalizers = []string{
|
||||
ephemeralRunnerFinalizerName,
|
||||
ephemeralRunnerActionsFinalizerName,
|
||||
}
|
||||
runner.DeletionTimestamp = &now
|
||||
runner.Status.RunnerID = 12345
|
||||
runner.Spec.GitHubConfigSecret = "config-secret"
|
||||
|
||||
configSecret := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "config-secret",
|
||||
Namespace: "default",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"github_token": []byte("test-token"),
|
||||
},
|
||||
}
|
||||
|
||||
k8sClient := clientfake.NewClientBuilder().
|
||||
WithScheme(scheme).
|
||||
WithObjects(runner, configSecret).
|
||||
WithStatusSubresource(&actionsv1alpha1.EphemeralRunner{}).
|
||||
Build()
|
||||
|
||||
reconciler := &EphemeralRunnerReconciler{
|
||||
Client: k8sClient,
|
||||
Scheme: scheme,
|
||||
Log: logr.Discard(),
|
||||
ResourceBuilder: ResourceBuilder{
|
||||
SecretResolver: secretresolver.New(k8sClient, multiClient),
|
||||
},
|
||||
}
|
||||
|
||||
req := ctrl.Request{
|
||||
NamespacedName: types.NamespacedName{
|
||||
Namespace: "default",
|
||||
Name: "warmup-runner",
|
||||
},
|
||||
}
|
||||
|
||||
_, _ = reconciler.Reconcile(ctx, req)
|
||||
}
|
||||
|
||||
// Benchmark loop
|
||||
|
||||
for b.Loop() {
|
||||
b.StopTimer()
|
||||
|
||||
// Create EphemeralRunner marked for deletion with both finalizers
|
||||
now := metav1.Now()
|
||||
runner := NewMinimalEphemeralRunner("default", "test-runner")
|
||||
runner.Finalizers = []string{
|
||||
ephemeralRunnerFinalizerName,
|
||||
ephemeralRunnerActionsFinalizerName,
|
||||
}
|
||||
runner.DeletionTimestamp = &now
|
||||
runner.Status.RunnerID = 12345
|
||||
runner.Spec.GitHubConfigSecret = "config-secret"
|
||||
|
||||
configSecret := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "config-secret",
|
||||
Namespace: "default",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"github_token": []byte("test-token"),
|
||||
},
|
||||
}
|
||||
|
||||
k8sClient := clientfake.NewClientBuilder().
|
||||
WithScheme(scheme).
|
||||
WithObjects(runner, configSecret).
|
||||
WithStatusSubresource(&actionsv1alpha1.EphemeralRunner{}).
|
||||
Build()
|
||||
|
||||
reconciler := &EphemeralRunnerReconciler{
|
||||
Client: k8sClient,
|
||||
Scheme: scheme,
|
||||
Log: logr.Discard(),
|
||||
ResourceBuilder: ResourceBuilder{
|
||||
SecretResolver: secretresolver.New(k8sClient, multiClient),
|
||||
},
|
||||
}
|
||||
|
||||
req := ctrl.Request{
|
||||
NamespacedName: types.NamespacedName{
|
||||
Namespace: "default",
|
||||
Name: "test-runner",
|
||||
},
|
||||
}
|
||||
|
||||
b.StartTimer()
|
||||
|
||||
_, err := reconciler.Reconcile(ctx, req)
|
||||
if err != nil {
|
||||
b.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkActionsGithub_Reconcile_EphemeralRunner_RetryableError benchmarks the
|
||||
// path where a retryable error occurs during reconciliation
|
||||
func BenchmarkActionsGithub_Reconcile_EphemeralRunner_RetryableError(b *testing.B) {
|
||||
ctx := context.Background()
|
||||
|
||||
// Setup: Create scheme
|
||||
scheme := runtime.NewScheme()
|
||||
_ = actionsv1alpha1.AddToScheme(scheme)
|
||||
_ = corev1.AddToScheme(scheme)
|
||||
|
||||
// Configure fake client to return transient error
|
||||
retryableErr := errors.New("transient API error")
|
||||
multiClient := fake.NewMultiClient(
|
||||
fake.WithClient(
|
||||
fake.NewClient(
|
||||
fake.WithRemoveRunner(retryableErr),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
// Warmup iteration
|
||||
{
|
||||
now := metav1.Now()
|
||||
runner := NewMinimalEphemeralRunner("default", "warmup-runner")
|
||||
runner.Finalizers = []string{
|
||||
ephemeralRunnerFinalizerName,
|
||||
ephemeralRunnerActionsFinalizerName,
|
||||
}
|
||||
runner.DeletionTimestamp = &now
|
||||
runner.Status.RunnerID = 12345
|
||||
runner.Spec.GitHubConfigSecret = "config-secret"
|
||||
|
||||
configSecret := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "config-secret",
|
||||
Namespace: "default",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"github_token": []byte("test-token"),
|
||||
},
|
||||
}
|
||||
|
||||
k8sClient := clientfake.NewClientBuilder().
|
||||
WithScheme(scheme).
|
||||
WithObjects(runner, configSecret).
|
||||
WithStatusSubresource(&actionsv1alpha1.EphemeralRunner{}).
|
||||
Build()
|
||||
|
||||
reconciler := &EphemeralRunnerReconciler{
|
||||
Client: k8sClient,
|
||||
Scheme: scheme,
|
||||
Log: logr.Discard(),
|
||||
ResourceBuilder: ResourceBuilder{
|
||||
SecretResolver: secretresolver.New(k8sClient, multiClient),
|
||||
},
|
||||
}
|
||||
|
||||
req := ctrl.Request{
|
||||
NamespacedName: types.NamespacedName{
|
||||
Namespace: "default",
|
||||
Name: "warmup-runner",
|
||||
},
|
||||
}
|
||||
|
||||
_, _ = reconciler.Reconcile(ctx, req)
|
||||
}
|
||||
|
||||
// Benchmark loop
|
||||
|
||||
for b.Loop() {
|
||||
b.StopTimer()
|
||||
|
||||
// Create EphemeralRunner that will trigger error during cleanup
|
||||
now := metav1.Now()
|
||||
runner := NewMinimalEphemeralRunner("default", "test-runner")
|
||||
runner.Finalizers = []string{
|
||||
ephemeralRunnerFinalizerName,
|
||||
ephemeralRunnerActionsFinalizerName,
|
||||
}
|
||||
runner.DeletionTimestamp = &now
|
||||
runner.Status.RunnerID = 12345
|
||||
runner.Spec.GitHubConfigSecret = "config-secret"
|
||||
|
||||
configSecret := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "config-secret",
|
||||
Namespace: "default",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"github_token": []byte("test-token"),
|
||||
},
|
||||
}
|
||||
|
||||
k8sClient := clientfake.NewClientBuilder().
|
||||
WithScheme(scheme).
|
||||
WithObjects(runner, configSecret).
|
||||
WithStatusSubresource(&actionsv1alpha1.EphemeralRunner{}).
|
||||
Build()
|
||||
|
||||
reconciler := &EphemeralRunnerReconciler{
|
||||
Client: k8sClient,
|
||||
Scheme: scheme,
|
||||
Log: logr.Discard(),
|
||||
ResourceBuilder: ResourceBuilder{
|
||||
SecretResolver: secretresolver.New(k8sClient, multiClient),
|
||||
},
|
||||
}
|
||||
|
||||
req := ctrl.Request{
|
||||
NamespacedName: types.NamespacedName{
|
||||
Namespace: "default",
|
||||
Name: "test-runner",
|
||||
},
|
||||
}
|
||||
|
||||
b.StartTimer()
|
||||
|
||||
result, err := reconciler.Reconcile(ctx, req)
|
||||
|
||||
// Verify error is returned (expected behavior for retryable errors)
|
||||
if err == nil {
|
||||
b.Fatalf("expected error, got nil")
|
||||
}
|
||||
if result.RequeueAfter != 0 {
|
||||
b.Fatalf("expected no requeue delay on error, got %v", result.RequeueAfter)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkActionsGithub_Reconcile_EphemeralRunner_DonePhaseCleanup benchmarks
|
||||
// the path where an EphemeralRunner in Done phase triggers resource cleanup
|
||||
func BenchmarkActionsGithub_Reconcile_EphemeralRunner_DonePhaseCleanup(b *testing.B) {
|
||||
ctx := context.Background()
|
||||
|
||||
// Setup: Create scheme
|
||||
scheme := runtime.NewScheme()
|
||||
_ = actionsv1alpha1.AddToScheme(scheme)
|
||||
_ = corev1.AddToScheme(scheme)
|
||||
|
||||
multiClient := NewFakeMultiClient()
|
||||
|
||||
// Warmup iteration
|
||||
{
|
||||
runner := NewMinimalEphemeralRunner("default", "warmup-runner")
|
||||
runner.Finalizers = []string{
|
||||
ephemeralRunnerFinalizerName,
|
||||
ephemeralRunnerActionsFinalizerName,
|
||||
}
|
||||
runner.Status.Phase = "Succeeded"
|
||||
runner.Spec.GitHubConfigSecret = "config-secret"
|
||||
|
||||
// Create a pod that will be cleaned up
|
||||
pod := &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "warmup-runner",
|
||||
Namespace: "default",
|
||||
OwnerReferences: []metav1.OwnerReference{
|
||||
{
|
||||
APIVersion: actionsv1alpha1.GroupVersion.String(),
|
||||
Kind: "EphemeralRunner",
|
||||
Name: "warmup-runner",
|
||||
UID: runner.UID,
|
||||
},
|
||||
},
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "runner",
|
||||
Image: "ghcr.io/actions/runner:latest",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
configSecret := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "config-secret",
|
||||
Namespace: "default",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"github_token": []byte("test-token"),
|
||||
},
|
||||
}
|
||||
|
||||
k8sClient := clientfake.NewClientBuilder().
|
||||
WithScheme(scheme).
|
||||
WithObjects(runner, pod, configSecret).
|
||||
WithStatusSubresource(&actionsv1alpha1.EphemeralRunner{}).
|
||||
Build()
|
||||
|
||||
reconciler := &EphemeralRunnerReconciler{
|
||||
Client: k8sClient,
|
||||
Scheme: scheme,
|
||||
Log: logr.Discard(),
|
||||
ResourceBuilder: ResourceBuilder{
|
||||
SecretResolver: secretresolver.New(k8sClient, multiClient),
|
||||
},
|
||||
}
|
||||
|
||||
req := ctrl.Request{
|
||||
NamespacedName: types.NamespacedName{
|
||||
Namespace: "default",
|
||||
Name: "warmup-runner",
|
||||
},
|
||||
}
|
||||
|
||||
_, _ = reconciler.Reconcile(ctx, req)
|
||||
}
|
||||
|
||||
// Benchmark loop
|
||||
|
||||
for b.Loop() {
|
||||
b.StopTimer()
|
||||
|
||||
// Create EphemeralRunner in Done phase (Succeeded)
|
||||
runner := NewMinimalEphemeralRunner("default", "test-runner")
|
||||
runner.Finalizers = []string{
|
||||
ephemeralRunnerFinalizerName,
|
||||
ephemeralRunnerActionsFinalizerName,
|
||||
}
|
||||
runner.Status.Phase = "Succeeded"
|
||||
runner.Spec.GitHubConfigSecret = "config-secret"
|
||||
|
||||
// Create a pod that will be cleaned up
|
||||
pod := &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-runner",
|
||||
Namespace: "default",
|
||||
OwnerReferences: []metav1.OwnerReference{
|
||||
{
|
||||
APIVersion: actionsv1alpha1.GroupVersion.String(),
|
||||
Kind: "EphemeralRunner",
|
||||
Name: "test-runner",
|
||||
UID: runner.UID,
|
||||
},
|
||||
},
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "runner",
|
||||
Image: "ghcr.io/actions/runner:latest",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
configSecret := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "config-secret",
|
||||
Namespace: "default",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"github_token": []byte("test-token"),
|
||||
},
|
||||
}
|
||||
|
||||
k8sClient := clientfake.NewClientBuilder().
|
||||
WithScheme(scheme).
|
||||
WithObjects(runner, pod, configSecret).
|
||||
WithStatusSubresource(&actionsv1alpha1.EphemeralRunner{}).
|
||||
Build()
|
||||
|
||||
reconciler := &EphemeralRunnerReconciler{
|
||||
Client: k8sClient,
|
||||
Scheme: scheme,
|
||||
Log: logr.Discard(),
|
||||
ResourceBuilder: ResourceBuilder{
|
||||
SecretResolver: secretresolver.New(k8sClient, multiClient),
|
||||
},
|
||||
}
|
||||
|
||||
req := ctrl.Request{
|
||||
NamespacedName: types.NamespacedName{
|
||||
Namespace: "default",
|
||||
Name: "test-runner",
|
||||
},
|
||||
}
|
||||
|
||||
b.StartTimer()
|
||||
|
||||
_, err := reconciler.Reconcile(ctx, req)
|
||||
if err != nil {
|
||||
b.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkActionsGithub_Reconcile_EphemeralRunner_RequeueAfter benchmarks the
|
||||
// path where cleanup is not finished and a requeue is requested
|
||||
func BenchmarkActionsGithub_Reconcile_EphemeralRunner_RequeueAfter(b *testing.B) {
|
||||
ctx := context.Background()
|
||||
|
||||
// Setup: Create scheme
|
||||
scheme := runtime.NewScheme()
|
||||
_ = actionsv1alpha1.AddToScheme(scheme)
|
||||
_ = corev1.AddToScheme(scheme)
|
||||
|
||||
multiClient := fake.NewMultiClient(
|
||||
fake.WithClient(
|
||||
fake.NewClient(
|
||||
fake.WithRemoveRunner(scaleset.JobStillRunningError),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
// Warmup iteration
|
||||
{
|
||||
now := metav1.Now()
|
||||
runner := NewMinimalEphemeralRunner("default", "warmup-runner")
|
||||
runner.Finalizers = []string{
|
||||
ephemeralRunnerFinalizerName,
|
||||
ephemeralRunnerActionsFinalizerName,
|
||||
}
|
||||
runner.DeletionTimestamp = &now
|
||||
runner.Status.RunnerID = 12345
|
||||
runner.Spec.GitHubConfigSecret = "config-secret"
|
||||
|
||||
configSecret := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "config-secret",
|
||||
Namespace: "default",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"github_token": []byte("test-token"),
|
||||
},
|
||||
}
|
||||
|
||||
k8sClient := clientfake.NewClientBuilder().
|
||||
WithScheme(scheme).
|
||||
WithObjects(runner, configSecret).
|
||||
WithStatusSubresource(&actionsv1alpha1.EphemeralRunner{}).
|
||||
Build()
|
||||
|
||||
reconciler := &EphemeralRunnerReconciler{
|
||||
Client: k8sClient,
|
||||
Scheme: scheme,
|
||||
Log: logr.Discard(),
|
||||
ResourceBuilder: ResourceBuilder{
|
||||
SecretResolver: secretresolver.New(k8sClient, multiClient),
|
||||
},
|
||||
}
|
||||
|
||||
req := ctrl.Request{
|
||||
NamespacedName: types.NamespacedName{
|
||||
Namespace: "default",
|
||||
Name: "warmup-runner",
|
||||
},
|
||||
}
|
||||
|
||||
_, _ = reconciler.Reconcile(ctx, req)
|
||||
}
|
||||
|
||||
// Benchmark loop
|
||||
|
||||
for b.Loop() {
|
||||
b.StopTimer()
|
||||
|
||||
now := metav1.Now()
|
||||
runner := NewMinimalEphemeralRunner("default", "test-runner")
|
||||
runner.Finalizers = []string{
|
||||
ephemeralRunnerFinalizerName,
|
||||
ephemeralRunnerActionsFinalizerName,
|
||||
}
|
||||
runner.DeletionTimestamp = &now
|
||||
runner.Status.RunnerID = 12345
|
||||
runner.Spec.GitHubConfigSecret = "config-secret"
|
||||
|
||||
configSecret := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "config-secret",
|
||||
Namespace: "default",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"github_token": []byte("test-token"),
|
||||
},
|
||||
}
|
||||
|
||||
k8sClient := clientfake.NewClientBuilder().
|
||||
WithScheme(scheme).
|
||||
WithObjects(runner, configSecret).
|
||||
WithStatusSubresource(&actionsv1alpha1.EphemeralRunner{}).
|
||||
Build()
|
||||
|
||||
reconciler := &EphemeralRunnerReconciler{
|
||||
Client: k8sClient,
|
||||
Scheme: scheme,
|
||||
Log: logr.Discard(),
|
||||
ResourceBuilder: ResourceBuilder{
|
||||
SecretResolver: secretresolver.New(k8sClient, multiClient),
|
||||
},
|
||||
}
|
||||
|
||||
req := ctrl.Request{
|
||||
NamespacedName: types.NamespacedName{
|
||||
Namespace: "default",
|
||||
Name: "test-runner",
|
||||
},
|
||||
}
|
||||
|
||||
b.StartTimer()
|
||||
|
||||
result, err := reconciler.Reconcile(ctx, req)
|
||||
if err != nil {
|
||||
b.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if result.RequeueAfter != 30*time.Second {
|
||||
b.Fatalf("expected RequeueAfter=30s, got %v", result.RequeueAfter)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,418 @@
|
||||
/*
|
||||
Copyright 2020 The actions-runner-controller authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package actionsgithubcom
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
actionsv1alpha1 "github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1"
|
||||
"github.com/go-logr/logr"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
)
|
||||
|
||||
// BenchmarkActionsGithub_Reconcile_EphemeralRunnerSet_NoOp benchmarks the steady-state
|
||||
// no-op reconciliation path where the EphemeralRunnerSet already has finalizer,
|
||||
// correct spec hash, and the desired number of runners
|
||||
func BenchmarkActionsGithub_Reconcile_EphemeralRunnerSet_NoOp(b *testing.B) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
replicas int
|
||||
}{
|
||||
{name: "small_3_replicas", replicas: 3},
|
||||
{name: "medium_10_replicas", replicas: 10},
|
||||
{name: "large_50_replicas", replicas: 50},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
b.Run(tc.name, func(b *testing.B) {
|
||||
scheme := runtime.NewScheme()
|
||||
_ = actionsv1alpha1.AddToScheme(scheme)
|
||||
_ = corev1.AddToScheme(scheme)
|
||||
|
||||
ers := NewMinimalEphemeralRunnerSet("default", "test-ers")
|
||||
ers.Spec.Replicas = tc.replicas
|
||||
controllerutil.AddFinalizer(ers, EphemeralRunnerSetFinalizerName)
|
||||
|
||||
integrityHash := ephemeralRunnerSetIntegrityHash(ers)
|
||||
if ers.Annotations == nil {
|
||||
ers.Annotations = make(map[string]string)
|
||||
}
|
||||
ers.Annotations[AnnotationKeyIntegrityHash] = integrityHash
|
||||
|
||||
ers.Status.CurrentReplicas = tc.replicas
|
||||
ers.Status.Phase = actionsv1alpha1.EphemeralRunnerSetPhaseRunning
|
||||
|
||||
runners := make([]client.Object, 0, tc.replicas+1)
|
||||
runners = append(runners, ers)
|
||||
|
||||
for i := 0; i < tc.replicas; i++ {
|
||||
runner := NewMinimalEphemeralRunner("default", fmt.Sprintf("test-runner-%d", i))
|
||||
runner.Status.Phase = actionsv1alpha1.EphemeralRunnerPhaseRunning
|
||||
runner.Status.RunnerID = i + 100
|
||||
runner.OwnerReferences = []metav1.OwnerReference{
|
||||
{
|
||||
APIVersion: actionsv1alpha1.GroupVersion.String(),
|
||||
Kind: "EphemeralRunnerSet",
|
||||
Name: ers.Name,
|
||||
UID: ers.UID,
|
||||
Controller: func() *bool { t := true; return &t }(),
|
||||
},
|
||||
}
|
||||
runners = append(runners, runner)
|
||||
}
|
||||
|
||||
fakeClient := fake.NewClientBuilder().
|
||||
WithScheme(scheme).
|
||||
WithObjects(runners...).
|
||||
WithStatusSubresource(&actionsv1alpha1.EphemeralRunnerSet{}, &actionsv1alpha1.EphemeralRunner{}).
|
||||
WithIndex(&actionsv1alpha1.EphemeralRunner{}, resourceOwnerKey, newGroupVersionOwnerKindIndexer("EphemeralRunnerSet")).
|
||||
Build()
|
||||
|
||||
reconciler := &EphemeralRunnerSetReconciler{
|
||||
Client: fakeClient,
|
||||
Scheme: scheme,
|
||||
Log: logr.Discard(),
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
req := ctrl.Request{
|
||||
NamespacedName: types.NamespacedName{
|
||||
Namespace: "default",
|
||||
Name: "test-ers",
|
||||
},
|
||||
}
|
||||
|
||||
WarmupIteration(b, func() {
|
||||
_, _ = reconciler.Reconcile(ctx, req)
|
||||
})
|
||||
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
_, _ = reconciler.Reconcile(ctx, req)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkActionsGithub_Reconcile_EphemeralRunnerSet_ScaleUp benchmarks the scale-up
|
||||
// reconciliation path where new EphemeralRunners need to be created
|
||||
func BenchmarkActionsGithub_Reconcile_EphemeralRunnerSet_ScaleUp(b *testing.B) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
current int
|
||||
desired int
|
||||
scaleUpBy int
|
||||
}{
|
||||
{name: "small_scale_0_to_3", current: 0, desired: 3, scaleUpBy: 3},
|
||||
{name: "medium_scale_5_to_15", current: 5, desired: 15, scaleUpBy: 10},
|
||||
{name: "large_scale_10_to_60", current: 10, desired: 60, scaleUpBy: 50},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
b.Run(tc.name, func(b *testing.B) {
|
||||
scheme := runtime.NewScheme()
|
||||
_ = actionsv1alpha1.AddToScheme(scheme)
|
||||
_ = corev1.AddToScheme(scheme)
|
||||
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
b.StopTimer()
|
||||
|
||||
ers := NewMinimalEphemeralRunnerSet("default", "test-ers")
|
||||
ers.Spec.Replicas = tc.desired
|
||||
controllerutil.AddFinalizer(ers, EphemeralRunnerSetFinalizerName)
|
||||
|
||||
integrityHash := ephemeralRunnerSetIntegrityHash(ers)
|
||||
if ers.Annotations == nil {
|
||||
ers.Annotations = make(map[string]string)
|
||||
}
|
||||
ers.Annotations[AnnotationKeyIntegrityHash] = integrityHash
|
||||
|
||||
ers.Status.CurrentReplicas = tc.current
|
||||
ers.Status.Phase = actionsv1alpha1.EphemeralRunnerSetPhaseRunning
|
||||
|
||||
runners := make([]client.Object, 0, tc.current+1)
|
||||
runners = append(runners, ers)
|
||||
|
||||
for j := 0; j < tc.current; j++ {
|
||||
runner := NewMinimalEphemeralRunner("default", fmt.Sprintf("test-runner-%d", j))
|
||||
runner.Status.Phase = actionsv1alpha1.EphemeralRunnerPhaseRunning
|
||||
runner.Status.RunnerID = j + 100
|
||||
runner.OwnerReferences = []metav1.OwnerReference{
|
||||
{
|
||||
APIVersion: actionsv1alpha1.GroupVersion.String(),
|
||||
Kind: "EphemeralRunnerSet",
|
||||
Name: ers.Name,
|
||||
UID: ers.UID,
|
||||
Controller: func() *bool { t := true; return &t }(),
|
||||
},
|
||||
}
|
||||
runners = append(runners, runner)
|
||||
}
|
||||
|
||||
fakeClient := fake.NewClientBuilder().
|
||||
WithScheme(scheme).
|
||||
WithObjects(runners...).
|
||||
WithStatusSubresource(&actionsv1alpha1.EphemeralRunnerSet{}, &actionsv1alpha1.EphemeralRunner{}).
|
||||
WithIndex(&actionsv1alpha1.EphemeralRunner{}, resourceOwnerKey, newGroupVersionOwnerKindIndexer("EphemeralRunnerSet")).
|
||||
Build()
|
||||
|
||||
reconciler := &EphemeralRunnerSetReconciler{
|
||||
Client: fakeClient,
|
||||
Scheme: scheme,
|
||||
Log: logr.Discard(),
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
req := ctrl.Request{
|
||||
NamespacedName: types.NamespacedName{
|
||||
Namespace: "default",
|
||||
Name: "test-ers",
|
||||
},
|
||||
}
|
||||
|
||||
b.StartTimer()
|
||||
_, _ = reconciler.Reconcile(ctx, req)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkActionsGithub_Reconcile_EphemeralRunnerSet_FinalizerAdd benchmarks the
|
||||
// reconciliation path where the finalizer needs to be added to a new EphemeralRunnerSet
|
||||
func BenchmarkActionsGithub_Reconcile_EphemeralRunnerSet_FinalizerAdd(b *testing.B) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
replicas int
|
||||
}{
|
||||
{name: "small_3_replicas", replicas: 3},
|
||||
{name: "medium_10_replicas", replicas: 10},
|
||||
{name: "large_50_replicas", replicas: 50},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
b.Run(tc.name, func(b *testing.B) {
|
||||
scheme := runtime.NewScheme()
|
||||
_ = actionsv1alpha1.AddToScheme(scheme)
|
||||
_ = corev1.AddToScheme(scheme)
|
||||
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
b.StopTimer()
|
||||
|
||||
ers := NewMinimalEphemeralRunnerSet("default", "test-ers")
|
||||
ers.Spec.Replicas = tc.replicas
|
||||
|
||||
fakeClient := fake.NewClientBuilder().
|
||||
WithScheme(scheme).
|
||||
WithObjects(ers).
|
||||
WithStatusSubresource(&actionsv1alpha1.EphemeralRunnerSet{}, &actionsv1alpha1.EphemeralRunner{}).
|
||||
WithIndex(&actionsv1alpha1.EphemeralRunner{}, resourceOwnerKey, newGroupVersionOwnerKindIndexer("EphemeralRunnerSet")).
|
||||
Build()
|
||||
|
||||
reconciler := &EphemeralRunnerSetReconciler{
|
||||
Client: fakeClient,
|
||||
Scheme: scheme,
|
||||
Log: logr.Discard(),
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
req := ctrl.Request{
|
||||
NamespacedName: types.NamespacedName{
|
||||
Namespace: "default",
|
||||
Name: "test-ers",
|
||||
},
|
||||
}
|
||||
|
||||
b.StartTimer()
|
||||
_, _ = reconciler.Reconcile(ctx, req)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkActionsGithub_Reconcile_EphemeralRunnerSet_HashChangeDetection benchmarks the
|
||||
// reconciliation path that detects a spec hash change (but before cleanup begins)
|
||||
func BenchmarkActionsGithub_Reconcile_EphemeralRunnerSet_HashChangeDetection(b *testing.B) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
replicas int
|
||||
}{
|
||||
{name: "small_5_replicas", replicas: 5},
|
||||
{name: "medium_20_replicas", replicas: 20},
|
||||
{name: "large_100_replicas", replicas: 100},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
b.Run(tc.name, func(b *testing.B) {
|
||||
scheme := runtime.NewScheme()
|
||||
_ = actionsv1alpha1.AddToScheme(scheme)
|
||||
_ = corev1.AddToScheme(scheme)
|
||||
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
b.StopTimer()
|
||||
|
||||
ers := NewMinimalEphemeralRunnerSet("default", "test-ers")
|
||||
ers.Spec.Replicas = tc.replicas
|
||||
controllerutil.AddFinalizer(ers, EphemeralRunnerSetFinalizerName)
|
||||
|
||||
if ers.Annotations == nil {
|
||||
ers.Annotations = make(map[string]string)
|
||||
}
|
||||
ers.Annotations[AnnotationKeyIntegrityHash] = "outdated-hash-12345"
|
||||
|
||||
ers.Status.CurrentReplicas = tc.replicas
|
||||
ers.Status.Phase = actionsv1alpha1.EphemeralRunnerSetPhaseRunning
|
||||
|
||||
runners := make([]client.Object, 0, tc.replicas+1)
|
||||
runners = append(runners, ers)
|
||||
|
||||
for j := 0; j < tc.replicas; j++ {
|
||||
runner := NewMinimalEphemeralRunner("default", fmt.Sprintf("test-runner-%d", j))
|
||||
runner.Status.Phase = actionsv1alpha1.EphemeralRunnerPhaseSucceeded
|
||||
runner.Status.RunnerID = j + 100
|
||||
runner.OwnerReferences = []metav1.OwnerReference{
|
||||
{
|
||||
APIVersion: actionsv1alpha1.GroupVersion.String(),
|
||||
Kind: "EphemeralRunnerSet",
|
||||
Name: ers.Name,
|
||||
UID: ers.UID,
|
||||
Controller: func() *bool { t := true; return &t }(),
|
||||
},
|
||||
}
|
||||
runners = append(runners, runner)
|
||||
}
|
||||
|
||||
fakeClient := fake.NewClientBuilder().
|
||||
WithScheme(scheme).
|
||||
WithObjects(runners...).
|
||||
WithStatusSubresource(&actionsv1alpha1.EphemeralRunnerSet{}, &actionsv1alpha1.EphemeralRunner{}).
|
||||
WithIndex(&actionsv1alpha1.EphemeralRunner{}, resourceOwnerKey, newGroupVersionOwnerKindIndexer("EphemeralRunnerSet")).
|
||||
Build()
|
||||
|
||||
reconciler := &EphemeralRunnerSetReconciler{
|
||||
Client: fakeClient,
|
||||
Scheme: scheme,
|
||||
Log: logr.Discard(),
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
req := ctrl.Request{
|
||||
NamespacedName: types.NamespacedName{
|
||||
Namespace: "default",
|
||||
Name: "test-ers",
|
||||
},
|
||||
}
|
||||
|
||||
b.StartTimer()
|
||||
_, _ = reconciler.Reconcile(ctx, req)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkActionsGithub_Reconcile_EphemeralRunnerSet_OutdatedPhaseStatusUpdate benchmarks the
|
||||
// reconciliation path where the EphemeralRunnerSet is in Outdated phase with only finished runners
|
||||
func BenchmarkActionsGithub_Reconcile_EphemeralRunnerSet_OutdatedPhaseStatusUpdate(b *testing.B) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
runnerCount int
|
||||
}{
|
||||
{name: "small_5_runners", runnerCount: 5},
|
||||
{name: "medium_20_runners", runnerCount: 20},
|
||||
{name: "large_100_runners", runnerCount: 100},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
b.Run(tc.name, func(b *testing.B) {
|
||||
scheme := runtime.NewScheme()
|
||||
_ = actionsv1alpha1.AddToScheme(scheme)
|
||||
_ = corev1.AddToScheme(scheme)
|
||||
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
b.StopTimer()
|
||||
|
||||
ers := NewMinimalEphemeralRunnerSet("default", "test-ers")
|
||||
ers.Spec.Replicas = tc.runnerCount
|
||||
controllerutil.AddFinalizer(ers, EphemeralRunnerSetFinalizerName)
|
||||
|
||||
integrityHash := ephemeralRunnerSetIntegrityHash(ers)
|
||||
if ers.Annotations == nil {
|
||||
ers.Annotations = make(map[string]string)
|
||||
}
|
||||
ers.Annotations[AnnotationKeyIntegrityHash] = integrityHash
|
||||
|
||||
ers.Status.CurrentReplicas = tc.runnerCount
|
||||
ers.Status.Phase = actionsv1alpha1.EphemeralRunnerSetPhaseOutdated
|
||||
|
||||
runners := make([]client.Object, 0, tc.runnerCount+1)
|
||||
runners = append(runners, ers)
|
||||
|
||||
for j := 0; j < tc.runnerCount; j++ {
|
||||
runner := NewMinimalEphemeralRunner("default", fmt.Sprintf("test-runner-%d", j))
|
||||
runner.Status.Phase = actionsv1alpha1.EphemeralRunnerPhaseSucceeded
|
||||
runner.Status.RunnerID = j + 100
|
||||
runner.OwnerReferences = []metav1.OwnerReference{
|
||||
{
|
||||
APIVersion: actionsv1alpha1.GroupVersion.String(),
|
||||
Kind: "EphemeralRunnerSet",
|
||||
Name: ers.Name,
|
||||
UID: ers.UID,
|
||||
Controller: func() *bool { t := true; return &t }(),
|
||||
},
|
||||
}
|
||||
runners = append(runners, runner)
|
||||
}
|
||||
|
||||
fakeClient := fake.NewClientBuilder().
|
||||
WithScheme(scheme).
|
||||
WithObjects(runners...).
|
||||
WithStatusSubresource(&actionsv1alpha1.EphemeralRunnerSet{}, &actionsv1alpha1.EphemeralRunner{}).
|
||||
WithIndex(&actionsv1alpha1.EphemeralRunner{}, resourceOwnerKey, newGroupVersionOwnerKindIndexer("EphemeralRunnerSet")).
|
||||
Build()
|
||||
|
||||
reconciler := &EphemeralRunnerSetReconciler{
|
||||
Client: fakeClient,
|
||||
Scheme: scheme,
|
||||
Log: logr.Discard(),
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
req := ctrl.Request{
|
||||
NamespacedName: types.NamespacedName{
|
||||
Namespace: "default",
|
||||
Name: "test-ers",
|
||||
},
|
||||
}
|
||||
|
||||
b.StartTimer()
|
||||
_, _ = reconciler.Reconcile(ctx, req)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user