diff --git a/deploy/crds/quickstart-resources-cr.yaml b/deploy/crds/quickstart-resources-cr.yaml new file mode 100644 index 00000000..504210e7 --- /dev/null +++ b/deploy/crds/quickstart-resources-cr.yaml @@ -0,0 +1,14 @@ +apiVersion: wildfly.org/v1alpha1 +kind: WildFlyServer +metadata: + name: quickstart +spec: + applicationImage: "quay.io/wildfly-quickstarts/wildfly-operator-quickstart:18.0" + replicas: 2 + resources: + limits: + cpu: "1" + memory: "512Mi" + requests: + cpu: "500m" + memory: "256Mi" diff --git a/deploy/crds/wildfly.org_wildflyservers_crd.yaml b/deploy/crds/wildfly.org_wildflyservers_crd.yaml index cab34bc4..04d89a60 100644 --- a/deploy/crds/wildfly.org_wildflyservers_crd.yaml +++ b/deploy/crds/wildfly.org_wildflyservers_crd.yaml @@ -210,6 +210,33 @@ spec: format: int32 minimum: 0 type: integer + resources: + description: 'ResourcesSpec defines the resources used by the WildFlyServer, + ie CPU and memory, use limits and requests. More info: https://pkg.go.dev/k8s.io/api@v0.18.14/core/v1#ResourceRequirements' + properties: + 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 + description: 'Limits describes the maximum amount of compute resources + allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' + 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 + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' + type: object + type: object secrets: description: Secrets is a list of Secrets in the same namespace as the WildFlyServer object, which shall be mounted into the WildFlyServer @@ -534,15 +561,20 @@ spec: \n Read-only." format: int32 type: integer + selector: + description: selector for pods, used by HorizontalPodAutoscaler + type: string required: - replicas - scalingdownPods + - selector type: object type: object served: true storage: true subresources: scale: + labelSelectorPath: .status.selector specReplicasPath: .spec.replicas statusReplicasPath: .status.replicas status: {} diff --git a/doc/apis.adoc b/doc/apis.adoc index 18defeed..8854441d 100644 --- a/doc/apis.adoc +++ b/doc/apis.adoc @@ -11,9 +11,9 @@ This document describes the types introduced by the WildFly Operator to be consu [options="header,footer"] |======================= | Field | Description |Scheme| Required -| `metadata` | Standard object’s metadata (https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#metadata[more info]) | https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.11/#objectmeta-v1-meta[metav1.ObjectMeta] | false +| `metadata` | Standard object's metadata (https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#metadata[more info]) | https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#objectmeta-v1-meta[metav1.ObjectMeta] | false | `spec` | Specification of the desired behaviour of the WildFly deployment (https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#spec-and-status[more info]) | <> | true -| `status` | Most recent observed status of the WildFly deployment. Read-only. (https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#spec-and-status#spec-and-status[more info]) | <> | false | +| `status` | Most recent observed status of the WildFly deployment. Read-only. (https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#spec-and-status#spec-and-status[more info]) | <> | false | |======================= [[wildflyservelist]] @@ -24,7 +24,7 @@ This document describes the types introduced by the WildFly Operator to be consu [options="header,footer"] |======================= | Field | Description |Scheme| Required -| `metadata` | Standard list's metadata (https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#metadata[more info]) | https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.11/#listmeta-v1-meta[metav1.ListMeta] | false +| `metadata` | Standard list's metadata (https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#metadata[more info]) | https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#listmeta-v1-meta[metav1.ListMeta] | false | `items` | List of `WildFlyServer` | []<> | true |======================= @@ -44,16 +44,31 @@ It uses a `StatefulSet` with a pod spec that mounts the volume specified by `sto | `bootableJar` | BootableJar specifies whether the application image is using WildFly S2I Builder/Runtime images or Bootable Jar. If omitted, it defaults to false (application image is expected to use WildFly S2I Builder/Runtime images) | bool | false | `standaloneConfigMap` | spec to specify how standalone configuration can be read from a `ConfigMap` | *<> |false +| `resources`| Resources spec to specify the request or limits of the Stateful Set. If ommited, the namespace defaults are used. | *<> | false | `storage` | Storage spec to specify how storage should be used. If omitted, an `EmptyDir` is used (that will not persist data across pod restart) | *<> |false -| `serviceAccountName` | Name of the ServiceAccount to use to run the WildFlyServer Pods | string | false -| `envFrom` | List of environment variable present in the containers from source (either `ConfigMap` or `Secret`) | []https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.11/#envfromsource-v1-core[corev1.EnvFromSource] |false -| `env` | List of environment variable present in the containers | []https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.11/#envvar-v1-core[corev1.EnvVar] | false +| `serviceAccountName` | Name of the ServiceAccount to use to run the WildFlyServer Pods | string | false +| `envFrom` | List of environment variable present in the containers from source (either `ConfigMap` or `Secret`) | []https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#envfromsource-v1-core[corev1.EnvFromSource] |false +| `env` | List of environment variable present in the containers | []https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#envvar-v1-core[corev1.EnvVar] | false | `secrets` | List of secret names to mount as volumes in the containers. Each secret is mounted as a read-only volume under `/etc/secrets/` | string[] | false | `configMaps` | List of ConfigMap names to mount as volumes in the containers. Each config map is mounted as a read-only volume under `/etc/configmaps/` | string[] | false | `disableHTTPRoute`| Disable the creation a route to the HTTP port of the application service (false if omitted) | bool | false | `sessionAffinity`| If connections from the same client IP are passed to the same WildFlyServer instance/pod each time (false if omitted) | bool | false |======================= +[[resources]] +## `Resources` + +`Resources` defines the configured resources for a `WildflyServer` resources. If the resource is not defined or `Request` or `Limits` is empty this resources will removed from the `Stateful Set` +The description of this resource is a standard `Container` resource and is the same as the kubernetes api found on kubernetes. + +This resource is not limited to only cpu and memory for more information on container resources: +https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + +[options="header,footer"] +|======================= +| Field | Description |Scheme| Required +| `resources` | ResourceRequirements describes the compute resource requirements. | https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#resourcerequirements-v1-core[corev1.ResourceRequirements] | false +|======================= [[storagespec]] ## `StorageSpec` @@ -68,8 +83,8 @@ transaction, make sure to specify a `volumeClaimTemplate` that so that the same [options="header,footer"] |======================= | Field | Description |Scheme| Required -| `emptyDir` | EmptyDirVolumeSource to be used by the WildFly `StatefulSet` | https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.11/#emptydirvolumesource-v1-core[*corev1.EmptyDirVolumeSource] | false -| `volumeClaimTemplate` | A PersistentVolumeClaim spec to configure `Resources` requirements to store WildFly standalone data directory. The name of the template is derived from the `WildFlyServer` name. The corresponding volume will be mounted in `ReadWriteOnce` access mode. | https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.11/#persistentvolumeclaim-v1-core[corev1.PersistentVolumeClaim] | false +| `emptyDir` | EmptyDirVolumeSource to be used by the WildFly `StatefulSet` | https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#emptydirvolumesource-v1-core[*corev1.EmptyDirVolumeSource] | false +| `volumeClaimTemplate` | A PersistentVolumeClaim spec to configure `Resources` requirements to store WildFly standalone data directory. The name of the template is derived from the `WildFlyServer` name. The corresponding volume will be mounted in `ReadWriteOnce` access mode. | https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#persistentvolumeclaim-v1-core[corev1.PersistentVolumeClaim] | false |======================= [[standaloneconfigmapspec]] @@ -80,7 +95,7 @@ transaction, make sure to specify a `volumeClaimTemplate` that so that the same [options="header,footer"] |======================= | Field | Description |Scheme| Required -| `name` | Name of the `ConfigMap` containing the standalone configuration XML file. | string | true +| `name` | Name of the `ConfigMap` containing the standalone configuration XML file. | string | true | `key` | Key of the ConfigMap whose value is the standalone configuration XML file. If omitted, the spec will look for the `standalone.xml` key. | string |false |======================= diff --git a/doc/user-guide.adoc b/doc/user-guide.adoc index 1bd3a3a3..9ba8cd32 100644 --- a/doc/user-guide.adoc +++ b/doc/user-guide.adoc @@ -68,6 +68,29 @@ spec: replicas:2 ---- +[[resources]] +## Specify the Resource requirements for the container + +The `resources` spec is defined in the link:../apis.adoc#Resources[Resources API Documentation]. + +[source,yaml] +.Example of resources configuration +``` +spec: + resources: + limits: + cpu: 500m + memory: 2Gi + requests: + cpu: 100m + memory: 1Gi +``` + +For more examples visit: + +* https://kubernetes.io/docs/tasks/configure-pod-container/assign-cpu-resource/ +* https://kubernetes.io/docs/tasks/configure-pod-container/assign-memory-resource/ + [[storage]] ## Specify the Storage Requirements for the Server Data Directory diff --git a/pkg/apis/wildfly/v1alpha1/wildflyserver_types.go b/pkg/apis/wildfly/v1alpha1/wildflyserver_types.go index 531f8db0..1c4e2909 100644 --- a/pkg/apis/wildfly/v1alpha1/wildflyserver_types.go +++ b/pkg/apis/wildfly/v1alpha1/wildflyserver_types.go @@ -48,6 +48,9 @@ type WildFlyServerSpec struct { // +kubebuilder:validation:MinItems=1 // +listType=set ConfigMaps []string `json:"configMaps,omitempty"` + // ResourcesSpec defines the resources used by the WildFlyServer, ie CPU and memory, use limits and requests. + // More info: https://pkg.go.dev/k8s.io/api@v0.18.14/core/v1#ResourceRequirements + Resources *corev1.ResourceRequirements `json:"resources,omitempty"` } // StandaloneConfigMapSpec defines the desired configMap configuration to obtain the standalone configuration for WildFlyServer @@ -83,6 +86,8 @@ type WildFlyServerStatus struct { // // Read-only. ScalingdownPods int32 `json:"scalingdownPods"` + // selector for pods, used by HorizontalPodAutoscaler + Selector string `json:"selector"` } const ( @@ -119,7 +124,7 @@ type PodStatus struct { // +k8s:openapi-gen=true // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +kubebuilder:subresource:status -// +kubebuilder:subresource:scale:specpath=.spec.replicas,statuspath=.status.replicas +// +kubebuilder:subresource:scale:specpath=.spec.replicas,statuspath=.status.replicas,selectorpath=.status.selector // +kubebuilder:printcolumn:name="Replicas",type="integer",JSONPath=".spec.replicas" // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" // +kubebuilder:resource:shortName=wfly diff --git a/pkg/apis/wildfly/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/wildfly/v1alpha1/zz_generated.deepcopy.go index 7ab83014..4fc74184 100644 --- a/pkg/apis/wildfly/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/wildfly/v1alpha1/zz_generated.deepcopy.go @@ -161,6 +161,11 @@ func (in *WildFlyServerSpec) DeepCopyInto(out *WildFlyServerSpec) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.Resources != nil { + in, out := &in.Resources, &out.Resources + *out = new(v1.ResourceRequirements) + (*in).DeepCopyInto(*out) + } return } diff --git a/pkg/apis/wildfly/v1alpha1/zz_generated.openapi.go b/pkg/apis/wildfly/v1alpha1/zz_generated.openapi.go index edb540dd..9046470c 100644 --- a/pkg/apis/wildfly/v1alpha1/zz_generated.openapi.go +++ b/pkg/apis/wildfly/v1alpha1/zz_generated.openapi.go @@ -13,12 +13,12 @@ import ( func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition { return map[string]common.OpenAPIDefinition{ - "github.com/wildfly/wildfly-operator/pkg/apis/wildfly/v1alpha1.PodStatus": schema_pkg_apis_wildfly_v1alpha1_PodStatus(ref), - "github.com/wildfly/wildfly-operator/pkg/apis/wildfly/v1alpha1.StandaloneConfigMapSpec": schema_pkg_apis_wildfly_v1alpha1_StandaloneConfigMapSpec(ref), - "github.com/wildfly/wildfly-operator/pkg/apis/wildfly/v1alpha1.StorageSpec": schema_pkg_apis_wildfly_v1alpha1_StorageSpec(ref), - "github.com/wildfly/wildfly-operator/pkg/apis/wildfly/v1alpha1.WildFlyServer": schema_pkg_apis_wildfly_v1alpha1_WildFlyServer(ref), - "github.com/wildfly/wildfly-operator/pkg/apis/wildfly/v1alpha1.WildFlyServerSpec": schema_pkg_apis_wildfly_v1alpha1_WildFlyServerSpec(ref), - "github.com/wildfly/wildfly-operator/pkg/apis/wildfly/v1alpha1.WildFlyServerStatus": schema_pkg_apis_wildfly_v1alpha1_WildFlyServerStatus(ref), + "./pkg/apis/wildfly/v1alpha1.PodStatus": schema_pkg_apis_wildfly_v1alpha1_PodStatus(ref), + "./pkg/apis/wildfly/v1alpha1.StandaloneConfigMapSpec": schema_pkg_apis_wildfly_v1alpha1_StandaloneConfigMapSpec(ref), + "./pkg/apis/wildfly/v1alpha1.StorageSpec": schema_pkg_apis_wildfly_v1alpha1_StorageSpec(ref), + "./pkg/apis/wildfly/v1alpha1.WildFlyServer": schema_pkg_apis_wildfly_v1alpha1_WildFlyServer(ref), + "./pkg/apis/wildfly/v1alpha1.WildFlyServerSpec": schema_pkg_apis_wildfly_v1alpha1_WildFlyServerSpec(ref), + "./pkg/apis/wildfly/v1alpha1.WildFlyServerStatus": schema_pkg_apis_wildfly_v1alpha1_WildFlyServerStatus(ref), } } @@ -136,19 +136,19 @@ func schema_pkg_apis_wildfly_v1alpha1_WildFlyServer(ref common.ReferenceCallback }, "spec": { SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/wildfly/wildfly-operator/pkg/apis/wildfly/v1alpha1.WildFlyServerSpec"), + Ref: ref("./pkg/apis/wildfly/v1alpha1.WildFlyServerSpec"), }, }, "status": { SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/wildfly/wildfly-operator/pkg/apis/wildfly/v1alpha1.WildFlyServerStatus"), + Ref: ref("./pkg/apis/wildfly/v1alpha1.WildFlyServerStatus"), }, }, }, }, }, Dependencies: []string{ - "github.com/wildfly/wildfly-operator/pkg/apis/wildfly/v1alpha1.WildFlyServerSpec", "github.com/wildfly/wildfly-operator/pkg/apis/wildfly/v1alpha1.WildFlyServerStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + "./pkg/apis/wildfly/v1alpha1.WildFlyServerSpec", "./pkg/apis/wildfly/v1alpha1.WildFlyServerStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, } } @@ -196,13 +196,13 @@ func schema_pkg_apis_wildfly_v1alpha1_WildFlyServerSpec(ref common.ReferenceCall }, "standaloneConfigMap": { SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/wildfly/wildfly-operator/pkg/apis/wildfly/v1alpha1.StandaloneConfigMapSpec"), + Ref: ref("./pkg/apis/wildfly/v1alpha1.StandaloneConfigMapSpec"), }, }, "storage": { SchemaProps: spec.SchemaProps{ Description: "StorageSpec defines specific storage required for the server own data directory. If omitted, an EmptyDir is used (that will not persist data across pod restart).", - Ref: ref("github.com/wildfly/wildfly-operator/pkg/apis/wildfly/v1alpha1.StorageSpec"), + Ref: ref("./pkg/apis/wildfly/v1alpha1.StorageSpec"), }, }, "serviceAccountName": { @@ -285,12 +285,18 @@ func schema_pkg_apis_wildfly_v1alpha1_WildFlyServerSpec(ref common.ReferenceCall }, }, }, + "resources": { + SchemaProps: spec.SchemaProps{ + Description: "Configuration of the resources used by the WildFlyServer, ie CPU and memory, use limits and requests More info: https://pkg.go.dev/k8s.io/api@v0.18.14/core/v1#ResourceRequirements", + Ref: ref("k8s.io/api/core/v1.ResourceRequirements"), + }, + }, }, Required: []string{"applicationImage", "replicas"}, }, }, Dependencies: []string{ - "github.com/wildfly/wildfly-operator/pkg/apis/wildfly/v1alpha1.StandaloneConfigMapSpec", "github.com/wildfly/wildfly-operator/pkg/apis/wildfly/v1alpha1.StorageSpec", "k8s.io/api/core/v1.EnvFromSource", "k8s.io/api/core/v1.EnvVar"}, + "./pkg/apis/wildfly/v1alpha1.StandaloneConfigMapSpec", "./pkg/apis/wildfly/v1alpha1.StorageSpec", "k8s.io/api/core/v1.EnvFromSource", "k8s.io/api/core/v1.EnvVar", "k8s.io/api/core/v1.ResourceRequirements"}, } } @@ -319,7 +325,7 @@ func schema_pkg_apis_wildfly_v1alpha1_WildFlyServerStatus(ref common.ReferenceCa Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/wildfly/wildfly-operator/pkg/apis/wildfly/v1alpha1.PodStatus"), + Ref: ref("./pkg/apis/wildfly/v1alpha1.PodStatus"), }, }, }, @@ -350,11 +356,18 @@ func schema_pkg_apis_wildfly_v1alpha1_WildFlyServerStatus(ref common.ReferenceCa Format: "int32", }, }, + "selector": { + SchemaProps: spec.SchemaProps{ + Description: "selector for pods, used by HorizontalPodAutoscaler", + Type: []string{"string"}, + Format: "", + }, + }, }, - Required: []string{"replicas", "scalingdownPods"}, + Required: []string{"replicas", "scalingdownPods", "selector"}, }, }, Dependencies: []string{ - "github.com/wildfly/wildfly-operator/pkg/apis/wildfly/v1alpha1.PodStatus"}, + "./pkg/apis/wildfly/v1alpha1.PodStatus"}, } } diff --git a/pkg/controller/wildflyserver/wildflyserver_controller.go b/pkg/controller/wildflyserver/wildflyserver_controller.go index f244397d..19ddcfc9 100644 --- a/pkg/controller/wildflyserver/wildflyserver_controller.go +++ b/pkg/controller/wildflyserver/wildflyserver_controller.go @@ -145,6 +145,9 @@ func (r *ReconcileWildFlyServer) Reconcile(request reconcile.Request) (reconcile return r.manageError(wildflyServer, err) } + // Use the unique CR name as the selector label, must be implemented in the pods as well. + wildflyServer.Status.Selector = fmt.Sprintf("app.kubernetes.io/name=%s", wildflyServer.Name) + // If statefulset was deleted during processing recovery scaledown the number of replicas in WildflyServer spec // does not defines the number of pods which should be left active until recovered desiredReplicaSizeForNewStatefulSet := wildflyServer.Spec.Replicas + wildflyServer.Status.ScalingdownPods diff --git a/pkg/controller/wildflyserver/wildflyserver_controller_test.go b/pkg/controller/wildflyserver/wildflyserver_controller_test.go index aca6ce32..041642ab 100644 --- a/pkg/controller/wildflyserver/wildflyserver_controller_test.go +++ b/pkg/controller/wildflyserver/wildflyserver_controller_test.go @@ -10,6 +10,7 @@ import ( "github.com/wildfly/wildfly-operator/pkg/resources/services" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" testifyAssert "github.com/stretchr/testify/assert" @@ -79,6 +80,8 @@ func TestWildFlyServerControllerCreatesStatefulSet(t *testing.T) { require.NoError(t, err) assert.Equal(replicas, *statefulSet.Spec.Replicas) assert.Equal(applicationImage, statefulSet.Spec.Template.Spec.Containers[0].Image) + // Check if the stateful set has the correct name label used by the HPA as a selector label. + assert.Contains(statefulSet.Spec.Template.GetLabels()["app.kubernetes.io/name"], "myapp") // cluster service will be created _, err = r.Reconcile(req) @@ -406,3 +409,73 @@ func TestWildFlyServerWithConfigMap(t *testing.T) { } assert.True(foundVolumeMount) } + +func TestWildFlyServerWithResources(t *testing.T) { + // Set the logger to development mode for verbose logs. + logf.SetLogger(zap.Logger()) + assert := testifyAssert.New(t) + + var ( + requestCpu = resource.MustParse("250m") + requestMem = resource.MustParse("128Mi") + limitCpu = resource.MustParse("1") + limitMem = resource.MustParse("512Mi") + ) + + // A WildFlyServer resource with metadata and spec. + wildflyServer := &wildflyv1alpha1.WildFlyServer{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: wildflyv1alpha1.WildFlyServerSpec{ + ApplicationImage: applicationImage, + Replicas: replicas, + Resources: &corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceCPU: requestCpu, + corev1.ResourceMemory: requestMem, + }, + Limits: corev1.ResourceList{ + corev1.ResourceCPU: limitCpu, + corev1.ResourceMemory: limitMem, + }, + }, + }, + } + // Objects to track in the fake client. + objs := []runtime.Object{ + wildflyServer, + } + + // Register operator types with the runtime scheme. + s := scheme.Scheme + s.AddKnownTypes(wildflyv1alpha1.SchemeGroupVersion, wildflyServer) + // Create a fake client to mock API calls. + cl := fake.NewFakeClient(objs...) + // Create a ReconcileWildFlyServer object with the scheme and fake client. + r := &ReconcileWildFlyServer{client: cl, scheme: s} + + // Mock request to simulate Reconcile() being called on an event for a + // watched resource . + req := reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: name, + Namespace: namespace, + }, + } + // statefulset will be created + _, err := r.Reconcile(req) + require.NoError(t, err) + + // Check if stateful set has been created with the correct configuration. + statefulSet := &appsv1.StatefulSet{} + err = cl.Get(context.TODO(), req.NamespacedName, statefulSet) + require.NoError(t, err) + assert.Equal(replicas, *statefulSet.Spec.Replicas) + assert.Equal(applicationImage, statefulSet.Spec.Template.Spec.Containers[0].Image) + assert.Equal(requestCpu, statefulSet.Spec.Template.Spec.Containers[0].Resources.Requests[corev1.ResourceCPU]) + assert.Equal(requestMem, statefulSet.Spec.Template.Spec.Containers[0].Resources.Requests[corev1.ResourceMemory]) + assert.Equal(limitCpu, statefulSet.Spec.Template.Spec.Containers[0].Resources.Limits[corev1.ResourceCPU]) + assert.Equal(limitMem, statefulSet.Spec.Template.Spec.Containers[0].Resources.Limits[corev1.ResourceMemory]) +} diff --git a/pkg/resources/statefulsets/statefulset.go b/pkg/resources/statefulsets/statefulset.go index 14fb75be..2843d70a 100644 --- a/pkg/resources/statefulsets/statefulset.go +++ b/pkg/resources/statefulsets/statefulset.go @@ -100,6 +100,8 @@ func NewStatefulSet(w *wildflyv1alpha1.WildFlyServer, labels map[string]string, LivenessProbe: createLivenessProbe(w), // Readiness Probe is optional ReadinessProbe: createReadinessProbe(w), + // Resources + Resources: createResources(w.Spec.Resources), }}, ServiceAccountName: w.Spec.ServiceAccountName, }, @@ -107,6 +109,11 @@ func NewStatefulSet(w *wildflyv1alpha1.WildFlyServer, labels map[string]string, }, } + // if the user specified the resources directive propagate it to the container (required for HPA). + if w.Spec.Resources != nil { + statefulSet.Spec.Template.Spec.Containers[0].Resources = *w.Spec.Resources + } + if len(w.Spec.EnvFrom) > 0 { statefulSet.Spec.Template.Spec.Containers[0].EnvFrom = append(statefulSet.Spec.Template.Spec.Containers[0].EnvFrom, w.Spec.EnvFrom...) } @@ -149,14 +156,7 @@ func NewStatefulSet(w *wildflyv1alpha1.WildFlyServer, labels map[string]string, pvcTemplate.Name = standaloneDataVolumeName } pvcTemplate.Spec.AccessModes = []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce} - pvcTemplate.Spec.Resources = storageSpec.VolumeClaimTemplate.Spec.Resources - // Kubernetes initializes empty map as nil, the reflect.DeepEqual causes troubles when we do not use nil too - if pvcTemplate.Spec.Resources.Limits != nil && len(pvcTemplate.Spec.Resources.Limits) == 0 { - pvcTemplate.Spec.Resources.Limits = nil - } - if pvcTemplate.Spec.Resources.Requests != nil && len(pvcTemplate.Spec.Resources.Requests) == 0 { - pvcTemplate.Spec.Resources.Requests = nil - } + pvcTemplate.Spec.Resources = createResources(&storageSpec.VolumeClaimTemplate.Spec.Resources) pvcTemplate.Spec.Selector = storageSpec.VolumeClaimTemplate.Spec.Selector statefulSet.Spec.VolumeClaimTemplates = append(statefulSet.Spec.VolumeClaimTemplates, pvcTemplate) } @@ -250,6 +250,26 @@ func NewStatefulSet(w *wildflyv1alpha1.WildFlyServer, labels map[string]string, return statefulSet } +// createResources supplements a default ResourceRequirements and returns it. +func createResources(r *corev1.ResourceRequirements) corev1.ResourceRequirements { + rTemplate := corev1.ResourceRequirements{ + Limits: nil, + Requests: nil, + } + + if r != nil { + if r.Limits != nil && len(r.Limits) > 0 { + rTemplate.Limits = r.Limits + } + + if r.Requests != nil && len(r.Requests) > 0 { + rTemplate.Requests = r.Requests + } + } + + return rTemplate +} + // createLivenessProbe create a Exec probe if the SERVER_LIVENESS_SCRIPT env var is present // *and* the application is not using Bootable Jar. // Otherwise, it creates a HTTPGet probe that checks the /health/live endpoint on the admin port. diff --git a/test/framework/wildlfyserver.go b/test/framework/wildlfyserver.go index 414e562e..5810635b 100644 --- a/test/framework/wildlfyserver.go +++ b/test/framework/wildlfyserver.go @@ -5,12 +5,13 @@ import ( "context" "fmt" "io" - "k8s.io/apimachinery/pkg/api/resource" "regexp" "strings" "testing" "time" + "k8s.io/apimachinery/pkg/api/resource" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/wait" diff --git a/version/version.go b/version/version.go index 1a92e27b..7ced633f 100644 --- a/version/version.go +++ b/version/version.go @@ -2,5 +2,5 @@ package version var ( // Version represents the software version of the WildFly Operator - Version = "0.5.2" + Version = "0.5.3" )