From 1e2d59e5042e107634211f83f27c87e99bdba12f Mon Sep 17 00:00:00 2001 From: Olha Yevtushenko Date: Tue, 12 Sep 2023 17:34:42 +0300 Subject: [PATCH] add TestRun CRD with shared logic with K6 CRD --- PROJECT | 9 + api/v1alpha1/k6_types.go | 16 +- api/v1alpha1/k6conditions.go | 44 +- api/v1alpha1/testrun_types.go | 76 + api/v1alpha1/testruni.go | 22 + api/v1alpha1/zz_generated.deepcopy.go | 163 +- config/crd/bases/k6.io_testruns.yaml | 4900 +++++++++++++++++ config/crd/kustomization.yaml | 1 + .../crd/patches/cainjection_in_testruns.yaml | 8 + config/crd/patches/webhook_in_testruns.yaml | 17 + config/rbac/role.yaml | 21 + config/rbac/testrun_editor_role.yaml | 24 + config/rbac/testrun_viewer_role.yaml | 20 + config/samples/k6_v1alpha1_testrun.yaml | 10 + config/samples/kustomization.yaml | 1 + controllers/common.go | 10 +- controllers/k6_controller.go | 305 +- controllers/k6_create.go | 24 +- controllers/k6_finish.go | 14 +- controllers/k6_initialize.go | 46 +- controllers/k6_start.go | 18 +- controllers/k6_stop.go | 16 +- controllers/k6_stopped_jobs.go | 22 +- controllers/plz_controller.go | 2 +- controllers/plz_factory.go | 2 +- controllers/testrun_controller.go | 397 ++ main.go | 13 +- pkg/resources/jobs/helpers.go | 2 +- pkg/resources/jobs/initializer.go | 60 +- pkg/resources/jobs/initializer_test.go | 4 +- pkg/resources/jobs/runner.go | 110 +- pkg/resources/jobs/runner_test.go | 54 +- pkg/resources/jobs/starter.go | 44 +- pkg/resources/jobs/starter_test.go | 8 +- pkg/resources/jobs/stopper.go | 14 +- pkg/resources/jobs/stopper_test.go | 8 +- pkg/testrun/plz.go | 6 +- 37 files changed, 5903 insertions(+), 608 deletions(-) create mode 100644 api/v1alpha1/testrun_types.go create mode 100644 api/v1alpha1/testruni.go create mode 100644 config/crd/bases/k6.io_testruns.yaml create mode 100644 config/crd/patches/cainjection_in_testruns.yaml create mode 100644 config/crd/patches/webhook_in_testruns.yaml create mode 100644 config/rbac/testrun_editor_role.yaml create mode 100644 config/rbac/testrun_viewer_role.yaml create mode 100644 config/samples/k6_v1alpha1_testrun.yaml create mode 100644 controllers/testrun_controller.go diff --git a/PROJECT b/PROJECT index 83512d80..090f6e8a 100644 --- a/PROJECT +++ b/PROJECT @@ -25,4 +25,13 @@ resources: kind: PrivateLoadZone path: github.com/grafana/k6-operator/api/v1alpha1 version: v1alpha1 +- api: + crdVersion: v1beta1 + namespaced: true + controller: true + domain: io + group: k6 + kind: TestRun + path: github.com/grafana/k6-operator/api/v1alpha1 + version: v1alpha1 version: "3" diff --git a/api/v1alpha1/k6_types.go b/api/v1alpha1/k6_types.go index 73a776dd..91ca7669 100644 --- a/api/v1alpha1/k6_types.go +++ b/api/v1alpha1/k6_types.go @@ -73,8 +73,8 @@ type K6Scuttle struct { QuitWithoutEnvoyTimeout string `json:"quitWithoutEnvoyTimeout,omitempty"` } -// K6Spec defines the desired state of K6 -type K6Spec struct { +// TestRunSpec defines the desired state of TestRun +type TestRunSpec struct { Script K6Script `json:"script"` Parallelism int32 `json:"parallelism"` Separate bool `json:"separate,omitempty"` @@ -121,8 +121,8 @@ type Cleanup string // +kubebuilder:validation:Enum=initialization;initialized;created;started;stopped;finished;error type Stage string -// K6Status defines the observed state of K6 -type K6Status struct { +// TestRunStatus defines the observed state of TestRun +type TestRunStatus struct { Stage Stage `json:"stage,omitempty"` TestRunID string `json:"testRunId,omitempty"` AggregationVars string `json:"aggregationVars,omitempty"` @@ -136,12 +136,14 @@ type K6Status struct { // +kubebuilder:printcolumn:name="Stage",type="string",JSONPath=".status.stage",description="Stage" // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" // +kubebuilder:printcolumn:name="TestRunID",type="string",JSONPath=".status.testRunId" +// +kubebuilder:deprecated:warning="This CRD is deprecated in favor of testruns.k6.io/v1alpha1" + type K6 struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - Spec K6Spec `json:"spec,omitempty"` - Status K6Status `json:"status,omitempty"` + Spec TestRunSpec `json:"spec,omitempty"` + Status TestRunStatus `json:"status,omitempty"` } // K6List contains a list of K6 @@ -157,7 +159,7 @@ func init() { } // Parse extracts Script data bits from K6 spec and performs basic validation -func (k6 K6Spec) ParseScript() (*types.Script, error) { +func (k6 TestRunSpec) ParseScript() (*types.Script, error) { spec := k6.Script s := &types.Script{ Filename: "test.js", diff --git a/api/v1alpha1/k6conditions.go b/api/v1alpha1/k6conditions.go index 2745ffdb..71d7df03 100644 --- a/api/v1alpha1/k6conditions.go +++ b/api/v1alpha1/k6conditions.go @@ -54,9 +54,9 @@ const ( ) // Initialize defines only conditions common to all test runs. -func (k6 *K6) Initialize() { +func Initialize(k6 TestRunI) { t := metav1.Now() - k6.Status.Conditions = []metav1.Condition{ + k6.GetStatus().Conditions = []metav1.Condition{ metav1.Condition{ Type: CloudTestRun, Status: metav1.ConditionUnknown, @@ -74,39 +74,39 @@ func (k6 *K6) Initialize() { } // PLZ test run case - if len(k6.Spec.TestRunID) > 0 { - k6.UpdateCondition(CloudTestRun, metav1.ConditionTrue) - k6.UpdateCondition(CloudPLZTestRun, metav1.ConditionTrue) - k6.UpdateCondition(CloudTestRunCreated, metav1.ConditionTrue) - k6.UpdateCondition(CloudTestRunFinalized, metav1.ConditionFalse) - k6.UpdateCondition(CloudTestRunAborted, metav1.ConditionFalse) - - k6.Status.TestRunID = k6.Spec.TestRunID + if len(k6.GetSpec().TestRunID) > 0 { + UpdateCondition(k6, CloudTestRun, metav1.ConditionTrue) + UpdateCondition(k6, CloudPLZTestRun, metav1.ConditionTrue) + UpdateCondition(k6, CloudTestRunCreated, metav1.ConditionTrue) + UpdateCondition(k6, CloudTestRunFinalized, metav1.ConditionFalse) + UpdateCondition(k6, CloudTestRunAborted, metav1.ConditionFalse) + + k6.GetStatus().TestRunID = k6.GetSpec().TestRunID } else { - k6.UpdateCondition(CloudPLZTestRun, metav1.ConditionFalse) + UpdateCondition(k6, CloudPLZTestRun, metav1.ConditionFalse) // PLZ test run can be defined only via spec.testRunId; // otherwise it's not a PLZ test run. } } -func (k6 *K6) UpdateCondition(conditionType string, conditionStatus metav1.ConditionStatus) { - types.UpdateCondition(&k6.Status.Conditions, conditionType, conditionStatus) +func UpdateCondition(k6 TestRunI, conditionType string, conditionStatus metav1.ConditionStatus) { + types.UpdateCondition(&k6.GetStatus().Conditions, conditionType, conditionStatus) } -func (k6 K6) IsTrue(conditionType string) bool { - return meta.IsStatusConditionTrue(k6.Status.Conditions, conditionType) +func IsTrue(k6 TestRunI, conditionType string) bool { + return meta.IsStatusConditionTrue(k6.GetStatus().Conditions, conditionType) } -func (k6 K6) IsFalse(conditionType string) bool { - return meta.IsStatusConditionFalse(k6.Status.Conditions, conditionType) +func IsFalse(k6 TestRunI, conditionType string) bool { + return meta.IsStatusConditionFalse(k6.GetStatus().Conditions, conditionType) } -func (k6 K6) IsUnknown(conditionType string) bool { - return !k6.IsFalse(conditionType) && !k6.IsTrue(conditionType) +func IsUnknown(k6 TestRunI, conditionType string) bool { + return !IsFalse(k6, conditionType) && !IsTrue(k6, conditionType) } -func (k6 K6) LastUpdate(conditionType string) (time.Time, bool) { - cond := meta.FindStatusCondition(k6.Status.Conditions, conditionType) +func LastUpdate(k6 TestRunI, conditionType string) (time.Time, bool) { + cond := meta.FindStatusCondition(k6.GetStatus().Conditions, conditionType) if cond != nil { return cond.LastTransitionTime.Time, true } @@ -116,7 +116,7 @@ func (k6 K6) LastUpdate(conditionType string) (time.Time, bool) { // SetIfNewer changes k6status only if changes in proposedStatus are consistent // with the expected progression of a test run. If there were any acceptable // changes proposed, it returns true. -func (k6status *K6Status) SetIfNewer(proposedStatus K6Status) (isNewer bool) { +func (k6status *TestRunStatus) SetIfNewer(proposedStatus TestRunStatus) (isNewer bool) { isNewer = types.SetIfNewer(&k6status.Conditions, proposedStatus.Conditions, func(proposedCondition metav1.Condition) (isNewer bool) { // Accept change of test run ID only if it's not set yet and together with diff --git a/api/v1alpha1/testrun_types.go b/api/v1alpha1/testrun_types.go new file mode 100644 index 00000000..94c608f5 --- /dev/null +++ b/api/v1alpha1/testrun_types.go @@ -0,0 +1,76 @@ +/* + + +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 v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" +) + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status +//+kubebuilder:printcolumn:name="Stage",type="string",JSONPath=".status.stage",description="Stage" +//+kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" +//+kubebuilder:printcolumn:name="TestRunID",type="string",JSONPath=".status.testRunId" + +// TestRun is the Schema for the testruns API +type TestRun struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec TestRunSpec `json:"spec,omitempty"` + Status TestRunStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// TestRunList contains a list of TestRun +type TestRunList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []TestRun `json:"items"` +} + +func init() { + SchemeBuilder.Register(&TestRun{}, &TestRunList{}) +} + +// TestRunI implementation for TestRun +func (k6 *TestRun) GetStatus() *TestRunStatus { + return &k6.Status +} + +func (k6 *TestRun) GetSpec() *TestRunSpec { + return &k6.Spec +} + +func (k6 *TestRun) NamespacedName() types.NamespacedName { + return types.NamespacedName{Namespace: k6.Namespace, Name: k6.Name} +} + +// TestRunI implementation for TestRun +func (k6 *K6) GetStatus() *TestRunStatus { + return &k6.Status +} + +func (k6 *K6) GetSpec() *TestRunSpec { + return &k6.Spec +} + +func (k6 *K6) NamespacedName() types.NamespacedName { + return types.NamespacedName{Namespace: k6.Namespace, Name: k6.Name} +} diff --git a/api/v1alpha1/testruni.go b/api/v1alpha1/testruni.go new file mode 100644 index 00000000..5320e296 --- /dev/null +++ b/api/v1alpha1/testruni.go @@ -0,0 +1,22 @@ +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// TestRunI is meant as abstraction over both TestRun and K6 while +// both types are supported. Consider removing it, when K6 is deprecated. +// +kubebuilder:object:generate=false + +type TestRunI interface { + runtime.Object + metav1.Object + client.Object + + GetStatus() *TestRunStatus + GetSpec() *TestRunSpec + NamespacedName() types.NamespacedName +} diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 03a00354..599c8e2c 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -24,7 +24,7 @@ package v1alpha1 import ( "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - runtime "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime" ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. @@ -179,57 +179,6 @@ func (in *K6Scuttle) DeepCopy() *K6Scuttle { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *K6Spec) DeepCopyInto(out *K6Spec) { - *out = *in - out.Script = in.Script - if in.Ports != nil { - in, out := &in.Ports, &out.Ports - *out = make([]v1.ContainerPort, len(*in)) - copy(*out, *in) - } - if in.Initializer != nil { - in, out := &in.Initializer, &out.Initializer - *out = new(Pod) - (*in).DeepCopyInto(*out) - } - in.Starter.DeepCopyInto(&out.Starter) - in.Runner.DeepCopyInto(&out.Runner) - out.Scuttle = in.Scuttle -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new K6Spec. -func (in *K6Spec) DeepCopy() *K6Spec { - if in == nil { - return nil - } - out := new(K6Spec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *K6Status) DeepCopyInto(out *K6Status) { - *out = *in - if in.Conditions != nil { - in, out := &in.Conditions, &out.Conditions - *out = make([]metav1.Condition, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new K6Status. -func (in *K6Status) DeepCopy() *K6Status { - if in == nil { - return nil - } - out := new(K6Status) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *K6VolumeClaim) DeepCopyInto(out *K6VolumeClaim) { *out = *in @@ -470,3 +419,113 @@ func (in *PrivateLoadZoneStatus) DeepCopy() *PrivateLoadZoneStatus { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TestRun) DeepCopyInto(out *TestRun) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TestRun. +func (in *TestRun) DeepCopy() *TestRun { + if in == nil { + return nil + } + out := new(TestRun) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *TestRun) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TestRunList) DeepCopyInto(out *TestRunList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]TestRun, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TestRunList. +func (in *TestRunList) DeepCopy() *TestRunList { + if in == nil { + return nil + } + out := new(TestRunList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *TestRunList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TestRunSpec) DeepCopyInto(out *TestRunSpec) { + *out = *in + out.Script = in.Script + if in.Ports != nil { + in, out := &in.Ports, &out.Ports + *out = make([]v1.ContainerPort, len(*in)) + copy(*out, *in) + } + if in.Initializer != nil { + in, out := &in.Initializer, &out.Initializer + *out = new(Pod) + (*in).DeepCopyInto(*out) + } + in.Starter.DeepCopyInto(&out.Starter) + in.Runner.DeepCopyInto(&out.Runner) + out.Scuttle = in.Scuttle +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TestRunSpec. +func (in *TestRunSpec) DeepCopy() *TestRunSpec { + if in == nil { + return nil + } + out := new(TestRunSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TestRunStatus) DeepCopyInto(out *TestRunStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]metav1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TestRunStatus. +func (in *TestRunStatus) DeepCopy() *TestRunStatus { + if in == nil { + return nil + } + out := new(TestRunStatus) + in.DeepCopyInto(out) + return out +} diff --git a/config/crd/bases/k6.io_testruns.yaml b/config/crd/bases/k6.io_testruns.yaml new file mode 100644 index 00000000..a1d14cf5 --- /dev/null +++ b/config/crd/bases/k6.io_testruns.yaml @@ -0,0 +1,4900 @@ + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.3.0 + creationTimestamp: null + name: testruns.k6.io +spec: + group: k6.io + names: + kind: TestRun + listKind: TestRunList + plural: testruns + singular: testrun + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: Stage + jsonPath: .status.stage + name: Stage + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .status.testRunId + name: TestRunID + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + arguments: + type: string + cleanup: + enum: + - post + type: string + initializer: + properties: + affinity: + properties: + nodeAffinity: + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + preference: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + weight: + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + properties: + nodeSelectorTerms: + items: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + type: array + required: + - nodeSelectorTerms + type: object + type: object + podAffinity: + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + podAffinityTerm: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaceSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + weight: + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + items: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaceSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + type: array + type: object + podAntiAffinity: + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + podAffinityTerm: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaceSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + weight: + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + items: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaceSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + type: array + type: object + type: object + automountServiceAccountToken: + type: string + env: + items: + properties: + name: + type: string + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + secretKeyRef: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + envFrom: + items: + properties: + configMapRef: + properties: + name: + type: string + optional: + type: boolean + type: object + prefix: + type: string + secretRef: + properties: + name: + type: string + optional: + type: boolean + type: object + type: object + type: array + image: + type: string + imagePullPolicy: + type: string + imagePullSecrets: + items: + properties: + name: + type: string + type: object + type: array + initContainers: + items: + properties: + args: + items: + type: string + type: array + command: + items: + type: string + type: array + env: + items: + properties: + name: + type: string + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + secretKeyRef: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + envFrom: + items: + properties: + configMapRef: + properties: + name: + type: string + optional: + type: boolean + type: object + prefix: + type: string + secretRef: + properties: + name: + type: string + optional: + type: boolean + type: object + type: object + type: array + image: + type: string + name: + type: string + volumeMounts: + items: + properties: + mountPath: + type: string + mountPropagation: + type: string + name: + type: string + readOnly: + type: boolean + subPath: + type: string + subPathExpr: + type: string + required: + - mountPath + - name + type: object + type: array + workingDir: + type: string + type: object + type: array + livenessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + nodeSelector: + additionalProperties: + type: string + type: object + readinessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + resources: + properties: + claims: + items: + properties: + name: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + securityContext: + properties: + fsGroup: + format: int64 + type: integer + fsGroupChangePolicy: + type: string + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + supplementalGroups: + items: + format: int64 + type: integer + type: array + sysctls: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object + type: object + serviceAccountName: + type: string + tolerations: + items: + properties: + effect: + type: string + key: + type: string + operator: + type: string + tolerationSeconds: + format: int64 + type: integer + value: + type: string + type: object + type: array + volumeMounts: + items: + properties: + mountPath: + type: string + mountPropagation: + type: string + name: + type: string + readOnly: + type: boolean + subPath: + type: string + subPathExpr: + type: string + required: + - mountPath + - name + type: object + type: array + volumes: + items: + properties: + awsElasticBlockStore: + properties: + fsType: + type: string + partition: + format: int32 + type: integer + readOnly: + type: boolean + volumeID: + type: string + required: + - volumeID + type: object + azureDisk: + properties: + cachingMode: + type: string + diskName: + type: string + diskURI: + type: string + fsType: + type: string + kind: + type: string + readOnly: + type: boolean + required: + - diskName + - diskURI + type: object + azureFile: + properties: + readOnly: + type: boolean + secretName: + type: string + shareName: + type: string + required: + - secretName + - shareName + type: object + cephfs: + properties: + monitors: + items: + type: string + type: array + path: + type: string + readOnly: + type: boolean + secretFile: + type: string + secretRef: + properties: + name: + type: string + type: object + user: + type: string + required: + - monitors + type: object + cinder: + properties: + fsType: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + volumeID: + type: string + required: + - volumeID + type: object + configMap: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + name: + type: string + optional: + type: boolean + type: object + csi: + properties: + driver: + type: string + fsType: + type: string + nodePublishSecretRef: + properties: + name: + type: string + type: object + readOnly: + type: boolean + volumeAttributes: + additionalProperties: + type: string + type: object + required: + - driver + type: object + downwardAPI: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + mode: + format: int32 + type: integer + path: + type: string + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + required: + - path + type: object + type: array + type: object + emptyDir: + properties: + medium: + type: string + sizeLimit: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + ephemeral: + properties: + volumeClaimTemplate: + properties: + metadata: + type: object + spec: + properties: + accessModes: + items: + type: string + type: array + dataSource: + properties: + apiGroup: + type: string + kind: + type: string + name: + type: string + required: + - kind + - name + type: object + dataSourceRef: + properties: + apiGroup: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + required: + - kind + - name + type: object + resources: + properties: + claims: + items: + properties: + name: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + selector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + storageClassName: + type: string + volumeMode: + type: string + volumeName: + type: string + type: object + required: + - spec + type: object + type: object + fc: + properties: + fsType: + type: string + lun: + format: int32 + type: integer + readOnly: + type: boolean + targetWWNs: + items: + type: string + type: array + wwids: + items: + type: string + type: array + type: object + flexVolume: + properties: + driver: + type: string + fsType: + type: string + options: + additionalProperties: + type: string + type: object + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + required: + - driver + type: object + flocker: + properties: + datasetName: + type: string + datasetUUID: + type: string + type: object + gcePersistentDisk: + properties: + fsType: + type: string + partition: + format: int32 + type: integer + pdName: + type: string + readOnly: + type: boolean + required: + - pdName + type: object + gitRepo: + properties: + directory: + type: string + repository: + type: string + revision: + type: string + required: + - repository + type: object + glusterfs: + properties: + endpoints: + type: string + path: + type: string + readOnly: + type: boolean + required: + - endpoints + - path + type: object + hostPath: + properties: + path: + type: string + type: + type: string + required: + - path + type: object + iscsi: + properties: + chapAuthDiscovery: + type: boolean + chapAuthSession: + type: boolean + fsType: + type: string + initiatorName: + type: string + iqn: + type: string + iscsiInterface: + type: string + lun: + format: int32 + type: integer + portals: + items: + type: string + type: array + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + targetPortal: + type: string + required: + - iqn + - lun + - targetPortal + type: object + name: + type: string + nfs: + properties: + path: + type: string + readOnly: + type: boolean + server: + type: string + required: + - path + - server + type: object + persistentVolumeClaim: + properties: + claimName: + type: string + readOnly: + type: boolean + required: + - claimName + type: object + photonPersistentDisk: + properties: + fsType: + type: string + pdID: + type: string + required: + - pdID + type: object + portworxVolume: + properties: + fsType: + type: string + readOnly: + type: boolean + volumeID: + type: string + required: + - volumeID + type: object + projected: + properties: + defaultMode: + format: int32 + type: integer + sources: + items: + properties: + configMap: + properties: + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + name: + type: string + optional: + type: boolean + type: object + downwardAPI: + properties: + items: + items: + properties: + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + mode: + format: int32 + type: integer + path: + type: string + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + required: + - path + type: object + type: array + type: object + secret: + properties: + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + name: + type: string + optional: + type: boolean + type: object + serviceAccountToken: + properties: + audience: + type: string + expirationSeconds: + format: int64 + type: integer + path: + type: string + required: + - path + type: object + type: object + type: array + type: object + quobyte: + properties: + group: + type: string + readOnly: + type: boolean + registry: + type: string + tenant: + type: string + user: + type: string + volume: + type: string + required: + - registry + - volume + type: object + rbd: + properties: + fsType: + type: string + image: + type: string + keyring: + type: string + monitors: + items: + type: string + type: array + pool: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + user: + type: string + required: + - image + - monitors + type: object + scaleIO: + properties: + fsType: + type: string + gateway: + type: string + protectionDomain: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + sslEnabled: + type: boolean + storageMode: + type: string + storagePool: + type: string + system: + type: string + volumeName: + type: string + required: + - gateway + - secretRef + - system + type: object + secret: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + optional: + type: boolean + secretName: + type: string + type: object + storageos: + properties: + fsType: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + volumeName: + type: string + volumeNamespace: + type: string + type: object + vsphereVolume: + properties: + fsType: + type: string + storagePolicyID: + type: string + storagePolicyName: + type: string + volumePath: + type: string + required: + - volumePath + type: object + required: + - name + type: object + type: array + type: object + parallelism: + format: int32 + type: integer + paused: + type: string + ports: + items: + properties: + containerPort: + format: int32 + type: integer + hostIP: + type: string + hostPort: + format: int32 + type: integer + name: + type: string + protocol: + type: string + required: + - containerPort + type: object + type: array + quiet: + type: string + runner: + properties: + affinity: + properties: + nodeAffinity: + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + preference: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + weight: + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + properties: + nodeSelectorTerms: + items: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + type: array + required: + - nodeSelectorTerms + type: object + type: object + podAffinity: + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + podAffinityTerm: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaceSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + weight: + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + items: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaceSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + type: array + type: object + podAntiAffinity: + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + podAffinityTerm: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaceSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + weight: + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + items: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaceSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + type: array + type: object + type: object + automountServiceAccountToken: + type: string + env: + items: + properties: + name: + type: string + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + secretKeyRef: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + envFrom: + items: + properties: + configMapRef: + properties: + name: + type: string + optional: + type: boolean + type: object + prefix: + type: string + secretRef: + properties: + name: + type: string + optional: + type: boolean + type: object + type: object + type: array + image: + type: string + imagePullPolicy: + type: string + imagePullSecrets: + items: + properties: + name: + type: string + type: object + type: array + initContainers: + items: + properties: + args: + items: + type: string + type: array + command: + items: + type: string + type: array + env: + items: + properties: + name: + type: string + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + secretKeyRef: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + envFrom: + items: + properties: + configMapRef: + properties: + name: + type: string + optional: + type: boolean + type: object + prefix: + type: string + secretRef: + properties: + name: + type: string + optional: + type: boolean + type: object + type: object + type: array + image: + type: string + name: + type: string + volumeMounts: + items: + properties: + mountPath: + type: string + mountPropagation: + type: string + name: + type: string + readOnly: + type: boolean + subPath: + type: string + subPathExpr: + type: string + required: + - mountPath + - name + type: object + type: array + workingDir: + type: string + type: object + type: array + livenessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + nodeSelector: + additionalProperties: + type: string + type: object + readinessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + resources: + properties: + claims: + items: + properties: + name: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + securityContext: + properties: + fsGroup: + format: int64 + type: integer + fsGroupChangePolicy: + type: string + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + supplementalGroups: + items: + format: int64 + type: integer + type: array + sysctls: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object + type: object + serviceAccountName: + type: string + tolerations: + items: + properties: + effect: + type: string + key: + type: string + operator: + type: string + tolerationSeconds: + format: int64 + type: integer + value: + type: string + type: object + type: array + volumeMounts: + items: + properties: + mountPath: + type: string + mountPropagation: + type: string + name: + type: string + readOnly: + type: boolean + subPath: + type: string + subPathExpr: + type: string + required: + - mountPath + - name + type: object + type: array + volumes: + items: + properties: + awsElasticBlockStore: + properties: + fsType: + type: string + partition: + format: int32 + type: integer + readOnly: + type: boolean + volumeID: + type: string + required: + - volumeID + type: object + azureDisk: + properties: + cachingMode: + type: string + diskName: + type: string + diskURI: + type: string + fsType: + type: string + kind: + type: string + readOnly: + type: boolean + required: + - diskName + - diskURI + type: object + azureFile: + properties: + readOnly: + type: boolean + secretName: + type: string + shareName: + type: string + required: + - secretName + - shareName + type: object + cephfs: + properties: + monitors: + items: + type: string + type: array + path: + type: string + readOnly: + type: boolean + secretFile: + type: string + secretRef: + properties: + name: + type: string + type: object + user: + type: string + required: + - monitors + type: object + cinder: + properties: + fsType: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + volumeID: + type: string + required: + - volumeID + type: object + configMap: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + name: + type: string + optional: + type: boolean + type: object + csi: + properties: + driver: + type: string + fsType: + type: string + nodePublishSecretRef: + properties: + name: + type: string + type: object + readOnly: + type: boolean + volumeAttributes: + additionalProperties: + type: string + type: object + required: + - driver + type: object + downwardAPI: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + mode: + format: int32 + type: integer + path: + type: string + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + required: + - path + type: object + type: array + type: object + emptyDir: + properties: + medium: + type: string + sizeLimit: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + ephemeral: + properties: + volumeClaimTemplate: + properties: + metadata: + type: object + spec: + properties: + accessModes: + items: + type: string + type: array + dataSource: + properties: + apiGroup: + type: string + kind: + type: string + name: + type: string + required: + - kind + - name + type: object + dataSourceRef: + properties: + apiGroup: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + required: + - kind + - name + type: object + resources: + properties: + claims: + items: + properties: + name: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + selector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + storageClassName: + type: string + volumeMode: + type: string + volumeName: + type: string + type: object + required: + - spec + type: object + type: object + fc: + properties: + fsType: + type: string + lun: + format: int32 + type: integer + readOnly: + type: boolean + targetWWNs: + items: + type: string + type: array + wwids: + items: + type: string + type: array + type: object + flexVolume: + properties: + driver: + type: string + fsType: + type: string + options: + additionalProperties: + type: string + type: object + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + required: + - driver + type: object + flocker: + properties: + datasetName: + type: string + datasetUUID: + type: string + type: object + gcePersistentDisk: + properties: + fsType: + type: string + partition: + format: int32 + type: integer + pdName: + type: string + readOnly: + type: boolean + required: + - pdName + type: object + gitRepo: + properties: + directory: + type: string + repository: + type: string + revision: + type: string + required: + - repository + type: object + glusterfs: + properties: + endpoints: + type: string + path: + type: string + readOnly: + type: boolean + required: + - endpoints + - path + type: object + hostPath: + properties: + path: + type: string + type: + type: string + required: + - path + type: object + iscsi: + properties: + chapAuthDiscovery: + type: boolean + chapAuthSession: + type: boolean + fsType: + type: string + initiatorName: + type: string + iqn: + type: string + iscsiInterface: + type: string + lun: + format: int32 + type: integer + portals: + items: + type: string + type: array + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + targetPortal: + type: string + required: + - iqn + - lun + - targetPortal + type: object + name: + type: string + nfs: + properties: + path: + type: string + readOnly: + type: boolean + server: + type: string + required: + - path + - server + type: object + persistentVolumeClaim: + properties: + claimName: + type: string + readOnly: + type: boolean + required: + - claimName + type: object + photonPersistentDisk: + properties: + fsType: + type: string + pdID: + type: string + required: + - pdID + type: object + portworxVolume: + properties: + fsType: + type: string + readOnly: + type: boolean + volumeID: + type: string + required: + - volumeID + type: object + projected: + properties: + defaultMode: + format: int32 + type: integer + sources: + items: + properties: + configMap: + properties: + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + name: + type: string + optional: + type: boolean + type: object + downwardAPI: + properties: + items: + items: + properties: + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + mode: + format: int32 + type: integer + path: + type: string + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + required: + - path + type: object + type: array + type: object + secret: + properties: + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + name: + type: string + optional: + type: boolean + type: object + serviceAccountToken: + properties: + audience: + type: string + expirationSeconds: + format: int64 + type: integer + path: + type: string + required: + - path + type: object + type: object + type: array + type: object + quobyte: + properties: + group: + type: string + readOnly: + type: boolean + registry: + type: string + tenant: + type: string + user: + type: string + volume: + type: string + required: + - registry + - volume + type: object + rbd: + properties: + fsType: + type: string + image: + type: string + keyring: + type: string + monitors: + items: + type: string + type: array + pool: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + user: + type: string + required: + - image + - monitors + type: object + scaleIO: + properties: + fsType: + type: string + gateway: + type: string + protectionDomain: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + sslEnabled: + type: boolean + storageMode: + type: string + storagePool: + type: string + system: + type: string + volumeName: + type: string + required: + - gateway + - secretRef + - system + type: object + secret: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + optional: + type: boolean + secretName: + type: string + type: object + storageos: + properties: + fsType: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + volumeName: + type: string + volumeNamespace: + type: string + type: object + vsphereVolume: + properties: + fsType: + type: string + storagePolicyID: + type: string + storagePolicyName: + type: string + volumePath: + type: string + required: + - volumePath + type: object + required: + - name + type: object + type: array + type: object + script: + properties: + configMap: + properties: + file: + type: string + name: + type: string + required: + - name + type: object + localFile: + type: string + volumeClaim: + properties: + file: + type: string + name: + type: string + required: + - name + type: object + type: object + scuttle: + properties: + disableLogging: + type: boolean + enabled: + type: string + envoyAdminApi: + type: string + genericQuitEndpoint: + type: string + istioQuitApi: + type: string + neverKillIstio: + type: boolean + neverKillIstioOnFailure: + type: boolean + quitWithoutEnvoyTimeout: + type: string + startWithoutEnvoy: + type: boolean + waitForEnvoyTimeout: + type: string + type: object + separate: + type: boolean + starter: + properties: + affinity: + properties: + nodeAffinity: + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + preference: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + weight: + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + properties: + nodeSelectorTerms: + items: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + type: array + required: + - nodeSelectorTerms + type: object + type: object + podAffinity: + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + podAffinityTerm: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaceSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + weight: + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + items: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaceSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + type: array + type: object + podAntiAffinity: + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + podAffinityTerm: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaceSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + weight: + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + items: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaceSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + type: array + type: object + type: object + automountServiceAccountToken: + type: string + env: + items: + properties: + name: + type: string + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + secretKeyRef: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + envFrom: + items: + properties: + configMapRef: + properties: + name: + type: string + optional: + type: boolean + type: object + prefix: + type: string + secretRef: + properties: + name: + type: string + optional: + type: boolean + type: object + type: object + type: array + image: + type: string + imagePullPolicy: + type: string + imagePullSecrets: + items: + properties: + name: + type: string + type: object + type: array + initContainers: + items: + properties: + args: + items: + type: string + type: array + command: + items: + type: string + type: array + env: + items: + properties: + name: + type: string + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + secretKeyRef: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + envFrom: + items: + properties: + configMapRef: + properties: + name: + type: string + optional: + type: boolean + type: object + prefix: + type: string + secretRef: + properties: + name: + type: string + optional: + type: boolean + type: object + type: object + type: array + image: + type: string + name: + type: string + volumeMounts: + items: + properties: + mountPath: + type: string + mountPropagation: + type: string + name: + type: string + readOnly: + type: boolean + subPath: + type: string + subPathExpr: + type: string + required: + - mountPath + - name + type: object + type: array + workingDir: + type: string + type: object + type: array + livenessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + nodeSelector: + additionalProperties: + type: string + type: object + readinessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + failureThreshold: + format: int32 + type: integer + grpc: + properties: + port: + format: int32 + type: integer + service: + type: string + required: + - port + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + required: + - port + type: object + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + format: int64 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + resources: + properties: + claims: + items: + properties: + name: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + securityContext: + properties: + fsGroup: + format: int64 + type: integer + fsGroupChangePolicy: + type: string + runAsGroup: + format: int64 + type: integer + runAsNonRoot: + type: boolean + runAsUser: + format: int64 + type: integer + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + required: + - type + type: object + supplementalGroups: + items: + format: int64 + type: integer + type: array + sysctls: + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object + type: object + serviceAccountName: + type: string + tolerations: + items: + properties: + effect: + type: string + key: + type: string + operator: + type: string + tolerationSeconds: + format: int64 + type: integer + value: + type: string + type: object + type: array + volumeMounts: + items: + properties: + mountPath: + type: string + mountPropagation: + type: string + name: + type: string + readOnly: + type: boolean + subPath: + type: string + subPathExpr: + type: string + required: + - mountPath + - name + type: object + type: array + volumes: + items: + properties: + awsElasticBlockStore: + properties: + fsType: + type: string + partition: + format: int32 + type: integer + readOnly: + type: boolean + volumeID: + type: string + required: + - volumeID + type: object + azureDisk: + properties: + cachingMode: + type: string + diskName: + type: string + diskURI: + type: string + fsType: + type: string + kind: + type: string + readOnly: + type: boolean + required: + - diskName + - diskURI + type: object + azureFile: + properties: + readOnly: + type: boolean + secretName: + type: string + shareName: + type: string + required: + - secretName + - shareName + type: object + cephfs: + properties: + monitors: + items: + type: string + type: array + path: + type: string + readOnly: + type: boolean + secretFile: + type: string + secretRef: + properties: + name: + type: string + type: object + user: + type: string + required: + - monitors + type: object + cinder: + properties: + fsType: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + volumeID: + type: string + required: + - volumeID + type: object + configMap: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + name: + type: string + optional: + type: boolean + type: object + csi: + properties: + driver: + type: string + fsType: + type: string + nodePublishSecretRef: + properties: + name: + type: string + type: object + readOnly: + type: boolean + volumeAttributes: + additionalProperties: + type: string + type: object + required: + - driver + type: object + downwardAPI: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + mode: + format: int32 + type: integer + path: + type: string + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + required: + - path + type: object + type: array + type: object + emptyDir: + properties: + medium: + type: string + sizeLimit: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + ephemeral: + properties: + volumeClaimTemplate: + properties: + metadata: + type: object + spec: + properties: + accessModes: + items: + type: string + type: array + dataSource: + properties: + apiGroup: + type: string + kind: + type: string + name: + type: string + required: + - kind + - name + type: object + dataSourceRef: + properties: + apiGroup: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + required: + - kind + - name + type: object + resources: + properties: + claims: + items: + properties: + name: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + selector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + storageClassName: + type: string + volumeMode: + type: string + volumeName: + type: string + type: object + required: + - spec + type: object + type: object + fc: + properties: + fsType: + type: string + lun: + format: int32 + type: integer + readOnly: + type: boolean + targetWWNs: + items: + type: string + type: array + wwids: + items: + type: string + type: array + type: object + flexVolume: + properties: + driver: + type: string + fsType: + type: string + options: + additionalProperties: + type: string + type: object + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + required: + - driver + type: object + flocker: + properties: + datasetName: + type: string + datasetUUID: + type: string + type: object + gcePersistentDisk: + properties: + fsType: + type: string + partition: + format: int32 + type: integer + pdName: + type: string + readOnly: + type: boolean + required: + - pdName + type: object + gitRepo: + properties: + directory: + type: string + repository: + type: string + revision: + type: string + required: + - repository + type: object + glusterfs: + properties: + endpoints: + type: string + path: + type: string + readOnly: + type: boolean + required: + - endpoints + - path + type: object + hostPath: + properties: + path: + type: string + type: + type: string + required: + - path + type: object + iscsi: + properties: + chapAuthDiscovery: + type: boolean + chapAuthSession: + type: boolean + fsType: + type: string + initiatorName: + type: string + iqn: + type: string + iscsiInterface: + type: string + lun: + format: int32 + type: integer + portals: + items: + type: string + type: array + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + targetPortal: + type: string + required: + - iqn + - lun + - targetPortal + type: object + name: + type: string + nfs: + properties: + path: + type: string + readOnly: + type: boolean + server: + type: string + required: + - path + - server + type: object + persistentVolumeClaim: + properties: + claimName: + type: string + readOnly: + type: boolean + required: + - claimName + type: object + photonPersistentDisk: + properties: + fsType: + type: string + pdID: + type: string + required: + - pdID + type: object + portworxVolume: + properties: + fsType: + type: string + readOnly: + type: boolean + volumeID: + type: string + required: + - volumeID + type: object + projected: + properties: + defaultMode: + format: int32 + type: integer + sources: + items: + properties: + configMap: + properties: + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + name: + type: string + optional: + type: boolean + type: object + downwardAPI: + properties: + items: + items: + properties: + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + mode: + format: int32 + type: integer + path: + type: string + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + required: + - path + type: object + type: array + type: object + secret: + properties: + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + name: + type: string + optional: + type: boolean + type: object + serviceAccountToken: + properties: + audience: + type: string + expirationSeconds: + format: int64 + type: integer + path: + type: string + required: + - path + type: object + type: object + type: array + type: object + quobyte: + properties: + group: + type: string + readOnly: + type: boolean + registry: + type: string + tenant: + type: string + user: + type: string + volume: + type: string + required: + - registry + - volume + type: object + rbd: + properties: + fsType: + type: string + image: + type: string + keyring: + type: string + monitors: + items: + type: string + type: array + pool: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + user: + type: string + required: + - image + - monitors + type: object + scaleIO: + properties: + fsType: + type: string + gateway: + type: string + protectionDomain: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + sslEnabled: + type: boolean + storageMode: + type: string + storagePool: + type: string + system: + type: string + volumeName: + type: string + required: + - gateway + - secretRef + - system + type: object + secret: + properties: + defaultMode: + format: int32 + type: integer + items: + items: + properties: + key: + type: string + mode: + format: int32 + type: integer + path: + type: string + required: + - key + - path + type: object + type: array + optional: + type: boolean + secretName: + type: string + type: object + storageos: + properties: + fsType: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + volumeName: + type: string + volumeNamespace: + type: string + type: object + vsphereVolume: + properties: + fsType: + type: string + storagePolicyID: + type: string + storagePolicyName: + type: string + volumePath: + type: string + required: + - volumePath + type: object + required: + - name + type: object + type: array + type: object + testRunId: + type: string + token: + type: string + required: + - parallelism + - script + type: object + status: + properties: + aggregationVars: + type: string + conditions: + items: + properties: + lastTransitionTime: + format: date-time + type: string + message: + maxLength: 32768 + type: string + observedGeneration: + format: int64 + minimum: 0 + type: integer + reason: + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + enum: + - "True" + - "False" + - Unknown + type: string + type: + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + stage: + enum: + - initialization + - initialized + - created + - started + - stopped + - finished + - error + type: string + testRunId: + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index 50f49e77..284cc795 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -5,6 +5,7 @@ resources: - bases/k6.io_k6s.yaml - bases/k6.io_privateloadzones.yaml + - bases/k6.io_testruns.yaml # +kubebuilder:scaffold:crdkustomizeresource patchesStrategicMerge: diff --git a/config/crd/patches/cainjection_in_testruns.yaml b/config/crd/patches/cainjection_in_testruns.yaml new file mode 100644 index 00000000..a24c9d5c --- /dev/null +++ b/config/crd/patches/cainjection_in_testruns.yaml @@ -0,0 +1,8 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +# CRD conversion requires k8s 1.13 or later. +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: testruns.k6.io diff --git a/config/crd/patches/webhook_in_testruns.yaml b/config/crd/patches/webhook_in_testruns.yaml new file mode 100644 index 00000000..69fb6208 --- /dev/null +++ b/config/crd/patches/webhook_in_testruns.yaml @@ -0,0 +1,17 @@ +# The following patch enables conversion webhook for CRD +# CRD conversion requires k8s 1.13 or later. +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: testruns.k6.io +spec: + conversion: + strategy: Webhook + webhookClientConfig: + # this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank, + # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager) + caBundle: Cg== + service: + namespace: system + name: webhook-service + path: /convert diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index c9ab65b4..b0e73f53 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -116,3 +116,24 @@ rules: - get - patch - update +- apiGroups: + - k6.io + resources: + - testruns + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - k6.io + resources: + - testruns/finalizers + - testruns/status + verbs: + - get + - patch + - update diff --git a/config/rbac/testrun_editor_role.yaml b/config/rbac/testrun_editor_role.yaml new file mode 100644 index 00000000..65c47ad9 --- /dev/null +++ b/config/rbac/testrun_editor_role.yaml @@ -0,0 +1,24 @@ +# permissions for end users to edit testruns. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: testrun-editor-role +rules: +- apiGroups: + - k6.io + resources: + - testruns + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - k6.io + resources: + - testruns/status + verbs: + - get diff --git a/config/rbac/testrun_viewer_role.yaml b/config/rbac/testrun_viewer_role.yaml new file mode 100644 index 00000000..f0815f4b --- /dev/null +++ b/config/rbac/testrun_viewer_role.yaml @@ -0,0 +1,20 @@ +# permissions for end users to view testruns. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: testrun-viewer-role +rules: +- apiGroups: + - k6.io + resources: + - testruns + verbs: + - get + - list + - watch +- apiGroups: + - k6.io + resources: + - testruns/status + verbs: + - get diff --git a/config/samples/k6_v1alpha1_testrun.yaml b/config/samples/k6_v1alpha1_testrun.yaml new file mode 100644 index 00000000..2d1aff3d --- /dev/null +++ b/config/samples/k6_v1alpha1_testrun.yaml @@ -0,0 +1,10 @@ +apiVersion: k6.io/v1alpha1 +kind: TestRun +metadata: + name: testrun-sample +spec: + parallelism: 4 + script: + configMap: + name: k6-test + file: test.js diff --git a/config/samples/kustomization.yaml b/config/samples/kustomization.yaml index c7a7de29..1024e4ae 100644 --- a/config/samples/kustomization.yaml +++ b/config/samples/kustomization.yaml @@ -4,4 +4,5 @@ resources: - k6_v1alpha1_configmap.yaml - k6_v1alpha1_k6.yaml - k6_v1alpha1_privateloadzone.yaml + - k6_v1alpha1_testrun.yaml # +kubebuilder:scaffold:manifestskustomizesamples diff --git a/controllers/common.go b/controllers/common.go index c0c775bd..2a7ecbee 100644 --- a/controllers/common.go +++ b/controllers/common.go @@ -22,15 +22,15 @@ import ( // It may take some time to retrieve inspect output so indicate with boolean if it's ready // and use returnErr only for errors that require a change of behaviour. All other errors // should just be logged. -func inspectTestRun(ctx context.Context, log logr.Logger, k6 v1alpha1.K6, c client.Client) ( +func inspectTestRun(ctx context.Context, log logr.Logger, k6 v1alpha1.TestRunI, c client.Client) ( inspectOutput cloud.InspectOutput, ready bool, returnErr error) { var ( listOpts = &client.ListOptions{ - Namespace: k6.Namespace, + Namespace: k6.NamespacedName().Namespace, LabelSelector: labels.SelectorFromSet(map[string]string{ "app": "k6", - "k6_cr": k6.Name, - "job-name": fmt.Sprintf("%s-initializer", k6.Name), + "k6_cr": k6.NamespacedName().Name, + "job-name": fmt.Sprintf("%s-initializer", k6.NamespacedName().Name), }), } podList = &corev1.PodList{} @@ -69,7 +69,7 @@ func inspectTestRun(ctx context.Context, log logr.Logger, k6 v1alpha1.K6, c clie log.Error(err, "unable to get access to clientset") return } - req := clientset.CoreV1().Pods(k6.Namespace).GetLogs(podList.Items[0].Name, &corev1.PodLogOptions{ + req := clientset.CoreV1().Pods(k6.NamespacedName().Namespace).GetLogs(podList.Items[0].Name, &corev1.PodLogOptions{ Container: "k6", }) ctx, cancel := context.WithTimeout(context.Background(), time.Second*60) diff --git a/controllers/k6_controller.go b/controllers/k6_controller.go index af7ee257..2925f80f 100644 --- a/controllers/k6_controller.go +++ b/controllers/k6_controller.go @@ -16,21 +16,13 @@ package controllers import ( "context" - "errors" - "fmt" - "time" - "go.k6.io/k6/cloudapi" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + k8sErrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/types" - "github.com/go-logr/logr" "github.com/grafana/k6-operator/api/v1alpha1" - "github.com/grafana/k6-operator/pkg/cloud" batchv1 "k8s.io/api/batch/v1" v1 "k8s.io/api/core/v1" - k8sErrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/client" @@ -41,17 +33,16 @@ import ( "sigs.k8s.io/controller-runtime/pkg/source" ) -const k6CrLabelName = "k6_cr" +const ( + k6CrLabelName = "k6_cr" +) -// K6Reconciler reconciles a K6 object type K6Reconciler struct { - client.Client - Log logr.Logger - Scheme *runtime.Scheme + t *TestRunReconciler +} - // Note: here we assume that all users of the operator are allowed to use - // the same token / cloud client. - k6CloudClient *cloudapi.Client +func NewK6Reconciler(t *TestRunReconciler) *K6Reconciler { + return &K6Reconciler{t} } // Reconcile takes a K6 object and takes the appropriate action in the cluster @@ -65,11 +56,12 @@ type K6Reconciler struct { // +kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch func (r *K6Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - log := r.Log.WithValues("namespace", req.Namespace, "name", req.Name, "reconcileID", controller.ReconcileIDFromContext(ctx)) + log := r.t.Log.WithValues("namespace", req.Namespace, "name", req.Name, "reconcileID", controller.ReconcileIDFromContext(ctx)) // Fetch the CRD k6 := &v1alpha1.K6{} - err := r.Get(ctx, req.NamespacedName, k6) + err := r.t.Get(ctx, req.NamespacedName, k6) + if err != nil { if k8sErrors.IsNotFound(err) { log.Info("Request deleted. Nothing to reconcile.") @@ -79,199 +71,7 @@ func (r *K6Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Re return ctrl.Result{Requeue: true}, err } - if k6.IsTrue(v1alpha1.CloudPLZTestRun) { - // bootstrap the client - found, err := r.createClient(ctx, k6, log) - if err != nil { - log.Error(err, "A problem while getting token.") - return ctrl.Result{}, err - } - if !found { - log.Info(fmt.Sprintf("Token `%s` is not found yet.", k6.Spec.Token)) - return ctrl.Result{RequeueAfter: time.Second}, nil - } - } - - log.Info(fmt.Sprintf("Reconcile(); stage = %s", k6.Status.Stage)) - - // Decision making here is now a mix between stages and conditions. - // TODO: refactor further. - - switch k6.Status.Stage { - case "": - log.Info("Initialize test") - - k6.Initialize() - - if _, err := r.UpdateStatus(ctx, k6, log); err != nil { - return ctrl.Result{}, err - } - - log.Info("Changing stage of K6 status to initialization") - k6.Status.Stage = "initialization" - if updateHappened, err := r.UpdateStatus(ctx, k6, log); err != nil { - return ctrl.Result{}, err - } else if updateHappened { - return InitializeJobs(ctx, log, k6, r) - } - return ctrl.Result{}, nil - - case "initialization": - if k6.IsUnknown(v1alpha1.CloudTestRun) { - return RunValidations(ctx, log, k6, r) - } - - if k6.IsFalse(v1alpha1.CloudTestRun) { - // RunValidations has already happened and this is not a - // cloud test: we can move on - log.Info("Changing stage of K6 status to initialized") - - k6.Status.Stage = "initialized" - - if updateHappened, err := r.UpdateStatus(ctx, k6, log); err != nil { - return ctrl.Result{}, err - } else if updateHappened { - return ctrl.Result{}, nil - } - } - - if k6.IsTrue(v1alpha1.CloudTestRun) { - - if k6.IsFalse(v1alpha1.CloudTestRunCreated) { - return SetupCloudTest(ctx, log, k6, r) - - } else { - // if test run was created, then only changing status is left - log.Info("Changing stage of K6 status to initialized") - - k6.Status.Stage = "initialized" - - if _, err := r.UpdateStatus(ctx, k6, log); err != nil { - return ctrl.Result{}, err - } - } - } - - return ctrl.Result{}, nil - - case "initialized": - return CreateJobs(ctx, log, k6, r) - - case "created": - return StartJobs(ctx, log, k6, r) - - case "started": - if k6.IsTrue(v1alpha1.CloudTestRun) && k6.IsTrue(v1alpha1.CloudTestRunFinalized) { - // a fluke - nothing to do - return ctrl.Result{}, nil - } - - if k6.IsTrue(v1alpha1.CloudTestRunAborted) { - // a fluke - nothing to do - return ctrl.Result{}, nil - } - - // wait for the test to finish - if !FinishJobs(ctx, log, k6, r) { - - if k6.IsTrue(v1alpha1.CloudPLZTestRun) && k6.IsFalse(v1alpha1.CloudTestRunAborted) { - // check in with the BE for status - if r.ShouldAbort(ctx, k6, log) { - log.Info("Received an abort signal from the k6 Cloud: stopping the test.") - return StopJobs(ctx, log, k6, r) - } - } - - // The test continues to execute. - - // Test runs can take a long time and usually they aren't supposed - // to be too quick. So check in only periodically. - return ctrl.Result{RequeueAfter: time.Second * 15}, nil - } - - log.Info("All runner pods are finished") - - // now mark it as stopped - - if k6.IsTrue(v1alpha1.TestRunRunning) { - k6.UpdateCondition(v1alpha1.TestRunRunning, metav1.ConditionFalse) - - log.Info("Changing stage of K6 status to stopped") - k6.Status.Stage = "stopped" - - _, err := r.UpdateStatus(ctx, k6, log) - if err != nil { - return ctrl.Result{}, err - } - // log.Info(fmt.Sprintf("Debug updating status after finalize %v", updateHappened)) - } - - return ctrl.Result{}, nil - - case "stopped": - if k6.IsTrue(v1alpha1.CloudPLZTestRun) && k6.IsTrue(v1alpha1.CloudTestRunAborted) { - // This is a "forced" abort of the PLZ test run. - // Wait until all the test runs are stopped, kill jobs and proceed. - if StoppedJobs(ctx, log, k6, r) { - if allDeleted, err := KillJobs(ctx, log, k6, r); err != nil { - return ctrl.Result{RequeueAfter: time.Second}, err - } else { - // if we just have deleted all jobs, update status and go for reconcile - if allDeleted { - k6.UpdateCondition(v1alpha1.CloudTestRunAborted, metav1.ConditionTrue) - _, err := r.UpdateStatus(ctx, k6, log) - if err != nil { - return ctrl.Result{}, err - } - } - } - } - } - - // If this is a cloud test run in any mode, try to finalize it. - if k6.IsTrue(v1alpha1.CloudTestRun) && - k6.IsFalse(v1alpha1.CloudTestRunFinalized) { - - // If TestRunRunning has just been updated, wait for a bit before - // acting, to avoid race condition between different reconcile loops. - t, _ := k6.LastUpdate(v1alpha1.TestRunRunning) - if time.Now().Sub(t) < 5*time.Second { - return ctrl.Result{RequeueAfter: time.Second * 2}, nil - } - - if err = cloud.FinishTestRun(r.k6CloudClient, k6.Status.TestRunID); err != nil { - log.Error(err, "Failed to finalize the test run with cloud output") - return ctrl.Result{}, nil - } else { - log.Info(fmt.Sprintf("Cloud test run %s was finalized successfully", k6.Status.TestRunID)) - - k6.UpdateCondition(v1alpha1.CloudTestRunFinalized, metav1.ConditionTrue) - } - } - - log.Info("Changing stage of K6 status to finished") - k6.Status.Stage = "finished" - - _, err := r.UpdateStatus(ctx, k6, log) - if err != nil { - return ctrl.Result{}, err - } - - return ctrl.Result{RequeueAfter: time.Second}, nil - - case "error", "finished": - // delete if configured - if k6.Spec.Cleanup == "post" { - log.Info("Cleaning up all resources") - r.Delete(ctx, k6) - } - // notify if configured - return ctrl.Result{}, nil - } - - err = fmt.Errorf("invalid status") - log.Error(err, "Invalid status for the k6 resource.") - return ctrl.Result{}, err + return r.t.reconcile(ctx, req, log, k6) } // SetupWithManager sets up a managed controller that will reconcile all events for the K6 CRD @@ -308,84 +108,3 @@ func (r *K6Reconciler) SetupWithManager(mgr ctrl.Manager) error { }). Complete(r) } - -func (r *K6Reconciler) UpdateStatus(ctx context.Context, k6 *v1alpha1.K6, log logr.Logger) (updateHappened bool, err error) { - proposedStatus := k6.Status - - // re-fetch resource - err = r.Get(ctx, types.NamespacedName{Namespace: k6.Namespace, Name: k6.Name}, k6) - if err != nil { - if k8sErrors.IsNotFound(err) { - log.Info("Request deleted. No status to update.") - return false, nil - } - log.Error(err, "Could not fetch request") - return false, err - } - - cleanObj := k6.DeepCopyObject().(client.Object) - - // Update only if it's truly a newer version of the resource - // in comparison to the recently fetched resource. - isNewer := k6.Status.SetIfNewer(proposedStatus) - if !isNewer { - return false, nil - } - - err = r.Client.Status().Patch(ctx, k6, client.MergeFrom(cleanObj)) - - // TODO: look into retry.RetryOnConflict(retry.DefaultRetry, func() error{...}) - // to have retries of failing update here, in case of conflicts; - // with optional retry bool arg probably. - - // TODO: what if resource was deleted right before Patch? - // Add a check for IsNotFound(err). - - if err != nil { - log.Error(err, "Could not update status of custom resource") - return false, err - } - - return true, nil -} - -// ShouldAbort retrieves the status of test run from the Cloud and whether it should -// cause a forced stop. It is meant to be used only by PLZ test runs. -func (r *K6Reconciler) ShouldAbort(ctx context.Context, k6 *v1alpha1.K6, log logr.Logger) bool { - // sanity check - if len(k6.Status.TestRunID) == 0 { - log.Error(errors.New("empty test run ID"), "Trying to get state of test run with empty test run ID") - return false - } - - status, err := cloud.GetTestRunState(r.k6CloudClient, k6.Status.TestRunID, log) - if err != nil { - log.Error(err, "Failed to get test run state.") - return false - } - - isAborted := status.Aborted() - - log.Info(fmt.Sprintf("Received test run status %v", status)) - - return isAborted -} - -func (r *K6Reconciler) createClient(ctx context.Context, k6 *v1alpha1.K6, log logr.Logger) (bool, error) { - if r.k6CloudClient == nil { - token, tokenReady, err := loadToken(ctx, log, r.Client, k6.Spec.Token, &client.ListOptions{Namespace: k6.Namespace}) - if err != nil { - log.Error(err, "A problem while getting token.") - return false, err - } - if !tokenReady { - return false, nil - } - - host := getEnvVar(k6.Spec.Runner.Env, "K6_CLOUD_HOST") - - r.k6CloudClient = cloud.NewClient(log, token, host) - } - - return true, nil -} diff --git a/controllers/k6_create.go b/controllers/k6_create.go index d31e0d65..9a08825c 100644 --- a/controllers/k6_create.go +++ b/controllers/k6_create.go @@ -17,26 +17,26 @@ import ( ) // CreateJobs creates jobs that will spawn k6 pods for distributed test -func CreateJobs(ctx context.Context, log logr.Logger, k6 *v1alpha1.K6, r *K6Reconciler) (ctrl.Result, error) { +func CreateJobs(ctx context.Context, log logr.Logger, k6 v1alpha1.TestRunI, r *TestRunReconciler) (ctrl.Result, error) { var ( err error res ctrl.Result token string // only for cloud tests ) - if k6.IsTrue(v1alpha1.CloudTestRun) && k6.IsTrue(v1alpha1.CloudTestRunCreated) { - log = log.WithValues("testRunId", k6.Status.TestRunID) + if v1alpha1.IsTrue(k6, v1alpha1.CloudTestRun) && v1alpha1.IsTrue(k6, v1alpha1.CloudTestRunCreated) { + log = log.WithValues("testRunId", k6.GetStatus().TestRunID) var ( tokenReady bool sOpts *client.ListOptions ) - if k6.IsTrue(v1alpha1.CloudPLZTestRun) { - sOpts = &client.ListOptions{Namespace: k6.Namespace} + if v1alpha1.IsTrue(k6, v1alpha1.CloudPLZTestRun) { + sOpts = &client.ListOptions{Namespace: k6.NamespacedName().Namespace} } - token, tokenReady, err = loadToken(ctx, log, r.Client, k6.Spec.Token, sOpts) + token, tokenReady, err = loadToken(ctx, log, r.Client, k6.GetSpec().Token, sOpts) if err != nil { // An error here means a very likely mis-configuration of the token. // Consider updating status to error to let a user know quicker? @@ -55,7 +55,7 @@ func CreateJobs(ctx context.Context, log logr.Logger, k6 *v1alpha1.K6, r *K6Reco } log.Info("Changing stage of K6 status to created") - k6.Status.Stage = "created" + k6.GetStatus().Stage = "created" if updateHappened, err := r.UpdateStatus(ctx, k6, log); err != nil { return ctrl.Result{}, err @@ -65,11 +65,11 @@ func CreateJobs(ctx context.Context, log logr.Logger, k6 *v1alpha1.K6, r *K6Reco return ctrl.Result{}, nil } -func createJobSpecs(ctx context.Context, log logr.Logger, k6 *v1alpha1.K6, r *K6Reconciler, token string) (ctrl.Result, error) { +func createJobSpecs(ctx context.Context, log logr.Logger, k6 v1alpha1.TestRunI, r *TestRunReconciler, token string) (ctrl.Result, error) { found := &batchv1.Job{} namespacedName := types.NamespacedName{ - Name: fmt.Sprintf("%s-1", k6.Name), - Namespace: k6.Namespace, + Name: fmt.Sprintf("%s-1", k6.NamespacedName().Name), + Namespace: k6.NamespacedName().Namespace, } if err := r.Get(ctx, namespacedName, found); err == nil || !errors.IsNotFound(err) { @@ -77,7 +77,7 @@ func createJobSpecs(ctx context.Context, log logr.Logger, k6 *v1alpha1.K6, r *K6 return ctrl.Result{}, err } - for i := 1; i <= int(k6.Spec.Parallelism); i++ { + for i := 1; i <= int(k6.GetSpec().Parallelism); i++ { if err := launchTest(ctx, k6, i, log, r, token); err != nil { return ctrl.Result{}, err } @@ -85,7 +85,7 @@ func createJobSpecs(ctx context.Context, log logr.Logger, k6 *v1alpha1.K6, r *K6 return ctrl.Result{}, nil } -func launchTest(ctx context.Context, k6 *v1alpha1.K6, index int, log logr.Logger, r *K6Reconciler, token string) error { +func launchTest(ctx context.Context, k6 v1alpha1.TestRunI, index int, log logr.Logger, r *TestRunReconciler, token string) error { var job *batchv1.Job var service *corev1.Service var err error diff --git a/controllers/k6_finish.go b/controllers/k6_finish.go index dad2bfab..4344a072 100644 --- a/controllers/k6_finish.go +++ b/controllers/k6_finish.go @@ -12,20 +12,20 @@ import ( ) // FinishJobs checks if the runners pods have finished execution. -func FinishJobs(ctx context.Context, log logr.Logger, k6 *v1alpha1.K6, r *K6Reconciler) (allFinished bool) { - if len(k6.Status.TestRunID) > 0 { - log = log.WithValues("testRunId", k6.Status.TestRunID) +func FinishJobs(ctx context.Context, log logr.Logger, k6 v1alpha1.TestRunI, r *TestRunReconciler) (allFinished bool) { + if len(k6.GetStatus().TestRunID) > 0 { + log = log.WithValues("testRunId", k6.GetStatus().TestRunID) } log.Info("Checking if all runner pods are finished") selector := labels.SelectorFromSet(map[string]string{ "app": "k6", - "k6_cr": k6.Name, + "k6_cr": k6.NamespacedName().Name, "runner": "true", }) - opts := &client.ListOptions{LabelSelector: selector, Namespace: k6.Namespace} + opts := &client.ListOptions{LabelSelector: selector, Namespace: k6.NamespacedName().Namespace} jl := &batchv1.JobList{} var err error @@ -43,9 +43,9 @@ func FinishJobs(ctx context.Context, log logr.Logger, k6 *v1alpha1.K6, r *K6Reco finished++ } - log.Info(fmt.Sprintf("%d/%d jobs complete", finished, k6.Spec.Parallelism)) + log.Info(fmt.Sprintf("%d/%d jobs complete", finished, k6.GetSpec().Parallelism)) - if finished < k6.Spec.Parallelism { + if finished < k6.GetSpec().Parallelism { return } diff --git a/controllers/k6_initialize.go b/controllers/k6_initialize.go index 93d0ae18..97f54ae1 100644 --- a/controllers/k6_initialize.go +++ b/controllers/k6_initialize.go @@ -17,11 +17,11 @@ import ( ) // InitializeJobs creates jobs that will run initial checks for distributed test if any are necessary -func InitializeJobs(ctx context.Context, log logr.Logger, k6 *v1alpha1.K6, r *K6Reconciler) (res ctrl.Result, err error) { +func InitializeJobs(ctx context.Context, log logr.Logger, k6 v1alpha1.TestRunI, r *TestRunReconciler) (res ctrl.Result, err error) { // initializer is a quick job so check in frequently res = ctrl.Result{RequeueAfter: time.Second * 5} - cli := types.ParseCLI(k6.Spec.Arguments) + cli := types.ParseCLI(k6.GetSpec().Arguments) var initializer *batchv1.Job if initializer, err = jobs.NewInitializerJob(k6, cli.ArchiveArgs); err != nil { @@ -44,13 +44,13 @@ func InitializeJobs(ctx context.Context, log logr.Logger, k6 *v1alpha1.K6, r *K6 return res, nil } -func RunValidations(ctx context.Context, log logr.Logger, k6 *v1alpha1.K6, r *K6Reconciler) (res ctrl.Result, err error) { +func RunValidations(ctx context.Context, log logr.Logger, k6 v1alpha1.TestRunI, r *TestRunReconciler) (res ctrl.Result, err error) { // initializer is a quick job so check in frequently res = ctrl.Result{RequeueAfter: time.Second * 5} - cli := types.ParseCLI(k6.Spec.Arguments) + cli := types.ParseCLI(k6.GetSpec().Arguments) - inspectOutput, inspectReady, err := inspectTestRun(ctx, log, *k6, r.Client) + inspectOutput, inspectReady, err := inspectTestRun(ctx, log, k6, r.Client) if err != nil { // inspectTestRun made a log message already so just return without requeue return ctrl.Result{}, nil @@ -61,16 +61,16 @@ func RunValidations(ctx context.Context, log logr.Logger, k6 *v1alpha1.K6, r *K6 log.Info(fmt.Sprintf("k6 inspect: %+v", inspectOutput)) - if int32(inspectOutput.MaxVUs) < k6.Spec.Parallelism { + if int32(inspectOutput.MaxVUs) < k6.GetSpec().Parallelism { err = fmt.Errorf("number of instances > number of VUs") // TODO maybe change this to a warning and simply set parallelism = maxVUs and proceed with execution? // But logr doesn't seem to have warning level by default, only with V() method... // It makes sense to return to this after / during logr VS logrus issue https://github.com/grafana/k6-operator/issues/84 log.Error(err, "Parallelism argument cannot be larger than maximum VUs in the script", "maxVUs", inspectOutput.MaxVUs, - "parallelism", k6.Spec.Parallelism) + "parallelism", k6.GetSpec().Parallelism) - k6.Status.Stage = "error" + k6.GetStatus().Stage = "error" if _, err := r.UpdateStatus(ctx, k6, log); err != nil { return ctrl.Result{}, err @@ -81,16 +81,16 @@ func RunValidations(ctx context.Context, log logr.Logger, k6 *v1alpha1.K6, r *K6 } if cli.HasCloudOut { - k6.UpdateCondition(v1alpha1.CloudTestRun, metav1.ConditionTrue) + v1alpha1.UpdateCondition(k6, v1alpha1.CloudTestRun, metav1.ConditionTrue) - if k6.IsUnknown(v1alpha1.CloudTestRunCreated) { + if v1alpha1.IsUnknown(k6, v1alpha1.CloudTestRunCreated) { // In case of PLZ test run, this is already set to true - k6.UpdateCondition(v1alpha1.CloudTestRunCreated, metav1.ConditionFalse) + v1alpha1.UpdateCondition(k6, v1alpha1.CloudTestRunCreated, metav1.ConditionFalse) } - k6.UpdateCondition(v1alpha1.CloudTestRunFinalized, metav1.ConditionFalse) + v1alpha1.UpdateCondition(k6, v1alpha1.CloudTestRunFinalized, metav1.ConditionFalse) } else { - k6.UpdateCondition(v1alpha1.CloudTestRun, metav1.ConditionFalse) + v1alpha1.UpdateCondition(k6, v1alpha1.CloudTestRun, metav1.ConditionFalse) } if _, err := r.UpdateStatus(ctx, k6, log); err != nil { @@ -102,10 +102,10 @@ func RunValidations(ctx context.Context, log logr.Logger, k6 *v1alpha1.K6, r *K6 // SetupCloudTest inspects the output of initializer and creates a new // test run. It is meant to be used only in cloud output mode. -func SetupCloudTest(ctx context.Context, log logr.Logger, k6 *v1alpha1.K6, r *K6Reconciler) (res ctrl.Result, err error) { +func SetupCloudTest(ctx context.Context, log logr.Logger, k6 v1alpha1.TestRunI, r *TestRunReconciler) (res ctrl.Result, err error) { res = ctrl.Result{RequeueAfter: time.Second * 5} - inspectOutput, inspectReady, err := inspectTestRun(ctx, log, *k6, r.Client) + inspectOutput, inspectReady, err := inspectTestRun(ctx, log, k6, r.Client) if err != nil { // This *shouldn't* fail since it was already done once. Don't requeue. // Alternatively: store inspect options in K6 Status? Get rid off reading logs? @@ -126,13 +126,13 @@ func SetupCloudTest(ctx context.Context, log logr.Logger, k6 *v1alpha1.K6, r *K6 return res, nil } - host := getEnvVar(k6.Spec.Runner.Env, "K6_CLOUD_HOST") + host := getEnvVar(k6.GetSpec().Runner.Env, "K6_CLOUD_HOST") - if k6.IsFalse(v1alpha1.CloudTestRunCreated) { + if v1alpha1.IsFalse(k6, v1alpha1.CloudTestRunCreated) { // If CloudTestRunCreated has just been updated, wait for a bit before // acting, to avoid race condition between different reconcile loops. - t, _ := k6.LastUpdate(v1alpha1.CloudTestRunCreated) + t, _ := v1alpha1.LastUpdate(k6, v1alpha1.CloudTestRunCreated) if time.Now().Sub(t) < 5*time.Second { return ctrl.Result{RequeueAfter: time.Second * 2}, nil } @@ -140,21 +140,21 @@ func SetupCloudTest(ctx context.Context, log logr.Logger, k6 *v1alpha1.K6, r *K6 if len(inspectOutput.External.Loadimpact.Name) < 1 { // script has already been parsed for initializer job definition, // so this is safe - script, _ := k6.Spec.ParseScript() + script, _ := k6.GetSpec().ParseScript() inspectOutput.External.Loadimpact.Name = script.Filename } - if testRunData, err := cloud.CreateTestRun(inspectOutput, k6.Spec.Parallelism, host, token, log); err != nil { + if testRunData, err := cloud.CreateTestRun(inspectOutput, k6.GetSpec().Parallelism, host, token, log); err != nil { log.Error(err, "Failed to create a new cloud test run.") return res, nil } else { log = log.WithValues("testRunId", testRunData.ReferenceID) log.Info(fmt.Sprintf("Created cloud test run: %s", testRunData.ReferenceID)) - k6.Status.TestRunID = testRunData.ReferenceID - k6.UpdateCondition(v1alpha1.CloudTestRunCreated, metav1.ConditionTrue) + k6.GetStatus().TestRunID = testRunData.ReferenceID + v1alpha1.UpdateCondition(k6, v1alpha1.CloudTestRunCreated, metav1.ConditionTrue) - k6.Status.AggregationVars = cloud.EncodeAggregationConfig(testRunData) + k6.GetStatus().AggregationVars = cloud.EncodeAggregationConfig(testRunData) _, err := r.UpdateStatus(ctx, k6, log) // log.Info(fmt.Sprintf("Debug updating status after create %v", updateHappened)) diff --git a/controllers/k6_start.go b/controllers/k6_start.go index 07fba7f9..13a0b7f9 100644 --- a/controllers/k6_start.go +++ b/controllers/k6_start.go @@ -28,23 +28,23 @@ func isServiceReady(log logr.Logger, service *v1.Service) bool { } // StartJobs in the Ready phase using a curl container -func StartJobs(ctx context.Context, log logr.Logger, k6 *v1alpha1.K6, r *K6Reconciler) (res ctrl.Result, err error) { +func StartJobs(ctx context.Context, log logr.Logger, k6 v1alpha1.TestRunI, r *TestRunReconciler) (res ctrl.Result, err error) { // It may take some time to get Services up, so check in frequently res = ctrl.Result{RequeueAfter: time.Second} - if len(k6.Status.TestRunID) > 0 { - log = log.WithValues("testRunId", k6.Status.TestRunID) + if len(k6.GetStatus().TestRunID) > 0 { + log = log.WithValues("testRunId", k6.GetStatus().TestRunID) } log.Info("Waiting for pods to get ready") selector := labels.SelectorFromSet(map[string]string{ "app": "k6", - "k6_cr": k6.Name, + "k6_cr": k6.NamespacedName().Name, "runner": "true", }) - opts := &client.ListOptions{LabelSelector: selector, Namespace: k6.Namespace} + opts := &client.ListOptions{LabelSelector: selector, Namespace: k6.NamespacedName().Namespace} pl := &v1.PodList{} if err = r.List(ctx, pl, opts); err != nil { log.Error(err, "Could not list pods") @@ -59,9 +59,9 @@ func StartJobs(ctx context.Context, log logr.Logger, k6 *v1alpha1.K6, r *K6Recon count++ } - log.Info(fmt.Sprintf("%d/%d runner pods ready", count, k6.Spec.Parallelism)) + log.Info(fmt.Sprintf("%d/%d runner pods ready", count, k6.GetSpec().Parallelism)) - if count != int(k6.Spec.Parallelism) { + if count != int(k6.GetSpec().Parallelism) { return res, nil } @@ -100,8 +100,8 @@ func StartJobs(ctx context.Context, log logr.Logger, k6 *v1alpha1.K6, r *K6Recon log.Info("Created starter job") log.Info("Changing stage of K6 status to started") - k6.Status.Stage = "started" - k6.UpdateCondition(v1alpha1.TestRunRunning, metav1.ConditionTrue) + k6.GetStatus().Stage = "started" + v1alpha1.UpdateCondition(k6, v1alpha1.TestRunRunning, metav1.ConditionTrue) if updateHappened, err := r.UpdateStatus(ctx, k6, log); err != nil { return ctrl.Result{}, err diff --git a/controllers/k6_stop.go b/controllers/k6_stop.go index dd98b3d8..2c13d9c9 100644 --- a/controllers/k6_stop.go +++ b/controllers/k6_stop.go @@ -16,18 +16,18 @@ import ( // StopJobs in the Ready phase using a curl container // It assumes that Services of the runners are already up and // test is being executed. -func StopJobs(ctx context.Context, log logr.Logger, k6 *v1alpha1.K6, r *K6Reconciler) (res ctrl.Result, err error) { - if len(k6.Status.TestRunID) > 0 { - log = log.WithValues("testRunId", k6.Status.TestRunID) +func StopJobs(ctx context.Context, log logr.Logger, k6 v1alpha1.TestRunI, r *TestRunReconciler) (res ctrl.Result, err error) { + if len(k6.GetStatus().TestRunID) > 0 { + log = log.WithValues("testRunId", k6.GetStatus().TestRunID) } selector := labels.SelectorFromSet(map[string]string{ "app": "k6", - "k6_cr": k6.Name, + "k6_cr": k6.NamespacedName().Name, "runner": "true", }) - opts := &client.ListOptions{LabelSelector: selector, Namespace: k6.Namespace} + opts := &client.ListOptions{LabelSelector: selector, Namespace: k6.NamespacedName().Namespace} var hostnames []string sl := &v1.ServiceList{} @@ -57,9 +57,9 @@ func StopJobs(ctx context.Context, log logr.Logger, k6 *v1alpha1.K6, r *K6Reconc log.Info("Created stop job") log.Info("Changing stage of K6 status to stopped") - k6.Status.Stage = "stopped" - k6.UpdateCondition(v1alpha1.TestRunRunning, metav1.ConditionFalse) - k6.UpdateCondition(v1alpha1.CloudTestRunAborted, metav1.ConditionTrue) + k6.GetStatus().Stage = "stopped" + v1alpha1.UpdateCondition(k6, v1alpha1.TestRunRunning, metav1.ConditionFalse) + v1alpha1.UpdateCondition(k6, v1alpha1.CloudTestRunAborted, metav1.ConditionTrue) if updateHappened, err := r.UpdateStatus(ctx, k6, log); err != nil { return ctrl.Result{}, err diff --git a/controllers/k6_stopped_jobs.go b/controllers/k6_stopped_jobs.go index e15c242a..7f95b3e4 100644 --- a/controllers/k6_stopped_jobs.go +++ b/controllers/k6_stopped_jobs.go @@ -48,20 +48,20 @@ func isJobRunning(log logr.Logger, service *v1.Service) bool { } // StoppedJobs checks if the runners pods have stopped execution. -func StoppedJobs(ctx context.Context, log logr.Logger, k6 *v1alpha1.K6, r *K6Reconciler) (allStopped bool) { - if len(k6.Status.TestRunID) > 0 { - log = log.WithValues("testRunId", k6.Status.TestRunID) +func StoppedJobs(ctx context.Context, log logr.Logger, k6 v1alpha1.TestRunI, r *TestRunReconciler) (allStopped bool) { + if len(k6.GetStatus().TestRunID) > 0 { + log = log.WithValues("testRunId", k6.GetStatus().TestRunID) } log.Info("Waiting for pods to stop the test run") selector := labels.SelectorFromSet(map[string]string{ "app": "k6", - "k6_cr": k6.Name, + "k6_cr": k6.NamespacedName().Name, "runner": "true", }) - opts := &client.ListOptions{LabelSelector: selector, Namespace: k6.Namespace} + opts := &client.ListOptions{LabelSelector: selector, Namespace: k6.NamespacedName().Namespace} var hostnames []string sl := &v1.ServiceList{} @@ -80,7 +80,7 @@ func StoppedJobs(ctx context.Context, log logr.Logger, k6 *v1alpha1.K6, r *K6Rec } } - log.Info(fmt.Sprintf("%d/%d runners stopped execution", k6.Spec.Parallelism-count, k6.Spec.Parallelism)) + log.Info(fmt.Sprintf("%d/%d runners stopped execution", k6.GetSpec().Parallelism-count, k6.GetSpec().Parallelism)) if count > 0 { return @@ -94,20 +94,20 @@ func StoppedJobs(ctx context.Context, log logr.Logger, k6 *v1alpha1.K6, r *K6Rec // with propagation policy so that corresponding pods are deleted as well. // On failure, error is returned. // On success, error is nil and allDeleted shows if all retrieved jobs were deleted. -func KillJobs(ctx context.Context, log logr.Logger, k6 *v1alpha1.K6, r *K6Reconciler) (allDeleted bool, err error) { - if len(k6.Status.TestRunID) > 0 { - log = log.WithValues("testRunId", k6.Status.TestRunID) +func KillJobs(ctx context.Context, log logr.Logger, k6 v1alpha1.TestRunI, r *TestRunReconciler) (allDeleted bool, err error) { + if len(k6.GetStatus().TestRunID) > 0 { + log = log.WithValues("testRunId", k6.GetStatus().TestRunID) } log.Info("Killing all runner jobs.") selector := labels.SelectorFromSet(map[string]string{ "app": "k6", - "k6_cr": k6.Name, + "k6_cr": k6.NamespacedName().Name, "runner": "true", }) - opts := &client.ListOptions{LabelSelector: selector, Namespace: k6.Namespace} + opts := &client.ListOptions{LabelSelector: selector, Namespace: k6.NamespacedName().Namespace} jl := &batchv1.JobList{} if err = r.List(ctx, jl, opts); err != nil { diff --git a/controllers/plz_controller.go b/controllers/plz_controller.go index 6061dba7..738b2c23 100644 --- a/controllers/plz_controller.go +++ b/controllers/plz_controller.go @@ -156,7 +156,7 @@ func (r *PrivateLoadZoneReconciler) SetupWithManager(mgr ctrl.Manager) error { Complete(r) } -// UpdateStatus is now using similar logic to K6Reconciler: +// UpdateStatus is now using similar logic to TestRunReconciler: // see if it can / should be refactored. func (r *PrivateLoadZoneReconciler) UpdateStatus( ctx context.Context, diff --git a/controllers/plz_factory.go b/controllers/plz_factory.go index dfe43e8b..35096b44 100644 --- a/controllers/plz_factory.go +++ b/controllers/plz_factory.go @@ -26,7 +26,7 @@ func (r *PrivateLoadZoneReconciler) startFactory(plz *v1alpha1.PrivateLoadZone, Namespace: plz.Namespace, } - k6 := &v1alpha1.K6{} + k6 := &v1alpha1.TestRun{} if err := r.Get(context.Background(), namespacedName, k6); err == nil || !errors.IsNotFound(err) { logger.Info(fmt.Sprintf("Test run `%s` has already been started.", testRunId)) // fmt.Println(k6) diff --git a/controllers/testrun_controller.go b/controllers/testrun_controller.go new file mode 100644 index 00000000..d30be236 --- /dev/null +++ b/controllers/testrun_controller.go @@ -0,0 +1,397 @@ +/* +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 controllers + +import ( + "context" + "errors" + "fmt" + "time" + + "go.k6.io/k6/cloudapi" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + + "github.com/go-logr/logr" + "github.com/grafana/k6-operator/api/v1alpha1" + "github.com/grafana/k6-operator/pkg/cloud" + batchv1 "k8s.io/api/batch/v1" + v1 "k8s.io/api/core/v1" + k8sErrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + "sigs.k8s.io/controller-runtime/pkg/source" +) + +// TestRunReconciler reconciles a K6 object +type TestRunReconciler struct { + client.Client + Log logr.Logger + Scheme *runtime.Scheme + + // Note: here we assume that all users of the operator are allowed to use + // the same token / cloud client. + k6CloudClient *cloudapi.Client +} + +// Reconcile takes a K6 object and takes the appropriate action in the cluster +// +kubebuilder:rbac:groups=k6.io,resources=testruns,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=k6.io,resources=testruns/status;testruns/finalizers,verbs=get;update;patch +// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=core,resources=pods;pods/log,verbs=get;list;watch +// +kubebuilder:rbac:groups=batch,resources=jobs,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=coordination.k8s.io,resources=leases,verbs=get;list;create;update +// +kubebuilder:rbac:groups="",resources=services,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch + +func (r *TestRunReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + log := r.Log.WithValues("namespace", req.Namespace, "name", req.Name, "reconcileID", controller.ReconcileIDFromContext(ctx)) + + // Fetch the CRD + k6 := &v1alpha1.TestRun{} + err := r.Get(ctx, req.NamespacedName, k6) + + if err != nil { + if k8sErrors.IsNotFound(err) { + log.Info("Request deleted. Nothing to reconcile.") + return ctrl.Result{}, nil + } + log.Error(err, "Could not fetch request") + return ctrl.Result{Requeue: true}, err + } + + return r.reconcile(ctx, req, log, k6) +} + +func (r *TestRunReconciler) reconcile(ctx context.Context, req ctrl.Request, log logr.Logger, k6 v1alpha1.TestRunI) (ctrl.Result, error) { + var err error + if v1alpha1.IsTrue(k6, v1alpha1.CloudPLZTestRun) { + // bootstrap the client + found, err := r.createClient(ctx, k6, log) + if err != nil { + log.Error(err, "A problem while getting token.") + return ctrl.Result{}, err + } + if !found { + log.Info(fmt.Sprintf("Token `%s` is not found yet.", k6.GetSpec().Token)) + return ctrl.Result{RequeueAfter: time.Second}, nil + } + } + + log.Info(fmt.Sprintf("Reconcile(); stage = %s", k6.GetStatus().Stage)) + + // Decision making here is now a mix between stages and conditions. + // TODO: refactor further. + + switch k6.GetStatus().Stage { + case "": + log.Info("Initialize test") + + v1alpha1.Initialize(k6) + + if _, err := r.UpdateStatus(ctx, k6, log); err != nil { + return ctrl.Result{}, err + } + + log.Info("Changing stage of K6 status to initialization") + k6.GetStatus().Stage = "initialization" + + if updateHappened, err := r.UpdateStatus(ctx, k6, log); err != nil { + return ctrl.Result{}, err + } else if updateHappened { + return InitializeJobs(ctx, log, k6, r) + } + + return ctrl.Result{}, nil + + case "initialization": + if v1alpha1.IsUnknown(k6, v1alpha1.CloudTestRun) { + return RunValidations(ctx, log, k6, r) + } + + if v1alpha1.IsFalse(k6, v1alpha1.CloudTestRun) { + // RunValidations has already happened and this is not a + // cloud test: we can move on + log.Info("Changing stage of K6 status to initialized") + + k6.GetStatus().Stage = "initialized" + + if updateHappened, err := r.UpdateStatus(ctx, k6, log); err != nil { + return ctrl.Result{}, err + } else if updateHappened { + return ctrl.Result{}, nil + } + } + + if v1alpha1.IsTrue(k6, v1alpha1.CloudTestRun) { + + if v1alpha1.IsFalse(k6, v1alpha1.CloudTestRunCreated) { + return SetupCloudTest(ctx, log, k6, r) + + } else { + // if test run was created, then only changing status is left + log.Info("Changing stage of K6 status to initialized") + + k6.GetStatus().Stage = "initialized" + + if _, err := r.UpdateStatus(ctx, k6, log); err != nil { + return ctrl.Result{}, err + } + } + } + + return ctrl.Result{}, nil + + case "initialized": + return CreateJobs(ctx, log, k6, r) + + case "created": + return StartJobs(ctx, log, k6, r) + + case "started": + if v1alpha1.IsTrue(k6, v1alpha1.CloudTestRun) && v1alpha1.IsTrue(k6, v1alpha1.CloudTestRunFinalized) { + // a fluke - nothing to do + return ctrl.Result{}, nil + } + + if v1alpha1.IsTrue(k6, v1alpha1.CloudTestRunAborted) { + // a fluke - nothing to do + return ctrl.Result{}, nil + } + + // wait for the test to finish + if !FinishJobs(ctx, log, k6, r) { + + if v1alpha1.IsTrue(k6, v1alpha1.CloudPLZTestRun) && v1alpha1.IsFalse(k6, v1alpha1.CloudTestRunAborted) { + // check in with the BE for status + if r.ShouldAbort(ctx, k6, log) { + log.Info("Received an abort signal from the k6 Cloud: stopping the test.") + return StopJobs(ctx, log, k6, r) + } + } + + // The test continues to execute. + + // Test runs can take a long time and usually they aren't supposed + // to be too quick. So check in only periodically. + return ctrl.Result{RequeueAfter: time.Second * 15}, nil + } + + log.Info("All runner pods are finished") + + // now mark it as stopped + + if v1alpha1.IsTrue(k6, v1alpha1.TestRunRunning) { + v1alpha1.UpdateCondition(k6, v1alpha1.TestRunRunning, metav1.ConditionFalse) + + log.Info("Changing stage of K6 status to stopped") + k6.GetStatus().Stage = "stopped" + + _, err := r.UpdateStatus(ctx, k6, log) + if err != nil { + return ctrl.Result{}, err + } + // log.Info(fmt.Sprintf("Debug updating status after finalize %v", updateHappened)) + } + + return ctrl.Result{}, nil + + case "stopped": + if v1alpha1.IsTrue(k6, v1alpha1.CloudPLZTestRun) && v1alpha1.IsTrue(k6, v1alpha1.CloudTestRunAborted) { + // This is a "forced" abort of the PLZ test run. + // Wait until all the test runs are stopped, kill jobs and proceed. + if StoppedJobs(ctx, log, k6, r) { + if allDeleted, err := KillJobs(ctx, log, k6, r); err != nil { + return ctrl.Result{RequeueAfter: time.Second}, err + } else { + // if we just have deleted all jobs, update status and go for reconcile + if allDeleted { + v1alpha1.UpdateCondition(k6, v1alpha1.CloudTestRunAborted, metav1.ConditionTrue) + _, err := r.UpdateStatus(ctx, k6, log) + if err != nil { + return ctrl.Result{}, err + } + } + } + } + } + + // If this is a cloud test run in any mode, try to finalize it. + if v1alpha1.IsTrue(k6, v1alpha1.CloudTestRun) && + v1alpha1.IsFalse(k6, v1alpha1.CloudTestRunFinalized) { + + // If TestRunRunning has just been updated, wait for a bit before + // acting, to avoid race condition between different reconcile loops. + t, _ := v1alpha1.LastUpdate(k6, v1alpha1.TestRunRunning) + if time.Now().Sub(t) < 5*time.Second { + return ctrl.Result{RequeueAfter: time.Second * 2}, nil + } + + if err = cloud.FinishTestRun(r.k6CloudClient, k6.GetStatus().TestRunID); err != nil { + log.Error(err, "Failed to finalize the test run with cloud output") + return ctrl.Result{}, nil + } else { + log.Info(fmt.Sprintf("Cloud test run %s was finalized successfully", k6.GetStatus().TestRunID)) + + v1alpha1.UpdateCondition(k6, v1alpha1.CloudTestRunFinalized, metav1.ConditionTrue) + } + } + + log.Info("Changing stage of K6 status to finished") + k6.GetStatus().Stage = "finished" + + _, err := r.UpdateStatus(ctx, k6, log) + if err != nil { + return ctrl.Result{}, err + } + + return ctrl.Result{RequeueAfter: time.Second}, nil + + case "error", "finished": + // delete if configured + if k6.GetSpec().Cleanup == "post" { + log.Info("Cleaning up all resources") + r.Delete(ctx, k6) + } + // notify if configured + return ctrl.Result{}, nil + } + + err = fmt.Errorf("invalid status") + log.Error(err, "Invalid status for the k6 resource.") + return ctrl.Result{}, err +} + +// SetupWithManager sets up a managed controller that will reconcile all events for the K6 CRD +func (r *TestRunReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&v1alpha1.TestRun{}). + Owns(&batchv1.Job{}). + Watches(&source.Kind{Type: &v1.Pod{}}, + handler.EnqueueRequestsFromMapFunc( + func(object client.Object) []reconcile.Request { + pod := object.(*v1.Pod) + k6CrName, ok := pod.GetLabels()[k6CrLabelName] + if !ok { + return nil + } + return []reconcile.Request{ + {NamespacedName: types.NamespacedName{ + Name: k6CrName, + Namespace: object.GetNamespace(), + }}} + }), + builder.WithPredicates(predicate.NewPredicateFuncs( + func(object client.Object) bool { + pod := object.(*v1.Pod) + _, ok := pod.GetLabels()[k6CrLabelName] + if !ok { + return false + } + return true + }))). + WithOptions(controller.Options{ + MaxConcurrentReconciles: 1, + // RateLimiter - ? + }). + Complete(r) +} + +func (r *TestRunReconciler) UpdateStatus(ctx context.Context, k6 v1alpha1.TestRunI, log logr.Logger) (updateHappened bool, err error) { + proposedStatus := k6.GetStatus().DeepCopy() + + // re-fetch resource + err = r.Get(ctx, k6.NamespacedName(), k6) + if err != nil { + if k8sErrors.IsNotFound(err) { + log.Info("Request deleted. No status to update.") + return false, nil + } + log.Error(err, "Could not fetch request") + return false, err + } + + cleanObj := k6.DeepCopyObject().(client.Object) + + // Update only if it's truly a newer version of the resource + // in comparison to the recently fetched resource. + isNewer := k6.GetStatus().SetIfNewer(*proposedStatus) + if !isNewer { + return false, nil + } + + err = r.Client.Status().Patch(ctx, k6, client.MergeFrom(cleanObj)) + + // TODO: look into retry.RetryOnConflict(retry.DefaultRetry, func() error{...}) + // to have retries of failing update here, in case of conflicts; + // with optional retry bool arg probably. + + // TODO: what if resource was deleted right before Patch? + // Add a check for IsNotFound(err). + + if err != nil { + log.Error(err, "Could not update status of custom resource") + return false, err + } + + return true, nil +} + +// ShouldAbort retrieves the status of test run from the Cloud and whether it should +// cause a forced stop. It is meant to be used only by PLZ test runs. +func (r *TestRunReconciler) ShouldAbort(ctx context.Context, k6 v1alpha1.TestRunI, log logr.Logger) bool { + // sanity check + if len(k6.GetStatus().TestRunID) == 0 { + log.Error(errors.New("empty test run ID"), "Trying to get state of test run with empty test run ID") + return false + } + + status, err := cloud.GetTestRunState(r.k6CloudClient, k6.GetStatus().TestRunID, log) + if err != nil { + log.Error(err, "Failed to get test run state.") + return false + } + + isAborted := status.Aborted() + + log.Info(fmt.Sprintf("Received test run status %v", status)) + + return isAborted +} + +func (r *TestRunReconciler) createClient(ctx context.Context, k6 v1alpha1.TestRunI, log logr.Logger) (bool, error) { + if r.k6CloudClient == nil { + token, tokenReady, err := loadToken(ctx, log, r.Client, k6.GetSpec().Token, &client.ListOptions{Namespace: k6.NamespacedName().Namespace}) + if err != nil { + log.Error(err, "A problem while getting token.") + return false, err + } + if !tokenReady { + return false, nil + } + + host := getEnvVar(k6.GetSpec().Runner.Env, "K6_CLOUD_HOST") + + r.k6CloudClient = cloud.NewClient(log, token, host) + } + + return true, nil +} diff --git a/main.go b/main.go index 02b1da88..1e63356f 100644 --- a/main.go +++ b/main.go @@ -78,14 +78,22 @@ func main() { mgr.AddHealthzCheck("health", healthz.Ping) mgr.AddReadyzCheck("ready", healthz.Ping) - if err = (&controllers.K6Reconciler{ + if err = (controllers.NewK6Reconciler(&controllers.TestRunReconciler{ Client: mgr.GetClient(), Log: ctrl.Log.WithName("controllers").WithName("K6"), Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { + })).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "K6") os.Exit(1) } + if err = (&controllers.TestRunReconciler{ + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controllers").WithName("TestRun"), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "TestRun") + os.Exit(1) + } if err = (&controllers.PrivateLoadZoneReconciler{ Client: mgr.GetClient(), Log: ctrl.Log.WithName("controllers").WithName("PrivateLoadZone"), @@ -94,6 +102,7 @@ func main() { setupLog.Error(err, "unable to create controller", "controller", "PrivateLoadZone") os.Exit(1) } + // +kubebuilder:scaffold:builder setupLog.Info("starting manager") diff --git a/pkg/resources/jobs/helpers.go b/pkg/resources/jobs/helpers.go index cb758ad1..1c763fbf 100644 --- a/pkg/resources/jobs/helpers.go +++ b/pkg/resources/jobs/helpers.go @@ -119,7 +119,7 @@ func newIstioEnvVar(istio v1alpha1.K6Scuttle, istioEnabled bool) []corev1.EnvVar } // TODO: Envoy variables are not passed to init containers -func getInitContainers(k6Spec *v1alpha1.K6Spec, script *types.Script) []corev1.Container { +func getInitContainers(k6Spec *v1alpha1.TestRunSpec, script *types.Script) []corev1.Container { var initContainers []corev1.Container for i, k6InitContainer := range k6Spec.Runner.InitContainers { diff --git a/pkg/resources/jobs/initializer.go b/pkg/resources/jobs/initializer.go index 2e7c53ff..3cf6a73b 100644 --- a/pkg/resources/jobs/initializer.go +++ b/pkg/resources/jobs/initializer.go @@ -11,8 +11,8 @@ import ( ) // NewInitializerJob builds a template used to initializefor creating a starter job -func NewInitializerJob(k6 *v1alpha1.K6, argLine string) (*batchv1.Job, error) { - script, err := k6.Spec.ParseScript() +func NewInitializerJob(k6 v1alpha1.TestRunI, argLine string) (*batchv1.Job, error) { + script, err := k6.GetSpec().ParseScript() if err != nil { return nil, err } @@ -20,38 +20,38 @@ func NewInitializerJob(k6 *v1alpha1.K6, argLine string) (*batchv1.Job, error) { var ( image = "ghcr.io/grafana/k6-operator:latest-runner" annotations = make(map[string]string) - labels = newLabels(k6.Name) + labels = newLabels(k6.NamespacedName().Name) serviceAccountName = "default" automountServiceAccountToken = true - ports = append([]corev1.ContainerPort{{ContainerPort: 6565}}, k6.Spec.Ports...) + ports = append([]corev1.ContainerPort{{ContainerPort: 6565}}, k6.GetSpec().Ports...) ) - if k6.Spec.Initializer == nil { - k6.Spec.Initializer = k6.Spec.Runner.DeepCopy() + if k6.GetSpec().Initializer == nil { + k6.GetSpec().Initializer = k6.GetSpec().Runner.DeepCopy() } - if k6.Spec.Initializer.Image != "" { - image = k6.Spec.Initializer.Image + if k6.GetSpec().Initializer.Image != "" { + image = k6.GetSpec().Initializer.Image } - if k6.Spec.Initializer.Metadata.Annotations != nil { - annotations = k6.Spec.Initializer.Metadata.Annotations + if k6.GetSpec().Initializer.Metadata.Annotations != nil { + annotations = k6.GetSpec().Initializer.Metadata.Annotations } - if k6.Spec.Initializer.Metadata.Labels != nil { - for k, v := range k6.Spec.Initializer.Metadata.Labels { + if k6.GetSpec().Initializer.Metadata.Labels != nil { + for k, v := range k6.GetSpec().Initializer.Metadata.Labels { if _, ok := labels[k]; !ok { labels[k] = v } } } - if k6.Spec.Initializer.ServiceAccountName != "" { - serviceAccountName = k6.Spec.Initializer.ServiceAccountName + if k6.GetSpec().Initializer.ServiceAccountName != "" { + serviceAccountName = k6.GetSpec().Initializer.ServiceAccountName } - if k6.Spec.Initializer.AutomountServiceAccountToken != "" { - automountServiceAccountToken, _ = strconv.ParseBool(k6.Spec.Initializer.AutomountServiceAccountToken) + if k6.GetSpec().Initializer.AutomountServiceAccountToken != "" { + automountServiceAccountToken, _ = strconv.ParseBool(k6.GetSpec().Initializer.AutomountServiceAccountToken) } var ( @@ -59,7 +59,7 @@ func NewInitializerJob(k6 *v1alpha1.K6, argLine string) (*batchv1.Job, error) { scriptName = script.FullName() archiveName = fmt.Sprintf("/tmp/%s.archived.tar", script.Filename) ) - istioCommand, istioEnabled := newIstioCommand(k6.Spec.Scuttle.Enabled, []string{"sh", "-c"}) + istioCommand, istioEnabled := newIstioCommand(k6.GetSpec().Scuttle.Enabled, []string{"sh", "-c"}) command := append(istioCommand, fmt.Sprintf( // There can be several scenarios from k6 command here: // a) script is correct and `k6 inspect` outputs JSON @@ -80,19 +80,19 @@ func NewInitializerJob(k6 *v1alpha1.K6, argLine string) (*batchv1.Job, error) { archiveName, scriptName, archiveName, argLine, archiveName)) - env := append(newIstioEnvVar(k6.Spec.Scuttle, istioEnabled), k6.Spec.Initializer.Env...) + env := append(newIstioEnvVar(k6.GetSpec().Scuttle, istioEnabled), k6.GetSpec().Initializer.Env...) volumes := script.Volume() - volumes = append(volumes, k6.Spec.Initializer.Volumes...) + volumes = append(volumes, k6.GetSpec().Initializer.Volumes...) volumeMounts := script.VolumeMount() - volumeMounts = append(volumeMounts, k6.Spec.Initializer.VolumeMounts...) + volumeMounts = append(volumeMounts, k6.GetSpec().Initializer.VolumeMounts...) var zero32 int32 job := &batchv1.Job{ ObjectMeta: metav1.ObjectMeta{ - Name: fmt.Sprintf("%s-initializer", k6.Name), - Namespace: k6.Namespace, + Name: fmt.Sprintf("%s-initializer", k6.NamespacedName().Name), + Namespace: k6.NamespacedName().Namespace, Labels: labels, Annotations: annotations, }, @@ -106,21 +106,21 @@ func NewInitializerJob(k6 *v1alpha1.K6, argLine string) (*batchv1.Job, error) { Spec: corev1.PodSpec{ AutomountServiceAccountToken: &automountServiceAccountToken, ServiceAccountName: serviceAccountName, - Affinity: k6.Spec.Initializer.Affinity, - NodeSelector: k6.Spec.Initializer.NodeSelector, - Tolerations: k6.Spec.Initializer.Tolerations, - SecurityContext: &k6.Spec.Initializer.SecurityContext, + Affinity: k6.GetSpec().Initializer.Affinity, + NodeSelector: k6.GetSpec().Initializer.NodeSelector, + Tolerations: k6.GetSpec().Initializer.Tolerations, + SecurityContext: &k6.GetSpec().Initializer.SecurityContext, RestartPolicy: corev1.RestartPolicyNever, - ImagePullSecrets: k6.Spec.Initializer.ImagePullSecrets, - InitContainers: getInitContainers(&k6.Spec, script), + ImagePullSecrets: k6.GetSpec().Initializer.ImagePullSecrets, + InitContainers: getInitContainers(k6.GetSpec(), script), Containers: []corev1.Container{ { Image: image, - ImagePullPolicy: k6.Spec.Initializer.ImagePullPolicy, + ImagePullPolicy: k6.GetSpec().Initializer.ImagePullPolicy, Name: "k6", Command: command, Env: env, - Resources: k6.Spec.Initializer.Resources, + Resources: k6.GetSpec().Initializer.Resources, VolumeMounts: volumeMounts, Ports: ports, }, diff --git a/pkg/resources/jobs/initializer_test.go b/pkg/resources/jobs/initializer_test.go index 923d8c93..cb37a972 100644 --- a/pkg/resources/jobs/initializer_test.go +++ b/pkg/resources/jobs/initializer_test.go @@ -75,12 +75,12 @@ func TestNewInitializerJob(t *testing.T) { }, } - k6 := &v1alpha1.K6{ + k6 := &v1alpha1.TestRun{ ObjectMeta: metav1.ObjectMeta{ Name: "test", Namespace: "test", }, - Spec: v1alpha1.K6Spec{ + Spec: v1alpha1.TestRunSpec{ Script: v1alpha1.K6Script{ ConfigMap: v1alpha1.K6Configmap{ Name: "test", diff --git a/pkg/resources/jobs/runner.go b/pkg/resources/jobs/runner.go index 5db6efb4..37f47850 100644 --- a/pkg/resources/jobs/runner.go +++ b/pkg/resources/jobs/runner.go @@ -17,39 +17,39 @@ import ( ) // NewRunnerJob creates a new k6 job from a CRD -func NewRunnerJob(k6 *v1alpha1.K6, index int, token string) (*batchv1.Job, error) { - name := fmt.Sprintf("%s-%d", k6.Name, index) +func NewRunnerJob(k6 v1alpha1.TestRunI, index int, token string) (*batchv1.Job, error) { + name := fmt.Sprintf("%s-%d", k6.NamespacedName().Name, index) postCommand := []string{"k6", "run"} - command, istioEnabled := newIstioCommand(k6.Spec.Scuttle.Enabled, postCommand) + command, istioEnabled := newIstioCommand(k6.GetSpec().Scuttle.Enabled, postCommand) quiet := true - if k6.Spec.Quiet != "" { - quiet, _ = strconv.ParseBool(k6.Spec.Quiet) + if k6.GetSpec().Quiet != "" { + quiet, _ = strconv.ParseBool(k6.GetSpec().Quiet) } if quiet { command = append(command, "--quiet") } - if k6.Spec.Parallelism > 1 { + if k6.GetSpec().Parallelism > 1 { var args []string var err error - if args, err = segmentation.NewCommandFragments(index, int(k6.Spec.Parallelism)); err != nil { + if args, err = segmentation.NewCommandFragments(index, int(k6.GetSpec().Parallelism)); err != nil { return nil, err } command = append(command, args...) } - script, err := k6.Spec.ParseScript() + script, err := k6.GetSpec().ParseScript() if err != nil { return nil, err } - if k6.Spec.Arguments != "" { - args := strings.Split(k6.Spec.Arguments, " ") + if k6.GetSpec().Arguments != "" { + args := strings.Split(k6.GetSpec().Arguments, " ") command = append(command, args...) } @@ -59,8 +59,8 @@ func NewRunnerJob(k6 *v1alpha1.K6, index int, token string) (*batchv1.Job, error "--address=0.0.0.0:6565") paused := true - if k6.Spec.Paused != "" { - paused, _ = strconv.ParseBool(k6.Spec.Paused) + if k6.GetSpec().Paused != "" { + paused, _ = strconv.ParseBool(k6.GetSpec().Paused) } if paused { @@ -81,19 +81,19 @@ func NewRunnerJob(k6 *v1alpha1.K6, index int, token string) (*batchv1.Job, error ) image := "ghcr.io/grafana/k6-operator:latest-runner" - if k6.Spec.Runner.Image != "" { - image = k6.Spec.Runner.Image + if k6.GetSpec().Runner.Image != "" { + image = k6.GetSpec().Runner.Image } runnerAnnotations := make(map[string]string) - if k6.Spec.Runner.Metadata.Annotations != nil { - runnerAnnotations = k6.Spec.Runner.Metadata.Annotations + if k6.GetSpec().Runner.Metadata.Annotations != nil { + runnerAnnotations = k6.GetSpec().Runner.Metadata.Annotations } - runnerLabels := newLabels(k6.Name) + runnerLabels := newLabels(k6.NamespacedName().Name) runnerLabels["runner"] = "true" - if k6.Spec.Runner.Metadata.Labels != nil { - for k, v := range k6.Spec.Runner.Metadata.Labels { // Order not specified + if k6.GetSpec().Runner.Metadata.Labels != nil { + for k, v := range k6.GetSpec().Runner.Metadata.Labels { // Order not specified if _, ok := runnerLabels[k]; !ok { runnerLabels[k] = v } @@ -101,35 +101,35 @@ func NewRunnerJob(k6 *v1alpha1.K6, index int, token string) (*batchv1.Job, error } serviceAccountName := "default" - if k6.Spec.Runner.ServiceAccountName != "" { - serviceAccountName = k6.Spec.Runner.ServiceAccountName + if k6.GetSpec().Runner.ServiceAccountName != "" { + serviceAccountName = k6.GetSpec().Runner.ServiceAccountName } automountServiceAccountToken := true - if k6.Spec.Runner.AutomountServiceAccountToken != "" { - automountServiceAccountToken, _ = strconv.ParseBool(k6.Spec.Runner.AutomountServiceAccountToken) + if k6.GetSpec().Runner.AutomountServiceAccountToken != "" { + automountServiceAccountToken, _ = strconv.ParseBool(k6.GetSpec().Runner.AutomountServiceAccountToken) } ports := []corev1.ContainerPort{{ContainerPort: 6565}} - ports = append(ports, k6.Spec.Ports...) + ports = append(ports, k6.GetSpec().Ports...) - env := newIstioEnvVar(k6.Spec.Scuttle, istioEnabled) + env := newIstioEnvVar(k6.GetSpec().Scuttle, istioEnabled) // this is a cloud output run - if len(k6.Status.TestRunID) > 0 { + if len(k6.GetStatus().TestRunID) > 0 { // temporary hack - if k6.IsTrue(v1alpha1.CloudPLZTestRun) { - k6.Status.AggregationVars = "50|3s|8s|6s|10000|10" + if v1alpha1.IsTrue(k6, v1alpha1.CloudPLZTestRun) { + k6.GetStatus().AggregationVars = "50|3s|8s|6s|10000|10" } - aggregationVars, err := cloud.DecodeAggregationConfig(k6.Status.AggregationVars) + aggregationVars, err := cloud.DecodeAggregationConfig(k6.GetStatus().AggregationVars) if err != nil { return nil, err } env = append(env, aggregationVars...) env = append(env, corev1.EnvVar{ Name: "K6_CLOUD_PUSH_REF_ID", - Value: k6.Status.TestRunID, + Value: k6.GetStatus().TestRunID, }, corev1.EnvVar{ Name: "K6_CLOUD_TOKEN", Value: token, @@ -137,18 +137,18 @@ func NewRunnerJob(k6 *v1alpha1.K6, index int, token string) (*batchv1.Job, error ) } - env = append(env, k6.Spec.Runner.Env...) + env = append(env, k6.GetSpec().Runner.Env...) volumes := script.Volume() - volumes = append(volumes, k6.Spec.Runner.Volumes...) + volumes = append(volumes, k6.GetSpec().Runner.Volumes...) volumeMounts := script.VolumeMount() - volumeMounts = append(volumeMounts, k6.Spec.Runner.VolumeMounts...) + volumeMounts = append(volumeMounts, k6.GetSpec().Runner.VolumeMounts...) job := &batchv1.Job{ ObjectMeta: metav1.ObjectMeta{ Name: name, - Namespace: k6.Namespace, + Namespace: k6.NamespacedName().Namespace, Labels: runnerLabels, Annotations: runnerAnnotations, }, @@ -164,24 +164,24 @@ func NewRunnerJob(k6 *v1alpha1.K6, index int, token string) (*batchv1.Job, error ServiceAccountName: serviceAccountName, Hostname: name, RestartPolicy: corev1.RestartPolicyNever, - Affinity: k6.Spec.Runner.Affinity, - NodeSelector: k6.Spec.Runner.NodeSelector, - Tolerations: k6.Spec.Runner.Tolerations, - SecurityContext: &k6.Spec.Runner.SecurityContext, - ImagePullSecrets: k6.Spec.Runner.ImagePullSecrets, - InitContainers: getInitContainers(&k6.Spec, script), + Affinity: k6.GetSpec().Runner.Affinity, + NodeSelector: k6.GetSpec().Runner.NodeSelector, + Tolerations: k6.GetSpec().Runner.Tolerations, + SecurityContext: &k6.GetSpec().Runner.SecurityContext, + ImagePullSecrets: k6.GetSpec().Runner.ImagePullSecrets, + InitContainers: getInitContainers(k6.GetSpec(), script), Containers: []corev1.Container{{ Image: image, - ImagePullPolicy: k6.Spec.Runner.ImagePullPolicy, + ImagePullPolicy: k6.GetSpec().Runner.ImagePullPolicy, Name: "k6", Command: command, Env: env, - Resources: k6.Spec.Runner.Resources, + Resources: k6.GetSpec().Runner.Resources, VolumeMounts: volumeMounts, Ports: ports, - EnvFrom: k6.Spec.Runner.EnvFrom, - LivenessProbe: generateProbe(k6.Spec.Runner.LivenessProbe), - ReadinessProbe: generateProbe(k6.Spec.Runner.ReadinessProbe), + EnvFrom: k6.GetSpec().Runner.EnvFrom, + LivenessProbe: generateProbe(k6.GetSpec().Runner.LivenessProbe), + ReadinessProbe: generateProbe(k6.GetSpec().Runner.ReadinessProbe), }}, TerminationGracePeriodSeconds: &zero, Volumes: volumes, @@ -190,26 +190,26 @@ func NewRunnerJob(k6 *v1alpha1.K6, index int, token string) (*batchv1.Job, error }, } - if k6.Spec.Separate { + if k6.GetSpec().Separate { job.Spec.Template.Spec.Affinity = newAntiAffinity() } return job, nil } -func NewRunnerService(k6 *v1alpha1.K6, index int) (*corev1.Service, error) { - serviceName := fmt.Sprintf("%s-%s-%d", k6.Name, "service", index) - runnerName := fmt.Sprintf("%s-%d", k6.Name, index) +func NewRunnerService(k6 v1alpha1.TestRunI, index int) (*corev1.Service, error) { + serviceName := fmt.Sprintf("%s-%s-%d", k6.NamespacedName().Name, "service", index) + runnerName := fmt.Sprintf("%s-%d", k6.NamespacedName().Name, index) runnerAnnotations := make(map[string]string) - if k6.Spec.Runner.Metadata.Annotations != nil { - runnerAnnotations = k6.Spec.Runner.Metadata.Annotations + if k6.GetSpec().Runner.Metadata.Annotations != nil { + runnerAnnotations = k6.GetSpec().Runner.Metadata.Annotations } - runnerLabels := newLabels(k6.Name) + runnerLabels := newLabels(k6.NamespacedName().Name) runnerLabels["runner"] = "true" - if k6.Spec.Runner.Metadata.Labels != nil { - for k, v := range k6.Spec.Runner.Metadata.Labels { // Order not specified + if k6.GetSpec().Runner.Metadata.Labels != nil { + for k, v := range k6.GetSpec().Runner.Metadata.Labels { // Order not specified if _, ok := runnerLabels[k]; !ok { runnerLabels[k] = v } @@ -225,7 +225,7 @@ func NewRunnerService(k6 *v1alpha1.K6, index int) (*corev1.Service, error) { service := &corev1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: serviceName, - Namespace: k6.Namespace, + Namespace: k6.NamespacedName().Namespace, Labels: runnerLabels, Annotations: runnerAnnotations, }, diff --git a/pkg/resources/jobs/runner_test.go b/pkg/resources/jobs/runner_test.go index 32afd6d8..b8fcb904 100644 --- a/pkg/resources/jobs/runner_test.go +++ b/pkg/resources/jobs/runner_test.go @@ -46,7 +46,7 @@ func TestNewScriptVolumeClaim(t *testing.T) { Type: "VolumeClaim", } - k6 := v1alpha1.K6Spec{ + k6 := v1alpha1.TestRunSpec{ Script: v1alpha1.K6Script{ VolumeClaim: v1alpha1.K6VolumeClaim{ Name: "Test", @@ -72,7 +72,7 @@ func TestNewScriptConfigMap(t *testing.T) { Type: "ConfigMap", } - k6 := v1alpha1.K6Spec{ + k6 := v1alpha1.TestRunSpec{ Script: v1alpha1.K6Script{ ConfigMap: v1alpha1.K6Configmap{ Name: "Test", @@ -99,7 +99,7 @@ func TestNewScriptLocalFile(t *testing.T) { Type: "LocalFile", } - k6 := v1alpha1.K6Spec{ + k6 := v1alpha1.TestRunSpec{ Script: v1alpha1.K6Script{ LocalFile: "/custom/my_test.js", }, @@ -115,7 +115,7 @@ func TestNewScriptLocalFile(t *testing.T) { } func TestNewScriptNoScript(t *testing.T) { - k6 := v1alpha1.K6Spec{} + k6 := v1alpha1.TestRunSpec{} script, err := k6.ParseScript() if err == nil && script != nil { @@ -247,12 +247,12 @@ func TestNewRunnerService(t *testing.T) { }, } - k6 := &v1alpha1.K6{ + k6 := &v1alpha1.TestRun{ ObjectMeta: metav1.ObjectMeta{ Name: "test", Namespace: "test", }, - Spec: v1alpha1.K6Spec{ + Spec: v1alpha1.TestRunSpec{ Runner: v1alpha1.Pod{ Metadata: v1alpha1.PodMetadata{ Labels: map[string]string{ @@ -362,12 +362,12 @@ func TestNewRunnerJob(t *testing.T) { }, }, } - k6 := &v1alpha1.K6{ + k6 := &v1alpha1.TestRun{ ObjectMeta: metav1.ObjectMeta{ Name: "test", Namespace: "test", }, - Spec: v1alpha1.K6Spec{ + Spec: v1alpha1.TestRunSpec{ Script: v1alpha1.K6Script{ ConfigMap: v1alpha1.K6Configmap{ Name: "test", @@ -484,12 +484,12 @@ func TestNewRunnerJobNoisy(t *testing.T) { }, }, } - k6 := &v1alpha1.K6{ + k6 := &v1alpha1.TestRun{ ObjectMeta: metav1.ObjectMeta{ Name: "test", Namespace: "test", }, - Spec: v1alpha1.K6Spec{ + Spec: v1alpha1.TestRunSpec{ Quiet: "false", Script: v1alpha1.K6Script{ ConfigMap: v1alpha1.K6Configmap{ @@ -597,12 +597,12 @@ func TestNewRunnerJobUnpaused(t *testing.T) { }, }, } - k6 := &v1alpha1.K6{ + k6 := &v1alpha1.TestRun{ ObjectMeta: metav1.ObjectMeta{ Name: "test", Namespace: "test", }, - Spec: v1alpha1.K6Spec{ + Spec: v1alpha1.TestRunSpec{ Paused: "false", Script: v1alpha1.K6Script{ ConfigMap: v1alpha1.K6Configmap{ @@ -711,12 +711,12 @@ func TestNewRunnerJobArguments(t *testing.T) { }, } - k6 := &v1alpha1.K6{ + k6 := &v1alpha1.TestRun{ ObjectMeta: metav1.ObjectMeta{ Name: "test", Namespace: "test", }, - Spec: v1alpha1.K6Spec{ + Spec: v1alpha1.TestRunSpec{ Arguments: "--cool-thing", Script: v1alpha1.K6Script{ ConfigMap: v1alpha1.K6Configmap{ @@ -825,12 +825,12 @@ func TestNewRunnerJobServiceAccount(t *testing.T) { }, } - k6 := &v1alpha1.K6{ + k6 := &v1alpha1.TestRun{ ObjectMeta: metav1.ObjectMeta{ Name: "test", Namespace: "test", }, - Spec: v1alpha1.K6Spec{ + Spec: v1alpha1.TestRunSpec{ Script: v1alpha1.K6Script{ ConfigMap: v1alpha1.K6Configmap{ @@ -952,12 +952,12 @@ func TestNewRunnerJobIstio(t *testing.T) { }, }, } - k6 := &v1alpha1.K6{ + k6 := &v1alpha1.TestRun{ ObjectMeta: metav1.ObjectMeta{ Name: "test", Namespace: "test", }, - Spec: v1alpha1.K6Spec{ + Spec: v1alpha1.TestRunSpec{ Scuttle: v1alpha1.K6Scuttle{ Enabled: "true", }, @@ -1075,12 +1075,12 @@ func TestNewRunnerJobCloud(t *testing.T) { }, }, } - k6 := &v1alpha1.K6{ + k6 := &v1alpha1.TestRun{ ObjectMeta: metav1.ObjectMeta{ Name: "test", Namespace: "test", }, - Spec: v1alpha1.K6Spec{ + Spec: v1alpha1.TestRunSpec{ Script: v1alpha1.K6Script{ ConfigMap: v1alpha1.K6Configmap{ Name: "test", @@ -1098,7 +1098,7 @@ func TestNewRunnerJobCloud(t *testing.T) { }, // Since this test only creates a runner's spec so // testrunid has to be set hard-coded here. - Status: v1alpha1.K6Status{ + Status: v1alpha1.TestRunStatus{ TestRunID: "testrunid", AggregationVars: "50|3s|8s|6s|10000|10", }, @@ -1190,12 +1190,12 @@ func TestNewRunnerJobLocalFile(t *testing.T) { }, }, } - k6 := &v1alpha1.K6{ + k6 := &v1alpha1.TestRun{ ObjectMeta: metav1.ObjectMeta{ Name: "test", Namespace: "test", }, - Spec: v1alpha1.K6Spec{ + Spec: v1alpha1.TestRunSpec{ Scuttle: v1alpha1.K6Scuttle{ Enabled: "false", }, @@ -1329,12 +1329,12 @@ func TestNewRunnerJobWithInitContainer(t *testing.T) { }, }, } - k6 := &v1alpha1.K6{ + k6 := &v1alpha1.TestRun{ ObjectMeta: metav1.ObjectMeta{ Name: "test", Namespace: "test", }, - Spec: v1alpha1.K6Spec{ + Spec: v1alpha1.TestRunSpec{ Script: v1alpha1.K6Script{ ConfigMap: v1alpha1.K6Configmap{ Name: "test", @@ -1505,12 +1505,12 @@ func TestNewRunnerJobWithVolume(t *testing.T) { }, }, } - k6 := &v1alpha1.K6{ + k6 := &v1alpha1.TestRun{ ObjectMeta: metav1.ObjectMeta{ Name: "test", Namespace: "test", }, - Spec: v1alpha1.K6Spec{ + Spec: v1alpha1.TestRunSpec{ Script: v1alpha1.K6Script{ ConfigMap: v1alpha1.K6Configmap{ Name: "test", diff --git a/pkg/resources/jobs/starter.go b/pkg/resources/jobs/starter.go index e3b34f6c..b5b043cd 100644 --- a/pkg/resources/jobs/starter.go +++ b/pkg/resources/jobs/starter.go @@ -12,41 +12,41 @@ import ( ) // NewStarterJob builds a template used for creating a starter job -func NewStarterJob(k6 *v1alpha1.K6, hostname []string) *batchv1.Job { +func NewStarterJob(k6 v1alpha1.TestRunI, hostname []string) *batchv1.Job { starterAnnotations := make(map[string]string) - if k6.Spec.Starter.Metadata.Annotations != nil { - starterAnnotations = k6.Spec.Starter.Metadata.Annotations + if k6.GetSpec().Starter.Metadata.Annotations != nil { + starterAnnotations = k6.GetSpec().Starter.Metadata.Annotations } starterImage := "ghcr.io/grafana/k6-operator:latest-starter" - if k6.Spec.Starter.Image != "" { - starterImage = k6.Spec.Starter.Image + if k6.GetSpec().Starter.Image != "" { + starterImage = k6.GetSpec().Starter.Image } - starterLabels := newLabels(k6.Name) - if k6.Spec.Starter.Metadata.Labels != nil { - for k, v := range k6.Spec.Starter.Metadata.Labels { // Order not specified + starterLabels := newLabels(k6.NamespacedName().Name) + if k6.GetSpec().Starter.Metadata.Labels != nil { + for k, v := range k6.GetSpec().Starter.Metadata.Labels { // Order not specified if _, ok := starterLabels[k]; !ok { starterLabels[k] = v } } } serviceAccountName := "default" - if k6.Spec.Starter.ServiceAccountName != "" { - serviceAccountName = k6.Spec.Starter.ServiceAccountName + if k6.GetSpec().Starter.ServiceAccountName != "" { + serviceAccountName = k6.GetSpec().Starter.ServiceAccountName } automountServiceAccountToken := true - if k6.Spec.Starter.AutomountServiceAccountToken != "" { - automountServiceAccountToken, _ = strconv.ParseBool(k6.Spec.Starter.AutomountServiceAccountToken) + if k6.GetSpec().Starter.AutomountServiceAccountToken != "" { + automountServiceAccountToken, _ = strconv.ParseBool(k6.GetSpec().Starter.AutomountServiceAccountToken) } - command, istioEnabled := newIstioCommand(k6.Spec.Scuttle.Enabled, []string{"sh", "-c"}) - env := newIstioEnvVar(k6.Spec.Scuttle, istioEnabled) + command, istioEnabled := newIstioCommand(k6.GetSpec().Scuttle.Enabled, []string{"sh", "-c"}) + env := newIstioEnvVar(k6.GetSpec().Scuttle, istioEnabled) return &batchv1.Job{ ObjectMeta: metav1.ObjectMeta{ - Name: fmt.Sprintf("%s-starter", k6.Name), - Namespace: k6.Namespace, + Name: fmt.Sprintf("%s-starter", k6.NamespacedName().Name), + Namespace: k6.NamespacedName().Namespace, Labels: starterLabels, Annotations: starterAnnotations, }, @@ -59,14 +59,14 @@ func NewStarterJob(k6 *v1alpha1.K6, hostname []string) *batchv1.Job { Spec: corev1.PodSpec{ AutomountServiceAccountToken: &automountServiceAccountToken, ServiceAccountName: serviceAccountName, - Affinity: k6.Spec.Starter.Affinity, - NodeSelector: k6.Spec.Starter.NodeSelector, - Tolerations: k6.Spec.Starter.Tolerations, + Affinity: k6.GetSpec().Starter.Affinity, + NodeSelector: k6.GetSpec().Starter.NodeSelector, + Tolerations: k6.GetSpec().Starter.Tolerations, RestartPolicy: corev1.RestartPolicyNever, - SecurityContext: &k6.Spec.Starter.SecurityContext, - ImagePullSecrets: k6.Spec.Starter.ImagePullSecrets, + SecurityContext: &k6.GetSpec().Starter.SecurityContext, + ImagePullSecrets: k6.GetSpec().Starter.ImagePullSecrets, Containers: []corev1.Container{ - containers.NewStartContainer(hostname, starterImage, k6.Spec.Starter.ImagePullPolicy, command, env), + containers.NewStartContainer(hostname, starterImage, k6.GetSpec().Starter.ImagePullPolicy, command, env), }, }, }, diff --git a/pkg/resources/jobs/starter_test.go b/pkg/resources/jobs/starter_test.go index 12ea96a8..d727e532 100644 --- a/pkg/resources/jobs/starter_test.go +++ b/pkg/resources/jobs/starter_test.go @@ -57,12 +57,12 @@ func TestNewStarterJob(t *testing.T) { }, } - k6 := &v1alpha1.K6{ + k6 := &v1alpha1.TestRun{ ObjectMeta: metav1.ObjectMeta{ Name: "test", Namespace: "test", }, - Spec: v1alpha1.K6Spec{ + Spec: v1alpha1.TestRunSpec{ Script: v1alpha1.K6Script{ ConfigMap: v1alpha1.K6Configmap{ Name: "test", @@ -148,12 +148,12 @@ func TestNewStarterJobIstio(t *testing.T) { }, } - k6 := &v1alpha1.K6{ + k6 := &v1alpha1.TestRun{ ObjectMeta: metav1.ObjectMeta{ Name: "test", Namespace: "test", }, - Spec: v1alpha1.K6Spec{ + Spec: v1alpha1.TestRunSpec{ Scuttle: v1alpha1.K6Scuttle{ Enabled: "true", }, diff --git a/pkg/resources/jobs/stopper.go b/pkg/resources/jobs/stopper.go index 940f6402..4476d7d7 100644 --- a/pkg/resources/jobs/stopper.go +++ b/pkg/resources/jobs/stopper.go @@ -10,22 +10,22 @@ import ( ) // NewStopJob builds a template used for creating a stop job -func NewStopJob(k6 *v1alpha1.K6, hostname []string) *batchv1.Job { +func NewStopJob(k6 v1alpha1.TestRunI, hostname []string) *batchv1.Job { // this job is almost identical to the starter so re-use the definitions job := NewStarterJob(k6, hostname) - job.Name = fmt.Sprintf("%s-stopper", k6.Name) + job.Name = fmt.Sprintf("%s-stopper", k6.NamespacedName().Name) image := "ghcr.io/grafana/k6-operator:latest-starter" - if k6.Spec.Starter.Image != "" { - image = k6.Spec.Starter.Image + if k6.GetSpec().Starter.Image != "" { + image = k6.GetSpec().Starter.Image } - command, istioEnabled := newIstioCommand(k6.Spec.Scuttle.Enabled, []string{"sh", "-c"}) - env := newIstioEnvVar(k6.Spec.Scuttle, istioEnabled) + command, istioEnabled := newIstioCommand(k6.GetSpec().Scuttle.Enabled, []string{"sh", "-c"}) + env := newIstioEnvVar(k6.GetSpec().Scuttle, istioEnabled) job.Spec.Template.Spec.Containers = []corev1.Container{ - containers.NewStopContainer(hostname, image, k6.Spec.Starter.ImagePullPolicy, command, env), + containers.NewStopContainer(hostname, image, k6.GetSpec().Starter.ImagePullPolicy, command, env), } return job diff --git a/pkg/resources/jobs/stopper_test.go b/pkg/resources/jobs/stopper_test.go index e1859b98..6016a890 100644 --- a/pkg/resources/jobs/stopper_test.go +++ b/pkg/resources/jobs/stopper_test.go @@ -56,12 +56,12 @@ func TestNewStopperJob(t *testing.T) { }, } - k6 := &v1alpha1.K6{ + k6 := &v1alpha1.TestRun{ ObjectMeta: metav1.ObjectMeta{ Name: "test", Namespace: "test", }, - Spec: v1alpha1.K6Spec{ + Spec: v1alpha1.TestRunSpec{ Script: v1alpha1.K6Script{ ConfigMap: v1alpha1.K6Configmap{ Name: "test", @@ -146,12 +146,12 @@ func TestNewStopJobIstio(t *testing.T) { }, } - k6 := &v1alpha1.K6{ + k6 := &v1alpha1.TestRun{ ObjectMeta: metav1.ObjectMeta{ Name: "test", Namespace: "test", }, - Spec: v1alpha1.K6Spec{ + Spec: v1alpha1.TestRunSpec{ Scuttle: v1alpha1.K6Scuttle{ Enabled: "true", }, diff --git a/pkg/testrun/plz.go b/pkg/testrun/plz.go index 4a0ec348..3706f640 100644 --- a/pkg/testrun/plz.go +++ b/pkg/testrun/plz.go @@ -16,7 +16,7 @@ func TestName(testRunId string) string { } // ingestURL is a temp hack -func NewPLZTestRun(plz *v1alpha1.PrivateLoadZone, trData *cloud.TestRunData, ingestUrl string) *v1alpha1.K6 { +func NewPLZTestRun(plz *v1alpha1.PrivateLoadZone, trData *cloud.TestRunData, ingestUrl string) *v1alpha1.TestRun { volume := corev1.Volume{ Name: "archive-volume", VolumeSource: corev1.VolumeSource{ @@ -34,12 +34,12 @@ func NewPLZTestRun(plz *v1alpha1.PrivateLoadZone, trData *cloud.TestRunData, ing volumeMount, ) - return &v1alpha1.K6{ + return &v1alpha1.TestRun{ ObjectMeta: metav1.ObjectMeta{ Name: TestName(trData.TestRunID()), Namespace: plz.Namespace, }, - Spec: v1alpha1.K6Spec{ + Spec: v1alpha1.TestRunSpec{ Runner: v1alpha1.Pod{ Image: trData.RunnerImage, ServiceAccountName: plz.Spec.ServiceAccountName,