Updated Branches: refs/heads/trunk 556102801 -> e6e6a5ef7
AMBARI-4556. Refactor and Unit tests for host summary. (onechiporenko) Project: http://git-wip-us.apache.org/repos/asf/ambari/repo Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/e6e6a5ef Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/e6e6a5ef Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/e6e6a5ef Branch: refs/heads/trunk Commit: e6e6a5ef71858d9746745dbd8cbe3b09992418fe Parents: 5561028 Author: Oleg Nechiporenko <onechipore...@apache.org> Authored: Fri Feb 7 15:47:56 2014 +0200 Committer: Oleg Nechiporenko <onechipore...@apache.org> Committed: Fri Feb 7 15:47:56 2014 +0200 ---------------------------------------------------------------------- ambari-web/app/app.js | 9 + ambari-web/app/assets/test/tests.js | 2 + ambari-web/app/templates/main/host/summary.hbs | 4 +- .../main/host/details/host_component_view.js | 237 +++++++----- ambari-web/app/views/main/host/summary.js | 192 ++++----- ambari-web/test/app_test.js | 10 + .../host/details/host_component_view_test.js | 315 +++++++++++++++ ambari-web/test/views/main/host/summary_test.js | 386 +++++++++++++++++++ 8 files changed, 956 insertions(+), 199 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ambari/blob/e6e6a5ef/ambari-web/app/app.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/app.js b/ambari-web/app/app.js index 06797f2..252edd5 100644 --- a/ambari-web/app/app.js +++ b/ambari-web/app/app.js @@ -73,11 +73,17 @@ module.exports = Em.Application.create({ return !this.HostComponent.find().someProperty('componentName', 'SECONDARY_NAMENODE'); }.property('router.clusterController.isLoaded'), + /** + * List of components with allowed action for them + * @type {Em.Object} + */ components: Ember.Object.create({ reassignable: ['NAMENODE', 'SECONDARY_NAMENODE', 'JOBTRACKER', 'RESOURCEMANAGER'], restartable: ['APP_TIMELINE_SERVER'], deletable: ['SUPERVISOR', 'HBASE_MASTER', 'DATANODE', 'TASKTRACKER', 'NODEMANAGER', 'HBASE_REGIONSERVER'], rollinRestartAllowed: ["DATANODE", "TASKTRACKER", "NODEMANAGER", "HBASE_REGIONSERVER", "SUPERVISOR"], + decommissionAllowed: ["DATANODE", "TASKTRACKER", "NODEMANAGER", "HBASE_REGIONSERVER"], + addableToHost: ["DATANODE", "TASKTRACKER", "NODEMANAGER", "HBASE_REGIONSERVER", "HBASE_MASTER", "ZOOKEEPER_SERVER", "SUPERVISOR"], slaves: function() { return require('data/service_components').filter(function(component){ return !component.isClient && !component.isMaster @@ -86,6 +92,9 @@ module.exports = Em.Application.create({ masters: function() { return require('data/service_components').filterProperty('isMaster', true).mapProperty('component_name').uniq(); + }.property().cacheable(), + clients: function() { + return require('data/service_components').filterProperty('isClient', true).mapProperty('component_name').uniq(); }.property().cacheable() }) }); http://git-wip-us.apache.org/repos/asf/ambari/blob/e6e6a5ef/ambari-web/app/assets/test/tests.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/assets/test/tests.js b/ambari-web/app/assets/test/tests.js index 1550d0b..fd7cb29 100644 --- a/ambari-web/app/assets/test/tests.js +++ b/ambari-web/app/assets/test/tests.js @@ -98,6 +98,8 @@ require('test/views/main/dashboard/widgets/resource_manager_uptime_test'); require('test/views/main/dashboard/widgets/links_widget_test'); require('test/views/main/dashboard/widgets/pie_chart_widget_test'); require('test/views/main/dashboard/widgets/namenode_cpu_test'); +require('test/views/main/host/summary_test'); +require('test/views/main/host/details/host_component_view_test'); require('test/views/common/configs/services_config_test'); require('test/views/wizard/step9_view_test'); require('test/models/host_test'); http://git-wip-us.apache.org/repos/asf/ambari/blob/e6e6a5ef/ambari-web/app/templates/main/host/summary.hbs ---------------------------------------------------------------------- diff --git a/ambari-web/app/templates/main/host/summary.hbs b/ambari-web/app/templates/main/host/summary.hbs index 4866c7f..de4fd8b 100644 --- a/ambari-web/app/templates/main/host/summary.hbs +++ b/ambari-web/app/templates/main/host/summary.hbs @@ -58,7 +58,7 @@ {{#each component in view.sortedComponents}} <div class="row-fluid"> - {{view App.HostComponentView contentBinding="component" decommissionDataNodeHostNamesBinding="view.decommissionDataNodeHostNames"}} + {{view App.HostComponentView contentBinding="component"}} </div> {{/each}} {{/if}} @@ -77,7 +77,7 @@ <div class="span5 row"> {{#if App.isAdmin}} <div class="btn-group pull-right"> - <button id="add_component" data-toggle="dropdown" {{bindAttr class="view.addComponentDisabled:disabled :btn :btn-info :dropdown-toggle"}}> + <button id="add_component" data-toggle="dropdown" {{bindAttr class=":btn :btn-info :dropdown-toggle"}}> {{t common.installed}} <span class="caret pull-right"></span> </button> http://git-wip-us.apache.org/repos/asf/ambari/blob/e6e6a5ef/ambari-web/app/views/main/host/details/host_component_view.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/views/main/host/details/host_component_view.js b/ambari-web/app/views/main/host/details/host_component_view.js index b515b18..20ae8de 100644 --- a/ambari-web/app/views/main/host/details/host_component_view.js +++ b/ambari-web/app/views/main/host/details/host_component_view.js @@ -20,30 +20,14 @@ var App = require('app'); var uiEffects = require('utils/ui_effects'); App.HostComponentView = Em.View.extend({ + templateName: require('templates/main/host/details/host_component'), + /** * @type {App.HostComponent} */ content: null, - didInsertElement: function () { - App.tooltip($('[rel=componentHealthTooltip]')); - App.tooltip($('[rel=passiveTooltip]')); - if (this.get('isInProgress')) { - this.doBlinking(); - } - if (this.get('isDataNode')){ - this.loadDataNodeDecommissionStatus(); - } - if (this.get('isNodeManager')){ - this.loadNodeManagerDecommissionStatus(); - } - if (this.get('isTaskTracker')){ - this.loadTaskTrackerDecommissionStatus(); - } - if (this.get('isRegionServer')){ - this.loadRegionServerDecommissionStatus(); - } - }, + /** * @type {App.HostComponent} */ @@ -128,6 +112,10 @@ App.HostComponentView = Em.View.extend({ return ''; }.property('content.passiveState','parentView.content.passiveState'), + /** + * CSS-class for host component status + * @type {String} + */ statusClass: function () { //If the component is DataNode if (this.get('isDataNode')) { @@ -178,67 +166,57 @@ App.HostComponentView = Em.View.extend({ }.property('content.passiveState','workStatus', 'isDataNodeRecommissionAvailable', 'isNodeManagerRecommissionAvailable', 'isTaskTrackerRecommissionAvailable', 'isRegionServerRecommissionAvailable'), /** + * CSS-class for disabling drop-down menu with list of host component actions + * Disabled if host's <code>healthClass</code> is health-status-DEAD-YELLOW (lost heartbeat) * @type {String} */ disabled: function () { return (this.get('parentView.content.healthClass') === "health-status-DEAD-YELLOW") ? 'disabled' : ''; }.property('parentView.content.healthClass'), + /** * For Upgrade failed state + * @type {bool} */ isUpgradeFailed: function () { return App.HostComponentStatus.getKeyName(this.get('workStatus')) === "upgrade_failed"; }.property("workStatus"), + /** * For Install failed state + * @type {bool} */ isInstallFailed: function () { return App.HostComponentStatus.getKeyName(this.get('workStatus')) === "install_failed"; }.property("workStatus"), + /** - * Do blinking for 1 minute - */ - doBlinking: function () { - var workStatus = this.get('workStatus'); - var self = this; - var pulsate = [ App.HostComponentStatus.starting, App.HostComponentStatus.stopping, App.HostComponentStatus.installing].contains(workStatus); - if (!pulsate && (this.get('isDataNode') || this.get('isRegionServer') || this.get('isNodeManager') || this.get('isTaskTracker'))) { - var component = this.get('content'); - if (component && workStatus != "INSTALLED") { - pulsate = this.get('isDecommissioning'); - } - } - if (pulsate && !self.get('isBlinking')) { - self.set('isBlinking', true); - uiEffects.pulsate(self.$('.components-health'), 1000, function () { - self.set('isBlinking', false); - self.doBlinking(); - }); - } - }, - /** - * Start blinking when host component is starting/stopping/decommissioning + * For Started and Starting states + * @type {bool} */ - startBlinking: function () { - this.$('.components-health').stop(true, true); - this.$('.components-health').css({opacity: 1.0}); - this.doBlinking(); - }.observes('workStatus','isDataNodeRecommissionAvailable', 'isDecommissioning', 'isRegionServerRecommissionAvailable', - 'isNodeManagerRecommissionAvailable', 'isTaskTrackerRecommissionAvailable'), - isStart: function () { return (this.get('workStatus') == App.HostComponentStatus.started || this.get('workStatus') == App.HostComponentStatus.starting); }.property('workStatus'), + /** + * For Installed state + * @type {bool} + */ isStop: function () { return (this.get('workStatus') == App.HostComponentStatus.stopped); }.property('workStatus'), + /** + * For Installing state + * @type {bool} + */ isInstalling: function () { return (this.get('workStatus') == App.HostComponentStatus.installing); }.property('workStatus'), + /** * No action available while component is starting/stopping/unknown + * @type {String} */ noActionAvailable: function () { var workStatus = this.get('workStatus'); @@ -249,38 +227,148 @@ App.HostComponentView = Em.View.extend({ } }.property('workStatus'), + /** + * For Stopping or Starting states, also for decommissioning + * @type {bool} + */ isInProgress: function () { return (this.get('workStatus') === App.HostComponentStatus.stopping || this.get('workStatus') === App.HostComponentStatus.starting) || this.get('isDecommissioning'); }.property('workStatus', 'isDecommissioning'), + /** + * For ACTIVE <code>passiveState</code> of host component + * @type {bool} + */ + isActive: function () { + return (this.get('content.passiveState') == "ACTIVE"); + }.property('content.passiveState'), + + /** + * For PASSIVE <code>passiveState</code> of host or service + * @type {bool} + */ + isImplied: function() { + return (this.get('parentView.content.passiveState') === 'PASSIVE' || this.get('content.service.passiveState') === 'PASSIVE'); + }.property('parentView.content.passiveState', 'content.service.passiveState'), + + /** + * + * @type {bool} + */ + isDecommissioning: function () { + return ( (this.get('isDataNode') && this.get("isDataNodeDecommissioning")) || (this.get('isRegionServer') && this.get("isRegionServerDecommissioning")) + || (this.get('isNodeManager') && this.get("isNodeManagerDecommissioning")) || (this.get('isTaskTracker') && this.get('isTaskTrackerDecommissioning'))); + }.property("workStatus", "isDataNodeDecommissioning", "isRegionServerDecommissioning", "isNodeManagerDecommissioning", "isTaskTrackerDecommissioning"), + + /** + * Shows whether we need to show Delete button + * @type {bool} + */ + isDeletableComponent: function () { + return App.get('components.deletable').contains(this.get('content.componentName')); + }.property('content'), + + /** + * Host component with some <code>workStatus</code> can't be deleted (so, disable such action in the dropdown list) + * @type {bool} + */ + isDeleteComponentDisabled: function () { + return ![App.HostComponentStatus.stopped, App.HostComponentStatus.unknown, App.HostComponentStatus.install_failed, App.HostComponentStatus.upgrade_failed].contains(this.get('workStatus')); + }.property('workStatus'), + + /** + * Check if component may be reassinged to another host + * @type {bool} + */ + isReassignable: function () { + return App.supports.reassignMaster && App.get('components.reassignable').contains(this.get('content.componentName')) && App.Host.find().content.length > 1; + }.property('content.componentName'), + + /** + * Check if component is restartable + * @type {bool} + */ + isRestartableComponent: function() { + return App.get('components.restartable').contains(this.get('content.componentName')); + }.property('content'), + + /** + * Host component with some <code>workStatus</code> can't be restarted (so, disable such action in the dropdown list) + * @type {bool} + */ + isRestartComponentDisabled: function() { + return ![App.HostComponentStatus.started].contains(this.get('workStatus')); + }.property('workStatus'), + + didInsertElement: function () { + App.tooltip($('[rel=componentHealthTooltip]')); + App.tooltip($('[rel=passiveTooltip]')); + if (this.get('isInProgress')) { + this.doBlinking(); + } + if (this.get('isDataNode')){ + this.loadDataNodeDecommissionStatus(); + } + if (this.get('isNodeManager')){ + this.loadNodeManagerDecommissionStatus(); + } + if (this.get('isTaskTracker')){ + this.loadTaskTrackerDecommissionStatus(); + } + if (this.get('isRegionServer')){ + this.loadRegionServerDecommissionStatus(); + } + }, + + /** + * Do blinking for 1 minute + */ + doBlinking: function () { + var workStatus = this.get('workStatus'); + var self = this; + var pulsate = [ App.HostComponentStatus.starting, App.HostComponentStatus.stopping, App.HostComponentStatus.installing].contains(workStatus); + if (!pulsate && (this.get('isDataNode') || this.get('isRegionServer') || this.get('isNodeManager') || this.get('isTaskTracker'))) { + var component = this.get('content'); + if (component && workStatus != "INSTALLED") { + pulsate = this.get('isDecommissioning'); + } + } + if (pulsate && !self.get('isBlinking')) { + self.set('isBlinking', true); + uiEffects.pulsate(self.$('.components-health'), 1000, function () { + self.set('isBlinking', false); + self.doBlinking(); + }); + } + }, + /** + * Start blinking when host component is starting/stopping/decommissioning + */ + startBlinking: function () { + this.$('.components-health').stop(true, true); + this.$('.components-health').css({opacity: 1.0}); + this.doBlinking(); + }.observes('workStatus','isDataNodeRecommissionAvailable', 'isDecommissioning', 'isRegionServerRecommissionAvailable', + 'isNodeManagerRecommissionAvailable', 'isTaskTrackerRecommissionAvailable'), + isDataNode: function () { return this.get('content.componentName') === 'DATANODE'; }.property('content'), + isNodeManager: function () { return this.get('content.componentName') === 'NODEMANAGER'; }.property('content'), + isTaskTracker: function () { return this.get('content.componentName') === 'TASKTRACKER'; }.property('content'), + isRegionServer: function () { return this.get('content.componentName') === 'HBASE_REGIONSERVER'; }.property('content'), - isActive: function () { - return (this.get('content.passiveState') == "ACTIVE"); - }.property('content.passiveState'), - - isImplied: function() { - return (this.get('parentView.content.passiveState') === 'PASSIVE' || this.get('content.service.passiveState') === 'PASSIVE'); - }.property('content.passiveState'), - - isDecommissioning: function () { - return ( (this.get('isDataNode') && this.get("isDataNodeDecommissioning")) || (this.get('isRegionServer') && this.get("isRegionServerDecommissioning")) - || (this.get('isNodeManager') && this.get("isNodeManagerDecommissioning")) || (this.get('isTaskTracker') && this.get('isTaskTrackerDecommissioning'))); - }.property("workStatus", "isDataNodeDecommissioning", "isRegionServerDecommissioning", "isNodeManagerDecommissioning", "isTaskTrackerDecommissioning"), - isDataNodeDecommissioning: null, isDataNodeDecommissionAvailable: null, isDataNodeRecommissionAvailable: null, @@ -620,31 +708,6 @@ App.HostComponentView = Em.View.extend({ deferred.resolve(desired_admin_state); }); return deferred.promise(); - }.observes('App.router.mainHostDetailsController.content'), - - /** - * Shows whether we need to show Delete button - */ - isDeletableComponent: function () { - return App.get('components.deletable').contains(this.get('content.componentName')); - }.property('content'), - - isDeleteComponentDisabled: function () { - return !(this.get('workStatus') == App.HostComponentStatus.stopped || this.get('workStatus') == App.HostComponentStatus.unknown || - this.get('workStatus') == App.HostComponentStatus.install_failed || this.get('workStatus') == App.HostComponentStatus.upgrade_failed); - }.property('workStatus'), - - isReassignable: function () { - return App.supports.reassignMaster && App.get('components.reassignable').contains(this.get('content.componentName')) && App.Host.find().content.length > 1; - }.property('content.componentName'), - - isRestartableComponent: function() { - return App.get('components.restartable').contains(this.get('content.componentName')); - }.property('content'), - - isRestartComponentDisabled: function() { - var allowableStates = [App.HostComponentStatus.started]; - return !allowableStates.contains(this.get('workStatus')); - }.property('workStatus') + }.observes('App.router.mainHostDetailsController.content') }); http://git-wip-us.apache.org/repos/asf/ambari/blob/e6e6a5ef/ambari-web/app/views/main/host/summary.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/views/main/host/summary.js b/ambari-web/app/views/main/host/summary.js index 4c8903f..520b4c7 100644 --- a/ambari-web/app/views/main/host/summary.js +++ b/ambari-web/app/views/main/host/summary.js @@ -17,13 +17,19 @@ */ var App = require('app'); -var uiEffects = require('utils/ui_effects'); App.MainHostSummaryView = Em.View.extend({ + templateName: require('templates/main/host/summary'), + /** + * @type {bool} + */ isStopCommand:true, + /** + * @type {App.Host} + */ content: function () { return App.router.get('mainHostDetailsController.content'); }.property('App.router.mainHostDetailsController.content'), @@ -34,6 +40,10 @@ App.MainHostSummaryView = Em.View.extend({ window.open(gangliaMobileUrl); }, + /** + * Message for "restart" block + * @type {String} + */ needToRestartMessage: function() { var componentsCount, word; componentsCount = this.get('content.componentsWithStaleConfigsCount'); @@ -48,11 +58,29 @@ App.MainHostSummaryView = Em.View.extend({ didInsertElement: function () { this.addToolTip(); }, + + /** + * Create tooltip for "Add" button if nothing to add to the current host + */ addToolTip: function() { if (this.get('addComponentDisabled')) { App.tooltip($('#add_component'), {title: Em.I18n.t('services.nothingToAdd')}); } }.observes('addComponentDisabled'), + + /** + * List of installed services + * @type {String[]} + */ + installedServices: function() { + return App.Service.find().mapProperty('serviceName'); + }.property('App.Service.@each'), + + /** + * List of installed masters and slaves + * Masters first, then slaves + * @type {DS.Model[]} + */ sortedComponents: function () { var slaveComponents = []; var masterComponents = []; @@ -64,7 +92,12 @@ App.MainHostSummaryView = Em.View.extend({ } }, this); return masterComponents.concat(slaveComponents); - }.property('content', 'content.hostComponents.length'), + }.property('content.hostComponents.length'), + + /** + * List of installed clients + * @type {DS.Model[]} + */ clients: function () { var clients = []; this.get('content.hostComponents').forEach(function (component) { @@ -93,7 +126,10 @@ App.MainHostSummaryView = Em.View.extend({ }).length; }.property('clients.@each.staleConfigs'), - + /** + * Template for addable component + * @type {Em.Object} + */ addableComponentObject: Em.Object.extend({ componentName: '', subComponentNames: null, @@ -104,134 +140,70 @@ App.MainHostSummaryView = Em.View.extend({ return App.format.role(this.get('componentName')); }.property('componentName') }), + + /** + * If host lost heartbeat, components can't be added on it + * @type {bool} + */ isAddComponent: function () { return this.get('content.healthClass') !== 'health-status-DEAD-YELLOW'; }.property('content.healthClass'), + /** + * Disable "Add" button if components can't be added to the current host + * @type {bool} + */ addComponentDisabled: function() { return (!this.get('isAddComponent')) || (this.get('addableComponents.length') == 0); }.property('isAddComponent', 'addableComponents.length'), + /** + * List of client's that may be installed to the current host + * @type {String[]} + */ installableClientComponents: function() { - var installableClients = []; if (!App.supports.deleteHost) { - return installableClients; + return []; } - App.Service.find().forEach(function(svc){ - switch(svc.get('serviceName')){ - case 'PIG': - installableClients.push('PIG'); - break; - case 'SQOOP': - installableClients.push('SQOOP'); - break; - case 'HCATALOG': - installableClients.push('HCAT'); - break; - case 'HDFS': - installableClients.push('HDFS_CLIENT'); - break; - case 'OOZIE': - installableClients.push('OOZIE_CLIENT'); - break; - case 'ZOOKEEPER': - installableClients.push('ZOOKEEPER_CLIENT'); - break; - case 'HIVE': - installableClients.push('HIVE_CLIENT'); - break; - case 'HBASE': - installableClients.push('HBASE_CLIENT'); - break; - case 'YARN': - installableClients.push('YARN_CLIENT'); - break; - case 'MAPREDUCE': - installableClients.push('MAPREDUCE_CLIENT'); - break; - case 'MAPREDUCE2': - installableClients.push('MAPREDUCE2_CLIENT'); - break; - case 'TEZ': - installableClients.push('TEZ_CLIENT'); - break; - } + var componentServiceMap = App.QuickDataMapper.componentServiceMap(); + var allClients = App.get('components.clients'); + var installedServices = this.get('installedServices'); + var installedClients = this.get('clients').mapProperty('componentName'); + return allClients.filter(function(componentName) { + // service for current client is installed but client isn't installed on current host + return installedServices.contains(componentServiceMap[componentName]) && !installedClients.contains(componentName); }); - this.get('content.hostComponents').forEach(function (component) { - var index = installableClients.indexOf(component.get('componentName')); - if (index > -1) { - installableClients.splice(index, 1); - } - }, this); - return installableClients; - }.property('content', 'content.hostComponents.length', 'App.Service', 'App.supports.deleteHost'), - + }.property('content.hostComponents.length', 'installedServices.length', 'App.supports.deleteHost'), + + /** + * List of components that may be added to the current host + * @type {Em.Object[]} + */ addableComponents: function () { var components = []; - var services = App.Service.find(); - var dataNodeExists = false; - var taskTrackerExists = false; - var regionServerExists = false; - var zookeeperServerExists = false; - var nodeManagerExists = false; - var hbaseMasterExists = false; - var supervisorExists = false; - + var self = this; var installableClients = this.get('installableClientComponents'); - - this.get('content.hostComponents').forEach(function (component) { - switch (component.get('componentName')) { - case 'DATANODE': - dataNodeExists = true; - break; - case 'TASKTRACKER': - taskTrackerExists = true; - break; - case 'HBASE_REGIONSERVER': - regionServerExists = true; - break; - case 'ZOOKEEPER_SERVER': - zookeeperServerExists = true; - break; - case 'NODEMANAGER': - nodeManagerExists = true; - break; - case 'HBASE_MASTER': - hbaseMasterExists = true; - break; - case 'SUPERVISOR': - supervisorExists = true; - break; - } - }, this); + var installedComponents = this.get('content.hostComponents').mapProperty('componentName'); + var addableToHostComponents = App.get('components.addableToHost'); + var installedServices = this.get('installedServices'); + var componentServiceMap = App.QuickDataMapper.componentServiceMap(); - if (!dataNodeExists) { - components.pushObject(this.addableComponentObject.create({ 'componentName': 'DATANODE' })); - } - if (!taskTrackerExists && services.findProperty('serviceName', 'MAPREDUCE')) { - components.pushObject(this.addableComponentObject.create({ 'componentName': 'TASKTRACKER' })); - } - if (!regionServerExists && services.findProperty('serviceName', 'HBASE')) { - components.pushObject(this.addableComponentObject.create({ 'componentName': 'HBASE_REGIONSERVER' })); - } - if (!hbaseMasterExists && services.findProperty('serviceName', 'HBASE')) { - components.pushObject(this.addableComponentObject.create({ 'componentName': 'HBASE_MASTER' })); - } - if (!zookeeperServerExists && services.findProperty('serviceName', 'ZOOKEEPER')) { - components.pushObject(this.addableComponentObject.create({ 'componentName': 'ZOOKEEPER_SERVER' })); - } - if (!nodeManagerExists && services.findProperty('serviceName', 'YARN')) { - components.pushObject(this.addableComponentObject.create({ 'componentName': 'NODEMANAGER' })); - } - if (!supervisorExists && services.findProperty('serviceName', 'STORM')) { - components.pushObject(this.addableComponentObject.create({ 'componentName': 'SUPERVISOR' })); - } + addableToHostComponents.forEach(function(addableComponent) { + if(installedServices.contains(componentServiceMap[addableComponent]) && !installedComponents.contains(addableComponent)) { + components.pushObject(self.addableComponentObject.create({'componentName': addableComponent})); + } + }); if (installableClients.length > 0) { components.pushObject(this.addableComponentObject.create({ 'componentName': 'CLIENTS', subComponentNames: installableClients })); } + return components; - }.property('content', 'content.hostComponents.length', 'installableClientComponents'), + }.property('content.hostComponents.length', 'installableClientComponents'), + /** + * Formatted with <code>$.timeago</code> value of host's last heartbeat + * @type {String} + */ timeSinceHeartBeat: function () { var d = this.get('content.lastHeartBeatTime'); if (d) { http://git-wip-us.apache.org/repos/asf/ambari/blob/e6e6a5ef/ambari-web/test/app_test.js ---------------------------------------------------------------------- diff --git a/ambari-web/test/app_test.js b/ambari-web/test/app_test.js index 59ec825..cc3155c 100644 --- a/ambari-web/test/app_test.js +++ b/ambari-web/test/app_test.js @@ -19,10 +19,20 @@ var App = require('app'); describe('#App.components', function() { + it('slaves and masters should not intersect', function() { var intersected = App.get('components.slaves').filter(function(item){ return App.get('components.masters').contains(item); }); expect(intersected).to.eql([]); }); + + it('decommissionAllowed', function() { + expect(App.get('components.decommissionAllowed')).to.eql(["DATANODE", "TASKTRACKER", "NODEMANAGER", "HBASE_REGIONSERVER"]); + }); + + it('addableToHost', function() { + expect(App.get('components.addableToHost')).to.eql(["DATANODE", "TASKTRACKER", "NODEMANAGER", "HBASE_REGIONSERVER", "HBASE_MASTER", "ZOOKEEPER_SERVER", "SUPERVISOR"]); + }); + }); http://git-wip-us.apache.org/repos/asf/ambari/blob/e6e6a5ef/ambari-web/test/views/main/host/details/host_component_view_test.js ---------------------------------------------------------------------- diff --git a/ambari-web/test/views/main/host/details/host_component_view_test.js b/ambari-web/test/views/main/host/details/host_component_view_test.js new file mode 100644 index 0000000..bd30686 --- /dev/null +++ b/ambari-web/test/views/main/host/details/host_component_view_test.js @@ -0,0 +1,315 @@ +/** + * 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 App = require('app'); +require('models/host_component'); +require('views/main/host/details/host_component_view'); + +var hostComponentView; + +describe('App.HostComponentView', function() { + + beforeEach(function() { + hostComponentView = App.HostComponentView.create({ + startBlinking: function(){}, + doBlinking: function(){} + }); + }); + + describe('#componentTextStatus', function() { + + var tests = Em.A([ + { + content: Em.Object.create({passiveState: 'PASSIVE'}), + m: 'PASSIVE state', + e: Em.I18n.t('hosts.component.passive.short.mode') + }, + { + content: Em.Object.create({passiveState: 'IMPLIED'}), + m: 'IMPLIED state', + e: Em.I18n.t('hosts.component.passive.short.mode') + } + ]); + + tests.forEach(function(test) { + it(test.m, function() { + hostComponentView.set('content', test.content); + expect(hostComponentView.get('componentTextStatus')).to.equal(test.e); + }); + }); + + }); + + describe('#passiveImpliedTextStatus', function() { + + var tests = Em.A([ + { + content: {service: {passiveState: 'PASSIVE'}}, + parentView: {content: {passiveState: 'PASSIVE'}}, + m: 'service in PASSIVE, host in PASSIVE', + e: Em.I18n.t('hosts.component.passive.implied.host.mode.tooltip') + }, + { + content: {service: {passiveState: 'PASSIVE', serviceName:'SERVICE_NAME'}}, + parentView: {content: {passiveState: 'ACTIVE'}}, + m: 'service in PASSIVE, host in ACTIVE', + e: Em.I18n.t('hosts.component.passive.implied.service.mode.tooltip').format('SERVICE_NAME') + }, + { + content: {service: {passiveState: 'ACTIVE'}}, + parentView: {content: {passiveState: 'ACTIVE'}}, + m: 'service in ACTIVE, host in ACTIVE', + e: '' + } + ]); + + tests.forEach(function(test) { + it(test.m, function() { + hostComponentView = App.HostComponentView.create({ + startBlinking: function(){}, + doBlinking: function(){}, + parentView: test.parentView, + content: test.content + }); + expect(hostComponentView.get('passiveImpliedTextStatus')).to.equal(test.e); + }); + }); + + }); + + describe('#disabled', function() { + + var tests = Em.A([ + { + parentView: {content: {healthClass: 'health-status-DEAD-YELLOW'}}, + e: 'disabled' + }, + { + parentView: {content: {healthClass: 'another-class'}}, + e: '' + } + ]); + + tests.forEach(function(test) { + it(test.m, function() { + hostComponentView = App.HostComponentView.create({ + startBlinking: function(){}, + doBlinking: function(){}, + parentView: test.parentView + }); + expect(hostComponentView.get('disabled')).to.equal(test.e); + }); + }); + + }); + + describe('#isUpgradeFailed', function() { + + var tests = Em.A([ + {workStatus: 'UPGRADE_FAILED', e: true}, + {workStatus: 'OTHER_STATUS', e: false} + ]); + + tests.forEach(function(test) { + it(test.workStatus, function() { + hostComponentView.set('content', {workStatus: test.workStatus}); + expect(hostComponentView.get('isUpgradeFailed')).to.equal(test.e); + }); + }); + + }); + + describe('#isInstallFailed', function() { + + var tests = Em.A([ + {workStatus: 'INSTALL_FAILED', e: true}, + {workStatus: 'OTHER_STATUS', e: false} + ]); + + tests.forEach(function(test) { + it(test.workStatus, function() { + hostComponentView.set('content', {workStatus: test.workStatus}); + expect(hostComponentView.get('isInstallFailed')).to.equal(test.e); + }); + }); + + }); + + describe('#isStart', function() { + + var tests = Em.A([ + {workStatus: 'STARTED', e: true}, + {workStatus: 'STARTING', e: true}, + {workStatus: 'OTHER_STATUS', e: false} + ]); + + tests.forEach(function(test) { + it(test.workStatus, function() { + hostComponentView.set('content', {workStatus: test.workStatus}); + expect(hostComponentView.get('isStart')).to.equal(test.e); + }); + }); + + }); + + describe('#isStop', function() { + + var tests = Em.A([ + {workStatus: 'INSTALLED', e: true}, + {workStatus: 'OTHER_STATUS', e: false} + ]); + + tests.forEach(function(test) { + it(test.workStatus, function() { + hostComponentView.set('content', {workStatus: test.workStatus}); + expect(hostComponentView.get('isStop')).to.equal(test.e); + }); + }); + + }); + + describe('#isInstalling', function() { + + var tests = Em.A([ + {workStatus: 'INSTALLING', e: true}, + {workStatus: 'OTHER_STATUS', e: false} + ]); + + tests.forEach(function(test) { + it(test.workStatus, function() { + hostComponentView.set('content', {workStatus: test.workStatus}); + expect(hostComponentView.get('isInstalling')).to.equal(test.e); + }); + }); + + }); + + describe('#noActionAvailable', function() { + + var tests = Em.A([ + {workStatus: 'STARTING', e: 'hidden'}, + {workStatus: 'STOPPING', e: 'hidden'}, + {workStatus: 'UNKNOWN', e: 'hidden'}, + {workStatus: 'OTHER_STATUS', e: ''} + ]); + + tests.forEach(function(test) { + it(test.workStatus, function() { + hostComponentView.set('content', {workStatus: test.workStatus}); + expect(hostComponentView.get('noActionAvailable')).to.equal(test.e); + }); + }); + + }); + + describe('#isActive', function() { + + var tests = Em.A([ + {passiveState: 'ACTIVE', e: true}, + {passiveState: 'PASSIVE', e: false}, + {passiveState: 'IMPLIED', e: false} + ]); + + tests.forEach(function(test) { + it(test.workStatus, function() { + hostComponentView.set('content', {passiveState: test.passiveState}); + expect(hostComponentView.get('isActive')).to.equal(test.e); + }); + }); + + }); + + describe('#isImplied', function() { + + var tests = Em.A([ + { + content: {service: {passiveState: 'PASSIVE'}}, + parentView: {content: {passiveState: 'PASSIVE'}}, + m: 'service in PASSIVE, host in PASSIVE', + e: true + }, + { + content: {service: {passiveState: 'PASSIVE', serviceName:'SERVICE_NAME'}}, + parentView: {content: {passiveState: 'ACTIVE'}}, + m: 'service in PASSIVE, host in ACTIVE', + e: true + }, + { + content: {service: {passiveState: 'ACTIVE', serviceName:'SERVICE_NAME'}}, + parentView: {content: {passiveState: 'PASSIVE'}}, + m: 'service in ACTIVE, host in PASSIVE', + e: true + }, + { + content: {service: {passiveState: 'ACTIVE'}}, + parentView: {content: {passiveState: 'ACTIVE'}}, + m: 'service in ACTIVE, host in ACTIVE', + e: false + } + ]); + + tests.forEach(function(test) { + it(test.m, function() { + hostComponentView = App.HostComponentView.create({ + startBlinking: function(){}, + doBlinking: function(){}, + parentView: test.parentView, + content: test.content + }); + expect(hostComponentView.get('isImplied')).to.equal(test.e); + }); + }); + + }); + + describe('#isRestartComponentDisabled', function() { + + var tests = Em.A([ + {workStatus: 'STARTED', e: false}, + {workStatus: 'OTHER_STATUS', e: true} + ]); + + tests.forEach(function(test) { + it(test.workStatus, function() { + hostComponentView.set('content', {workStatus: test.workStatus}); + expect(hostComponentView.get('isRestartComponentDisabled')).to.equal(test.e); + }); + }); + + }); + + describe('#isDeleteComponentDisabled', function() { + + var tests = Em.A([ + {workStatus: 'INSTALLED', e: false}, + {workStatus: 'UNKNOWN', e: false}, + {workStatus: 'INSTALL_FAILED', e: false}, + {workStatus: 'UPGRADE_FAILED', e: false}, + {workStatus: 'OTHER_STATUS', e: true} + ]); + + tests.forEach(function(test) { + it(test.workStatus, function() { + hostComponentView.set('content', {workStatus: test.workStatus}); + expect(hostComponentView.get('isDeleteComponentDisabled')).to.equal(test.e); + }); + }); + + }); + +}); http://git-wip-us.apache.org/repos/asf/ambari/blob/e6e6a5ef/ambari-web/test/views/main/host/summary_test.js ---------------------------------------------------------------------- diff --git a/ambari-web/test/views/main/host/summary_test.js b/ambari-web/test/views/main/host/summary_test.js new file mode 100644 index 0000000..588a2b6 --- /dev/null +++ b/ambari-web/test/views/main/host/summary_test.js @@ -0,0 +1,386 @@ +/** + * 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 App = require('app'); +require('models/host'); +require('models/service'); +require('models/host_component'); +require('mappers/server_data_mapper'); +require('views/main/host/summary'); + +var mainHostSummaryView; +var extendedMainHostSummaryView = App.MainHostSummaryView.extend({content: {}, addToolTip: function(){}, installedServices: []}); + +describe('App.MainHostSummaryView', function() { + + beforeEach(function() { + mainHostSummaryView = extendedMainHostSummaryView.create({}); + }); + + describe('#sortedComponents', function() { + + var tests = Em.A([ + { + content: Em.Object.create({ + hostComponents: Em.A([ + Em.Object.create({isMaster: false, isSlave: true, componentName: 'B'}), + Em.Object.create({isMaster: true, isSlave: false, componentName: 'A'}), + Em.Object.create({isMaster: true, isSlave: false, componentName: 'C'}), + Em.Object.create({isMaster: false, isSlave: false, componentName: 'D'}) + ]) + }), + m: 'List of masters, slaves and clients', + e: ['A', 'C', 'B'] + }, + { + content: Em.Object.create({ + hostComponents: Em.A([ + Em.Object.create({isMaster: false, isSlave: true, componentName: 'B'}), + Em.Object.create({isMaster: true, isSlave: false, componentName: 'A'}), + Em.Object.create({isMaster: true, isSlave: false, componentName: 'C'}), + Em.Object.create({isMaster: true, isSlave: false, componentName: 'D'}) + ]) + }), + m: 'List of masters and slaves', + e: ['A', 'C', 'D', 'B'] + }, + { + content: Em.Object.create({ + hostComponents: Em.A([ + Em.Object.create({isMaster: true, isSlave: false, componentName: 'B'}), + Em.Object.create({isMaster: true, isSlave: false, componentName: 'A'}), + Em.Object.create({isMaster: true, isSlave: false, componentName: 'C'}), + Em.Object.create({isMaster: true, isSlave: false, componentName: 'D'}) + ]) + }), + m: 'List of masters', + e: ['B', 'A', 'C', 'D'] + }, + { + content: Em.Object.create({ + hostComponents: Em.A([ + Em.Object.create({isMaster: false, isSlave: true, componentName: 'B'}), + Em.Object.create({isMaster: false, isSlave: true, componentName: 'A'}), + Em.Object.create({isMaster: false, isSlave: true, componentName: 'C'}), + Em.Object.create({isMaster: false, isSlave: true, componentName: 'D'}) + ]) + }), + m: 'List of slaves', + e: ['B', 'A', 'C', 'D'] + }, + { + content: Em.Object.create({ + hostComponents: Em.A([]) + }), + m: 'Empty list', + e: [] + }, + { + content: Em.Object.create({ + hostComponents: Em.A([ + Em.Object.create({isMaster: false, isSlave: false, componentName: 'B'}), + Em.Object.create({isMaster: false, isSlave: false, componentName: 'A'}), + Em.Object.create({isMaster: false, isSlave: false, componentName: 'C'}), + Em.Object.create({isMaster: false, isSlave: false, componentName: 'D'}) + ]) + }), + m: 'List of clients', + e: [] + } + ]); + + tests.forEach(function(test) { + it(test.m, function() { + mainHostSummaryView.set('content', test.content); + expect(mainHostSummaryView.get('sortedComponents').mapProperty('componentName')).to.eql(test.e); + }); + }); + + }); + + describe('#clients', function() { + + var tests = Em.A([ + { + content: Em.Object.create({ + hostComponents: Em.A([ + Em.Object.create({isMaster: false, isSlave: true, componentName: 'B'}), + Em.Object.create({isMaster: true, isSlave: false, componentName: 'A'}), + Em.Object.create({isMaster: true, isSlave: false, componentName: 'C'}), + Em.Object.create({isMaster: false, isSlave: false, componentName: 'D'}) + ]) + }), + m: 'List of masters, slaves and clients', + e: ['D'] + }, + { + content: Em.Object.create({ + hostComponents: Em.A([ + Em.Object.create({isMaster: false, isSlave: true, componentName: 'B'}), + Em.Object.create({isMaster: true, isSlave: false, componentName: 'A'}), + Em.Object.create({isMaster: true, isSlave: false, componentName: 'C'}), + Em.Object.create({isMaster: true, isSlave: false, componentName: 'D'}) + ]) + }), + m: 'List of masters and slaves', + e: [] + }, + { + content: Em.Object.create({ + hostComponents: Em.A([ + Em.Object.create({isMaster: true, isSlave: false, componentName: 'B'}), + Em.Object.create({isMaster: true, isSlave: false, componentName: 'A'}), + Em.Object.create({isMaster: true, isSlave: false, componentName: 'C'}), + Em.Object.create({isMaster: true, isSlave: false, componentName: 'D'}) + ]) + }), + m: 'List of masters', + e: [] + }, + { + content: Em.Object.create({ + hostComponents: Em.A([ + Em.Object.create({isMaster: false, isSlave: true, componentName: 'B'}), + Em.Object.create({isMaster: false, isSlave: true, componentName: 'A'}), + Em.Object.create({isMaster: false, isSlave: true, componentName: 'C'}), + Em.Object.create({isMaster: false, isSlave: true, componentName: 'D'}) + ]) + }), + m: 'List of slaves', + e: [] + }, + { + content: Em.Object.create({ + hostComponents: Em.A([]) + }), + m: 'Empty list', + e: [] + }, + { + content: Em.Object.create({ + hostComponents: Em.A([ + Em.Object.create({isMaster: false, isSlave: false, componentName: 'B'}), + Em.Object.create({isMaster: false, isSlave: false, componentName: 'A'}), + Em.Object.create({isMaster: false, isSlave: false, componentName: 'C'}), + Em.Object.create({isMaster: false, isSlave: false, componentName: 'D'}) + ]) + }), + m: 'List of clients', + e: ['B', 'A', 'C', 'D'] + } + ]); + + tests.forEach(function(test) { + it(test.m, function() { + mainHostSummaryView.set('content', test.content); + expect(mainHostSummaryView.get('clients').mapProperty('componentName')).to.eql(test.e); + }); + }); + + }); + + describe('#areClientWithStaleConfigs', function() { + + var tests = Em.A([ + { + content: Em.Object.create({ + hostComponents: Em.A([ + Em.Object.create({isMaster: false, isSlave: false, componentName: 'D', staleConfigs: true}), + Em.Object.create({isMaster: false, isSlave: false, componentName: 'C', staleConfigs: false}) + ]) + }), + m: 'Some clients with stale configs', + e: true + }, + { + content: Em.Object.create({ + hostComponents: Em.A([ + Em.Object.create({isMaster: false, isSlave: false, componentName: 'D', staleConfigs: false}), + Em.Object.create({isMaster: false, isSlave: false, componentName: 'C', staleConfigs: false}) + ]) + }), + m: 'No clients with stale configs', + e: false + }, + { + content: Em.Object.create({ + hostComponents: Em.A([ + Em.Object.create({isMaster: false, isSlave: false, componentName: 'D', staleConfigs: true}), + Em.Object.create({isMaster: false, isSlave: false, componentName: 'C', staleConfigs: true}) + ]) + }), + m: 'All clients with stale configs', + e: true + }, + { + content: Em.Object.create({ + hostComponents: Em.A([]) + }), + m: 'Empty list', + e: false + } + ]); + + tests.forEach(function(test) { + it(test.m, function() { + mainHostSummaryView.set('content', test.content); + expect(mainHostSummaryView.get('areClientWithStaleConfigs')).to.equal(test.e); + }); + }); + + }); + + describe('#isAddComponent', function() { + + var tests = Em.A([ + {content: {healthClass: 'health-status-DEAD-YELLOW'}, e: false}, + {content: {healthClass: 'OTHER_VALUE'}, e: true} + ]); + + tests.forEach(function(test) { + it(test.content.healthClass, function() { + mainHostSummaryView.set('content', test.content); + expect(mainHostSummaryView.get('isAddComponent')).to.equal(test.e); + }); + }); + + }); + + describe('#installableClientComponents', function() { + + it('delete host not supported', function() { + App.set('supports.deleteHost', false); + expect(mainHostSummaryView.get('installableClientComponents')).to.eql([]); + App.set('supports.deleteHost', true); + }); + + var tests = Em.A([ + { + content: Em.Object.create({ + hostComponents: Em.A([]) + }), + services: ['HDFS', 'YARN', 'MAPREDUCE2'], + e: ['HDFS_CLIENT', 'YARN_CLIENT', 'MAPREDUCE2_CLIENT'], + m: 'no one client installed' + }, + { + content: Em.Object.create({ + hostComponents: Em.A([ + Em.Object.create({ + componentName: 'HDFS_CLIENT' + }) + ]) + }), + services: ['HDFS', 'YARN', 'MAPREDUCE2'], + e: ['YARN_CLIENT', 'MAPREDUCE2_CLIENT'], + m: 'some clients are already installed' + }, + { + content: Em.Object.create({ + hostComponents: Em.A([ + Em.Object.create({ + componentName: 'HDFS_CLIENT' + }), + Em.Object.create({ + componentName: 'YARN_CLIENT' + }), + Em.Object.create({ + componentName: 'MAPREDUCE2_CLIENT' + }) + ]) + }), + services: ['HDFS', 'YARN', 'MAPREDUCE2'], + e: [], + m: 'all clients are already installed' + } + ]); + + tests.forEach(function(test) { + it(test.m, function() { + mainHostSummaryView.set('content', test.content); + mainHostSummaryView.set('installedServices', test.services); + expect(mainHostSummaryView.get('installableClientComponents')).to.include.members(test.e); + expect(test.e).to.include.members(mainHostSummaryView.get('installableClientComponents')); + }); + }); + + }); + + describe('#addableComponents', function() { + + var tests = Em.A([ + { + content: Em.Object.create({ + hostComponents: Em.A([]) + }), + services: ['HDFS', 'YARN', 'MAPREDUCE2'], + e: ['DATANODE', 'NODEMANAGER', 'CLIENTS'], + m: 'no components on host (impossible IRL, but should be tested)' + }, + { + content: Em.Object.create({ + hostComponents: Em.A([ + Em.Object.create({ + componentName: 'HDFS_CLIENT' + }), + Em.Object.create({ + componentName: 'DATANODE' + }) + ]) + }), + services: ['HDFS', 'YARN', 'MAPREDUCE2'], + e: ['NODEMANAGER', 'CLIENTS'], + m: 'some components are already installed' + }, + { + content: Em.Object.create({ + hostComponents: Em.A([ + Em.Object.create({ + componentName: 'HDFS_CLIENT' + }), + Em.Object.create({ + componentName: 'YARN_CLIENT' + }), + Em.Object.create({ + componentName: 'MAPREDUCE2_CLIENT' + }), + Em.Object.create({ + componentName: 'NODEMANAGER' + }) + ]) + }), + services: ['HDFS', 'YARN', 'MAPREDUCE2'], + e: ['DATANODE'], + m: 'all clients and some other components are already installed' + } + ]); + + tests.forEach(function(test) { + it(test.m, function() { + mainHostSummaryView.set('content', test.content); + mainHostSummaryView.set('installedServices', test.services); + expect(mainHostSummaryView.get('addableComponents').mapProperty('componentName')).to.include.members(test.e); + expect(test.e).to.include.members(mainHostSummaryView.get('addableComponents').mapProperty('componentName')); + }); + }); + + }); + + + +});