You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by ab...@apache.org on 2018/04/03 10:21:44 UTC

[ambari] branch trunk updated: AMBARI-23407 Issues for no NN federation case appearing after federation support implementation. (ababiichuk)

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

ababiichuk 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 a7a34e4  AMBARI-23407 Issues for no NN federation case appearing after federation support implementation. (ababiichuk)
a7a34e4 is described below

commit a7a34e40399d482edcc06fbb64e932519db0daeb
Author: aBabiichuk <ab...@hortonworks.com>
AuthorDate: Tue Apr 3 13:21:39 2018 +0300

    AMBARI-23407 Issues for no NN federation case appearing after federation support implementation. (ababiichuk)
---
 ambari-web/app/app.js                              |   2 +-
 .../app/controllers/global/update_controller.js    |   2 +-
 ambari-web/app/models/service/hdfs.js              |   4 +-
 .../app/templates/main/dashboard/widgets.hbs       |   4 +-
 .../app/templates/main/service/services/hdfs.hbs   |   7 +-
 ambari-web/app/views/main/dashboard/widgets.js     | 170 +++++++++++++++------
 .../views/main/dashboard/widgets/namenode_cpu.js   |   2 +-
 ambari-web/app/views/main/service/info/summary.js  |   5 +-
 .../main/service/info/summary/hdfs/widgets.js      |  10 +-
 .../test/views/main/dashboard/widgets_test.js      |  28 +++-
 10 files changed, 159 insertions(+), 75 deletions(-)

diff --git a/ambari-web/app/app.js b/ambari-web/app/app.js
index 2c6a67e..23afaa0 100644
--- a/ambari-web/app/app.js
+++ b/ambari-web/app/app.js
@@ -243,7 +243,7 @@ module.exports = Em.Application.create({
   }.property('router.clusterController.dataLoadList.services', 'router.clusterController.isServiceContentFullyLoaded'),
 
   hasNameNodeFederation: function () {
-    return App.HDFSService.find().objectAt(0).get('masterComponentGroups.length') > 1;
+    return App.HDFSService.find('HDFS').get('masterComponentGroups.length') > 1;
   }.property('router.clusterController.isHostComponentMetricsLoaded', 'router.clusterController.isHDFSNameSpacesLoaded'),
 
   /**
diff --git a/ambari-web/app/controllers/global/update_controller.js b/ambari-web/app/controllers/global/update_controller.js
index 006fcaa..80af63e 100644
--- a/ambari-web/app/controllers/global/update_controller.js
+++ b/ambari-web/app/controllers/global/update_controller.js
@@ -241,7 +241,7 @@ App.UpdateController = Em.Controller.extend({
         'host_components/metrics/dfs/FSNamesystem/CapacityTotal,' +
         'host_components/metrics/dfs/FSNamesystem/CapacityRemaining,' +
         'host_components/metrics/dfs/FSNamesystem/CapacityNonDFSUsed,' +
-        'host_components/metrics/rpc/client/RpcQueueTime/avg_time,' +
+        'host_components/metrics/rpc/client/RpcQueueTime_avg_time,' +
         'host_components/metrics/runtime/StartTime,';
 
     url = url.replace("<stackVersions>", stackVersionInfo);
diff --git a/ambari-web/app/models/service/hdfs.js b/ambari-web/app/models/service/hdfs.js
index a66e4b1..f4b7f45 100644
--- a/ambari-web/app/models/service/hdfs.js
+++ b/ambari-web/app/models/service/hdfs.js
@@ -100,9 +100,9 @@ App.HDFSService = App.Service.extend({
     let result = [];
     this.get('hostComponents').forEach(component => {
       if (component.get('componentName') === 'NAMENODE') {
-        const nameSpace = component.get('haNameSpace'),
+        const nameSpace = component.get('haNameSpace') || 'default',
           hostName = component.get('hostName'),
-          clusterId = component.get('clusterIdValue'),
+          clusterId = component.get('clusterIdValue') || 'default',
           existingNameSpace = result.findProperty('name', nameSpace),
           currentNameSpace = existingNameSpace || {
               name: nameSpace,
diff --git a/ambari-web/app/templates/main/dashboard/widgets.hbs b/ambari-web/app/templates/main/dashboard/widgets.hbs
index fba5d84..a4e74dc 100644
--- a/ambari-web/app/templates/main/dashboard/widgets.hbs
+++ b/ambari-web/app/templates/main/dashboard/widgets.hbs
@@ -53,8 +53,8 @@
           {{/if}}
         {{/each}}
       </div>
-      {{#if view.widgetGroups.length}}
-        {{#each group in view.widgetGroups}}
+      {{#if view.displayedWidgetGroups.length}}
+        {{#each group in view.displayedWidgetGroups}}
           <div class="dashboard-widget-groups-container">
             <div class="col-md-12">
               <h5 class="widgets-group-title">{{group.title}}</h5>
diff --git a/ambari-web/app/templates/main/service/services/hdfs.hbs b/ambari-web/app/templates/main/service/services/hdfs.hbs
index e51cb06..1b11143 100644
--- a/ambari-web/app/templates/main/service/services/hdfs.hbs
+++ b/ambari-web/app/templates/main/service/services/hdfs.hbs
@@ -144,9 +144,4 @@
       </div>
     </div>
   </div>
-
-  {{#unless view.hasMultipleMasterGroups}}
-    {{! Service Metrics Section }}
-    {{view App.HDFSSummaryWidgetsView}}
-  {{/unless}}
-</div>
\ No newline at end of file
+</div>
diff --git a/ambari-web/app/views/main/dashboard/widgets.js b/ambari-web/app/views/main/dashboard/widgets.js
index e163ed1..0eea55d 100644
--- a/ambari-web/app/views/main/dashboard/widgets.js
+++ b/ambari-web/app/views/main/dashboard/widgets.js
@@ -42,13 +42,15 @@ App.MainDashboardWidgetsView = Em.View.extend(App.Persist, App.LocalStorage, App
 
   widgetGroupsDeferred: $.Deferred(),
 
+  displayedWidgetGroups: Em.computed.filterBy('widgetGroups', 'isDisplayed', true),
+
   setWidgetGroups: function () {
     if (App.get('router.clusterController.isHDFSNameSpacesLoaded')) {
       let groups = [];
       const hdfsService = App.HDFSService.find().objectAt(0),
         hdfsMasterGroups = hdfsService ? hdfsService.get('masterComponentGroups') : [];
       this.removeObserver('App.router.clusterController.isHDFSNameSpacesLoaded', this, 'setWidgetGroups');
-      if (hdfsMasterGroups.length > 1) {
+      if (hdfsMasterGroups.length) {
         const nameSpacesListItems = hdfsMasterGroups.map(nameSpace => {
           const {name, title} = nameSpace;
           return {
@@ -70,7 +72,8 @@ App.MainDashboardWidgetsView = Em.View.extend(App.Persist, App.LocalStorage, App
             ...nameSpacesListItems
           ],
           activeSubGroup: Em.computed.findBy('subGroups', 'isActive', true),
-          allWidgets: this.get('allNameNodeWidgets')
+          allWidgets: this.get('allNameNodeWidgets'),
+          isDisplayed: App.get('hasNameNodeFederation')
         }));
       }
       this.set('widgetGroups', groups);
@@ -215,6 +218,18 @@ App.MainDashboardWidgetsView = Em.View.extend(App.Persist, App.LocalStorage, App
     return widgetsDefinition;
   },
 
+  getWidgetSubGroupsObject: function (subGroups) {
+    return subGroups.reduce((current, subGroup) => {
+      return Object.assign({}, current, {
+        [subGroup.name]: {
+          visible: [],
+          hidden: [],
+          threshold: {}
+        }
+      });
+    }, {});
+  },
+
   generateDefaultUserPreferences: function() {
     var widgetsDefinition = this.get('widgetsDefinition');
     var preferences = {
@@ -227,28 +242,29 @@ App.MainDashboardWidgetsView = Em.View.extend(App.Persist, App.LocalStorage, App
     this.resolveConfigDependencies(widgetsDefinition);
     widgetsDefinition.forEach(widget => {
       const {sourceName, id} = widget,
-        widgetGroups = this.get('widgetGroups');
+        widgetGroups = this.get('displayedWidgetGroups');
       if (App.Service.find(sourceName).get('isLoaded') || sourceName === 'HOST_METRICS') {
         const state = widget.isHiddenByDefault ? 'hidden' : 'visible',
           {threshold} = widget,
           widgetGroup = widgetGroups.findProperty('serviceName', sourceName);
         if (widgetGroup) {
           const widgetGroupName = widgetGroup.get('name'),
-            subGroups = widgetGroup.get('subGroups'),
+            allSubGroups = widgetGroup.get('subGroups'),
+            subGroupForAllItems = allSubGroups.findProperty('name', '*'),
+            subGroups = allSubGroups.rejectProperty('name', '*'),
             existingEntry = preferences.groups[widgetGroupName],
-            currentEntry = existingEntry || subGroups.reduce((current, subGroup) => {
-                return Object.assign({}, current, {
-                  [subGroup.name]: {
-                    visible: [],
-                    hidden: [],
-                    threshold: {}
-                  }
-                });
-              }, {});
+            currentEntry = existingEntry || this.getWidgetSubGroupsObject(subGroups);
+          if (subGroupForAllItems && !currentEntry['*']) {
+            currentEntry['*'] =this.getWidgetSubGroupsObject(subGroups);
+          }
           subGroups.forEach(subGroup => {
             const {name} = subGroup;
             currentEntry[name][state].push(id);
             currentEntry[name].threshold[id] = threshold;
+            if (subGroupForAllItems) {
+              currentEntry['*'][name][state].push(id);
+              currentEntry['*'][name].threshold[id] = threshold;
+            }
           });
           if (!existingEntry) {
             preferences.groups[widgetGroupName] = currentEntry;
@@ -277,15 +293,18 @@ App.MainDashboardWidgetsView = Em.View.extend(App.Persist, App.LocalStorage, App
    *
    * @param {number} id
    * @param {boolean} isVisible
+   * @param {string} subGroupId
    * @returns {WidgetObject}
    * @private
    */
-  _createWidgetObj(id, isVisible) {
+  _createWidgetObj(id, isVisible, subGroupId) {
     var widget = this.get('widgetsDefinitionMap')[id];
     return WidgetObject.create({
       id,
       threshold: this.get('userPreferences.threshold')[id],
-      viewClass: App[widget.viewName],
+      viewClass: App[widget.viewName].extend({
+        subGroupId
+      }),
       sourceName: widget.sourceName,
       title: widget.title,
       isVisible
@@ -295,18 +314,21 @@ App.MainDashboardWidgetsView = Em.View.extend(App.Persist, App.LocalStorage, App
   /**
    *
    * @param {number} id
+   * @param {boolean} isVisible
    * @param {string} groupId
    * @param {string} subGroupId
-   * @param {boolean} isVisible
+   * @param {boolean} isAllSubGroupsDisplay
    * @returns {WidgetObject}
    * @private
    */
-  _createGroupWidgetObj(id, groupId, subGroupId, isVisible) {
+  _createGroupWidgetObj(id, isVisible, groupId, subGroupId, isAllSubGroupsDisplay = false) {
     const widget = this.get('widgetsDefinitionMap')[id];
     subGroupId = subGroupId || 'default';
     return WidgetObject.create({
       id: `${id}-${groupId}-${subGroupId}`,
-      threshold: this.get('userPreferences.groups')[groupId][subGroupId].threshold[id],
+      threshold: isAllSubGroupsDisplay ?
+        this.get('userPreferences.groups')[groupId]['*'][subGroupId].threshold[id] :
+        this.get('userPreferences.groups')[groupId][subGroupId].threshold[id],
       viewClass: App[widget.viewName].extend({
         subGroupId
       }),
@@ -327,13 +349,14 @@ App.MainDashboardWidgetsView = Em.View.extend(App.Persist, App.LocalStorage, App
       newHiddenWidgets = [];
     widgetGroups.forEach(group => group.get('allWidgets').clear());
     widgetsDefinition.forEach(widget => {
-      const {id, groupName} = widget;
-      if (groupName && widgetGroups.someProperty('name', groupName)) {
-        const widgetGroup = widgetGroups.findProperty('name', groupName),
-          groupPreferences = userPreferences.groups[groupName];
+      const {id, groupName} = widget,
+        widgetGroup = widgetGroups.findProperty('name', groupName);
+      if (groupName && widgetGroup && widgetGroup.get('isDisplayed')) {
+        const groupPreferences = userPreferences.groups[groupName];
         if (groupPreferences) {
-          const allWidgets = widgetGroup.get('allWidgets'),
-            subGroupNames = widgetGroup.get('subGroups').mapProperty('name');
+          const subGroupNames = widgetGroup.get('subGroups').mapProperty('name').without('*'),
+            subGroupForAllItems = widgetGroup.get('subGroups').findProperty('name', '*');
+          let allWidgets = widgetGroup.get('allWidgets');
           subGroupNames.forEach(subGroupName => {
             const subGroupPreferences = groupPreferences[subGroupName],
               existingSubGroup = allWidgets.findProperty('subGroupName', subGroupName),
@@ -342,40 +365,66 @@ App.MainDashboardWidgetsView = Em.View.extend(App.Persist, App.LocalStorage, App
                   parentGroup: widgetGroup,
                   isActive: Em.computed.equal('parentGroup.activeSubGroup.name', subGroupName),
                   widgets: []
+                }),
+              visibleIndex = subGroupPreferences.visible.indexOf(id),
+              hiddenIndex = subGroupPreferences.hidden.indexOf(id),
+              visibleCount = subGroupPreferences.visible.length;
+            if (!existingSubGroup) {
+              allWidgets.pushObject(currentSubGroup);
+            }
+            if (visibleIndex > -1) {
+              currentSubGroup.get('widgets')[visibleIndex] = this._createGroupWidgetObj(id, true, groupName, subGroupName);
+            }
+            if (hiddenIndex > -1) {
+              currentSubGroup.get('widgets')[hiddenIndex + visibleCount] = this._createGroupWidgetObj(id, false, groupName, subGroupName);
+            }
+          });
+          if (subGroupForAllItems) {
+            const subGroupPreferences = groupPreferences['*'],
+              existingSubGroup = allWidgets.findProperty('subGroupName', '*'),
+              currentSubGroup = existingSubGroup || Em.Object.create({
+                  subGroupName: '*',
+                  parentGroup: widgetGroup,
+                  isActive: Em.computed.equal('parentGroup.activeSubGroup.name', '*'),
+                  widgets: []
                 });
             if (!existingSubGroup) {
               allWidgets.pushObject(currentSubGroup);
             }
-            if (subGroupName === '*') {
-              subGroupNames.forEach(name => {
-                if (name !== '*') {
-                  if (subGroupPreferences.visible.contains(id)) {
-                    currentSubGroup.get('widgets').push(this._createGroupWidgetObj(id, groupName, name, true));
-                  }
-                  if (subGroupPreferences.hidden.contains(id)) {
-                    currentSubGroup.get('widgets').push(this._createGroupWidgetObj(id, groupName, name, false));
-                  }
-                }
-              });
-            } else {
-              if (subGroupPreferences.visible.contains(id)) {
-                currentSubGroup.get('widgets').push(this._createGroupWidgetObj(id, groupName, subGroupName, true));
+            Object.keys(subGroupPreferences).forEach(key => {
+              const existingWidgetsLength = currentSubGroup.get('widgets.length'),
+                preferences = subGroupPreferences[key],
+                visibleIndex = preferences.visible.indexOf(id),
+                hiddenIndex = preferences.hidden.indexOf(id),
+                visibleCount = preferences.visible.length;
+              let widgets = [];
+              if (visibleIndex > -1) {
+                currentSubGroup.get('widgets')[visibleIndex + existingWidgetsLength] = this._createGroupWidgetObj(id, true, groupName, key, true);
               }
-              if (subGroupPreferences.hidden.contains(id)) {
-                currentSubGroup.get('widgets').push(this._createGroupWidgetObj(id, groupName, subGroupName, false));
+              if (hiddenIndex > -1) {
+                currentSubGroup.get('widgets')[hiddenIndex + visibleCount + existingWidgetsLength] = this._createGroupWidgetObj(id, false, groupName, key, true);
               }
-            }
+            });
+          }
+          allWidgets.forEach(subGroup => {
+            const widgets = subGroup.get('widgets');
+            subGroup.set('widgets', widgets.filter(widget => !Em.isNone(widget)));
           });
         }
       } else {
-        if (userPreferences.visible.contains(id)) {
-          newVisibleWidgets.push(this._createWidgetObj(id, true));
+        const subGroupId = widgetGroup ? widgetGroup.get('subGroups.lastObject.name') : 'default',
+          visibleIndex = userPreferences.visible.indexOf(id),
+          hiddenIndex = userPreferences.hidden.indexOf(id);
+        if (visibleIndex > -1) {
+          newVisibleWidgets[visibleIndex] = this._createWidgetObj(id, true, subGroupId);
         }
-        if (userPreferences.hidden.contains(id)) {
-          newHiddenWidgets.push(this._createWidgetObj(id, false));
+        if (hiddenIndex > -1) {
+          newHiddenWidgets[hiddenIndex] = this._createWidgetObj(id, false, subGroupId);
         }
       }
     });
+    newVisibleWidgets = newVisibleWidgets.filter(widget => !Em.isNone(widget));
+    newHiddenWidgets = newHiddenWidgets.filter(widget => !Em.isNone(widget));
     this.set('allWidgets', newVisibleWidgets.concat(newHiddenWidgets));
   },
 
@@ -402,11 +451,12 @@ App.MainDashboardWidgetsView = Em.View.extend(App.Persist, App.LocalStorage, App
         }
       });
       Object.keys(defaultPreferences.groups).forEach(groupName => {
-        const groupPreferences = defaultPreferences.groups[groupName];
-        Object.keys(groupPreferences).forEach(subGroupName => {
+        const groupPreferences = defaultPreferences.groups[groupName],
+          subGroupForAllItems = groupPreferences['*'],
+          subGroups = Object.keys(groupPreferences).without('*');
+        subGroups.forEach(subGroupName => {
           groupPreferences[subGroupName][state].forEach(id => {
             if (!newValue.groups[groupName] || !newValue.groups[groupName][subGroupName]) {
-              isChanged = true;
               $.extend(true, newValue.groups, {
                 [groupName]: {
                   [subGroupName]: {
@@ -424,6 +474,30 @@ App.MainDashboardWidgetsView = Em.View.extend(App.Persist, App.LocalStorage, App
             }
           });
         });
+        if (subGroupForAllItems) {
+          Object.keys(subGroupForAllItems).forEach(subGroupName => {
+            subGroupForAllItems[subGroupName][state].forEach(id => {
+              if (!newValue.groups[groupName]['*'] || !newValue.groups[groupName]['*'][subGroupName]) {
+                $.extend(true, newValue.groups, {
+                  [groupName]: {
+                    '*': {
+                      [subGroupName]: {
+                        visible: [],
+                        hidden: [],
+                        threshold: {}
+                      }
+                    }
+                  }
+                });
+              }
+              const subGroupPreferences = newValue.groups[groupName]['*'][subGroupName];
+              if (!subGroupPreferences.visible.contains(id) && !subGroupPreferences.hidden.contains(id)) {
+                isChanged = true;
+                subGroupPreferences[state].push(id);
+              }
+            });
+          });
+        }
       });
     });
     if (isChanged) {
diff --git a/ambari-web/app/views/main/dashboard/widgets/namenode_cpu.js b/ambari-web/app/views/main/dashboard/widgets/namenode_cpu.js
index 40ef2dc..9d2866c 100644
--- a/ambari-web/app/views/main/dashboard/widgets/namenode_cpu.js
+++ b/ambari-web/app/views/main/dashboard/widgets/namenode_cpu.js
@@ -26,7 +26,7 @@ App.NameNodeCpuPieChartView = App.PieChartDashboardWidgetView.extend(App.NameNod
   intervalId: null,
 
   activeNameNodes: Em.computed.alias('model.activeNameNodes'),
-  nameNode: Em.computed.alias('model.activeNameNodes'),
+  nameNode: Em.computed.alias('model.nameNode'),
 
   willDestroyElement: function () {
     clearInterval(this.get("intervalId"));
diff --git a/ambari-web/app/views/main/service/info/summary.js b/ambari-web/app/views/main/service/info/summary.js
index ed51947..72b4621 100644
--- a/ambari-web/app/views/main/service/info/summary.js
+++ b/ambari-web/app/views/main/service/info/summary.js
@@ -457,7 +457,10 @@ App.MainServiceInfoSummaryView = Em.View.extend({
             } else {
               if (!groups.length) {
                 groups.push({
-                  components: []
+                  components: [],
+                  componentWidgetsView: App.HDFSSummaryWidgetsView.extend({
+                    nameSpace: component.get('haNameSpace') || 'default'
+                  })
                 });
               }
               const defaultGroupComponents = groups[0].components;
diff --git a/ambari-web/app/views/main/service/info/summary/hdfs/widgets.js b/ambari-web/app/views/main/service/info/summary/hdfs/widgets.js
index f937ef8..914db59 100644
--- a/ambari-web/app/views/main/service/info/summary/hdfs/widgets.js
+++ b/ambari-web/app/views/main/service/info/summary/hdfs/widgets.js
@@ -92,8 +92,8 @@ App.HDFSSummaryWidgetsView = Em.View.extend({
 
   nonDfsUsed: function () {
     const total = this.get('capacityTotal'),
-      remaining = this.get('service.capacityRemaining'),
-      dfsUsed = this.get('service.capacityUsed');
+      remaining = this.get('capacityRemaining'),
+      dfsUsed = this.get('capacityUsed');
     return (Em.isNone(total) || Em.isNone(remaining) || Em.isNone(dfsUsed)) ? null : total - remaining - dfsUsed;
   }.property('capacityTotal', 'capacityRemaining', 'capacityUsed'),
 
@@ -134,9 +134,9 @@ App.HDFSSummaryWidgetsView = Em.View.extend({
   upgradeStatus: function () {
     const upgradeStatus = this.get('upgradeStatusValue'),
       healthStatus = this.get('healthStatus');
-    if (upgradeStatus == 'true') {
+    if (upgradeStatus) {
       return Em.I18n.t('services.service.summary.pendingUpgradeStatus.notPending');
-    } else if (upgradeStatus == 'false' && healthStatus == 'green') {
+    } else if (upgradeStatus === false && healthStatus === 'green') {
       return Em.I18n.t('services.service.summary.pendingUpgradeStatus.notFinalized');
     } else {
       // upgrade status == null
@@ -145,7 +145,7 @@ App.HDFSSummaryWidgetsView = Em.View.extend({
   }.property('upgradeStatusValue', 'healthStatus'),
 
   isUpgradeStatusWarning: function () {
-    return this.get('upgradeStatusValue') == 'false' && this.get('healthStatus') == 'green';
+    return this.get('upgradeStatusValue') === false && this.get('healthStatus') === 'green';
   }.property('upgradeStatusValue', 'healthStatus'),
 
   safeModeStatusValue: Em.computed.getByKey('service.safeModeStatusValues', 'clusterId'),
diff --git a/ambari-web/test/views/main/dashboard/widgets_test.js b/ambari-web/test/views/main/dashboard/widgets_test.js
index 280c8a5..b1ae67c 100644
--- a/ambari-web/test/views/main/dashboard/widgets_test.js
+++ b/ambari-web/test/views/main/dashboard/widgets_test.js
@@ -265,13 +265,25 @@ describe('App.MainDashboardWidgetsView', function () {
     describe('should set visibleWidgets and hiddenWidgets', function() {
 
       beforeEach(function () {
-        view.set('userPreferences', {
-          visible: [1],
-          hidden: [2],
-          threshold: {
-            1: [],
-            2: [1,2]
-          }
+        view.setProperties({
+          userPreferences: {
+            visible: [1],
+            hidden: [2],
+            threshold: {
+              1: [],
+              2: [1,2]
+            }
+          },
+          widgetGroups: [
+            Em.Object.create({
+              allWidgets: [],
+              subGroups: [
+                {
+                  name: 'n'
+                }
+              ]
+            })
+          ]
         });
         view.renderWidgets();
       });
@@ -296,7 +308,7 @@ describe('App.MainDashboardWidgetsView', function () {
         });
 
         it('viewClass', function () {
-          expect(widget.get('viewClass')).to.be.eql(App.NameNodeHeapPieChartView);
+          expect(widget.get('viewClass').constructor).to.be.eql(App.NameNodeHeapPieChartView.extend().constructor);
         });
 
         it('sourceName', function () {

-- 
To stop receiving notification emails like this one, please contact
ababiichuk@apache.org.