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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions api/v1alpha1/testrun_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,11 @@ type TestRunSpec struct {

Cleanup Cleanup `json:"cleanup,omitempty"`

// TTLSecondsAfterFinished, when set, specifies the TTL for Jobs created by this TestRun
// after they finish successfully or fail. Mirrors Job's TTLSecondsAfterFinished behavior.
// +kubebuilder:validation:Minimum=0
TTLSecondsAfterFinished *int32 `json:"ttlSecondsAfterFinished,omitempty"`

// TestRunID is reserved by Grafana Cloud k6. Do not set it manually.
TestRunID string `json:"testRunId,omitempty"` // PLZ reserved field

Expand Down
5 changes: 5 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions config/crd/bases/k6.io_testruns.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5880,6 +5880,10 @@ spec:
type: string
token:
type: string
ttlSecondsAfterFinished:
format: int32
minimum: 0
type: integer
required:
- parallelism
- script
Expand Down
13 changes: 13 additions & 0 deletions config/samples/k6_v1alpha1_configmap.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,16 @@ data:
failRate.add(result.status !== 200);
sleep(1);
}

---
apiVersion: k6.io/v1alpha1
kind: TestRun
metadata:
name: testrun-sample
spec:
parallelism: 2
ttlSecondsAfterFinished: 600
script:
configMap:
name: k6-test
file: test.js
11 changes: 11 additions & 0 deletions docs/crd-generated.md
Original file line number Diff line number Diff line change
Expand Up @@ -707,6 +707,17 @@ using the podAntiAffinity rule.<br/>
Token is reserved by Grafana Cloud k6. Do not set it manually.<br/>
</td>
<td>false</td>
</tr><tr>
<td><b>ttlSecondsAfterFinished</b></td>
<td>integer</td>
<td>
TTLSecondsAfterFinished, when set, specifies the TTL for Jobs created by this TestRun
after they finish successfully or fail. Mirrors Job's TTLSecondsAfterFinished behavior.<br/>
<br/>
<i>Format</i>: int32<br/>
<i>Minimum</i>: 0<br/>
</td>
<td>false</td>
</tr></tbody>
</table>

Expand Down
6 changes: 5 additions & 1 deletion pkg/resources/jobs/initializer.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,5 +134,9 @@ func NewInitializerJob(k6 *v1alpha1.TestRun, argLine string) (*batchv1.Job, erro
},
}

return job, nil
if k6.GetSpec().TTLSecondsAfterFinished != nil {
job.Spec.TTLSecondsAfterFinished = k6.GetSpec().TTLSecondsAfterFinished
}

return job, nil
}
30 changes: 30 additions & 0 deletions pkg/resources/jobs/initializer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,3 +131,33 @@ func TestNewInitializerJob(t *testing.T) {
t.Error(diff)
}
}

func TestInitializerJob_TTLSecondsAfterFinished(t *testing.T) {
ttl := int32(600)

k6 := &v1alpha1.TestRun{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
Namespace: "test",
},
Spec: v1alpha1.TestRunSpec{
Script: v1alpha1.K6Script{
ConfigMap: v1alpha1.K6Configmap{
Name: "test",
File: "test.js",
},
},
TTLSecondsAfterFinished: &ttl,
Initializer: &v1alpha1.Pod{},
},
}

job, err := NewInitializerJob(k6, "")
if err != nil {
t.Fatalf("NewInitializerJob errored: %v", err)
}

if job.Spec.TTLSecondsAfterFinished == nil || *job.Spec.TTLSecondsAfterFinished != ttl {
t.Fatalf("expected TTLSecondsAfterFinished=%d, got %v", ttl, job.Spec.TTLSecondsAfterFinished)
}
}
108 changes: 56 additions & 52 deletions pkg/resources/jobs/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,58 +160,62 @@ func NewRunnerJob(k6 *v1alpha1.TestRun, index int, tokenInfo *cloud.TokenInfo) (
volumeMounts := script.VolumeMount()
volumeMounts = append(volumeMounts, k6.GetSpec().Runner.VolumeMounts...)

job := &batchv1.Job{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: k6.NamespacedName().Namespace,
Labels: runnerLabels,
Annotations: runnerAnnotations,
},
Spec: batchv1.JobSpec{
BackoffLimit: &zero32,
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: runnerLabels,
Annotations: runnerAnnotations,
},
Spec: corev1.PodSpec{
AutomountServiceAccountToken: &automountServiceAccountToken,
ServiceAccountName: serviceAccountName,
Hostname: name,
RestartPolicy: corev1.RestartPolicyNever,
Affinity: k6.GetSpec().Runner.Affinity,
NodeSelector: k6.GetSpec().Runner.NodeSelector,
Tolerations: k6.GetSpec().Runner.Tolerations,
TopologySpreadConstraints: k6.GetSpec().Runner.TopologySpreadConstraints,
SecurityContext: &k6.GetSpec().Runner.SecurityContext,
ImagePullSecrets: k6.GetSpec().Runner.ImagePullSecrets,
InitContainers: getInitContainers(&k6.GetSpec().Runner, script),
Containers: []corev1.Container{{
Image: image,
ImagePullPolicy: k6.GetSpec().Runner.ImagePullPolicy,
Name: "k6",
Command: command,
Env: env,
Resources: k6.GetSpec().Runner.Resources,
VolumeMounts: volumeMounts,
Ports: ports,
EnvFrom: k6.GetSpec().Runner.EnvFrom,
LivenessProbe: generateProbe(k6.GetSpec().Runner.LivenessProbe),
ReadinessProbe: generateProbe(k6.GetSpec().Runner.ReadinessProbe),
SecurityContext: &k6.GetSpec().Runner.ContainerSecurityContext,
}},
TerminationGracePeriodSeconds: &zero,
Volumes: volumes,
},
},
},
}

if k6.GetSpec().Separate {
job.Spec.Template.Spec.Affinity = newAntiAffinity()
}

return job, nil
job := &batchv1.Job{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: k6.NamespacedName().Namespace,
Labels: runnerLabels,
Annotations: runnerAnnotations,
},
Spec: batchv1.JobSpec{
BackoffLimit: &zero32,
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: runnerLabels,
Annotations: runnerAnnotations,
},
Spec: corev1.PodSpec{
AutomountServiceAccountToken: &automountServiceAccountToken,
ServiceAccountName: serviceAccountName,
Hostname: name,
RestartPolicy: corev1.RestartPolicyNever,
Affinity: k6.GetSpec().Runner.Affinity,
NodeSelector: k6.GetSpec().Runner.NodeSelector,
Tolerations: k6.GetSpec().Runner.Tolerations,
TopologySpreadConstraints: k6.GetSpec().Runner.TopologySpreadConstraints,
SecurityContext: &k6.GetSpec().Runner.SecurityContext,
ImagePullSecrets: k6.GetSpec().Runner.ImagePullSecrets,
InitContainers: getInitContainers(&k6.GetSpec().Runner, script),
Containers: []corev1.Container{{
Image: image,
ImagePullPolicy: k6.GetSpec().Runner.ImagePullPolicy,
Name: "k6",
Command: command,
Env: env,
Resources: k6.GetSpec().Runner.Resources,
VolumeMounts: volumeMounts,
Ports: ports,
EnvFrom: k6.GetSpec().Runner.EnvFrom,
LivenessProbe: generateProbe(k6.GetSpec().Runner.LivenessProbe),
ReadinessProbe: generateProbe(k6.GetSpec().Runner.ReadinessProbe),
SecurityContext: &k6.GetSpec().Runner.ContainerSecurityContext,
}},
TerminationGracePeriodSeconds: &zero,
Volumes: volumes,
},
},
},
}

if k6.GetSpec().Separate {
job.Spec.Template.Spec.Affinity = newAntiAffinity()
}

if k6.GetSpec().TTLSecondsAfterFinished != nil {
job.Spec.TTLSecondsAfterFinished = k6.GetSpec().TTLSecondsAfterFinished
}

return job, nil
}

func NewRunnerService(k6 *v1alpha1.TestRun, index int) (*corev1.Service, error) {
Expand Down
31 changes: 31 additions & 0 deletions pkg/resources/jobs/runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,37 @@ func TestNewAntiAffinity(t *testing.T) {
}
}

func TestRunnerJob_TTLSecondsAfterFinished(t *testing.T) {
ttl := int32(300)

k6 := &v1alpha1.TestRun{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
Namespace: "test",
},
Spec: v1alpha1.TestRunSpec{
Script: v1alpha1.K6Script{
ConfigMap: v1alpha1.K6Configmap{
Name: "test",
File: "test.js",
},
},
Runner: v1alpha1.Pod{},
TTLSecondsAfterFinished: &ttl,
Parallelism: 1,
},
}

job, err := NewRunnerJob(k6, 1, nil)
if err != nil {
t.Fatalf("NewRunnerJob errored: %v", err)
}

if job.Spec.TTLSecondsAfterFinished == nil || *job.Spec.TTLSecondsAfterFinished != ttl {
t.Fatalf("expected TTLSecondsAfterFinished=%d, got %v", ttl, job.Spec.TTLSecondsAfterFinished)
}
}

func TestNewRunnerService(t *testing.T) {
expectedOutcome := &corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Expand Down
82 changes: 44 additions & 38 deletions pkg/resources/jobs/starter.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,42 +62,48 @@ func NewStarterJob(k6 *v1alpha1.TestRun, hostname []string) *batchv1.Job {
resourceRequirements = k6.GetSpec().Starter.Resources
}

return &batchv1.Job{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("%s-starter", k6.NamespacedName().Name),
Namespace: k6.NamespacedName().Namespace,
Labels: starterLabels,
Annotations: starterAnnotations,
},
Spec: batchv1.JobSpec{
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: starterLabels,
Annotations: starterAnnotations,
},
Spec: corev1.PodSpec{
AutomountServiceAccountToken: &automountServiceAccountToken,
ServiceAccountName: serviceAccountName,
Affinity: k6.GetSpec().Starter.Affinity,
NodeSelector: k6.GetSpec().Starter.NodeSelector,
Tolerations: k6.GetSpec().Starter.Tolerations,
TopologySpreadConstraints: k6.GetSpec().Starter.TopologySpreadConstraints,
RestartPolicy: corev1.RestartPolicyNever,
SecurityContext: &k6.GetSpec().Starter.SecurityContext,
ImagePullSecrets: k6.GetSpec().Starter.ImagePullSecrets,
Containers: []corev1.Container{
containers.NewStartContainer(
hostname,
starterImage,
k6.GetSpec().Starter.ImagePullPolicy,
command,
env,
k6.GetSpec().Starter.ContainerSecurityContext,
resourceRequirements,
),
},
},
},
},
}
job := &batchv1.Job{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("%s-starter", k6.NamespacedName().Name),
Namespace: k6.NamespacedName().Namespace,
Labels: starterLabels,
Annotations: starterAnnotations,
},
Spec: batchv1.JobSpec{
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: starterLabels,
Annotations: starterAnnotations,
},
Spec: corev1.PodSpec{
AutomountServiceAccountToken: &automountServiceAccountToken,
ServiceAccountName: serviceAccountName,
Affinity: k6.GetSpec().Starter.Affinity,
NodeSelector: k6.GetSpec().Starter.NodeSelector,
Tolerations: k6.GetSpec().Starter.Tolerations,
TopologySpreadConstraints: k6.GetSpec().Starter.TopologySpreadConstraints,
RestartPolicy: corev1.RestartPolicyNever,
SecurityContext: &k6.GetSpec().Starter.SecurityContext,
ImagePullSecrets: k6.GetSpec().Starter.ImagePullSecrets,
Containers: []corev1.Container{
containers.NewStartContainer(
hostname,
starterImage,
k6.GetSpec().Starter.ImagePullPolicy,
command,
env,
k6.GetSpec().Starter.ContainerSecurityContext,
resourceRequirements,
),
},
},
},
},
}

if k6.GetSpec().TTLSecondsAfterFinished != nil {
job.Spec.TTLSecondsAfterFinished = k6.GetSpec().TTLSecondsAfterFinished
}

return job
}
29 changes: 29 additions & 0 deletions pkg/resources/jobs/starter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -278,3 +278,32 @@ func TestNewStarterJobCustomResources(t *testing.T) {
t.Errorf("custom resources not applied: %v", diff)
}
}

func TestStarterJob_TTLSecondsAfterFinished(t *testing.T) {
ttl := int32(120)

k6 := &v1alpha1.TestRun{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
Namespace: "test",
},
Spec: v1alpha1.TestRunSpec{
Script: v1alpha1.K6Script{
ConfigMap: v1alpha1.K6Configmap{
Name: "test",
File: "test.js",
},
},
Starter: v1alpha1.Pod{
Image: "image",
},
TTLSecondsAfterFinished: &ttl,
},
}

job := NewStarterJob(k6, []string{"runner-0"})

if job.Spec.TTLSecondsAfterFinished == nil || *job.Spec.TTLSecondsAfterFinished != ttl {
t.Fatalf("expected TTLSecondsAfterFinished=%d, got %v", ttl, job.Spec.TTLSecondsAfterFinished)
}
}