This is an automated email from the ASF dual-hosted git repository.

brahmareddybattula pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/ambari.git


The following commit(s) were added to refs/heads/trunk by this push:
     new 3cdadc742b Capacity Scheduler View: tolerant node label response 
parsing (#4150)
3cdadc742b is described below

commit 3cdadc742b68bb49a67b67fbceaddc09d69481b3
Author: SANRAJ RAJENDRA BANDRE <[email protected]>
AuthorDate: Sun Aug 23 23:23:42 2026 +0530

    Capacity Scheduler View: tolerant node label response parsing (#4150)
    
    getNodeLabels assumed the ResourceManager node-label response was always a
    JSON string and chose the response shape from the stack version. That check
    (stackVersion >= 2.5) is unreliable: Number('3.2.0') is NaN, so 3-part
    versions silently fell through to the legacy branch.
    
    Extract normalization into QueueAdapter#parseNodeLabels, which:
    - parses only when the response is a string; accepts an already-parsed 
object
    - unwraps an optional nodeLabelsInfo wrapper
    - supports nodeLabelInfo (array or single object) and legacy nodeLabels
    - coerces exclusivity to a Boolean (true / "true" -> true)
    - derives the shape from the payload, not the stack version
    
    isNodeLabelsConfiguredByRM semantics are unchanged. Adds unit tests covering
    wrapper, JSON string, array, single-object, legacy, boolean/string
    exclusivity, malformed JSON, and empty responses.
---
 .../src/main/resources/ui/app/adapters.js          |  97 +++++++++++--------
 .../ui/test/unit/adapters/adapters_test.js         | 107 +++++++++++++++++++++
 2 files changed, 162 insertions(+), 42 deletions(-)

diff --git 
a/contrib/views/capacity-scheduler/src/main/resources/ui/app/adapters.js 
b/contrib/views/capacity-scheduler/src/main/resources/ui/app/adapters.js
index 4c7da12937..edc51f6eb7 100644
--- a/contrib/views/capacity-scheduler/src/main/resources/ui/app/adapters.js
+++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/adapters.js
@@ -243,55 +243,68 @@ App.QueueAdapter = DS.Adapter.extend({
     },'App: QueueAdapter#findAllTagged ' + tag);
   },
 
-  getNodeLabels:function (store) {
-    var uri = [_getCapacitySchedulerViewUri(this),'nodeLabels'].join('/');
-    var stackId = store.get('stackId'),
-    stackVersion = stackId.substr(stackId.indexOf('-') + 1);
+  getNodeLabels: function (store) {
+    var uri = [_getCapacitySchedulerViewUri(this), 'nodeLabels'].join('/'),
+        adapter = this;
 
     if (App.testMode)
       uri = uri + ".json";
 
-    return new Ember.RSVP.Promise(function(resolve, reject) {
-      _ajax(uri,'GET').then(function(data) {
-        var parsedData;
-        
-        try {
-          parsedData = JSON.parse(data);
-        } catch(e) {
-          console.warn('Failed to parse node labels data:', e);
-          parsedData = null;
-        }
-
-        if (parsedData !== null) {
-          store.set('isNodeLabelsConfiguredByRM', true);
-        } else {
-          store.set('isNodeLabelsConfiguredByRM', false);
-        }
-
-        if (stackVersion >= 2.5) {
-          if (parsedData && Em.isArray(parsedData.nodeLabelInfo)) {
-            labels = parsedData.nodeLabelInfo;
-          } else {
-            labels = (parsedData && 
parsedData.nodeLabelInfo)?[parsedData.nodeLabelInfo]:[];
-          }
-          Ember.run(null, resolve, labels.map(function (label) {
-            return {name:label.name,exclusivity:label.exclusivity};
-          }));
-        } else {
-          if (parsedData && Em.isArray(parsedData.nodeLabels)) {
-            labels = parsedData.nodeLabels;
-          } else {
-            labels = (parsedData && 
parsedData.nodeLabels)?[parsedData.nodeLabels]:[];
-          }
-          Ember.run(null, resolve, labels.map(function (label) {
-            return {name:label};
-          }));
-        }
-      }, function(jqXHR) {
+    return new Ember.RSVP.Promise(function (resolve, reject) {
+      _ajax(uri, 'GET').then(function (data) {
+        var result = adapter.parseNodeLabels(data);
+        store.set('isNodeLabelsConfiguredByRM', result.configured);
+        Ember.run(null, resolve, result.labels);
+      }, function (jqXHR) {
         jqXHR.then = null;
         Ember.run(null, reject, jqXHR);
       });
-    }.bind(this),'App: QueueAdapter#getNodeLabels');
+    }.bind(this), 'App: QueueAdapter#getNodeLabels');
+  },
+
+  /**
+   * Normalizes the ResourceManager node-label response into a flat list.
+   *
+   * Tolerates a JSON string or an already-parsed object, the optional
+   * `nodeLabelsInfo` wrapper, both the current `nodeLabelInfo` and the legacy
+   * `nodeLabels` shapes, and a single label object instead of an array.
+   * `exclusivity` is coerced to a real Boolean (Boolean or the strings
+   * "true"/"false"). The response format is derived from the payload shape,
+   * not from the stack version.
+   *
+   * @param  {String|Object} data  raw ResourceManager response
+   * @return {{configured: Boolean, labels: Array}}
+   */
+  parseNodeLabels: function (data) {
+    var parsedData;
+
+    try {
+      parsedData = (typeof data === 'string') ? JSON.parse(data) : data;
+    } catch (e) {
+      console.warn('Failed to parse node labels data:', e);
+      parsedData = null;
+    }
+
+    var configured = (parsedData !== null && parsedData !== undefined),
+        labels = [];
+
+    if (parsedData && parsedData.nodeLabelsInfo) {
+      parsedData = parsedData.nodeLabelsInfo;
+    }
+
+    if (parsedData && parsedData.nodeLabelInfo) {
+      labels = Em.isArray(parsedData.nodeLabelInfo) ? parsedData.nodeLabelInfo 
: [parsedData.nodeLabelInfo];
+      labels = labels.map(function (label) {
+        return { name: label.name, exclusivity: label.exclusivity === true || 
label.exclusivity === 'true' };
+      });
+    } else if (parsedData && parsedData.nodeLabels) {
+      labels = Em.isArray(parsedData.nodeLabels) ? parsedData.nodeLabels : 
[parsedData.nodeLabels];
+      labels = labels.map(function (label) {
+        return { name: (label && label.name !== undefined) ? label.name : 
label };
+      });
+    }
+
+    return { configured: configured, labels: labels };
   },
 
   getPrivilege:function () {
diff --git 
a/contrib/views/capacity-scheduler/src/main/resources/ui/test/unit/adapters/adapters_test.js
 
b/contrib/views/capacity-scheduler/src/main/resources/ui/test/unit/adapters/adapters_test.js
new file mode 100644
index 0000000000..aec980e1e5
--- /dev/null
+++ 
b/contrib/views/capacity-scheduler/src/main/resources/ui/test/unit/adapters/adapters_test.js
@@ -0,0 +1,107 @@
+/**
+ * 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.
+ */
+
+var adapter;
+
+QUnit.module('unit/adapters - QueueAdapter#parseNodeLabels', {
+  setup: function () {
+    adapter = App.QueueAdapter.create();
+  },
+  teardown: function () {
+    adapter = null;
+  }
+});
+
+test('parsed object with nodeLabelsInfo wrapper and mixed exclusivity', 
function () {
+  var result = adapter.parseNodeLabels({
+    nodeLabelsInfo: {
+      nodeLabelInfo: [
+        { name: 'label-a', exclusivity: true },
+        { name: 'label-b', exclusivity: 'false' }
+      ]
+    }
+  });
+  equal(result.configured, true, 'configured is true');
+  deepEqual(result.labels, [
+    { name: 'label-a', exclusivity: true },
+    { name: 'label-b', exclusivity: false }
+  ], 'wrapper unwrapped, exclusivity normalized');
+});
+
+test('JSON string response with single nodeLabelInfo object and string 
"true"', function () {
+  var result = adapter.parseNodeLabels(JSON.stringify({
+    nodeLabelInfo: { name: 'label-a', exclusivity: 'true' }
+  }));
+  equal(result.configured, true);
+  deepEqual(result.labels, [{ name: 'label-a', exclusivity: true }],
+    'string parsed, single object wrapped, "true" -> true');
+});
+
+test('nodeLabelInfo as array', function () {
+  var result = adapter.parseNodeLabels({
+    nodeLabelInfo: [{ name: 'label-a', exclusivity: false }]
+  });
+  deepEqual(result.labels, [{ name: 'label-a', exclusivity: false }]);
+});
+
+test('exclusivity string "false" and boolean false both normalize to false', 
function () {
+  var result = adapter.parseNodeLabels({
+    nodeLabelInfo: [
+      { name: 'label-a', exclusivity: 'false' },
+      { name: 'label-b', exclusivity: false }
+    ]
+  });
+  deepEqual(result.labels, [
+    { name: 'label-a', exclusivity: false },
+    { name: 'label-b', exclusivity: false }
+  ]);
+});
+
+test('legacy nodeLabels with mixed string and object entries', function () {
+  var result = adapter.parseNodeLabels({
+    nodeLabels: ['label-a', { name: 'label-b' }]
+  });
+  equal(result.configured, true);
+  deepEqual(result.labels, [{ name: 'label-a' }, { name: 'label-b' }],
+    'string and object legacy entries both normalize to {name}');
+});
+
+test('legacy nodeLabels as a single string', function () {
+  var result = adapter.parseNodeLabels({ nodeLabels: 'label-a' });
+  deepEqual(result.labels, [{ name: 'label-a' }]);
+});
+
+test('malformed JSON string logs a warning and returns empty labels', function 
() {
+  var warned = false,
+      originalWarn = console.warn;
+  console.warn = function () { warned = true; };
+  try {
+    var result = adapter.parseNodeLabels('{ this is not json');
+    ok(warned, 'a parse warning was logged');
+    equal(result.configured, false, 'configured is false on parse failure');
+    deepEqual(result.labels, [], 'empty label list on parse failure');
+  } finally {
+    console.warn = originalWarn;
+  }
+});
+
+test('empty valid object returns empty labels and configured true', function 
() {
+  var result = adapter.parseNodeLabels({});
+  equal(result.configured, true, 'a valid empty object still counts as 
configured');
+  deepEqual(result.labels, []);
+});


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

Reply via email to