Copilot commented on code in PR #17248:
URL: https://github.com/apache/pinot/pull/17248#discussion_r2657019812


##########
pinot-controller/src/main/resources/app/utils/PinotMethodUtils.ts:
##########
@@ -182,24 +182,87 @@ const getAllInstances = () => {
   });
 };
 
+// This method is used to check the health endpoint of an instance
+// API: http://{hostname}:{port}/health
+// Expected Output: 'OK' or error
+const checkInstanceHealth = async (hostname, port) => {
+  try {
+    const response = await baseApi.get(`http://${hostname}:${port}/health`, {
+      timeout: 5000 // 5 second timeout
+    });
+    return response.data === 'OK' || response.status === 200;
+  } catch (error) {
+    return false;
+  }
+};
+
 // This method is used to display instance data on cluster manager home page
 // API: /instances/:instanceName
 // Expected Output: {columns: [], records: []}
 const getInstanceData = (instances, liveInstanceArr) => {
   const promiseArr = [...instances.map((inst) => getInstance(inst))];
 
-  return Promise.all(promiseArr).then((result) => {
+  return Promise.all(promiseArr).then(async (result) => {
+    // First, get all instance configs
+    const instanceRecords = result.map(({ data }) => {
+      const isAlive = liveInstanceArr.indexOf(data.instanceName) > -1;
+      const isEnabled = data.enabled;
+      const queriesDisabled = data.queriesDisabled === 'true' || 
data.queriesDisabled === true;

Review Comment:
   The inconsistent type checking for boolean values (string 'true' vs boolean 
true) suggests the API response format is ambiguous. Consider normalizing this 
at the API layer or adding a comment explaining why both checks are necessary.



##########
pinot-controller/src/main/resources/app/utils/PinotMethodUtils.ts:
##########
@@ -182,24 +182,87 @@ const getAllInstances = () => {
   });
 };
 
+// This method is used to check the health endpoint of an instance
+// API: http://{hostname}:{port}/health
+// Expected Output: 'OK' or error
+const checkInstanceHealth = async (hostname, port) => {
+  try {
+    const response = await baseApi.get(`http://${hostname}:${port}/health`, {

Review Comment:
   Constructing HTTP URLs directly from hostname and port without validation 
could allow Server-Side Request Forgery (SSRF) attacks. Validate that hostname 
and port come from trusted sources (e.g., controller-managed instances) and 
consider using a relative path through the controller proxy instead of direct 
instance HTTP calls.
   ```suggestion
     // Basic validation to ensure hostname and port are well-formed before 
issuing the request.
     // This helps avoid constructing arbitrary URLs from untrusted input.
     if (typeof hostname !== 'string' || hostname.length === 0) {
       return false;
     }
     // Allow only typical hostname characters (letters, digits, dots and 
hyphens).
     if (!/^[a-zA-Z0-9.-]+$/.test(hostname)) {
       return false;
     }
     const portNumber = typeof port === 'string' ? Number(port) : port;
     if (!Number.isInteger(portNumber) || portNumber <= 0 || portNumber > 
65535) {
       return false;
     }
   
     try {
       const response = await 
baseApi.get(`http://${hostname}:${portNumber}/health`, {
   ```



##########
pinot-controller/src/main/resources/app/utils/PinotMethodUtils.ts:
##########
@@ -182,24 +182,87 @@ const getAllInstances = () => {
   });
 };
 
+// This method is used to check the health endpoint of an instance
+// API: http://{hostname}:{port}/health
+// Expected Output: 'OK' or error
+const checkInstanceHealth = async (hostname, port) => {
+  try {
+    const response = await baseApi.get(`http://${hostname}:${port}/health`, {
+      timeout: 5000 // 5 second timeout
+    });
+    return response.data === 'OK' || response.status === 200;
+  } catch (error) {
+    return false;
+  }
+};
+
 // This method is used to display instance data on cluster manager home page
 // API: /instances/:instanceName
 // Expected Output: {columns: [], records: []}
 const getInstanceData = (instances, liveInstanceArr) => {
   const promiseArr = [...instances.map((inst) => getInstance(inst))];
 
-  return Promise.all(promiseArr).then((result) => {
+  return Promise.all(promiseArr).then(async (result) => {
+    // First, get all instance configs
+    const instanceRecords = result.map(({ data }) => {
+      const isAlive = liveInstanceArr.indexOf(data.instanceName) > -1;
+      const isEnabled = data.enabled;
+      const queriesDisabled = data.queriesDisabled === 'true' || 
data.queriesDisabled === true;
+      const shutdownInProgress = data.shutdownInProgress === true;
+      
+      return {
+        instanceName: data.instanceName,
+        enabled: data.enabled,
+        hostName: data.hostName,
+        port: data.port,
+        adminPort: data.adminPort,
+        isAlive,
+        isEnabled,
+        queriesDisabled,
+        shutdownInProgress
+      };
+    });
+
+    // Then check health endpoints for alive instances
+    const healthCheckPromises = instanceRecords.map(async (record) => {
+      if (!record.isAlive) {
+        return { ...record, healthStatus: 'Dead' };
+      }
+
+      // Determine which port to use for health check
+      // For brokers and servers, use adminPort if available, otherwise use 
main port
+      const healthPort = record.adminPort && record.adminPort > 0 ? 
record.adminPort : record.port;

Review Comment:
   The logic for determining which port to use for health checks is embedded in 
this function. Consider extracting this into a separate helper function like 
`getHealthCheckPort(record)` for better reusability and testability.



##########
pinot-controller/src/main/resources/app/utils/PinotMethodUtils.ts:
##########
@@ -182,24 +182,87 @@ const getAllInstances = () => {
   });
 };
 
+// This method is used to check the health endpoint of an instance
+// API: http://{hostname}:{port}/health
+// Expected Output: 'OK' or error
+const checkInstanceHealth = async (hostname, port) => {
+  try {
+    const response = await baseApi.get(`http://${hostname}:${port}/health`, {
+      timeout: 5000 // 5 second timeout
+    });
+    return response.data === 'OK' || response.status === 200;
+  } catch (error) {
+    return false;
+  }
+};
+
 // This method is used to display instance data on cluster manager home page
 // API: /instances/:instanceName
 // Expected Output: {columns: [], records: []}
 const getInstanceData = (instances, liveInstanceArr) => {
   const promiseArr = [...instances.map((inst) => getInstance(inst))];
 
-  return Promise.all(promiseArr).then((result) => {
+  return Promise.all(promiseArr).then(async (result) => {
+    // First, get all instance configs
+    const instanceRecords = result.map(({ data }) => {
+      const isAlive = liveInstanceArr.indexOf(data.instanceName) > -1;
+      const isEnabled = data.enabled;
+      const queriesDisabled = data.queriesDisabled === 'true' || 
data.queriesDisabled === true;
+      const shutdownInProgress = data.shutdownInProgress === true;
+      
+      return {
+        instanceName: data.instanceName,
+        enabled: data.enabled,
+        hostName: data.hostName,
+        port: data.port,
+        adminPort: data.adminPort,
+        isAlive,
+        isEnabled,
+        queriesDisabled,
+        shutdownInProgress
+      };
+    });
+
+    // Then check health endpoints for alive instances
+    const healthCheckPromises = instanceRecords.map(async (record) => {

Review Comment:
   Health checks are performed sequentially for all instances using 
Promise.all, which could cause delays when checking many instances. Consider 
implementing concurrent health checks with a configurable concurrency limit or 
add result caching to reduce redundant checks on page refreshes.



-- 
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]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to