RockteMQ-AI commented on code in PR #51:
URL: https://github.com/apache/rocketmq-operator/pull/51#discussion_r3839470827


##########
pkg/apis/rocketmq/v1alpha1/nameservice_types.go:
##########
@@ -49,6 +49,17 @@ type NameServiceSpec struct {
        HostPath string `json:"hostPath"`
        // VolumeClaimTemplates defines the StorageClass
        VolumeClaimTemplates []corev1.PersistentVolumeClaim 
`json:"volumeClaimTemplates"`
+       // rocketmq exporter
+       Exporter RocketmqExporter `json:"exporter,omitempty"`
+}
+
+// RocketmqExporter defines the specification for the rocketmq exporter
+type RocketmqExporter struct {
+       Enabled bool `json:"enabled,omitempty"`
+       Image string `json:"image,omitempty"`
+       ImagePullPolicy corev1.PullPolicy `json:"imagePullPolicy,omitempty"`

Review Comment:
   RocketmqExporter.Env is tagged `json:"env"` (no omitempty), meaning it is a 
required field in JSON serialization. This will cause deserialization errors or 
validation failures when users omit the env field. It should be 
`json:"env,omitempty"` to be consistent with the other optional fields and 
match the intent of an optional exporter config.



##########
deploy/crds/rocketmq_v1alpha1_nameservice_crd.yaml:
##########
@@ -63,6 +63,9 @@ spec:
               items:
                 type: object
               type: array

Review Comment:
   The CRD schema defines `exporter` as `type: object` with no properties 
specified. This means Kubernetes will not validate any of the exporter 
subfields (enabled, image, env, resources, etc.). The schema should be expanded 
with proper property definitions to enable server-side validation and prevent 
misconfigured exporter specs from being accepted silently.



##########
pkg/controller/nameservice/nameservice_controller.go:
##########
@@ -342,8 +343,42 @@ func (r *ReconcileNameService) 
statefulSetForNameService(nameService *rocketmqv1
                        VolumeClaimTemplates: 
getVolumeClaimTemplates(nameService),
                },
        }
+       if nameService.Spec.Exporter.Enabled {
+               exporter := r.createRocketMQExporterContainer(nameService)
+               dep.Spec.Template.Spec.Containers = 
append(dep.Spec.Template.Spec.Containers, exporter)
+       }
        // Set Broker instance as the owner and controller
        controllerutil.SetControllerReference(nameService, dep, r.scheme)
 
        return dep
 }
+
+func (r *ReconcileNameService) createRocketMQExporterContainer(nameService 
*rocketmqv1alpha1.NameService) (container corev1.Container) {
+       container = corev1.Container{
+               Name: cons.ExporterContainerName,
+               Image: nameService.Spec.Exporter.Image,
+               Env: nameService.Spec.Exporter.Env,
+               ImagePullPolicy: func(specPolicy corev1.PullPolicy) 
corev1.PullPolicy {
+                       if specPolicy == "" {
+                               return corev1.PullAlways
+                       }
+                       return specPolicy
+               }(nameService.Spec.Exporter.ImagePullPolicy),
+               Resources: func(requirements corev1.ResourceRequirements) 
corev1.ResourceRequirements {
+                       if requirements.Limits.Memory().IsZero() && 
requirements.Requests.Memory().IsZero() {

Review Comment:
   The resource default logic checks `requirements.Limits.Memory().IsZero() && 
requirements.Requests.Memory().IsZero()`, but if the user specifies CPU 
limits/requests without memory (or vice versa), the entire requirements struct 
is replaced with defaults, silently discarding the user's CPU configuration. 
The check should be done per-resource and only fill in missing individual 
values, not replace the whole struct.



##########
pkg/apis/rocketmq/v1alpha1/nameservice_types.go:
##########
@@ -49,6 +49,17 @@ type NameServiceSpec struct {
        HostPath string `json:"hostPath"`
        // VolumeClaimTemplates defines the StorageClass
        VolumeClaimTemplates []corev1.PersistentVolumeClaim 
`json:"volumeClaimTemplates"`
+       // rocketmq exporter

Review Comment:
   The `Exporter` field uses a value type (`RocketmqExporter`) rather than a 
pointer (`*RocketmqExporter`). Combined with `omitempty`, a zero-value struct 
won't be omitted in JSON because omitempty only omits zero values for scalar 
types and pointers in Go. Users who don't specify an exporter will still have 
an empty struct serialized, and the `Enabled` bool defaulting to `false` is the 
only guard. Using `*RocketmqExporter` would make the omission semantics correct 
and allow distinguishing 'not set' from 'set but disabled'.



##########
pkg/controller/nameservice/nameservice_controller.go:
##########
@@ -342,8 +343,42 @@ func (r *ReconcileNameService) 
statefulSetForNameService(nameService *rocketmqv1
                        VolumeClaimTemplates: 
getVolumeClaimTemplates(nameService),
                },
        }
+       if nameService.Spec.Exporter.Enabled {
+               exporter := r.createRocketMQExporterContainer(nameService)
+               dep.Spec.Template.Spec.Containers = 
append(dep.Spec.Template.Spec.Containers, exporter)
+       }
        // Set Broker instance as the owner and controller
        controllerutil.SetControllerReference(nameService, dep, r.scheme)
 
        return dep
 }
+
+func (r *ReconcileNameService) createRocketMQExporterContainer(nameService 
*rocketmqv1alpha1.NameService) (container corev1.Container) {
+       container = corev1.Container{
+               Name: cons.ExporterContainerName,
+               Image: nameService.Spec.Exporter.Image,
+               Env: nameService.Spec.Exporter.Env,
+               ImagePullPolicy: func(specPolicy corev1.PullPolicy) 
corev1.PullPolicy {
+                       if specPolicy == "" {
+                               return corev1.PullAlways
+                       }
+                       return specPolicy
+               }(nameService.Spec.Exporter.ImagePullPolicy),
+               Resources: func(requirements corev1.ResourceRequirements) 
corev1.ResourceRequirements {
+                       if requirements.Limits.Memory().IsZero() && 
requirements.Requests.Memory().IsZero() {

Review Comment:
   Calling `requirements.Limits.Memory()` on a nil ResourceList (when Limits is 
not set at all) will panic. If `nameService.Spec.Exporter.Resources.Limits` is 
nil, `.Memory()` returns a zero quantity via the ResourceList getter, but 
`requirements.Limits` itself being nil means the map access is safe in Go — 
however `requirements.Requests` being nil is the same case. This is safe in 
current Go k8s API, but the dual-nil check should be made explicit (check 
`len(requirements.Limits) == 0 && len(requirements.Requests) == 0`) to make 
intent clear and guard against future regressions.



##########
pkg/controller/nameservice/nameservice_controller.go:
##########
@@ -342,8 +343,42 @@ func (r *ReconcileNameService) 
statefulSetForNameService(nameService *rocketmqv1
                        VolumeClaimTemplates: 
getVolumeClaimTemplates(nameService),
                },

Review Comment:
   When `nameService.Spec.Exporter.Enabled` is toggled from true to false on an 
existing StatefulSet, the reconciler will not remove the exporter container 
from the pod template because the existing StatefulSet update path is not shown 
to handle container list diffing. Verify that the StatefulSet update logic 
compares and removes containers when the exporter is disabled, otherwise the 
sidecar will persist until the StatefulSet is deleted and recreated.



##########
example/rocketmq_v1alpha1_rocketmq_exporter_cluster.yaml:
##########
@@ -0,0 +1,157 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: broker-config
+data:
+  # BROKER_MEM sets the broker JVM, if set to "" then Xms = Xmx = max(min(1/2 
ram, 1024MB), min(1/4 ram, 8GB))
+  BROKER_MEM: " -Xms2g -Xmx2g -Xmn1g "
+  broker-common.conf: |
+    # brokerClusterName, brokerName, brokerId are automatically generated by 
the operator and do not set it manually!!!
+    deleteWhen=04
+    fileReservedTime=48
+    flushDiskType=ASYNC_FLUSH
+    # set brokerRole to ASYNC_MASTER or SYNC_MASTER. DO NOT set to SLAVE 
because the replica instance will automatically be set!!!
+    brokerRole=ASYNC_MASTER
+
+---
+apiVersion: rocketmq.apache.org/v1alpha1
+kind: Broker
+metadata:
+  # name of broker cluster
+  name: broker
+spec:
+  # size is the number of the broker cluster, each broker cluster contains a 
master broker and [replicaPerGroup] replica brokers.
+  size: 1
+  # nameServers is the [ip:port] list of name service
+  nameServers: ""
+  # replicaPerGroup is the number of each broker cluster
+  replicaPerGroup: 0
+  # brokerImage is the customized docker image repo of the RocketMQ broker
+  brokerImage: apacherocketmq/rocketmq-broker:4.5.0-alpine-operator-0.3.0
+  # imagePullPolicy is the image pull policy
+  imagePullPolicy: Always
+  # resources describes the compute resource requirements and limits
+  resources:
+    requests:
+      memory: "2048Mi"
+      cpu: "250m"
+    limits:
+      memory: "12288Mi"
+      cpu: "500m"
+  # allowRestart defines whether allow pod restart
+  allowRestart: true
+  # storageMode can be EmptyDir, HostPath, StorageClass
+  storageMode: EmptyDir
+  # hostPath is the local path to store data
+  hostPath: /tmp/data/rocketmq/broker
+  # scalePodName is [Broker name]-[broker group number]-master-0
+  scalePodName: broker-0-master-0
+  # env defines custom env, e.g. BROKER_MEM
+  env:
+    - name: BROKER_MEM
+      valueFrom:
+        configMapKeyRef:
+          name: broker-config
+          key: BROKER_MEM
+  # volumes defines the broker.conf
+  volumes:
+    - name: broker-config
+      configMap:
+        name: broker-config
+        items:
+          - key: broker-common.conf
+            path: broker-common.conf
+  # volumeClaimTemplates defines the storageClass
+  volumeClaimTemplates:
+    - metadata:
+        name: broker-storage
+      spec:
+        accessModes:
+          - ReadWriteOnce
+        resources:
+          requests:
+            storage: 8Gi
+        selector:
+          matchLabels:
+            app: broker-storage-pv
+---
+apiVersion: rocketmq.apache.org/v1alpha1
+kind: NameService
+metadata:
+  name: name-service
+spec:
+  # size is the the name service instance number of the name service cluster
+  size: 1
+  # nameServiceImage is the customized docker image repo of the RocketMQ name 
service
+  nameServiceImage: 
apacherocketmq/rocketmq-nameserver:4.5.0-alpine-operator-0.3.0
+  # imagePullPolicy is the image pull policy
+  imagePullPolicy: Always
+  # hostNetwork can be true or false
+  hostNetwork: true
+  #  Set DNS policy for the pod.
+  #  Defaults to "ClusterFirst".
+  #  Valid values are 'ClusterFirstWithHostNet', 'ClusterFirst', 'Default' or 
'None'.
+  #  DNS parameters given in DNSConfig will be merged with the policy selected 
with DNSPolicy.
+  #  To have DNS options set along with hostNetwork, you have to specify DNS 
policy
+  #  explicitly to 'ClusterFirstWithHostNet'.
+  dnsPolicy: ClusterFirstWithHostNet
+  # resources describes the compute resource requirements and limits

Review Comment:
   The example file uses `image: miaolinjie/rocketmq-exporter:latest` (a 
personal DockerHub repo) while the README example and build script reference 
`apacherocketmq/rocketmq-exporter`. Using a personal/unofficial image in 
example manifests is a supply chain concern and will confuse users. The example 
should reference the official Apache image or the image built by the provided 
Dockerfile.



##########
images/rocketmq-exporter/alpine/Dockerfile:
##########
@@ -0,0 +1,51 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+FROM openjdk:8-alpine
+
+RUN apk add --no-cache bash gettext nmap-ncat curl git openssl busybox-extras
+
+ARG MAVEN_VERSION=3.6.3
+ARG BASE_URL=https://apache.osuosl.org/maven/maven-3/${MAVEN_VERSION}/binaries
+ARG 
SHA=c35a1803a6e70a126e80b2b3ae33eed961f83ed74d18fcd16909b2d44d7dada3203f1ffe726c17ef8dcca2dcaa9fca676987befeadc9b9f759967a8cb77181c0
+
+RUN mkdir -p /usr/share/maven /usr/share/maven/ref \
+  && echo "Downlaoding maven" \
+  && curl -fsSL -o /tmp/apache-maven.tar.gz 
${BASE_URL}/apache-maven-${MAVEN_VERSION}-bin.tar.gz \
+  \
+  && echo "Checking download hash" \
+  && echo "${SHA}  /tmp/apache-maven.tar.gz" | sha512sum -c - \
+  \
+  && echo "Unziping maven" \
+  && tar -xzf /tmp/apache-maven.tar.gz -C /usr/share/maven 
--strip-components=1 \
+  \
+  && echo "Cleaning and setting links" \
+  && rm -f /tmp/apache-maven.tar.gz \
+  && ln -s /usr/share/maven/bin/mvn /usr/bin/mvn
+
+RUN mkdir /exporter \
+  && git clone https://github.com/apache/rocketmq-exporter.git \
+  && cd /rocketmq-exporter \
+  && mvn clean package -Dmaven.test.skip=true \
+  && mv /rocketmq-exporter/target/rocketmq-exporter-*.jar 
/rocketmq-exporter/rocketmq-exporter.jar \

Review Comment:
   The Dockerfile clones rocketmq-exporter from 
`https://github.com/apache/rocketmq-exporter.git` at HEAD without pinning to a 
specific tag or commit SHA. This makes image builds non-reproducible and 
introduces a supply chain risk — a future upstream commit could break the build 
or introduce malicious code. Pin to a specific release tag or commit SHA.



##########
example/rocketmq_v1alpha1_rocketmq_exporter_cluster.yaml:
##########
@@ -0,0 +1,157 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: broker-config
+data:
+  # BROKER_MEM sets the broker JVM, if set to "" then Xms = Xmx = max(min(1/2 
ram, 1024MB), min(1/4 ram, 8GB))
+  BROKER_MEM: " -Xms2g -Xmx2g -Xmn1g "
+  broker-common.conf: |
+    # brokerClusterName, brokerName, brokerId are automatically generated by 
the operator and do not set it manually!!!
+    deleteWhen=04
+    fileReservedTime=48
+    flushDiskType=ASYNC_FLUSH
+    # set brokerRole to ASYNC_MASTER or SYNC_MASTER. DO NOT set to SLAVE 
because the replica instance will automatically be set!!!
+    brokerRole=ASYNC_MASTER
+
+---
+apiVersion: rocketmq.apache.org/v1alpha1
+kind: Broker
+metadata:
+  # name of broker cluster
+  name: broker
+spec:
+  # size is the number of the broker cluster, each broker cluster contains a 
master broker and [replicaPerGroup] replica brokers.
+  size: 1
+  # nameServers is the [ip:port] list of name service
+  nameServers: ""
+  # replicaPerGroup is the number of each broker cluster
+  replicaPerGroup: 0
+  # brokerImage is the customized docker image repo of the RocketMQ broker
+  brokerImage: apacherocketmq/rocketmq-broker:4.5.0-alpine-operator-0.3.0
+  # imagePullPolicy is the image pull policy
+  imagePullPolicy: Always
+  # resources describes the compute resource requirements and limits
+  resources:
+    requests:
+      memory: "2048Mi"
+      cpu: "250m"
+    limits:
+      memory: "12288Mi"
+      cpu: "500m"
+  # allowRestart defines whether allow pod restart
+  allowRestart: true
+  # storageMode can be EmptyDir, HostPath, StorageClass
+  storageMode: EmptyDir
+  # hostPath is the local path to store data
+  hostPath: /tmp/data/rocketmq/broker
+  # scalePodName is [Broker name]-[broker group number]-master-0
+  scalePodName: broker-0-master-0
+  # env defines custom env, e.g. BROKER_MEM
+  env:
+    - name: BROKER_MEM
+      valueFrom:
+        configMapKeyRef:
+          name: broker-config
+          key: BROKER_MEM
+  # volumes defines the broker.conf
+  volumes:
+    - name: broker-config
+      configMap:
+        name: broker-config
+        items:
+          - key: broker-common.conf
+            path: broker-common.conf
+  # volumeClaimTemplates defines the storageClass
+  volumeClaimTemplates:
+    - metadata:
+        name: broker-storage
+      spec:
+        accessModes:
+          - ReadWriteOnce
+        resources:
+          requests:
+            storage: 8Gi
+        selector:
+          matchLabels:
+            app: broker-storage-pv
+---
+apiVersion: rocketmq.apache.org/v1alpha1
+kind: NameService
+metadata:
+  name: name-service
+spec:
+  # size is the the name service instance number of the name service cluster
+  size: 1
+  # nameServiceImage is the customized docker image repo of the RocketMQ name 
service
+  nameServiceImage: 
apacherocketmq/rocketmq-nameserver:4.5.0-alpine-operator-0.3.0
+  # imagePullPolicy is the image pull policy
+  imagePullPolicy: Always
+  # hostNetwork can be true or false
+  hostNetwork: true
+  #  Set DNS policy for the pod.
+  #  Defaults to "ClusterFirst".
+  #  Valid values are 'ClusterFirstWithHostNet', 'ClusterFirst', 'Default' or 
'None'.

Review Comment:
   The exporter sidecar uses `NAMESRV_ADDR: 127.0.0.1:9876` (localhost). This 
only works correctly when the nameserver pod uses `hostNetwork: true` (which 
this example does set). However, the documentation does not make this 
dependency explicit. If a user enables the exporter without hostNetwork, the 
exporter will fail to reach the nameserver. This constraint should be 
documented or enforced/validated in the controller.



##########
pkg/controller/nameservice/nameservice_controller.go:
##########
@@ -342,8 +343,42 @@ func (r *ReconcileNameService) 
statefulSetForNameService(nameService *rocketmqv1
                        VolumeClaimTemplates: 
getVolumeClaimTemplates(nameService),
                },
        }
+       if nameService.Spec.Exporter.Enabled {
+               exporter := r.createRocketMQExporterContainer(nameService)
+               dep.Spec.Template.Spec.Containers = 
append(dep.Spec.Template.Spec.Containers, exporter)
+       }
        // Set Broker instance as the owner and controller
        controllerutil.SetControllerReference(nameService, dep, r.scheme)

Review Comment:
   No test coverage is added for `createRocketMQExporterContainer` or the 
exporter injection logic in `statefulSetForNameService`. Given the resource 
defaulting logic has edge cases (partial resources, nil maps), unit tests 
should cover: exporter disabled, exporter enabled with full resources, exporter 
enabled with no resources (defaults applied), and exporter enabled with partial 
resources.



##########
images/rocketmq-exporter/alpine/Dockerfile:
##########
@@ -0,0 +1,51 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+FROM openjdk:8-alpine
+
+RUN apk add --no-cache bash gettext nmap-ncat curl git openssl busybox-extras
+
+ARG MAVEN_VERSION=3.6.3
+ARG BASE_URL=https://apache.osuosl.org/maven/maven-3/${MAVEN_VERSION}/binaries
+ARG 
SHA=c35a1803a6e70a126e80b2b3ae33eed961f83ed74d18fcd16909b2d44d7dada3203f1ffe726c17ef8dcca2dcaa9fca676987befeadc9b9f759967a8cb77181c0
+
+RUN mkdir -p /usr/share/maven /usr/share/maven/ref \

Review Comment:
   The SHA512 checksum value for the Maven binary (`ARG SHA=c35a1803...`) 
appears truncated — a valid SHA-512 hex string is 128 characters, but the value 
here is shorter. If the checksum is wrong, the `sha512sum -c` check will fail 
at build time, breaking image builds. Verify and provide the correct full 
SHA-512 hash.



##########
pkg/apis/rocketmq/v1alpha1/nameservice_types.go:
##########
@@ -49,6 +49,17 @@ type NameServiceSpec struct {
        HostPath string `json:"hostPath"`
        // VolumeClaimTemplates defines the StorageClass
        VolumeClaimTemplates []corev1.PersistentVolumeClaim 
`json:"volumeClaimTemplates"`
+       // rocketmq exporter
+       Exporter RocketmqExporter `json:"exporter,omitempty"`
+}

Review Comment:
   The `RocketmqExporter` struct has no `Annotations` field, but the example 
YAML (`rocketmq_v1alpha1_rocketmq_exporter_cluster.yaml`) includes 
`annotations` under the exporter spec (for prometheus scraping). These 
annotations will be silently dropped during deserialization since the type 
doesn't define them. Either add an `Annotations map[string]string` field to 
`RocketmqExporter` and propagate them to the pod template metadata, or remove 
them from the example.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to